简介门户网站全套源码内含前台门户展示与后台管理端适合需要搭建企业官网、信息发布类平台的开发者也适合正在学习Java Web前后端分离开发的工程师参考。项目基于Java支持JDK1.7及以上版本数据库采用MySQL并自带初始化脚本后台默认登录路径及账号密码已给出admin/123456编辑器与图标字体配置均有说明便于快速搭建运行环境。前端为静态工程支持多端自适应可直接部署至Nginx通过接口获取数据整体前后端分离的结构清晰易于理解权限管理、内容发布等常见业务模块的实现方式。压缩包约308.58MB包含工程源码、SQL脚本和配置示例资源内按前后端模块组织目录接口地址与后台地址可在配置文件中统一调整方便对接不同运行环境帮助用户从零还原一套可运行的门户系统节省环境搭建和二次开发的时间成本。该资源已有1336人学习下载可用于毕业设计、课程实训或企业项目的快速起步与参考。1. 门户网站全套源码前台门户加后台管理一套能直接落地的完整工程如果你手上正好接到一个“做个门户网站”的需求最头疼的往往不是页面设计而是前后台两套东西怎么整合前台要展示栏目和文章后台要管发布和权限两者还得共用一套登录体系。这份门户网站全套源码解决的就是这个痛点——它把门户展示站和后台管理系统打包在一起前台负责内容展示和检索后台负责栏目管理、文章发布、用户权限、系统配置两者通过同一套数据层打通。适合三类人直接用接私活想快速交付的开发者需要给单位做内部信息门户的运维人员以及想拿完整前后台项目练手的新手。整个工程从数据库脚本到部署配置都是齐的改个连接串就能跑。2. 整体架构与技术选型前后台一体工程的模块划分与目录结构2.1 门户前台与后台的职能边界展示层与管理层怎么分工这套源码最值得先看懂的一点是它没有把前后台拆成两个独立部署的工程而是做成了同一个应用里的两个模块。门户前台面向匿名访客和登录用户承担栏目浏览、文章详情、搜索、个人信息维护这些面向展示的功能后台管理系统面向管理员承担栏目树维护、文章审批发布、用户管理、角色权限分配、系统参数配置这些面向管理的功能。这种单应用双模块的结构在实际交付场景里非常常见。它的好处是部署简单一套 Tomcat、一个数据库就能同时跑起门户和后台坏处是模块间耦合需要靠代码规范来约束。这套源码的做法是通过包名和访问路径来区分——前台访问路径以/portal开头后台以/admin开头在拦截器层面做了两套权限控制。2.2 目录结构全景从部署入口到业务代码的完整清单拿到源码后先不要急着启动花十分钟把目录结构过一遍后面排查问题会快很多。我拆过的门户类项目中这套的目录组织属于比较典型的 Java Web 工程布局portal-project ├── src/main/java │ ├── com/example/portal │ │ ├── common // 公共组件统一返回、异常处理、工具类 │ │ ├── portal // 门户前台模块控制器、服务、数据对象 │ │ │ ├── controller │ │ │ ├── service │ │ │ └── dao │ │ ├── admin // 后台管理模块控制器、服务、数据对象 │ │ │ ├── controller │ │ │ ├── service │ │ │ └── dao │ │ └── system // 系统级模块用户、角色、权限、字典 │ │ ├── entity │ │ ├── mapper │ │ └── security // 登录认证与权限拦截 │ ├── resources │ │ ├── mapper // SQL 映射文件按模块分目录 │ │ │ ├── portal │ │ │ └── admin │ │ ├── templates // 前台门户页面模板 │ │ │ ├── index.html │ │ │ ├── article.html │ │ │ └── search.html │ │ ├── static // 静态资源CSS、JS、图片 │ │ └── application.yml // 数据源、端口、文件上传等配置 │ └── webapp │ └── admin // 后台管理页面独立前端资源目录 │ ├── login.html │ ├── dashboard.html │ └── js ├── sql │ ├── portal_init.sql // 建库建表脚本 │ └── portal_data.sql // 模拟数据脚本 └── docs └── 部署说明.md这里有两个容易忽略的点。第一后台管理页面放在了webapp/admin而不是模板目录里意味着后台用的是静态页面加 AJAX 请求的方式和前台的服务端模板渲染是两套思路。第二system模块同时被portal和admin引用用户登录、权限校验、字典查询这些基础能力是共享的。2.3 技术选型思路为什么这种组合最适合门户类项目技术栈方面从application.yml和依赖配置可以判断这是一套基于 Spring Boot 的工程持久层用的是 MyBatis 加 XML 映射模板引擎用的是 FreeMarker前端资源基于 jQuery 和普通 JavaScript。这套组合在门户类项目中属于保守但稳定的选择。Spring Boot 负责应用的自动配置和启动MyBatis 让 SQL 控制权完全掌握在开发者手里门户站点经常有复杂的分页查询和多表关联SQL 写在 XML 里比 JPA 的自动生成 SQL 更容易调优FreeMarker 做服务端渲染对搜索引擎友好门户站点的文章页面需要被搜索引擎收录服务端渲染是必须的后台用静态页面加 AJAX开发效率高部署时也不需要额外处理。数据库用的是 MySQL脚本里默认字符集是utf8mb4这个细节在 5.1 节还会讲到。3. 核心模块拆解内容发布从后台录入到前台展示的完整链路3.1 后台内容管理模块栏目、文章的增删改查实现门户网站最核心的业务闭环是管理员在后台维护栏目树在栏目下发布文章访客在前台通过栏目导航浏览文章。这套源码的文章管理功能包含了栏目绑定、标题摘要关键字、正文富文本、置顶状态、发布状态这几个字段基本的门户需求都覆盖了。文章新增的控制器入口是ArticleController里的save方法核心逻辑是接收表单参数后做字段校验然后调用服务层保存数据。看一下这个接口的参数设计PostMapping(/admin/article/save) ResponseBody public Result save(Article article, HttpSession session) { // 从 Session 中获取当前登录用户作为文章作者 Integer userId (Integer) session.getAttribute(loginUserId); if (userId null) { return Result.error(登录状态已过期请重新登录); } article.setCreateBy(userId); // 新增和更新走同一个方法主键为空时执行插入 if (article.getId() null) { article.setCreateTime(new Date()); article.setStatus(0); // 0 草稿1 已发布 articleService.insert(article); } else { articleService.update(article); } return Result.success(); }这段代码的setStatus(0)表示新文章默认存为草稿后台列表里能看到但前台不会展示。这样的好处是给审核留了空间——管理员可以先录入多篇草稿统一检查后再批量发布。Result是公共组件里定义的统一返回对象所有后台接口都返回这个结构前端拿到后根据code字段判断成功失败。服务层insert方法的实现涉及一个典型的门户场景文章和栏目是多对一关系但门户首页经常要展示多栏目的聚合内容。这里用的是直接关联查询加状态过滤没有引入冗余的聚合表。Mapper 里的 SQL 大致是这个形态insert idinsert parameterTypeArticle useGeneratedKeystrue keyPropertyid INSERT INTO t_article ( column_id, title, summary, keywords, content, status, is_top, create_by, create_time ) VALUES ( #{columnId}, #{title}, #{summary}, #{keywords}, #{content}, #{status}, #{isTop}, #{createBy}, #{createTime} ) /insertuseGeneratedKeystrue配合keyPropertyid让插入完成后自动把自增主键回填到Article对象里后续如果需要继续处理附件或标签可以直接用article.getId()。这是后台模块非常通用的做法二次开发时新增表也可以照这个模式来。3.2 前台门户渲染模板引擎与数据装配逻辑门户前台的页面是 FreeMarker 模板渲染入口在PortalController的index方法和articleDetail方法。门户首页的数据装配逻辑是先查出需要展示的栏目再逐个栏目查询最近发布的文章列表最后把数据塞进 Model 返回给模板。GetMapping({/, /index}) public String index(Model model) { // 查询状态为启用的一级栏目 ListColumn columns columnService.listEnabled(); ListMapString, Object columnWithArticles new ArrayList(); for (Column column : columns) { MapString, Object item new HashMap(); // 每个栏目下取最近 10 条已发布文章 ListArticle articles articleService.listLatestByColumn(column.getId(), 10); item.put(column, column); item.put(articles, articles); columnWithArticles.add(item); } model.addAttribute(columnArticles, columnWithArticles); return index; }这里有个性能细节值得注意循环里逐个调articleService.listLatestByColumn栏目少的时候没问题但栏目超过十几个时会产生 N1 查询。这套源码的默认写法是常规做法如果你的站点栏目多、访问量大二次开发时可以改成一次关联查询把栏目和文章全部取出来这个我会在 6.1 节展开讲。模板侧对应的是 FreeMarker 的遍历语法。index.html里用#list遍历columnArticles外层是栏目名称内层是文章标题链接#list columnArticles as item div classcolumn-block h2${item.column.name}/h2 ul #list item.articles as article li a href/article/${article.id}.html${article.title}/a span${article.createTime?date}/span /li /#list /ul /div /#list/article/${article.id}.html这种伪静态路径是门户站点的常见做法——对外展示时像静态页面实际上还是走控制器查询数据库好处是搜索引擎收录友好且链接稳定。如果你做过 PHP 时代的门户站会发现这套路由风格非常眼熟。3.3 用户与权限后台登录态与前台会员的隔离设计门户项目的权限模型通常是两套并行的体系后台管理员走 Session 登录加角色权限校验前台会员可以走登录和注册。这套源码的system/security包就是干这个的。后台权限的核心是一个拦截器AdminAuthInterceptor它拦截所有/admin/**请求检查 Session 里是否存在登录用户没有就重定向到登录页。权限粒度控制到菜单按钮级别做法是登录时把当前用户的角色编码放进 Session每个后台页面用 FreeMarker 的#if判断是否显示某个操作按钮。Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session request.getSession(); Integer userId (Integer) session.getAttribute(loginUserId); if (userId null) { // AJAX 请求返回 JSON 提示普通请求重定向到登录页 String requestedWith request.getHeader(X-Requested-With); if (XMLHttpRequest.equals(requestedWith)) { response.setContentType(application/json;charsetUTF-8); response.getWriter().write({\code\: 401, \msg\: \未登录\}); } else { response.sendRedirect(/admin/login.html); } return false; } return true; }这段拦截器在后台模块里承担了全部未登录请求的守卫工作。注意它对普通请求和 AJAX 请求做了不同处理——AJAX 返回code: 401让前端弹出登录过期提示普通请求直接重定向。这个小细节在实际项目里非常实用否则后台页面点一个按钮直接跳到登录页体验会很难受。前台会员看的是另一个拦截器只拦截需要登录的功能比如个人信息页、文章收藏等浏览门户内容本身不需要登录这也符合门户站点的业务预期。4. 本地运行全流程从环境准备到前后台同时跑通4.1 环境依赖与准备JDK、数据库、构建工具清单运行这套源码的环境要求不算高我的建议清单是JDK 1.8 以上、MySQL 5.7 以上、Maven 3.6 以上、任意一款 IDE。前端不需要额外安装 Node 环境因为webapp/admin下的资源是原生静态文件直接加载。环境准备里有一个常见的坑MySQL 8.x 和 5.7 的驱动配置不一样。这套源码的application.yml里默认写的是 MySQL 5.7 的驱动路径com.mysql.jdbc.Driver如果你本机装的是 MySQL 8.x需要改成com.mysql.cj.jdbc.Driver否则启动时会在数据源初始化阶段报驱动类找不到的异常。这个属于新手最容易卡住的点我会在 5.1 节再展开一次。4.2 数据库初始化脚本执行与基础数据说明源码的sql目录下有两个脚本portal_init.sql负责建库建表portal_data.sql负责灌入初始数据。先用命令行或数据库客户端执行第一个脚本mysql -uroot -p sql/portal_init.sql脚本里包含创建数据库、创建用户表、栏目表、文章表、角色权限表等核心表结构。执行完portal_init.sql后继续执行数据脚本mysql -uroot -p sql/portal_data.sqlportal_data.sql里灌的数据主要包括三块一个管理员账号默认用户名admin密码在脚本里是 MD5 加密后的值明文是admin123、三个左右的栏目通知公告、行业动态、政策文件等、每个栏目下若干条演示文章。灌入数据后可以用快速验证的方法——直接查一下t_article表的记录数SELECT id, column_id, title, status FROM t_article;如果能看到status 1的已发布文章和status 0的草稿记录说明数据脚本执行完整。脚本执行完还需要修改application.yml里的数据库连接串spring: datasource: url: jdbc:mysql://localhost:3306/portal_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: your_password driver-class-name: com.mysql.jdbc.DrivercharacterEncodingutf8和serverTimezoneAsia/Shanghai这两项建议保留前一个保证中文正常存储后一个避免 MySQL 8 时区问题导致的日期偏差。密码部分换成你本机实际的数据库密码。4.3 启动与验证前台门户和后台管理的启动步骤与登录说明数据库准备好后在工程根目录执行 Maven 命令启动mvn spring-boot:run启动日志里看到Started Application in xxxx seconds说明成功了。默认端口在application.yml里配置的是8080这时浏览器访问http://localhost:8080/能看到门户首页顶部导航是栏目列表中间是每个栏目下的最新文章。访问http://localhost:8080/admin/login.html会跳到后台登录页输入管理员账号admin和密码admin123即可进入后台。进入后台后建议按这个顺序验证一遍核心链路先到栏目管理里新增一个栏目再到文章管理里往这个栏目下发一篇文章、保存为草稿回到门户首页确认不显示再去后台把这篇文章改为发布状态返回门户首页刷新能看到文章出现在对应栏目下。这条链路如果通了说明门户最核心的发布闭环没有问题。下一步可以测一下权限——用浏览器的无痕模式打开后台登录页直接访问http://localhost:8080/admin/dashboard.html会被拦截器重定向回登录页这就验证了后台的登录守卫生效。5. 常见问题与避坑指南部署和二次开发中的排查记录5.1 数据库连接报错驱动类找不到换 MySQL 版本后必踩的坑现象启动时报ClassNotFoundException: com.mysql.jdbc.Driver或者Unable to load authentication plugin caching_sha2_password。原因这两个其实是同一个问题——本机 MySQL 版本与源码默认配置不匹配。MySQL 5.7 及以下版本用com.mysql.jdbc.DriverMySQL 8.x 改成了com.mysql.cj.jdbc.Driver并且默认认证插件是caching_sha2_password而项目里的连接串没有显式指定。解决把application.yml里的驱动类改成com.mysql.cj.jdbc.Driver同时确认连接串里带了useSSLfalse。如果 MySQL 8 下还想坚持用原驱动需要在创建用户时指定mysql_native_password认证插件但那样做不如直接换驱动干净。5.2 后台页面样式全丢静态资源加载 404责任在部署路径现象后台登录页能打开但页面没有任何样式控制台里一堆.css和.js报 404。原因这套源码的后台页面引用的静态资源路径是绝对路径/admin/css/login.css发布部署时如果上下文路径不是根路径比如部署成http://ip:8080/portal/那么请求会变成/portal/admin/css/login.css才能命中的资源而页面里写死的是/admin/css/login.css自然找不到。本机测试时根路径没问题一上服务器就翻车。解决如果服务器上需要带上下文路径部署有两个选择。改所有页面的静态资源引用的工作量太大比较靠谱的是在工程里建一个公共的路径前缀变量全局替换生成静态资源路径。另一个简单粗暴的方案是保持根路径部署把应用作为独立站点直接监听 80 或 8080 端口。小项目我一般建议后者。5.3 后台登录验证码不显示Session 与响应流被提前提交的冲突现象点击登录页刷新验证码图片区域是裂的或者反复刷新偶尔能显示偶尔不显示。原因验证码的生成逻辑里写了输出图片流到响应对象后又去设置了响应头或者往 Session 里写值。某些情况下容器认为响应已经被提交再设置任何响应头都会抛异常。这类问题的根源是代码顺序应该先写 Session、设置缓存控制的响应头最后才把图片字节流写出去。解决调整验证码控制器的代码顺序保证session.setAttribute在response.getOutputStream().write()之前完成。另外注意验证码过期时间的设置一般用session.setMaxInactiveInterval(120)这类方式做两分钟过期即可。排查时看后台日志如果出现IllegalStateException: getWriter() has already been called那就是响应流顺序问题没跑。5.4 修改数据后前台不更新用了缓存却没做刷新机制现象后台把文章更新了内容前台刷新还是旧数据有时候过几分钟自己就好了。原因门户首页的栏目文章列表做了内存缓存这块逻辑在ColumnArticleCacheService里更新文章的接口没有同步清理对应栏目的缓存。缓存没有设过期时间时旧数据就会一直展示。这个问题在部署阶段不显眼因为重启一次就好了但二次开发时不处理会一直存在。解决在文章更新的service方法里调用缓存清理方法把对应栏目 id 的缓存删掉。我在改造这个模块时给缓存加了一个版本号字段任何栏目内容更新都让版本号加一前台取值时拿版本和缓存里的对比不一致就重新查询数据库。这样比精确到栏目级别的清理简单也不会出现清理漏掉的情况。6. 二次开发思路给门户加一个“政策文件下载”模块的完整操作清单拿到一套老门户源码最典型的改造需求就是往里面加新模块。以新增一个“政策文件下载”栏目为例走一遍这里给出的改法比抱着代码读更有效。第一步在 SQL 脚本里基于t_article扩展一张表t_policy_file字段包含文件名、文件路径、下载次数、所属栏目 id第二步在system模块下参照Article实体的写法增加 PolicyFile 实体和 MapperXML 里的 SQL 复制一份改表名第三步在后台管理菜单里加一个“政策文件管理”入口页面沿用webapp/admin下的列表页结构表单里多一个文件上传的 input第四步前台门户里加一个文件列表模板模板放src/main/resources/templates下控制器对应新增一个PolicyFileController。这个流程看起来机械但有两个位置要特别小心。一是新增表时别忘了包含create_by和create_time字段后台统一的列表查询和操作日志会默认读取这两个字段漏掉会导致接口报字段不存在。二是在AdminAuthInterceptor的白名单配置里把文件下载路径排除掉否则前台用户点下载会跳到后台登录页这种问题查起来非常隐蔽。从这套源码里真正值得带走的是它朴素的模块划分方式——公共组件、前台、后台、系统四级结构不夸张也不花哨。我用这套结构改用过不少项目后来形成的习惯是任何新功能先进common里看有没有能复用的工具再决定是放portal还是admin模块间只通过system的公共服务通信不跨模块直接调对方的 Service。这样做的好处是后续换模板、换前台框架时后台和公共层完全不用动。从那以后我每次拿到一套新的门户源码都会先花半小时把模块边界画清楚再动手改代码这个习惯帮我少走了很多弯路。希望帮到你。本文还有配套的精品资源点击获取