简介:基于Java Web技术构建的企业简历管理系统完整源码包,面向正在学习Java全栈开发的学生与开发者,尤其适合作为课程设计或毕业设计的参考实践。系统采用JSP+Servlet+Service+Dao+MySQL分层架构,清晰地划分了表现层、业务层与数据访问层,涵盖用户注册登录、简历创建编辑删除、信息查看、条件检索以及管理员权限管理等常见业务场景,可帮助理解MVC设计模式与Web应用从请求到响应的完整链路。压缩包共162个文件,以61个class、55个java、17个jsp为主,辅以10个jar依赖库、2个sql数据库脚本、2个xml配置及少量前端静态资源,整体大小2.75MB,结构清晰便于导入调试。目前已有301人学习下载,资源内除完整源码外还附带数据库脚本与项目配置,可直接运行体验,亦可在此基础上扩展简历模板、批量筛选等进阶功能,是快速上手企业级Java Web开发的不错素材。
1. 简历管理系统,先想清楚你要的是一份能跑的代码,还是一套能复用的方案
下载过“源码+数据库”压缩包的人大概都有过这种体验:解压后看到一堆 .java、.jsp、.sql 文件,跟着 README 一路点,最后卡在某个配置文件上,或者是数据库导入时报错,又或者是启动后页面一片 500。这套基于 Java web 的企业简历管理系统,本质上是一个打通了权限控制、简历流转、岗位发布三大主线的完整 Java Web 应用。它解决的不是“怎么写一个简历上传接口”这种零散问题,而是把候选人投递、HR 筛选、面试邀约、后台维护、数据统计这些真实业务流程,用 Servlet + JSP + MySQL 的方式整体串起来。
这套内容适合谁?如果你正在做毕业设计,或刚入职需要快速上手企业级项目结构,又或者想在本地搭一套能演示的简历管理 demo 去理解 Java Web 全流程,那这套源码是很好的参照物。它和市面上那种只写登录注册的半成品不一样,简历附件上传、投递状态流转、管理员后台这些环节都是完整闭环,甚至还有数据导入导出和统计图表这类加分项。本章先帮你把系统能做什么、该怎么看、核心功能在哪些文件里说清楚,再带你把它真正跑起来。跑不起来的技术栈不是好技术栈,落不了地的源码也只是僵尸代码,这期我们把它弄活。
2. 技术选型与系统骨架:Java Web 项目先看三张图
拿到开源 Java web 项目,我一般不会先碰代码,而是先看三样东西:数据库脚本、web.xml、pom.xml 或 lib 目录。理解了这三样,整个系统的奔跑路径基本就清楚了。
2.1 Servlet + JSP + MySQL 的经典结构,为什么至今值得用
这套系统没有用 Spring Boot,而是选择了更原始的 Servlet/JSP 技术栈。常见做法是:浏览器请求通过 web.xml 里配置的 Servlet 映射进入后端,业务逻辑封装在 Service 层,数据库操作由 Dao 层负责,JSP 负责渲染页面。这种结构的优势是让你一眼看穿 HTTP 请求的完整生命周期,不受框架自动装配的黑匣子影响,这也是很多教学项目依然坚持这种写法的原因。
实体类定义一般是标准的 JavaBean,比如简历基本信息类、用户类、岗位类。系统里通常会有一张 user_info 表关联候选人和 HR 角色,一张 resume_info 表存储简历主信息,一张 resume_attachment 表记录附件磁盘路径,外加 work_experience、project_experience 这些子表来支撑简历详情页的模块化展示。Java 代码部分,以 user 表对应的 UserDao 为例,核心方法通常长这样:
public class UserDao { private static final String URL = "jdbc:mysql://localhost:3306/resume_db"; private static final String USER = "root"; private static final String PASSWORD = "123456"; public User findByUsername(String username) { String sql = "SELECT * FROM user_info WHERE username = ?"; try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, username); ResultSet rs = ps.executeQuery(); if (rs.next()) { return new User(rs.getInt("id"), rs.getString("username"), rs.getString("password"), rs.getString("role")); } } catch (SQLException e) { e.printStackTrace(); } return null; } }这里你需要注意的是,连接信息集中在常量里只是为了方便教学演示,实际开发中我会改造成通过 properties 文件读取,避免每次改库密码都要重新编译,也更方便后续部署时用环境变量注入。参数方面,PreparedStatement 的占位符可以有效防止 SQL 注入,这是老项目里经常被忽略但又必须守住的一条底线。如果你在简历模板里描述自己的项目经验,把“使用了 PreparedStatement 防注入”写进去也是一个加分项。
2.2 数据库脚本结构:简历系统的建表思路
数据库脚本是这个压缩包里最值钱的文件之一,通常名为 resume_db.sql,里面按顺序完成建库、建表、插入初始数据。一个合格的简历系统数据库脚本,至少会覆盖以下几类核心表:
- 用户与权限表:包含 id、username、password、role 等字段。登录后从 session 里取用户信息,后端通过判断 role 值来控制进入候选人端还是 HR 管理端。
- 基础字典表:如学历、工作年限、求职状态枚举表,用数字或字母代替中文。标准化之后的好处是后续做筛选条件时不需要解析字符串。
- 简历主表:外键关联用户表,冗余一些展示査询高频字段,包括姓名、手机号、邮箱、期望职位、期望薪资、工作年限等。
- 简历子表:包含教育经历、工作经历、项目经历多条记录,满足一对多扩展。
- 岗位与投递表:岗位表保存 HR 发布的招聘信息;投递记录表关联候选人、岗位和状态,常见状态有已投递、被查看、面试邀请、已录用、未通过,后续所有流程都围绕这个表的状态字段展开。
导入时需要注意,如果你的本机 MySQL 版本是 8.0 及以上,而脚本是 5.7 环境导出的,可能会出现字符集警告或排序规则不兼容的问题。我建议你用命令行导入来代替图形化工具,因为工具容易在字符集转换这一步搞出乱码,命令行指定 utf8mb4 反而最稳:
mysql -u root -p --default-character-set=utf8mb4 resume_db < resume_db.sql这里默认是库已存在。如果库没建,可以去掉 resume_db 参数,进 mysql 后先执行 source 命令,或者在脚本头部找到 create database 语句先用命令行建好库。执行完成后,用 show tables 查看表清单,看是不是有十几二十张表,如果只有三四张,那这套系统多半是简化版,功能覆盖上就要打一个问号了。
2.3 WAR 包与目录结构:从解压到看懂分层的捷径
解压源码后,你大概率会看到一个标准 Web 项目目录:src 下按包名分层,webapp 或 WebRoot 下放着 JSP、静态资源和 WEB-INF。正常分层里,bean 包放实体类,dao 包放数据库操作,service 包写业务逻辑,servlet 或 controller 包写请求控制,filter 包处理编码和登录拦截,util 包放连接数据库、上传文件、字符串处理这些工具类。如果你的包结构里 service 层是直接调 dao 没有任何业务动作,或者全部逻辑都堆在 servlet 里,那说明项目在分层上还有改进空间。
看完了包结构,下一个重点要看的事 WEB-INF/web.xml。不问版本直接跑源码是大忌,因为 Servlet 2.5 时代用 web.xml 配置,而 Servlet 3.0 后推荐注解;文件上传组件如果配的是 Commons FileUpload 的解析器,那么对应包的版本是否与你本地 Tomcat 匹配也需要提前确认。如果你拿到的是老架构的 Java Web 项目,我建议你先把 web.xml 从头到尾读一遍,把每个 url-pattern 对应的 Servlet 类抄下来,再与源码包里的 servlet 做一遍映射核对,从中就能发现“哪个地址对应系统哪个功能”,这一步是快速掌握每个项目的最短路径。等以后接手真实系统时会发现,翻 web.xml 永远是理旧代码的第一课。
3. 把系统跑起来:环境准备、导入、启动的完整步骤
看懂了结构,接下来就是真正的落地环节。这一章的目标是在你自己的电脑上把项目跑起来,配置不对会直接影响你后续的二次开发和功能验证,所以每一步我都会给出推荐值和踩过的坑。
3.1 配置 Tomcat 9 与 JDK 版本:少走一年弯路的版本搭配
这套 Java Web 系统常见的运行环境是 JDK 1.8 + Tomcat 8/9。如果你只装了 JDK 11,建议直接改装 JDK 1.8,因为老项目在更高 JDK 上跑会遇到模块化限制、JSP 编译问题,这些坑排查起来比较费时间。Tomcat 版本与 JDK 版本有对应关系,Tomcat 9 要求 JDK 1.8 及以上,Tomcat 8.5 对老项目也更友好。如果你的日常主力是 Spring Boot,而这次只是为了跑通这个系统,单独保留一份 JDK 1.8 也不会影响其他开发环境。
环境变量配置三个:JAVA_HOME 指向 JDK 安装目录,CATALINA_HOME 指向 Tomcat 目录,PATH 里加%JAVA_HOME%\bin和%CATALINA_HOME%\bin。配置好后在命令行执行java -version和catalina version验证是否都能被识别。如果输出版本不一致,优先检查是不是 PATH 顺序问题,可能已经被其他 JDK 覆盖了。
3.2 导入 IDEA 与连接数据库:页面和管理端访问的事先准备
用 IDEA 打开这个项目时,可以创建一个空项目,再把源码目录作为一个 Module 导入。如果 IDEA 没有自动识别为 Web 项目,需要手动添加 Web 模块:在 Project Structure 的 Facets 里增加 Web,并把 webapp 目录设为 Web 资源目录。这一步漏掉的话,后期启动时会报找不到 JSP 页面或 404。
数据库方面,我建议你自己动手建一个专用账号,而不是一直用 root 跑:
CREATE DATABASE IF NOT EXISTS resume_db DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci; CREATE USER 'resume_user'@'localhost' IDENTIFIED BY 'Resume@123'; GRANT ALL PRIVILEGES ON resume_db.* TO 'resume_user'@'localhost'; FLUSH PRIVILEGES;把连接串里的账号密码对应改成这个,目的是避免误删或误改库。许多新手搞坏数据库都是因为手滑在 root 连接上执行了危险操作,而专门账号加上最小权限,可以用一个清晰的边界守住这个底线。
3.3 修改配置文件启动项目:一步到位跑起来
启动步骤一句话说不完,核心是三件事:改数据库连接、配置 Tomcat、启动项目。
数据库连接一般在 src 下的 jdbc.properties 或 db.properties 里改:
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/resume_db?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai jdbc.username=resume_user jdbc.password=Resume@123配置 Tomcat 时注意 Deploy 的 Application context。我把一个系统的访问路径定为/resume,这个值直接决定访问地址是什么。启动后分别访问:
系统主页:http://localhost:8080/resume/ 后台管理:http://localhost:8080/resume/admin/login.jsp常见的启动失败是 404 或 ClassNotFoundException,这背后通常是工件(Artifact)没打对。IDEA 里打开 Project Structure 的 Artifacts,确认有没有一个 Web Application: Exploded 类型的部署包,并且 Output Layout 里有没有把依赖的 jar 包放进 WEB-INF/lib。这一步没做对,代码本身是好的,但 Tomcat 运行的时候根本找不到需要的类。
3.4 数据库连接池选型:不用改代码但性能差一倍
系统默认可能用的是 DriverManager 直连数据库,这种方式做毕设可以,但你要把它当企业级应用来演示时就会露怯。一个常见做法是不改任何 Java 代码,只把连接获取方式换成 Druid 或 C3P0 连接池。前提是项目里引了对应的 jar 包,然后在工具类里用连接池的 DataSource 替换原来的 DriverManager 连接过程。
Druid 配置一般长这样:
DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/resume_db?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai"); dataSource.setUsername("resume_user"); dataSource.setPassword("Resume@123"); dataSource.setInitialSize(5); dataSource.setMaxActive(20); dataSource.setMinIdle(5);参数上我只修改两个关键值:initialSize 和 maxActive。initialSize 是启动时预创建的连接数,maxActive 是最大活动连接数。对于简历管理系统这种低并发应用,maxActive 设置在 20 左右就足够了,调大反而会让连接池的空闲连接占用数据库资源。
4. 典型功能模块的调试重点与避坑清单:简历上传、导出、路线拦截
在一个业务型项目里,功能永远是围绕用户角色展开的。候选人端需要填写和维护简历、投递岗位;企业端 HR 需要管理岗位、筛选简历、发起面试邀请;系统管理员需要维护用户和统计报表。这些都是主角,但真正决定项目能不能稳定跑的是这些功能底下的三个关键点:上传细节、导出细节、权限拦截。
4.1 简历附件上传的两个隐藏坑:目录与文件覆盖
这类系统几乎都允许上传 Word/PDF 简历附件,功能实现大多是基于 Commons FileUpload 组件。上传原理是老话题了,需要注意的坑却有不少,首先就是保存目录。
File uploads目录不能放在项目部署目录内,因为 Tomcat 重启或者重新部署时,有时候会清理掉这些文件,而且随着使用目录会越来越大,也会撑爆部署空间。我一般会在本地测试时把附件保存到 D:/resume_uploads,然后在代码里定义一个常量配置这个路径。到生产环境,docker 部署时再映射成 volume。
另一个坑是文件覆盖。如果上传时按原始文件名保存,那么同名的 A 简历就会覆盖掉 B 简历,这是极其低级的错误。核心处理逻辑里必须拼上时间戳或 UUID:
String originalFilename = item.getName(); String ext = originalFilename.substring(originalFilename.lastIndexOf(".")); String newFilename = System.currentTimeMillis() + "_" + UUID.randomUUID().toString().substring(0, 8) + ext; File saveFile = new File(uploadDir, newFilename); item.write(saveFile);注意这里用了时间戳加 UUID 前 8 位拼出新文件名。实际生产中我会再加一层按日期分目录,比如2025/06/,这样每天的文件散落在各自目录下,月底清理历史数据时直接按目录删除就够了,不用写额外的清理脚本。
4.2 投递状态流转:别在 JSP 里写 if-else 状态判断
简历管理系统的核心业务是投递状态流转。候选人投递后状态变成待筛选,HR 查看后变成已查看,可以进一步改成邀请面试或淘汰。很多新手会在 JSP 页面里写很多 if-else 判断当前状态该显示什么按钮,这样代码非常难维护,一旦需求变了还得改页面模板。
更好的做法是后端定义一个状态常量类,页面只根据状态值显示进度条样式:
public class ApplyStatus { public static final int SUBMITTED = 1; public static final int VIEWED = 2; public static final int INTERVIEW = 3; public static final int HIRED = 4; public static final int REJECTED = 5; }同时建议给投递状态加一个变更记录表,记录“什么时间、谁操作、从什么状态改成什么状态”。这一步在实际企业项目中几乎都是标配,因为一旦出现纠纷,你需要溯源每一步操作。它也能让面试官在听你讲项目时眼睛一亮,因为大多数人只会做状态更新,没想到记流水账这件事有多重要。
4.3 前端路由入口过多,后台管理页面被学生直接访问
主流后台页面通常都会配置一个过滤器,如 AdminFilter,专门在会话里校验登录用户的角色是不是管理员。但问题在于,JSP 页面本身可以被直接访问,Tomcat 会把 .jsp 文件也当作请求资源,所以即使 servlet 里加了拦截,JSP 仍然能被打到后台路径后打开。最稳妥的解决办法是所有 JSP 文件都放进 WEB-INF 目录下面,浏览器无法直接访问,只能通过 Servlet 转发才能进入,从根上杜绝绕过登录检查。如果你在项目里看到 WEB-INF 外面还散落着 admin 目录,这就是你这个版本的风险点。
4.4 避坑实战列表:排查跑不起来的 5 个高频问题
一路带下来,我把最常遇到的 5 个问题整理在下面,都是真实场景的现象、原因和解决方式,你可以直接把号入座。
第一类:Tomcat 启动后访问项目 404
现象是点击运行按钮以后,浏览器地址栏直接访问这个路径能进 Tomcat,但访问系统上下文路径就报 404。原因一般是 Artifact 没有正确部署,或者 Application context 设置错误。解决方式是检查 IDEA 里 Run Configuration 的 Deployment 栏,Tomcat 配置中点击 Deploy 选中这个项目的 Artifact,再确认 Application context 的值与预期路径一致并点击应用后重启。
第二类:登录时点登录按钮,页面跳转回登录页
现象是填了正确的账号密码还是跳回登录页。原因大概率是登录成功的 session 写入与过滤器读取的 session key 不一致,比如登录时 session 存的是 userInfo,过滤器取的是 user,那过滤器始终认为 cutdown 没登录。解决方式很简单,常量统一命名,并且不要写死字符串,直接用LoginConstant.SESSION_USER之类的东西。
第三类:控制台报 ClassNotFoundException
现象是 IDE 能编译,一运行就报找不到某些类。原因是依赖的 jar 没有部署到 Tomcat。解决方式是检查 Artifacts 里的 Output Layout,确认 WEB-INF/lib 下包含项目引用的所有 jar 包。IDEA 中经常有 provided scope 的包没打进去,你可以在 lib 目录上右键 Add Copy of -> Library Files,把所有依赖添加进来。
第四类:数据库中文乱码
现象是页面上看到的是问号或乱码。原因一般有三处,一处是数据库连接 URL 没有加characterEncoding=utf-8参数,另一处是数据库表和字段的排序规则不是 utf8mb4,还有一处是 JSP 页面没有指定pageEncoding="UTF-8"。解决方式先改连接串,再把库表排序规则换成 utf8mb4,最后在 JSP 顶部统一加上<%@ page pageEncoding="utf-8" %>。
第五类:修改了代码但刷新页面看不到效果
原因是 Tomcat 没有热部署。解决方式是确认 Debug 模式运行,并且把 On frame deactivation 设置为 Update classes and resources。更保险的做法是改完静态资源后刷新浏览器清缓存,改完 Java 代码后重启 Tomcat。在 IDEA 中开启热部署不保险,重启才是王道。
5. 项目改造与工程化:从能跑的 Demo 变成拿得出手的作品
当你能把系统完整跑起来并搞清楚核心模块后,下一步需要考虑的是如何在效率和可维护性之间做出取舍。这里我给出三条具体的改造路径,分别对应框架升级、部署迁移、数据安全三个方向,也是把 Java Web 老系统从 demo 变成作品的通用路径。
5.1 抽公共 BaseServlet:大幅减掉冗余代码
老的 Servlet 写法通常是一个请求对应一个类,每增加一个功能就新建一个 Servlet。如果你要管理候选人端、管理端和岗位端,Servlet 数量会膨胀得很厉害。一位刚转正的开发者的常见做法是抽一个公共 BaseServlet,用反射代替新增映射:
public class BaseServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getParameter("method"); if (method == null || method.isEmpty()) { method = "execute"; } try { Method m = this.getClass().getMethod(method, HttpServletRequest.class, HttpServletResponse.class); String result = (String) m.invoke(this, req, resp); if (result != null) { if (result.startsWith("redirect:")) { resp.sendRedirect(req.getContextPath() + result.substring("redirect:".length())); } else { req.getRequestDispatcher(result).forward(req, resp); } } } catch (NoSuchMethodException e) { resp.sendError(404); } catch (Exception e) { throw new ServletException(e); } } }通过这个类,后续各个业务 Servlet 继承它,只写 dispatch 方法和真正逻辑,一个页面一张 Servlet 就能支持增删改查和导出等多个动作。注意 method 参数最好校验一下,避免反射注入问题。在处理请求时只允许把参数映射到明确存在的方法上,这等于做了一个白名单。
5.2 从 Tomcat 部署模式迁移到 Docker:替换环境但保留架构
生产环境不太可能让大家在 IDE 里点运行按钮,更常用的方式是打 WAR 包扔进独立的 Tomcat,或者做成 Docker 镜像。这里我推荐后者,因为不需要改代码,只需要本地构建镜像:
FROM tomcat:9.0.89-jdk8 MAINTAINER dev@example.com WORKDIR /usr/local/tomcat/webapps/ COPY target/resume_web.war /usr/local/tomcat/webapps/resume.war EXPOSE 8080 CMD ["catalina.sh", "run"]构建时注意,MySQL 不放进同一个容器里,而是使用宿主机的 MySQL 或单独的 MySQL 容器,通过环境变量连接。上面这种做法的核心原因是让应用与数据库分离,这样应用容器可以随版本频繁发布,数据库容器则保持固定状态,后续做数据备份和灾备都变得清晰。
5.3 密码存储:别再用明文或 MD5,至少换成加盐
我在审阅一个模拟的项目时,发现用户的密码表里直接存了 MD5 明文,这距离不安全已经非常近了。MD5 依然被大量用于文件校验,但不能用于密码存储,彩虹表很容易碰撞出常见口令。一个可行的升级方式是用加盐的 SHA-256 甚至 BCrypt:
public static String encrypt(String password, String salt) { String target = salt + password; for (int i = 0; i < 1024; i++) { target = DigestUtils.sha256Hex(target.getBytes(StandardCharsets.UTF_8)); } return target; }迭代 1024 次是为了提高破解成本。真正上线时我会建议直接使用 BCrypt 库,它的 salt 自动加入密文中,校验时也不需要单独保存盐值,省去额外的字段,对旧代码改动更小。这一项改动不大,但写在简历上就是“具备安全意识”,比堆砌几个功能点更打动面试官。
5.4 一键备份数据库的脚本
演示系统最怕的是数据库被人乱改导致无法恢复,建议在项目 bin 目录下加一个备份脚本,每天自动导出备份数据:
mysqldump -u backup_user -p --single-transaction --routines --triggers resume_db > /data/backup/resume_$(date +\%Y\%m\%d).sql find /data/backup -name "*.sql" -mtime +14 -delete这条命令利用--single-transaction,备份 InnoDB 表时不会锁表妨碍业务;--routines --triggers是把存储过程和触发器也备份下来。配合find清理 14 天前的旧备份,不用人工操心磁盘被备份文件占满。
以我参与过的多个招聘系统实际维护经验来看,这类 Java Web 简历系统最容易翻车的从来不是代码,而是数据库备份不到位和数据恢复不熟练。把备份脚本跑到监管要求,比花大把精力去优化简历列表查询速度更有价值。用户最崩溃的时刻永远是“昨天还能打开的简历列表,今天数据库没了”。所以每次给任何招聘类系统做完部署,我做的第一件必然的事就是把自动备份脚本配好,之后才敢说“系统上线了”这句话。这个习惯也延续到所有项目里,希望帮到你。
本文还有配套的精品资源,点击获取