多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Java Web绩效考评系统:Servlet+DAO分层架构与部署全解析

Java Web绩效考评系统:Servlet+DAO分层架构与部署全解析 简介这套基于Java Web的绩效考评系统项目包面向Java Web初学者、高校在校生以及需要人事数字化的企业管理者采用Servlet、JSP、JavaBean等经典技术栈完成员工绩效考核全流程。项目共包含509个文件压缩包大小约982KB除Java源码、JSP页面、class编译文件外还提供SQL数据库脚本、js/css/images前端素材、SVN元数据及项目配置文件导入Eclipse或IDEA后即可查看目录结构并进行调试运行。系统功能覆盖员工档案管理、绩效指标自定义、考核周期设置、上级评价录入、综合得分自动计算、可视化报表生成以及结果反馈模块同时包含权限管理设计思路。数据库脚本可快速初始化员工表、部门表、绩效标准表和评价记录表省去手动建表步骤。已有174人学习/下载方便开发者直观理解Java Web分层架构、JDBC数据库交互与考核流程的业务封装适合作为课程设计、毕业设计或中小企业绩效系统二次开发的参考模板。1. Java Web绩效考评系统Servlet DAO 的完整闭环在企业做绩效这件事最麻烦的从来不是打分这个动作本身而是每个考评周期里员工数据、指标权重、评分记录和部门横向对比怎么汇总到一起。这个 Java Web 绩效考评系统压缩包走的正是经典 Servlet DAO 分层结构压缩包里能看到 LoginServlet、ScoreingServlet、ProjectAddServlet 这些控制器类以及 UserDAOImpl、ScoreDAOImpl、ProjectDAOImpl 这几个数据访问实现属于非常典型的基于 Java 的 web 绩效项目。它能解决的问题很直接把员工管理、绩效指标设置、周期设定、评价录入、自动计算和简单的报表输出串成一个可以运行的闭环。适合两类人——一类是 Java Web 课程设计或者毕设阶段的学生需要一套能演示、能讲清楚技术点的完整项目另一类是中小企业里想用内网 Web 后台替代 Excel 做绩效打分的管理人员。下文我就从架构拆解、部署、核心业务逻辑、权限控制和踩坑点五个层面把它完整剥开。2. 压缩包结构与部署准备先让项目本地跑起来拿到绩效考评系统压缩包第一步不是急着打开 IDE 写代码而是先把文件分类看清楚。这个包里放着 WdatePicker.js.bak、若干 .class 文件、Servlet 源码和数据库配置文件它们的组织方式直接决定了你要不要补依赖、改路径、换版本。2.1 文件分类从 .class 反推包结构与技术栈把压缩包解压后观察文件列表能发现几类信息。首先是 WdatePicker.js.bak这是 My97 日期选择器的一个备份文件说明前端用了日期控件来做考评周期的起止时间选择。其次是 LoginServlet.class、ScoreingServlet.class、ProjectAddServlet.class、UserUpdateMyinfoServlet.class、TZUpdateServlet.class、RPUpdateServlet.class 这一批编译后的字节码文件它们对应的是控制器层。提示压缩包里同时存在 .class 和 .java 时优先以 .java 源码为准。.class 文件只能用于反编译对照不能作为修改基础。从类名命名可以反推包结构。LoginServlet 负责登录入口ScoreingServlet 负责评分录入ProjectAddServlet 负责绩效项目或指标的新增UserUpdateMyinfoServlet 负责用户修改个人信息TZUpdateServlet 和 RPUpdateServlet 分别对应调整分更新和评语Report更新。再加上 UserDAOImpl、ScoreDAOImpl、ProjectDAOImpl这套代码的层次很清楚Servlet 接收请求调用 DAO 接口DAO 实现类操作数据库。没有 Spring、没有 MyBatis是原生 Servlet JDBC 的经典教学式结构。% page contentTypetext/html;charsetUTF-8 languagejava % html head title绩效考评系统/title script languagejavascript srcjs/WdatePicker.js/script /head body form actionscoreing methodpost 考评周期input typetext nameperiod onclickWdatePicker({dateFmt:yyyy-MM}) readonly input typesubmit value进入评分 /form /body /html这段 JSP 的核心是引入 WdatePicker.js 日期控件并绑定到考评周期输入框。dateFmt 设为 yyyy-MM代表以月为最小粒度的考评周期。这里我把 action 指向 scoreing 这个 Servlet 映射实际项目中如果你看到的是 scoreingServlet需要到 web.xml 里核对 url-pattern这是我部署时经常遇到的前后端路径不一致问题。2.2 数据库初始化员工表、部门表、绩效标准表与评价记录表建表脚本是这套系统能不能跑起来的前提。绩效考评系统的典型关系型数据库设计包含四类表。CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(64) NOT NULL, real_name VARCHAR(50), department_id INT, role TINYINT DEFAULT 1 COMMENT 1员工, 2主管, 3管理员 ); CREATE TABLE department ( id INT PRIMARY KEY AUTO_INCREMENT, dept_name VARCHAR(100) NOT NULL ); CREATE TABLE project ( id INT PRIMARY KEY AUTO_INCREMENT, project_name VARCHAR(100) NOT NULL, weight DECIMAL(5,2) COMMENT 指标权重如0.30表示30%, target_value DECIMAL(10,2) COMMENT 目标值, actual_value DECIMAL(10,2) COMMENT 实际完成值, dept_id INT, period VARCHAR(20) COMMENT 考评周期如2025-Q1 ); CREATE TABLE score ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT, project_id INT, score_value DECIMAL(5,2) NOT NULL, adjust_value DECIMAL(5,2) DEFAULT 0 COMMENT 调整分, comment TEXT, score_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP );user 表的核心是 role 字段它决定了登录后能看到哪些菜单。project 表里的 weight 是权重字段系统自动计算综合得分时就是拿 score_value 乘以 weight。score 表里我额外解释一下 adjust_valueTZUpdateServlet 对应的就是调整分回写比如员工对某个指标得分有异议主管可以给一个修正分差。性能方面如果记录量大建议给 score 表的 user_id 和 project_id 加联合索引否则每次汇总报表都会触发全表扫描这个后面在验证阶段会讲到。2.3 本地部署流程Tomcat 版本选择与数据源配置这个项目既然是原生 Servlet部署目标就是一个标准的 Tomcat Web 容器。我的做法是优先选 Tomcat 8.5 或 9.0注意 Servlet 版本与 web.xml 头部的对应关系。如果 web.xml 里声明的是 Servlet 3.0 规范Tomcat 7 也能跑但建议避免使用过低的版本。web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd version3.1 servlet servlet-nameLoginServlet/servlet-name servlet-classcom.performance.servlet.LoginServlet/servlet-class /servlet servlet-mapping servlet-nameLoginServlet/servlet-name url-pattern/login/url-pattern /servlet-mapping servlet servlet-nameScoreingServlet/servlet-name servlet-classcom.performance.servlet.ScoreingServlet/servlet-class /servlet servlet-mapping servlet-nameScoreingServlet/servlet-name url-pattern/scoreing/url-pattern /servlet-mapping welcome-file-list welcome-filelogin.jsp/welcome-file /welcome-file-list /web-app这段 web.xml 配置里最需要注意的是 servlet-class 的完整限定名必须和实际包路径一致。很多新手部署项目时报 404 或 500八成是这里的包名写错或者 url-pattern 里的路径和 JSP 表单提交的 action 不一致。我在部署时通常会全局搜索一下 form 标签的 action 属性再和 web.xml 的 url-pattern 逐一比对这样能省下大量查日志的时间。数据源方面常见做法是在 Tomcat 的 context.xml 里配置 JNDI 数据源也可以直接用 JDBC 工具类加载驱动。Context Resource namejdbc/performanceDB authContainer typejavax.sql.DataSource maxTotal20 maxIdle5 maxWaitMillis10000 usernameroot password123456 driverClassNamecom.mysql.jdbc.Driver urljdbc:mysql://localhost:3306/performance?useUnicodetruecharacterEncodingutf8/ /Context这里关键参数有三个。第一个是 maxTotal控制连接池最大连接数绩效考评系统这种内部系统一般 20 就够了。第二个是 maxWaitMillis如果设为 10000意味着连接池耗尽时最多等待 10 秒超过就抛异常。第三个是 url 里的 characterEncodingutf8这个参数决定中文会不会乱码很多跨平台部署翻车都是漏了它。3. 核心业务逻辑绩效得分计算与 DAO 层实现绩效考评系统一旦跑起来核心就看两件事得分怎么自动算评分记录怎么落库。这一章我会从得分模型开始再深入到 ScoreDAOImpl 和调整分更新的具体实现。3.1 定量与定性指标的权重计算逻辑考评系统最常见的得分算法是加权求和。先给每个考评项目设定 weight 字段再录入 actual_value 与 target_value 对比得到完成率最后乘以权重得到加权得分。计算公式可以概括为综合得分 Σ完成率 × 权重 调整分。如果一个员工的三个指标分别是销售额完成率 1.1权重 0.5、客户满意度完成率 0.95权重 0.3、考勤完成率 1.0权重 0.2那么不加调整分的情况是 1.1×0.5 0.95×0.3 1.0×0.2 1.035再换算成百分制就是 103.5 分。为了把这一逻辑落到数据库查询里常见做法是在 ScoreDAOImpl 里做一次联表聚合。3.2 ScoreDAOImpl 的聚合查询与结果映射ScoreDAOImpl 承担了最重的数据访问工作。该类里除了基本的增删改查方法还有一个核心的 getTotalScore 方法它把 user、project、score 三张表关联起来。public class ScoreDAOImpl implements ScoreDAO { private JdbcUtils utils new JdbcUtils(); public ListScoreVO getTotalScore(String period, String deptId) { ListScoreVO list new ArrayList(); String sql SELECT u.real_name, d.dept_name, SUM(p.weight * s.score_value) AS total_score, SUM(s.adjust_value) AS adjust_total FROM score s LEFT JOIN user u ON s.user_id u.id LEFT JOIN department d ON u.department_id d.id LEFT JOIN project p ON s.project_id p.id WHERE s.period ? (deptId ! null !deptId.isEmpty() ? AND u.department_id ? : ) GROUP BY u.id, d.dept_name; try (Connection conn utils.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, period); if (deptId ! null !deptId.isEmpty()) { ps.setString(2, deptId); } try (ResultSet rs ps.executeQuery()) { while (rs.next()) { ScoreVO vo new ScoreVO(); vo.setRealName(rs.getString(real_name)); vo.setDeptName(rs.getString(dept_name)); vo.setTotalScore(rs.getBigDecimal(total_score)); vo.setAdjustTotal(rs.getBigDecimal(adjust_total)); list.add(vo); } } } catch (SQLException e) { e.printStackTrace(); } return list; } }这段代码的逻辑分三层。第一层是 SQL 拼接period 必填deptId 可空所以用条件判断来决定是否追加过滤条件。第二层是 PreparedStatement 参数填充注意参数索引从 1 开始前面 condition 里如果加了 deptId第二个占位符才有效。第三层是结果集到 ScoreVO 的映射这种手工映射虽然写起来繁琐但对学习阶段的人来说反而更直观能看明白每一列去哪了。使用 LEFT JOIN 的原因是要保留没有评分记录的员工。如果用 INNER JOIN一个员工没有任何 score 记录时汇总结果里就会少掉这个人绩效报表就不完整了。这一点在写报表类功能时非常容易忽略。3.3 TZUpdateServlet 与 RPUpdateServlet调整分与评语的数据回写TZUpdateServlet 和 RPUpdateServlet 这两个类名在压缩包里很显眼。TZ 大概率是“调整”的拼音缩写RP 大概率是“评语”或“Report”的缩写。它们的作用场景不一样。WebServlet(/tzupdate) public class TZUpdateServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); String scoreId req.getParameter(scoreId); String adjustValue req.getParameter(adjustValue); if (scoreId null || adjustValue null) { resp.sendError(400, 参数缺失); return; } ScoreDAO dao new ScoreDAOImpl(); boolean flag dao.updateAdjustValue( Integer.parseInt(scoreId), new BigDecimal(adjustValue) ); if (flag) { resp.sendRedirect(report.jsp); } else { req.setAttribute(msg, 调整分更新失败); req.getRequestDispatcher(error.jsp).forward(req, resp); } } }这里要特别注意的是 BigDecimal 的构造问题。从 request 里拿到的 adjustValue 是字符串直接用 new BigDecimal(String) 是安全的但如果你写成 new BigDecimal(Double.parseDouble(adjustValue))就可能会引入浮点精度误差。调整分是直接改员工最终绩效结果的数据精度问题绝对不能马虎。RPUpdateServlet 的逻辑与之类似差别在于它更新的字段是 comment也就是评语区。评语一般较长从前端传过来时需要注意 getParameter 对长度没有内在限制但数据库字段如果是 TEXT 类型超过 65535 字节就会插入失败。这个问题在数据量大的时候会出现我一般会在 DAO 层做一次长度校验。4. 权限管理UserDAOImpl 的角色控制与防越权设计Web 系统里的权限控制说到底是两件事第一没登录的人能不能访问第二登录了但角色不够的人能不能越权操作。这个项目里的 UserDAOImpl 就是整个权限体系的底层支撑。4.1 基于 Session 的登录态与角色区分由于这是一个原生 Servlet 项目没有 Spring Security所以权限控制主要在 LoginServlet 和各个业务 Servlet 里手动完成。登录成功之后把当前用户对象塞进 session后续请求都先查 session 再放行。public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String username req.getParameter(username); String password req.getParameter(password); req.setCharacterEncoding(UTF-8); UserDAO dao new UserDAOImpl(); User user dao.findByUsernameAndPassword(username, password); if (user ! null) { HttpSession session req.getSession(); session.setAttribute(currentUser, user); session.setMaxInactiveInterval(30 * 60); resp.sendRedirect(index.jsp); } else { req.setAttribute(msg, 用户名或密码错误); req.getRequestDispatcher(login.jsp).forward(req, resp); } } }setMaxInactiveInterval(30 * 60) 的含义是 session 最大空闲 30 分钟超过就作废。企业绩效系统一般不会太长因为涉及敏感数据。角色区分通常通过 User 对象里的 role 字段实现页面端用 JSTL 或 scriptlet 来判断。c:if test${sessionScope.currentUser.role 2} a hrefproject_add.jsp新建绩效指标/a /c:if c:if test${sessionScope.currentUser.role 3} a hrefuser_manage.jsp员工管理/a /c:if这种写法的问题在于只做了页面隐藏没有做后端拦截。会翻车的地方是如果用户直接访问 project_add.jsp 或者对应的 Servlet系统不会识别他到底有没有权限。常见做法是写一个 Filter统一拦截需要鉴权的路径。4.2 密码存储与登录校验了解散列的必要性UserDAOImpl 里如果直接拿明文密码比对那就只能用于本地学习不能上生产。我一般会在代码里加入 MD5 或 BCrypt 哈希后再存储。public User findByUsernameAndPassword(String username, String password) { String hashed MD5Util.md5(password); String sql SELECT * FROM user WHERE username ? AND password ?; // 执行查询并返回结果 }如果数据库里存的是明文一旦数据泄露所有员工账号全裸奔。MD5 虽然不够强但至少能阻挡大部分意外泄露。对于课设项目请假条和答辩讲解用的登录模块写成这样是可以接受的但如果是真实企业部署建议至少换成 BCrypt。一个我常见的越权漏洞是员工可以通过改 URL 里的参数比如请求 /user/update/3把 id 改成别人的编号从而修改他人信息。解决办法是在 Servlet 里校验当前登录用户的 id 与目标 id 是否一致或者判断角色是否为管理员否则直接返回 403。这个防越权检查是绩效考评系统里最容易被忽视但又最关键的一道防线。5. 部署避坑Tomcat 版本、MySQL 编码与日期组件的常见排查这部分是实操记录。我先后在不同机器上部署过这套类似结构的 Java Web 系统踩过不少坑有些问题几乎每个初学者都会遇到。下面按照现象到原因再到解决的格式列出最常见的四条。5.1 404 错误现象访问 login.jsp 能打开但提交表单后提示 404 或页面找不到。原因web.xml 中 Servlet 映射的 url-pattern 与 form 表单 action 里的路径不一致比如 action 写了 scoreing 而配置是 /scoreing。解决以斜杠开头统一配置。在 web.xml 的 url-pattern 里写成 /scoreingJSP 里也写 scoreing或者统一用绝对路径。调试时用浏览器的 F12 查看 Network 面板能快速看出实际请求路径。5.2 中文乱码现象评分页面显示中文正常但保存到数据库后报表里出现乱码。原因MySQL 连接 url 缺少 characterEncoding数据库表默认排序规则不是 utf8或者 JSP 页面缺失 contentType 声明。解决连接 url 加 useUnicodetruecharacterEncodingutf8建表时统一用 DEFAULT CHARSETutf8mb4JSP 顶部保留 contentTypetext/html;charsetUTF-8。这三处都对了中文乱码基本可以避免。5.3 WdatePicker.js 点击没反应现象部署后点击日期输入框没有弹出日历控件浏览器控制台报找不到 WdatePicker。原因WdatePicker.js 文件没有拷贝到正确目录或者引用的路径大小写不对。压缩包中文件名是 WdatePicker.js.bak需要先重命名为 WdatePicker.js 再放进 js 目录。解决解压后立刻把所有 .bak 后缀改回正常文件后缀统一放到 WebContent/js 下。引用时注意项目路径推荐用 ${pageContext.request.contextPath}/js/WdatePicker.js防止路径在多级目录部署时出问题。5.4 连接池配置现象系统运行几个小时后页面偶尔报连接超时。原因连接池里的连接被 MySQL 主动断开但池中连接没有自动剔除。解决如果是 JNDI 数据源配置设置 testWhileIdletrue 和 validationQuerySELECT 1。如果是直接用 JDBC 工具类确保连接用完后在 finally 块里关闭。成功部署后登录系统、创建绩效指标、录入评分、生成汇总报表整个流程在一个小时内是可以走通的。这一步走通了后面再去研究更细的业务优化才有意义。6. 进阶验证绩效周期自动结转与报表导出技巧项目跑通之后还有一个经常被忽视的需求——绩效周期自动结转。每个季度末管理员需要把上一个周期的数据归档新的周期从零开始同时把汇总报表导出为 Excel 或 CSV 发给各部门确认。这里给你一个比较实用的做法在 ScoreDAOImpl 里加一个 archivePeriod 方法用一个事务完成两步操作——先把 score 表里当前周期的记录复制到 score_archive 表然后删除 score 表中的旧记录。public boolean archivePeriod(String oldPeriod, String newPeriod) { String copySql INSERT INTO score_archive (user_id, project_id, score_value, adjust_value, comment, period) SELECT user_id, project_id, score_value, adjust_value, comment, ? FROM score WHERE period ?; String deleteSql DELETE FROM score WHERE period ?; Connection conn null; try { conn utils.getConnection(); conn.setAutoCommit(false); try (PreparedStatement ps conn.prepareStatement(copySql)) { ps.setString(1, newPeriod); ps.setString(2, oldPeriod); ps.executeUpdate(); } try (PreparedStatement ps conn.prepareStatement(deleteSql)) { ps.setString(1, oldPeriod); ps.executeUpdate(); } conn.commit(); return true; } catch (SQLException e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } return false; } finally { if (conn ! null) { try { conn.setAutoCommit(true); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }这里有几个参数值得细看。setAutoCommit(false) 的含义是关闭自动提交让 copy 和 delete 变成一个原子操作。如果先删除旧数据再复制一旦复制失败这季度数据就全没了所以必须先复制后删除并且用 try-catch 把回滚兜住。报表导出也可以用 CSV 方式实现Tomcat 内置的 response 设置 CSV 下载格式即可。resp.setContentType(text/csv; charsetUTF-8); resp.setHeader(Content-Disposition, attachment; filenameperformance_ System.currentTimeMillis() .csv);这种导出方式对中文处理的可靠性较高因为 UTF-8 编码在 Excel 中只要不是太老的版本都能默认识别。如果想做 Excel 样式再引入 POI但 CSV 方式在绩效数据导出场景里完全够用。从那以后我每次部署这类 Java Web 系统都会强制走一遍“路径核对 → 编码验证 → 权限检查 → 周期归档”这四个步骤。特别是 WdatePicker 后缀和 web.xml 路径这两个细节真的是一步一个坑。希望这篇文章能帮你把这些坑提前绕过去顺利把项目跑起来也希望这份绩效考评系统的源码能成为你学习 Java Web 的一个扎实起点。希望帮到你。本文还有配套的精品资源点击获取
返回列表