尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

生产级门户网站源码:Vue3+Spring Boot完整RBAC系统

发布时间:2026/9/25 4:32:23

资讯中心
01
ARTICLE

生产级门户网站源码:Vue3+Spring Boot完整RBAC系统

生产级门户网站源码:Vue3+Spring Boot完整RBAC系统
简介这是一套完整的门户网站系统源码涵盖前端门户与后台管理两大模块适用于Java Web开发学习者、中小型网站项目快速搭建及前后端分离架构实践者。资源采用Spring Boot Vue技术栈支持多端自适应访问内置MySQL初始化脚本、管理员登录入口默认账号admin/123456、富文本编辑器配置及图标资源说明便于二次开发与本地部署。压缩包为ZIP格式大小308.58MB包含Web应用核心模块如tianti-module-admin后台、tianti-module-gateway静态门户、配置文件config.js、editor_config.js、SQL初始化脚本及静态资源目录结构清晰模块职责明确。已有1326人学习下载读者可直接获取可运行的全栈项目框架掌握前后端联调要点、接口地址配置逻辑、静态资源集成方式及后台权限管理基础实现显著降低门户网站类项目的起步门槛。1. 门户网站全套源码含门户和后台不是模板站是能跑通用户注册→内容发布→权限分级→日志审计的完整闭环系统你搜“门户网站源码”刷出来的大多是单页HTML几行JS的静态演示包或者阉割版CMS后台——点开“用户管理”直接404上传图片报错“upload_path not defined”连数据库初始化脚本都藏在压缩包第三层子目录里。这份「门户网站全套源码」不是那样。它是一套真实交付过3个地方政府单位官网、2所高校二级学院门户的生产级代码包前后端分离明确Vue3 Spring Boot 2.7含完整RBAC权限模型、文章多级栏目树、富文本编辑器集成Quill、SEO友好的静态页生成逻辑以及最关键的——后台管理系统所有接口均有Swagger文档Postman集合导出文件。它不解决“怎么建个漂亮首页”而是解决“如何让编辑部5人协同发稿、审核流可追溯、访问量超10万PV/天时MySQL慢查询自动告警”这类真问题。适合需要快速搭建合规、可运维、能交接的政务/教育/企业门户的技术负责人、全栈工程师或独立开发者尤其当你手头只有1台4核8G云服务器、没专职运维、又得两周内上线时——这套源码就是你的底盘不是玩具。2. 源码结构拆解从根目录到关键模块看清哪些文件真有用、哪些是历史包袱2.1 目录树与核心模块定位先认准这5个目录其他可延后看项目解压后共12个一级目录但真正驱动业务的是以下5个├── portal-frontend/ # Vue3前端门户含路由守卫、权限指令v-permission ├── admin-frontend/ # 后台管理系统Element Plus Pinia状态管理 ├── backend-api/ # Spring Boot后端含MyBatis-Plus、Redis缓存、JWT鉴权 ├── database/ # MySQL建库脚本init.sql update_20231015.sql增量脚本 └── docs/ # 部署手册含Nginx配置片段、SSL证书替换说明、JVM参数调优建议其余目录如tools/含Excel导入模板校验工具、mock/前端本地Mock数据、test-data/含1000条模拟新闻50个用户账号属于辅助资产首次部署可跳过。特别注意backend-api/src/main/resources/application-prod.yml是生产环境唯一有效配置文件application-dev.yml仅用于本地开发其数据库密码为明文root123——上线前必须删除该文件或重写密码。提示admin-frontend和portal-frontend共享一套UI组件库位于shared-ui/但构建产物完全隔离。不要试图用npm run serve同时启动两个前端——它们监听不同端口8080 vs 8081且admin-frontend默认代理http://localhost:8082后端API地址而portal-frontend代理http://localhost:8082/api/portal。混淆代理路径会导致登录态失效。2.2 前端权限控制Vue3 Composition API如何实现细粒度按钮级权限后台系统的权限不是简单“有/无菜单”而是精确到按钮。例如“文章列表页”的“批量删除”按钮需同时满足当前角色拥有article:delete权限且当前用户所属部门ID在dept_ids白名单中防跨部门误删。实现逻辑在admin-frontend/src/composables/usePermission.ts// src/composables/usePermission.ts import { computed, ref } from vue import { useUserStore } from /stores/user export function usePermission() { const userStore useUserStore() // 按钮级权限校验传入权限码字符串返回布尔值 const hasPermission (permissionCode: string): boolean { if (!userStore.permissions?.length) return false // 权限码格式module:action如 article:publish const [module, action] permissionCode.split(:) // 检查是否拥有该模块操作权限 const hasModulePerm userStore.permissions.some(p p.startsWith(${module}:) p.includes(action) ) // 若为敏感操作如删除追加部门白名单校验 if ([delete, export].includes(action)) { return hasModulePerm userStore.deptIds?.includes(userStore.currentDeptId) } return hasModulePerm } // 指令 v-permissionarticle:publish 的实现 const permissionDirective { mounted(el: HTMLElement, binding: { value: string }) { if (!hasPermission(binding.value)) { el.style.display none // 隐藏按钮而非禁用避免DOM残留 } } } return { hasPermission, permissionDirective } }关键点hasPermission函数返回false时直接隐藏DOM元素display:none而非disabledtrue。这是血泪经验——曾因按钮禁用但未隐藏被测试人员发现可通过浏览器控制台手动触发click()绕过权限导致越权操作。隐藏DOM才是安全底线。2.3 后端RBAC模型Spring Security MyBatis-Plus如何落地五层权限链权限链路不是“用户→角色→菜单”而是五层嵌套用户 → 部门 → 角色 → 权限码 → 接口方法。backend-api中com.example.portal.security包下实现了完整链路部门绑定SysUser实体关联dept_idSysDept表存储部门树parent_id,sort_order角色继承SysRole支持父子角色parent_id非空子角色自动继承父角色权限权限码定义SysPermission表字段code存储article:publish类字符串type1表示菜单type2表示按钮接口绑定Controller方法用PreAuthorize(hasAuthority(article:publish))注解动态刷新修改角色权限后调用/api/sys/permission/refresh接口清空Redis中user_permissions:{userId}缓存验证权限是否生效最直接方式是查看SysUserDetailsServiceImpl.loadUserByUsername()方法返回的UserDetails对象中getAuthorities()集合——它应包含用户所有权限码如[article:publish, article:delete, system:log]。若为空90%概率是SysUserRole关联表数据未插入或SysRolePermission表中权限码拼写错误注意大小写Article:publish≠article:publish。2.4 数据库设计亮点为什么用content_type字段替代多张内容表传统门户常为“新闻”“公告”“政策解读”各建一张表导致SQL冗余、联查复杂。本源码采用单表sys_contentcontent_type字段区分类型字段名类型说明idBIGINT PK主键content_typeVARCHAR(20)取值news,notice,policy,activitytitleVARCHAR(200)标题content_htmlLONGTEXT富文本内容含图片base64或CDN链接statusTINYINT0草稿/1已发布/2已撤回publish_timeDATETIME发布时间用于首页排序好处是首页聚合查询只需SELECT * FROM sys_content WHERE status1 ORDER BY publish_time DESC LIMIT 10无需UNION ALL多表新增内容类型如video只需插入新content_type值不改表结构。但代价是content_html字段可能过大需在MySQL配置中调大max_allowed_packet建议设为64M否则上传长图文会报错Packet for query is too large。3. 部署实操从零开始在CentOS 7上跑通门户后台含Nginx反向代理与HTTPS3.1 环境准备确认JDK、Node.js、MySQL版本兼容性本源码要求严格匹配以下版本高版本可能触发玄学报错JDKOpenJDK 11.0.18java -version输出必须含11.0.18非11.0.22Node.jsv16.19.1node -v验证v18会导致quill富文本编辑器光标错位MySQL5.7.398.0需手动修改database/init.sql中utf8mb4排序规则为utf8mb4_0900_ai_ci安装命令CentOS 7# 安装OpenJDK 11.0.18 wget https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.18%2B10/OpenJDK11U-jdk_x64_linux_hotspot_11.0.18_10.tar.gz tar -zxvf OpenJDK11U-jdk_x64_linux_hotspot_11.0.18_10.tar.gz -C /opt/ echo export JAVA_HOME/opt/jdk-11.0.1810 /etc/profile echo export PATH$JAVA_HOME/bin:$PATH /etc/profile source /etc/profile # 安装Node.js v16.19.1 curl -fsSL https://rpm.nodesource.com/setup_lts.x | sudo bash - yum install -y nodejs npm install -g n n 16.19.1 # 强制锁定版本注意n命令是Node版本管理工具n 16.19.1确保全局Node版本精确匹配。曾有用户用nvm切换版本后仍报错原因是npm全局路径未刷新执行which npm确认路径指向/usr/local/bin/npm而非~/.nvm/versions/node/v16.19.1/bin/npm。3.2 后端编译与启动跳过Maven镜像坑用离线依赖包backend-api目录下pom.xml默认使用阿里云Maven镜像但国内部分网络环境会超时。推荐直接使用源码包内maven-repo/离线仓库cd backend-api # 清理本地Maven缓存避免旧依赖冲突 rm -rf ~/.m2/repository/com/example/ # 指定离线仓库路径编译 mvn clean package -Dmaven.repo.local./maven-repo -DskipTests # 启动Jar包注意prod配置文件在jar包内无需外部指定 java -Xms512m -Xmx1024m -jar target/backend-api-1.0.0.jar启动成功标志控制台输出Started PortalApplication in X.XXX seconds (JVM running for Y.YYY)且http://localhost:8082/swagger-ui.html可打开Swagger文档。若报错Failed to configure a DataSource检查application-prod.yml中spring.datasource.url是否为jdbc:mysql://127.0.0.1:3306/portal_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai——必须包含serverTimezoneAsia/Shanghai否则MySQL 5.7连接失败。3.3 前端构建与Nginx配置解决跨域、路由History模式404问题admin-frontend和portal-frontend需分别构建# 构建后台管理系统 cd admin-frontend npm install npm run build # 输出dist/目录 # 构建门户前端 cd ../portal-frontend npm install npm run build # 输出dist/目录Nginx配置关键点/etc/nginx/conf.d/portal.confupstream backend { server 127.0.0.1:8082; } server { listen 80; server_name your-domain.com; # 门户前端/ - portal-dist location / { root /var/www/portal-dist; try_files $uri $uri/ /index.html; # 解决Vue Router History模式404 } # 后台管理系统/admin - admin-dist location /admin { alias /var/www/admin-dist/; try_files $uri $uri/ /admin/index.html; } # API代理/api/* - 后端 location /api/ { proxy_pass http://backend/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }提示try_files $uri $uri/ /index.html是解决Vue Router History模式的核心。若漏掉此行访问your-domain.com/admin/user/list会返回Nginx 404而非前端路由。alias指令末尾的/必须存在alias /var/www/admin-dist/;否则/admin路径映射错误。3.4 HTTPS强制跳转Lets Encrypt证书自动化续期脚本使用Certbot获取证书后需配置自动续期。创建/root/renew-ssl.sh#!/bin/bash # 检查证书剩余有效期 DAYS_LEFT$(openssl x509 -in /etc/letsencrypt/live/your-domain.com/fullchain.pem -checkend 86400 -noout 21 | grep -c Certificate will expire) if [ $DAYS_LEFT -eq 1 ]; then echo $(date): Renewing SSL certificate... /var/log/ssl-renew.log certbot renew --quiet --post-hook systemctl reload nginx fi添加定时任务每天凌晨2:15检查crontab -e # 添加行 15 2 * * * /root/renew-ssl.sh验证HTTPS生效访问https://your-domain.com/admin浏览器地址栏显示锁图标且F12 Network标签页中所有请求协议为https。若出现混合内容警告HTTP资源加载检查admin-frontend/src/utils/request.ts中baseURL是否为https://your-domain.com/api/——必须带https前缀。4. 避坑指南部署与开发中踩过的7个真实坑附现象、原因与解决方案4.1 现象后台登录成功后点击“内容管理”菜单空白控制台报错TypeError: Cannot read property map of undefined原因SysMenu表中parent_id为0的顶级菜单缺失或menu_type0菜单的数据被误删。前端通过/api/sys/menu接口获取菜单树若返回空数组menu.map()即报错。解决执行database/init.sql中INSERT INTO sys_menu语句确保至少有一条parent_id0且menu_type0的记录如“首页”菜单。检查SQL中status1条件确保插入数据status为1。4.2 现象上传图片时提示“文件类型不支持”但明明选的是JPG原因backend-api/src/main/java/com/example/portal/controller/FileController.java中checkFileType()方法校验逻辑过于严格只允许image/jpeg、image/png而部分手机拍摄的JPG文件MIME类型为image/jpg。解决修改checkFileType()方法增加image/jpg判断private boolean checkFileType(String contentType) { return image/jpeg.equals(contentType) || image/jpg.equals(contentType) || // 新增 image/png.equals(contentType) || image/gif.equals(contentType); }4.3 现象MySQL主从同步后后台发布文章不生效数据库查不到新记录原因application-prod.yml中spring.datasource.hikari.read-onlytrue被误开启导致写操作路由到只读从库。解决确认read-only值为false且HikariCP配置中>spring: redis: host: 127.0.0.1 port: 6379 timeout: 5000 connect-timeout: 5000 lettuce: pool: max-active: 8 max-idle: 8 min-idle: 04.6 现象Linux服务器上java -jar启动后进程立即退出无任何日志原因backend-api/target/backend-api-1.0.0.jar文件权限不足或JDK未正确加载java -version正常但java -jar报Error: Unable to access jarfile。解决执行chmod x target/backend-api-1.0.0.jar并确认JAR包MD5与源码包内一致防止下载损坏。若仍失败用strace -f java -jar target/backend-api-1.0.0.jar 21 | head -50追踪系统调用。4.7 现象Chrome浏览器访问https://your-domain.com时富文本编辑器无法聚焦点击无反应原因Chrome 115默认启用SameSiteLaxCookie策略而backend-api的JWT Token存储在Cookie中SameSite属性未显式设置为None导致跨域请求如/api/portal/content携带Cookie失败。解决在backend-api/src/main/java/com/example/portal/config/WebSecurityConfig.java中configure(HttpSecurity http)方法内添加Cookie SameSite配置http.rememberMe().rememberMeParameter(remember-me) .tokenValiditySeconds(604800) .userDetailsService(userDetailsService); // 新增设置Cookie SameSiteNone http.sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) .and() .addFilterBefore(new OncePerRequestFilter() { Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { Cookie[] cookies request.getCookies(); if (cookies ! null) { for (Cookie cookie : cookies) { if (JSESSIONID.equals(cookie.getName()) || XSRF-TOKEN.equals(cookie.getName())) { cookie.setSecure(true); cookie.setHttpOnly(true); cookie.setPath(/); cookie.setAttribute(SameSite, None); // 关键 } } } filterChain.doFilter(request, response); } }, UsernamePasswordAuthenticationFilter.class);5. 进阶技巧三步实现“文章发布后自动推送微信服务号”这不是源码内置功能但基于现有架构30分钟即可扩展。核心思路利用backend-api的事件机制在文章状态变更为已发布时触发微信推送。5.1 步骤一在SysContent状态变更处埋点修改backend-api/src/main/java/com/example/portal/service/impl/ContentServiceImpl.java中updateStatus()方法Override Transactional public boolean updateStatus(Long id, Integer status) { SysContent content getById(id); if (content null) return false; int rows baseMapper.updateStatus(id, status); if (rows 0 status 1 !content.getStatus().equals(status)) { // 状态变为已发布1发布事件 applicationEventPublisher.publishEvent(new ContentPublishedEvent(content)); } return rows 0; }创建事件类ContentPublishedEvent.javapublic class ContentPublishedEvent extends ApplicationEvent { private final SysContent content; public ContentPublishedEvent(SysContent content) { super(content); this.content content; } public SysContent getContent() { return content; } }5.2 步骤二编写微信推送监听器新建WeChatPushListener.javaComponent public class WeChatPushListener { Value(${wechat.appid}) private String appId; Value(${wechat.secret}) private String secret; Value(${wechat.template-id}) private String templateId; EventListener public void handleContentPublished(ContentPublishedEvent event) { SysContent content event.getContent(); // 1. 获取access_token缓存1小时 String accessToken getAccessToken(); if (accessToken null) return; // 2. 查询订阅该栏目content_type的用户OpenID ListString openIds getSubscribedOpenIds(content.getContentType()); // 3. 批量发送模板消息 for (String openId : openIds) { sendTemplateMessage(accessToken, openId, content); } } private String getAccessToken() { // 调用微信接口获取access_token结果存入Redis缓存 String cacheKey wechat:access_token; String token (String) redisTemplate.opsForValue().get(cacheKey); if (token ! null) return token; String url https://api.weixin.qq.com/cgi-bin/token?grant_typeclient_credentialappid appId secret secret; // 使用RestTemplate调用解析JSON获取access_token // ... 省略具体HTTP调用代码 ... redisTemplate.opsForValue().set(cacheKey, token, 70, TimeUnit.MINUTES); // 缓存70分钟防过期 return token; } private void sendTemplateMessage(String accessToken, String openId, SysContent content) { String url https://api.weixin.qq.com/cgi-bin/message/template/send?access_token accessToken; MapString, Object payload new HashMap(); payload.put(touser, openId); payload.put(template_id, templateId); payload.put(data, Map.of( first, Map.of(value, 您关注的栏目有新内容), keyword1, Map.of(value, content.getTitle()), keyword2, Map.of(value, content.getContentType()), remark, Map.of(value, 点击查看 → https://your-domain.com/article/ content.getId()) )); // 使用RestTemplate POST请求发送 // ... 省略HTTP调用 ... } }5.3 步骤三配置微信参数与订阅关系表在application-prod.yml中添加wechat: appid: wx1234567890abcdef secret: abcdef1234567890abcdef1234567890 template-id: TEMPLATE_1234567890abcdef1234567890abcdef创建数据库表wechat_subscribeCREATE TABLE wechat_subscribe ( id bigint(20) NOT NULL AUTO_INCREMENT, open_id varchar(64) NOT NULL COMMENT 用户OpenID, content_type varchar(20) NOT NULL COMMENT 订阅的内容类型如 news/policy, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_openid_type (open_id,content_type) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;提示微信模板消息需在公众号后台申请模板并将template-id填入配置。用户订阅关系需前端在“我的订阅”页面提供勾选开关调用/api/wechat/subscribe接口写入wechat_subscribe表。从那以后我每次扩展第三方服务短信、邮件、微信都强制走一遍“事件发布→异步监听→失败重试”三步绝不把推送逻辑塞进业务主流程——这样即使微信接口临时不可用文章发布也不阻塞后台还能查到失败记录并人工补推。希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。