
简介这是一套面向 Java Web 开发者的门户网站完整源码涵盖门户前台与后台管理两个部分适合需要快速搭建企业官网、资讯门户或后台管理系统的初中级开发者。项目采用前后端分离机制后台基于 MySQL内置初始化脚本 tianti_stage.sql 与默认管理员账号便于本地部署和登录验证前端为支持多端自适应的静态站点可部署到 Nginx 或 Web 容器并可通过 config.js 配置接口地址与后台地址。压缩包约 308.58MBzip 格式包内主体包括 Java 后端模块、前端 gateway 静态资源、数据库脚本及配置代码等结构清晰。目前已有 1337 人学习下载。学习这份源码可以快速理解门户前台展示、后台管理、前后端联调与多端适配的完整实现思路直接基于它二次开发能节省大量基础环境搭建时间。1. 门户网站全套源码一个项目同时拿捏前台展示和后台管理第一次拆这套门户网站全套源码时我的第一反应是「这不就是一套 CMS 吗」但深入看下去才发现它在权限控制、模板解析和内容发布上做了很多融合的活儿尤其是把门户展示端和管理后台放在同一个项目里却又能分开维护这个设计让我印象很深。这套源码对三类人特别有价值想快速上线一个企业官网或信息门户的人正在学完整 Web 项目开发流程的初学者以及把源码当脚手架改造成自己产品的开发者。对于只想找个软件/插件源码参考的人来说它也是一份难得的完整工程范例——门户该有的功能、后台该有的能力全都有。2. 技术栈与工程结构前后端一体的门户系统长什么样2.1 整体架构选型拿到的这套门户网站源码我把它归成两个大块门户前台和后台管理。前台负责内容展示、栏目导航、站内搜索后台负责内容录入、审核发布、用户权限、栏目配置。整体走的是单体应用里比较典型的分层思路没有刻意拆微服务对我来说这反而是好事——它让我在本地就能完整跑通一条业务链路排查问题也不用在多个服务之间来回跳。技术栈上我梳理了几个核心点。后端基于 Java 的 Spring Boot 框架构建持久层用 MyBatis 配合 MySQL 数据库权限认证采用 Shiro 框架做登录校验和角色控制前端页面使用 Thymeleaf 模板引擎渲染。门户页面和后台页面都在同一个 Web 应用里通过 URL 路径前缀做区分后台路径统一挂在 /admin 下。这种做法的好处是部署简单一个 Jar 包就能搞定全部功能配套资源少对还没完整上过生产环境的团队特别友好。有人可能会问为什么不选 Vue Spring Boot 的前后端分离方案原因也很现实如果只是内容展示和后台维护服务端渲染能直接输出完整 HTML对搜索引擎更友好门户网站要的就是 SEO 可读性。而管理后台虽然是交互密集型页面用 Thymeleaf 配合 JQuery 也已经足够没必要为了追求技术热度引入一套 Node 构建链。这套源码选择服务端渲染说明它是奔着实际落地去设计的。2.2 工程目录结构说完了选型再来看目录。一个合格的源码包必须能让接手的人一眼看出模块边界。这套源码的目录结构是这样的portal-project ├── portal-admin # 后台管理模块 │ ├── controller # 后台接口控制器 │ ├── service # 业务逻辑层 │ ├── mapper # MyBatis 数据访问接口 │ └── resources │ └── mapper # XML 映射文件 ├── portal-core # 公共核心模块 │ ├── entity # 实体类 │ ├── config # 全局配置 │ └── util # 工具类 ├── portal-web # 门户前台模块 │ ├── controller # 前台页面控制器 │ └── resources │ ├── templates # Thymeleaf 模板 │ ├── static # 静态资源 │ └── application.yml └── sql # 数据库初始化脚本这个拆分逻辑是清晰的portal-core 放公共代码portal-web 处理门户页面portal-admin 处理后端逻辑sql 目录放所有表结构。我第一次拿到源码时第一件事就是先看 sql 目录因为表结构能直接反映这个项目的业务深度。2.3 数据库设计要点数据库脚本里一共包含十几张业务表核心几张表这里重点说一下用户表存储后台账号角色表定义角色权限表绑定资源内容表是门户文章的核心栏目表维护导航结构这两张表通过栏目 ID 关联。CREATE TABLE t_content ( id int(11) NOT NULL AUTO_INCREMENT, category_id int(11) NOT NULL COMMENT 所属栏目, title varchar(255) NOT NULL COMMENT 文章标题, summary varchar(500) DEFAULT NULL COMMENT 摘要, content text COMMENT 正文内容, status tinyint(4) DEFAULT 0 COMMENT 0草稿 1待审核 2已发布, author_id int(11) DEFAULT NULL COMMENT 作者ID, publish_time datetime DEFAULT NULL COMMENT 发布时间, create_time datetime DEFAULT NULL COMMENT 创建时间, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;我把状态字段设计成数字枚举草稿、待审核、已发布三个状态后台审核时只做一个 UPDATE 操作比字符串状态管理起来方便得多。content 字段用 text 类型存 HTML而不是存 Markdown这是为了方便非技术编辑直接粘贴排版好的内容也从侧面说明这套源码更偏向运营管理场景。3. 核心模块拆解一个请求从进入到返回的完整链路3.1 登录与权限控制后台管理的入口是登录。Shiro 在这套源码里的配置很典型——把登录接口、权限过滤器和 Session 管理器串联起来。我挑权限校验这一段来说这是整套后台安全机制的基石Configuration public class ShiroConfig { Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean factoryBean new ShiroFilterFactoryBean(); factoryBean.setSecurityManager(securityManager); // 配置访问规则admin 路径下的所有资源都需要登录 MapString, String filterChainMap new LinkedHashMap(); filterChainMap.put(/admin/login, anon); filterChainMap.put(/admin/**, authc); filterChainMap.put(/static/**, anon); factoryBean.setFilterChainDefinitionMap(filterChainMap); // 未登录时跳转的地址 factoryBean.setLoginUrl(/admin/login); return factoryBean; } }filterChainMap 的 key 是路径模式value 是过滤器名称anon 表示匿名可访问authc 表示需要登录认证。这里有一个容易被忽略的细节/admin/login 必须放在 /admin/** 前面Shiro 是按顺序匹配的一旦先匹配到拦截规则登录页自己也会被拦下来形成重定向死循环。这个问题排障的时候特别常见后面我专门列进了踩坑清单。3.2 后台内容管理内容管理是后台的核心业务典型流程是列表查询 - 新增/编辑 - 审核 - 发布。以内容发布为例我贴 Service 层的部分代码public boolean publishContent(Integer contentId) { // 查询当前内容状态只有审核通过的内容才能发布 Content content contentMapper.selectById(contentId); if (content null) { throw new BusinessException(内容不存在); } if (content.getStatus() ! ContentStatus.AUDITED) { throw new BusinessException(仅审核通过的内容可以发布); } // 更新状态为已发布并记录发布时间 Content update new Content(); update.setId(contentId); update.setStatus(ContentStatus.PUBLISHED); update.setPublishTime(new Date()); return contentMapper.updateById(update) 0; }这种状态机式的写法好处是可控性强。内容从草稿到发布每一步都在代码里做了状态校验防止跳步操作。实际开发中我一般会把状态枚举抽到一个独立的常量类里避免散落在各处都写魔法数字。参数 contentId 从 Controller 传入Service 层只接受业务参数不直接感知 HTTP 层的细节这个习惯在改造时能省很多事。3.3 门户页面渲染门户前台页面的渲染走的是 Thymeleaf 模板通过 Controller 把数据塞进 Model再由模板引擎生成静态 HTML 返回给浏览器。我挑了栏目页的入口逻辑来展示Controller RequestMapping(/category) public class CategoryController { GetMapping(/{id}) public String categoryPage(PathVariable Integer id, Model model) { Category category categoryService.findById(id); ListContent contentList contentService.findPublishedByCategoryId(id); model.addAttribute(category, category); model.addAttribute(contents, contentList); return category; } }Model 里的 category 变量在模板中可以直接通过 ${category.name} 拿到contentList 则用 th:each 循环渲染列表。前端开发只需要关注 templates 目录下的 HTML 模板不用关心数据的来源这正是服务端渲染的协作优势——前端改样式后端改数据中间通过 Model 对象作为协议互不干扰。3.4 站内搜索的实现门户网站必备的站内搜索这套源码实现得很务实用的不是 Elasticsearch而是 MySQL 的 LIKE 模糊查询。核心在 Service 层public ListContent search(String keyword) { // 使用 LIKE 模糊匹配标题和正文 return contentMapper.searchByKeyword(% keyword %, % keyword %); }LIKE 查询在数据量不大时性能完全够用但一旦内容超过十万条这种写法就会成为瓶颈。那时候可以考虑引入更专业的搜索引擎或者改用全文索引。作为入门级的门户源码这种朴素做法反而是优点依赖少、容易懂想在本地把环境跑起来几乎零成本。4. 从零到一把门户和后台在本地完整跑起来4.1 环境准备与初始化我在本地把这套源码跑起来只用了三样东西JDK 8、Maven 3.6 和 MySQL 5.7。数据库的初始化也很直接用命令行导入 sql 目录里的初始化脚本mysql -u root -p sql/portal_init.sqlportal_init.sql 这个脚本里包含了建库、建表、插入初始管理员账号的语句。我建议你导入完成后先查一下 t_user 表确认初始账号的密码是否加密过。如果密码是明文登录时可能直接用初始密码就进去了这是一个需要第一时间改掉的隐患——很多拿源码二次开发的人上线后小半年都没想起改初始管理员口令血泪教训。4.2 配置文件修改初始化完数据库接下来要改 application.yml 里的数据源配置。这是整个项目跑通的关键几乎所有的连接问题都出在这一步spring: datasource: url: jdbc:mysql://localhost:3306/portal_db?useUnicodetruecharacterEncodingutf8 username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver这里要特别注意字符集参数 characterEncodingutf8很多人在这一步忽略了它页面中文就变成了乱码。驱动名称也要跟 MySQL 版本匹配MySQL 5.7 用 com.mysql.jdbc.DriverMySQL 8.0 则必须换成 com.mysql.cj.jdbc.Driver这个差异我在后面避坑章节里会详说。4.3 启动与访问路径一切就绪之后在项目根目录执行 Maven 打包命令mvn clean package -DskipTests java -jar portal-web/target/portal-web.jar等控制台输出 Spring Boot 的启动 Banner看到 Tomcat started on port 8080 表示启动成功。浏览器访问本地地址可以看到门户首页后台入口则通过 /admin/login 访问用初始账号登录后就能在后台录入内容回到门户前台刷新页面内容就会出现在对应栏目下。这个过程走完你就已经把整套源码的前台和后台都跑通了。4.4 用 Nginx 做反向代理本地跑通只是第一步。如果要把这套门户部署到服务器上我一般会在前面加一层 Nginx 做反向代理配置大致是这样server { listen 80; server_name your-domain.com; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location /static/ { alias /var/www/portal/static/; expires 7d; } }静态资源单独放在一个目录由 Nginx 直接返回这会减轻后端压力图片和 CSS 的加载速度也会有明显提升。动静分离在单体项目里也能做到这个程度核心思想就是让专业的人干专业的事——Nginx 擅长处理静态文件Java 进程专心跑业务逻辑即可。5. 避坑指南这套门户源码最常见的五个翻车点5.1 启动报驱动类找不到现象Spring Boot 启动时提示 ClassNotFoundException: com.mysql.jdbc.Driver应用直接退出。原因pom.xml 里的 MySQL 依赖版本和本地数据库版本不匹配旧驱动类在 MySQL 8.0 中已经被移除了。解决检查依赖版本MySQL 8.0 数据库必须使用 mysql-connector-java 8.x 版本并且把驱动类换成 com.mysql.cj.jdbc.Driver改完重新 clean package 再启动。5.2 后台登录后页面重定向死循环现象输入正确的账号密码之后浏览器一直跳回登录页甚至出现此页面无法正确重定向的报错。原因Shiro 的过滤器链中没有配置 /admin/login 为匿名访问或者配置顺序不对导致登录请求本身也被拦截认证失败又跳回登录页形成死循环。解决把 anon 规则的路径放在 authc 拦截规则之前确认 filterChainMap 中使用 LinkedHashMap 保持有序。5.3 门户页面中文全部变成问号现象内容管理后台录入中文后前台页面显示为????数据库里存进去的也是问号。原因数据库连接 URL 没有指定 characterEncodingutf8或者数据库表的字符集不是 utf8mb4导致中文在写入时就已经损坏。解决在 JDBC URL 中补上 useUnicodetruecharacterEncodingutf8 参数SQL 初始化脚本执行前把库的默认字符集设置为 utf8mb4已经损坏的数据只能删掉重新录入。5.4 图片上传成功但前台无法显示现象后台能选择图片并提示上传成功但门户页面上图片路径 404控制台无任何异常。原因上传的图片保存到了本地磁盘路径但没有映射为静态资源访问路径浏览器请求的 URL 在后端找不到对应的文件。解决在配置类里添加资源映射把本地目录映射到 /upload/** 路径同时确认 Nginx 代理没有拦截这个路径。这个坑我在两个项目里都踩过每一次都是先怀疑路径写错最后才发现是资源映射缺失。5.5 修改了模板页面但看不到变化现象改了 templates 目录下的 HTML 文件刷新页面还是老样子非要重新启动项目才能生效。原因Thymeleaf 默认开启模板缓存生产环境下缓存了编译结果开发时改模板不会立刻生效。解决在开发阶段把 spring.thymeleaf.cache 设置为 false改完模板就能实时看到效果上线前记得改回 true避免每次请求都重新解析模板带来的性能损耗。6. 二次开发如何把这个门户脚手架改成你自己的产品拿到了源码不改造就直接上生产是浪费。真实业务里门户模板的风格、栏目的结构、权限的粒度都要跟着业务走。我建议的改造路径是从模板开始再到数据模型最后才动权限逻辑这个顺序最不容易翻车。如果想换掉门户首页的风格最省力的路径是改 templates/index.html 的布局结构。Thymeleaf 模板和普通 HTML 区别不大你用熟悉的 CSS 框架替换掉原有样式即可。我这次把门户页面从两栏布局改成三栏卡片布局只改了模板文件和对应的 CSS没有动任何 Java 代码这正体现了这套源码的前后端低耦合设计——前台的改版完全可以通过模板和样式完成。如果要新增一个独立的业务模块比如招聘管理我会照葫芦画瓢先在数据库加两张表——职位表、投递记录表——再在 portal-admin 里增加 Controller、Service、Mapper然后在后台菜单配置里把新模块的路径挂上去最后在前台模板中增加入口。整套流程走下来我一般只需要三个小时这就是一套结构良好的源码带来的效率。核心经验是先找最相似的现有模块照抄一遍跑通之后再优化不要在第一次就写出标新立异的架构。权限粒度的扩展也算常见需求。原始的 Shiro 配置只区分了登录和未登录如果要增加编辑和管理员两级身份做法是在角色表里增加对应数据在 Controller 的方法上加上权限注解再在 ShiroConfig 里补充对应的过滤规则三步就能完成一套简单的分级权限。每一次改完代码我会强制走一遍完整的验证流程先 mvn clean package 确认编译无误再把最新的 Jar 包丢到测试环境跑一遍登录后台新建一篇内容并发布最后去前台确认展示效果。数据链路从头到尾是通的心里才踏实。有一次我就是跳过了中间的部署验证直接在 IDEA 里跑了个单测就提交了结果环境差异导致发布后报错从那次以后我每次改完源码都强制走一遍完整验证。这套源码给我最大的启发是——一个结构清晰的全栈项目就是最好的老师。希望帮到你。本文还有配套的精品资源点击获取