简介:Java Web绩效考评系统是一套面向企业人事管理场景的可运行项目,采用Servlet、JSP、Spring MVC及Hibernate/MyBatis等主流技术栈,覆盖员工管理、绩效指标设置、考核周期设定、评价录入、自动计分、报表生成与反馈闭环等环节,适合Java Web学习者、毕业设计者以及需要快速搭建绩效管理原型的技术人员参考。压缩包共509个文件,核心包含91个Java源文件、91个class文件、39个JSP页面以及SQL数据库脚本、配置文件等,包体约982KB,可完整还原项目结构并用于二次开发。资源包内置员工表、部门表、绩效标准表等数据库设计,以及登录、权限、评分处理等核心Servlet代码,有助于理解企业级Web应用的请求处理、数据持久化与业务分层。目前已有175人学习下载,适合作为理解Java EE经典分层架构及企业绩效考核流程的实践案例。
1. 绩效考评系统 java web 项目:一份 .rar 源码背后要解决的问题
第一次接手“绩效考评系统 java web”这份源码时,我没有把它当成一个简单的课程设计来看。这类基于 Java 的绩效系统,核心不是登录注册,而是“考核模板谁定、考核任务怎么派、分数怎么汇总、结果谁能看”这一串流程。它解决的是企业内部季度考核、年度考核从线下表格搬到线上的问题,适合用来做毕业设计、公司内网小工具,也适合初级 Java 开发用来复盘别人怎么组织一个完整 Web 工程。很多人下载 .rar 压缩包后第一个动作就是解压导入 IDE,结果被旧框架、乱码、驱动版本折腾到劝退。这篇文章会照着“先看懂设计,再跑起来,最后改得动”的顺序,把这条路走通。
2. 绩效考评系统的核心设计:角色权限、考核指标与 MySQL 表结构怎么搭
2.1 角色与考核流程:先把“谁考谁、怎么算分”定下来
大部分绩效考评系统的源码,无论界面多简陋,角色都不会少于三类:系统管理员、考评人、被考评人。管理员负责配置考核模板和指标权重,考评人通常是部门主管或指定同事,被考评人就是普通员工。如果你接手的项目里还多了一个“人事专员”角色,那多半是用来审核考核结果和发起申诉流程的,我一般会把它合并到管理员权限里,避免权限判断复杂化。
考核流程可以压缩成四步,理解了这四步,再看任何一份 java web 绩效项目代码都会快很多:第一步,管理员创建考核模板,比如“2024 年 Q4 绩效”,并给模板挂上若干个考核指标;第二步,管理员发起考核任务,指定哪些人参与打分,通常一个任务会有多个考评人和多个被考评人,形成一张打分关系表;第三步,考评人登录后看到分配给自己的待办,逐项打分并提交;第四步,系统按指标权重汇总分数,生成考核结果,被考评人可以查看自己的最终得分和分项得分。
这里有一个容易踩的坑:很多初学者把“角色”和“菜单权限”做成一棵树,每个页面都去查数据库判断能否访问,最后整个项目堆满了权限判断代码,性能还差。常见做法是直接在用户表上加一个role_type字段,用整数区分管理员、考核人、员工,然后在过滤器里做拦截。这样做对绩效考评这种角色固定、页面不超过 20 个的系统完全够用,后续要扩展成 RBAC 也不难,保留用户表和角色表,再把菜单表加上就行。
还有一个必须先定下来的事:分数计算规则。是取平均分,还是按考评人权重算?要不要去掉一个最高分和一个最低分?绩效考评系统最常见的规则是“按权重加权平均,且去掉最高最低分后再平均”,因为部门主管打分容易偏高或偏低,去掉极端值能减少关系分的影响。这个规则直接决定了你数据库表要怎么设计、Java 代码里要怎么写计算逻辑。我见过不少源码把“去掉最高最低分”写死在 SQL 里,导致后来要调规则就得重构,所以更推荐在 Java 层做计算,SQL 只负责取原始分。
2.2 五张核心数据表:用 SQL 把考核模板和评分结果落库
如果让我重新搭一套绩效考评系统的表,我绝不会一张表装下所有数据。比较合理的拆分是五张表:用户表、考核模板表、考核指标表、考核任务表、考核评分表。你可以先看看手里 .rar 源码里的sql目录,一般会有一个init.sql或者performance.sql文件,如果它把这五张表拆得干净,说明作者的基本功还可以;如果只有两三张表,那就得小心了,后续扩展会很痛苦。
先看用户表,这里用最简设计,适合演示也适合二次开发:
CREATE TABLE sys_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, real_name VARCHAR(50) NOT NULL, dept_id INT, role_type TINYINT NOT NULL DEFAULT 3 COMMENT '1=管理员 2=考评人 3=员工', status TINYINT DEFAULT 1 COMMENT '1=启用 0=停用', create_time DATETIME DEFAULT CURRENT_TIMESTAMP );密码字段用 64 位是为了能放 SHA-256 或 MD5 加盐后的结果,不建议明文存储。role_type是权限判断的核心,过滤器里直接用这个字段做判断。dept_id是为后续的“行级权限”预留的,如果你做的是企业内网部署,部门主管只能看到本部门员工的考核数据,这个字段就能派上用场。
接着是考核模板表:
CREATE TABLE assess_template ( id INT PRIMARY KEY AUTO_INCREMENT, template_name VARCHAR(100) NOT NULL, year INT NOT NULL, quarter INT NOT NULL, status TINYINT DEFAULT 0 COMMENT '0=草稿 1=已发布 2=已归档', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_template (year, quarter) );status字段很重要,已发布的模板不允许再修改指标,不然考核已经开始打分,指标却被改了,数据就乱套了。year + quarter加唯一约束,保证一个季度只能有一套模板,避免管理员手滑创建两套。
考核指标表要挂到模板下:
CREATE TABLE assess_indicator ( id INT PRIMARY KEY AUTO_INCREMENT, template_id INT NOT NULL, indicator_name VARCHAR(100) NOT NULL, weight DECIMAL(5,2) NOT NULL DEFAULT 0, max_score DECIMAL(5,2) NOT NULL DEFAULT 100, sort_no INT DEFAULT 0, KEY idx_template (template_id) );weight表示权重,0 到 1 之间的小数,比如 0.3 代表这个指标占总成绩的 30%。这里把权重存在指标行里,是最直观的做法,查询模板时一次拿到所有指标;没有必要为了“规范化”拆成模板表和模板指标关系表,因为绩效考核的指标就是从属于模板的,不会有多个模板共享指标的情况。
考核任务表用来记录“谁考核谁”:
CREATE TABLE assess_task ( id INT PRIMARY KEY AUTO_INCREMENT, template_id INT NOT NULL, assessor_id INT NOT NULL COMMENT '考评人id', target_user_id INT NOT NULL COMMENT '被考评人id', status TINYINT DEFAULT 0 COMMENT '0=待打分 1=已提交', submit_time DATETIME, KEY idx_template (template_id), KEY idx_assessor (assessor_id), KEY idx_target (target_user_id) );这张表是整个系统的连接点:管理员发起考核时,按“考评人-被考评人”关系批量插入任务。考评人登录后,WHERE assessor_id = 当前用户id AND status = 0就是他的待办列表。记住要加submit_time,报表统计时能用来判断是否逾期。
最后是评分表,它存的是考评人对被考评人每一个指标的打分:
CREATE TABLE assess_score ( id INT PRIMARY KEY AUTO_INCREMENT, task_id INT NOT NULL, indicator_id INT NOT NULL, score DECIMAL(5,2) NOT NULL, comment VARCHAR(500), UNIQUE KEY uk_task_indicator (task_id, indicator_id) );这张表设计成按task_id和indicator_id唯一,防止同一个人对同一个指标重复打分。综合成绩不用存,每次需要时从assess_score现算就行,一是保证数据一致性,二是绩效规则调整时不用改历史数据。如果你担心查询性能,后期可以加一张assess_result汇总表,但建议只在模板归档时生成一次。
这五张表基本上把一个绩效考评系统的数据底盘立住了。你在跑任何一份 .rar 源码前,先对照这个结构看它的建表 SQL,马上就能判断这个项目值不值得继续读下去,也能知道它当年的设计水平在什么位置。
3. 用 Java 实现绩效考评:评分汇总、权限拦截与页面交互的关键代码
3.1 加权评分与去最高最低分:ScoreCalculator 的写法与参数
绩效考评的计算逻辑是整个 Java 后端最值得复用的部分。我见过的情况是,原始项目把计算逻辑放在 JSP 页面里的不少,表单提交后直接用脚本算出结果再输出。这种写法很不好维护,页面上还动不动就报空指针。合理的做法是单独写一个ScoreCalculator类,里面只干一件事:传入原始分数,返回汇总结果。这样跑单元测试也好,改规则也好,都不会波及页面。
下面这段代码是一个比较典型的加权计算实现,去掉最高最低分后再按权重加权平均:
import java.math.BigDecimal; import java.math.RoundingMode; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * 绩效评分计算器 * 规则:去掉一个最高分和一个最低分,再按指标权重加权平均 */ public class ScoreCalculator { /** * @param scores 某个被考评人在某个指标下的所有打分,不能为空 * @param weights 所有指标的权重,顺序要与指标列表保持一致 * @return 汇总后的综合得分 */ public static BigDecimal calculate(List<BigDecimal> scores, List<BigDecimal> weights) { if (scores == null || scores.size() < 3) { // 打分人数少于3时,去掉最高最低分会丢失样本,直接取平均 return average(scores); } List<BigDecimal> sorted = new ArrayList<>(scores); Collections.sort(sorted); // 去掉一个最高分和一个最低分 BigDecimal sum = BigDecimal.ZERO; for (int i = 1; i < sorted.size() - 1; i++) { sum = sum.add(sorted.get(i)); } BigDecimal avg = sum.divide( BigDecimal.valueOf(sorted.size() - 2), 2, RoundingMode.HALF_UP ); // 加权计算,weights 的和一般是1 BigDecimal result = BigDecimal.ZERO; for (int i = 0; i < weights.size(); i++) { result = result.add(avg.multiply(weights.get(i))); } return result.setScale(2, RoundingMode.HALF_UP); } private static BigDecimal average(List<BigDecimal> scores) { BigDecimal sum = BigDecimal.ZERO; for (BigDecimal s : scores) { sum = sum.add(s); } return sum.divide(BigDecimal.valueOf(scores.size()), 2, RoundingMode.HALF_UP); } }这里有几个参数值得认真对待。scores.size() < 3时不能直接去最高最低分,否则两个考评人的数据会被削成一个,等于没算;权重列表weights的元素顺序必须和数据库查出来的指标顺序一致,所以在 Service 层查询指标时要按sort_no排序,否则加权就是错的;setScale(2, RoundingMode.HALF_UP)保留两位小数,是给报表展示用的,数据库里存储时需要先用BigDecimal处理,不能在 SQL 里做除法截断。
这个类可以继续扩展:如果规则要改成“不去最高最低分”,直接把中间那段排序逻辑删掉即可;如果某个指标要求所有人得分都一样,那是模板配置的事,计算器不用动。保持计算器只依赖List<BigDecimal>入参,是它能够被复用的关键。
3.2 登录与角色权限:用 Filter 拦截未授权请求,而不是每个页面判断
绩效考评系统的页面权限,常见做法是用 Filter 统一拦截,谁登录了、能不能访问某个功能,在过滤器里判断完,Servlet 里就只管业务。这么做能少写很多重复代码,也可以避免新人把“判断是否登录”写在每个 JSP 顶上。
看下面这段过滤器实现:
import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; public class AuthFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; HttpSession session = req.getSession(false); // 未登录用户一律跳转登录页 if (session == null || session.getAttribute("loginUser") == null) { resp.sendRedirect(req.getContextPath() + "/login.jsp"); return; } // 已经登录,但访问路径有角色要求时,再做一次角色校验 Integer roleType = (Integer) session.getAttribute("roleType"); String uri = req.getRequestURI(); if (uri.contains("/admin/") && roleType != 1) { resp.sendError(HttpServletResponse.SC_FORBIDDEN); return; } if (uri.contains("/assessor/") && roleType > 2) { resp.sendError(HttpServletResponse.SC_FORBIDDEN); return; } chain.doFilter(request, response); } }这段代码的核心点是:只在 URL 路径包含特定目录时检查角色,而不是对每个 URL 都查一遍数据库。绩效考评系统的页面可以按角色分成三个目录:/admin/管理员用,/assessor/考评人用,/employee/员工查询用。这种目录划分配合 Filter 拦截,清晰且不容易漏判。
还有一个细节要注意:/employee/这样的员工路径不能让员工通过修改 URL 去访问别人的考核结果,因为 URL 里很可能带着targetUserId=2这样的参数。过滤器只能保证“登录了”,不能保证“看的是自己的数据”,所以在 Servlet 里还要再校验一次目标对象和当前登录用户是否匹配。在绩效这种场景里,这就是真正的行级权限控制:部门主管只能看到本部门员工的数据,员工只能看自己的数据。我一般会在 Service 层做一个checkAccess(currentUser, targetUserId)方法,返回布尔值,避免把校验逻辑散落在各个 Servlet 里。这个校验方法本身也要写成单元测试覆盖住,因为绩效数据一旦泄露,挨投诉事小,考核公平性被质疑才是大问题。
3.3 JSP 表单如何把考核结果送到 Servlet:一次典型的前后端交互
旧式 Java Web 项目的页面交互,最典型的就是 JSP 表单 POST 到 Servlet,Servlet 再调 Service 落库。绩效考评里的打分页面就是这样:页面展示被考评人和一系列指标,考评人填分数后提交。你要能看懂这层交互,才看得懂绝大多数 .rar 源码。
表单部分长这样:
<form action="${pageContext.request.contextPath}/score/submit" method="post"> <input type="hidden" name="taskId" value="${task.id}" /> <input type="hidden" name="targetUserId" value="${task.targetUserId}" /> <c:forEach items="${indicators}" var="ind" varStatus="status"> <div class="indicator-row"> <label>${ind.indicatorName}(权重 ${ind.weight})</label> <input type="number" name="score_${ind.id}" min="0" max="${ind.maxScore}" required /> </div> </c:forEach> <button type="submit">提交考核结果</button> </form>注意每个输入框的name是score_指标id,比如score_3、score_5,这样在 Servlet 端能直接通过指标 id 识别分数,比前端用数组再人工对齐要可靠得多。taskId和targetUserId是隐藏字段,告诉后端这条打分记录属于谁。这里看起来有点把业务参数暴露在页面上,但旧项目里这是常规操作,代价是你需要在 Servlet 里校验这两个参数和登录用户的关系。
Servlet 端接收参数的写法:
@WebServlet("/score/submit") public class ScoreSubmitServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Integer taskId = Integer.valueOf(req.getParameter("taskId")); Integer targetUserId = Integer.valueOf(req.getParameter("targetUserId")); Integer currentUserId = (Integer) req.getSession().getAttribute("userId"); // 防止考评人替别人提交或提交不存在的任务 if (!scoreService.isTaskAssignedToMe(taskId, currentUserId)) { resp.sendError(403); return; } // 动态从参数里读取每个指标的分数 Map<Integer, BigDecimal> scoreMap = new HashMap<>(); for (String paramName : req.getParameterMap().keySet()) { if (paramName.startsWith("score_")) { Integer indicatorId = Integer.valueOf(paramName.substring(6)); BigDecimal score = new BigDecimal(req.getParameter(paramName)); scoreMap.put(indicatorId, score); } } scoreService.submitScores(taskId, targetUserId, scoreMap); resp.sendRedirect(req.getContextPath() + "/assessor/pending.jsp"); } }这段代码的亮点是“遍历参数名,以前缀识别业务字段”,即使指标数量在模板调整后变化了,Servlet 逻辑也不用改。你可以看到参数在 Java 侧变成了一个Map<Integer, BigDecimal>,这个 Map 正好可以作为上一节ScoreCalculator计算前的原始数据来源。
要注意的是req.getParameterMap()返回的是只读 Map,不能直接往里放数据;new BigDecimal(String)一定要捕获NumberFormatException,因为用户可能在输入框里填了非数字内容。我一般在 Service 的submitScores方法里先做一次scoreMap完整性检查,比对模板指标数量和传入指标数量,少了就返回“还有指标未打分”,多了就报“非法提交”。这步不做的话,考评人提交一个分数缺失的考核,结果算出来就是错的,非常难以排查。
4. 把 .rar 源码跑起来:从解压到 Tomcat 部署的完整步骤
4.1 解压后先认清目录结构:src、WebRoot、sql、lib 各自做什么
你下载到的通常是一个绩效考评系统—java web.rar压缩包,用解压工具解开后会看到至少四个目录或文件:src、WebRoot、sql和一堆.jar包(也可能在一个lib文件夹里)。在往 IDE 里导入之前,先手动点开看一眼,能帮你判断项目基于什么框架。
标准目录大概长这样:
绩效考评系统/ ├── src/ │ ├── com/company/action/ # Action 或 Servlet 类 │ ├── com/company/dao/ # 数据访问层 │ ├── com/company/service/ # 业务逻辑层 │ └── com/company/util/ # 工具类 ├── WebRoot/ │ ├── WEB-INF/ │ │ ├── web.xml │ │ └── lib/ # 项目依赖的 jar 包 │ ├── admin/ # 管理员页面 │ ├── assessor/ # 考评人页面 │ ├── employee/ # 员工查询页面 │ ├── css/ │ └── js/ ├── sql/ │ └── init.sql └── README.txt(可能有,也可能没有)如果src下面有一堆struts.xml或applicationContext.xml配置文件,那多半是 SSH(Struts + Spring + Hibernate)组合,SSH 项目的启动配置比纯 Servlet 要多;如果只有web.xml和 Servlet 类,那处理起来会轻松很多。无论哪种,先别急着改代码,下一步要做的是把数据库初始化跑通。
有一点提前给你打预防针:很多源码包自带的lib里只有少量 jar,而且版本很旧;项目能不能编译跑起来,很大程度取决于这些 jar 是否齐全。我一般会对照WEB-INF/lib下的文件,如果要用的 jar 缺失,再去 Maven 仓库按坐标下载同版本,不建议直接拿最新版,旧代码经常和新版本不兼容。
4.2 数据库初始化与连接配置:建库、导数据、改 jdbc.properties
几乎每个 java web 项目都会带一个 SQL 脚本,常见文件名是init.sql或performance.sql。打开看一眼,确认里面包含CREATE DATABASE语句。有些脚本只建表不建库,那你就得手动先建库。我习惯首先用它执行一遍,而不是自己手工创建表,因为脚本里可能还包含测试数据,没有那批数据,登录页面验证不了。
执行脚本我经常用命令行,环境变量配置好的情况下最直接:
mysql -uroot -p < sql/init.sql如果你的 MySQL 装在远程服务器上,就加-h指定主机;如果脚本里没有指定USE语句,进库后先执行USE performance_db;再跑脚本。这个脚本执行完之后,需要确认一下库里有没有用户表、考核模板表这些核心表,以及有没有插入管理员账号。管理员账号一般就写在 SQL 脚本里,比如INSERT INTO sys_user ... role_type = 1。如果脚本里不放初始账号,那项目启动后你没法登录,只能自己往表里插一条管理员记录。
接下来改数据库连接配置。源码包里一般会有一个jdbc.properties文件,在src根目录或resources目录下:
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/performance_db?useUnicode=true&characterEncoding=utf8 jdbc.username=root jdbc.password=123456这里最需要关注的是编码参数:useUnicode=true&characterEncoding=utf8一定要保留,否则页面提交中文到数据库后会乱码。如果你的 MySQL 是 8.x,上面的com.mysql.jdbc.Driver已经不是默认驱动了,得改成com.mysql.cj.jdbc.Driver,这个问题在下一章避坑里还会具体展开。
4.3 部署到 Tomcat:IDEA 2024 里配置 Artifact 并启动
把项目导入 IntelliJ IDEA 2024 后,不能直接按运行按钮,因为 java web 项目要先配置 Servlet 容器才能启动。我这里给的是在 IDEA 里从零配置 Tomcat 的步骤,在 Eclipse 里也一样能配,只是菜单名称略有不同。
首先,打开 Project Structure,确认 Project SDK 是 JDK 8。这套旧代码我用过 8 是最稳的,JDK 11 虽然也能跑,但某些 SSH 框架的反射机制在 11 下面会有兼容问题。然后添加一个 Web Facet,把WebRoot或webapp目录指定为 Web 资源目录,再在 Artifacts 里选择 Web Application Exploded,这样改 JSP 不用重启。
然后配置 Tomcat:在 Run Configuration 里添加 Tomcat Server Local,Application server 选到你的 Tomcat 8.5 或 9.0 安装目录,Deployment 菜单里把刚才建立的 Artifact 加进去,Application context 填/performance。这里我建议用/performance而不是/,因为后面 filter 拦截、页面跳转都会依赖这个 context path,太短容易和其他项目冲突。
启动前最后一项检查:确认 Tomcat 端口没被占用。如果 8080 端口被别的进程占用,在 Server 配置里把端口改成 8081 就行。启动后如果看到类似Deployment of web application archive ... has finished in ... ms的日志,就代表部署成功了。浏览器访问http://localhost:8080/performance/login.jsp,能出登录页,说明基本成了。
4.4 功能验证清单:管理员建考核、考评人打分、员工看结果
项目启动后,验证功能时不要漫无目的地到处点。绩效考评系统的核心链路就那么几条,按顺序走一遍,能快速确认系统可用。
第一,用管理员账号登录,创建一套考核模板,模板里至少填 2 到 3 个指标,并设置不同的权重。保存后把模板状态改为“已发布”。这一步验证的是模板管理流程,能成功保存并看到指标列表,说明数据库写入正常。
第二,发起一个考核任务,找一个考评人,一个被考评人,导入或手工生成考核关系。如果系统支持批量生成任务,就多生成几条,后面算分时数据更真实。
第三,退出管理员账号,换考评人账号登录,在待办列表里看到刚才分配的任务,进去给每个指标打分并提交。提交后检查assess_score表里是否多了对应的行,行数应该等于指标数。
第四,换被考评人账号登录,看是否能查到自己的汇总成绩。这时候你可能会发现成绩是 0 或者报错,那就是第 3 章的ScoreCalculator没有被正确调用,或者权重没求和,回代码里查。
这四步如果都走通,这套 java web 绩效考评系统的核心功能就算验证完毕。接下来要处理的就是各种边边角角的报错和性能问题。
5. 绩效考评系统部署与运维避坑:从中文乱码到 Tomcat 10 兼容性
5.1 页面和数据库全是问号:字符集不一致导致的乱码
现象:登录后页面上的中文全是问号,往数据库里插入考核指标名称,表里存进去的也是?????。更奇怪的是 CSS 和页面看起来都是正常的,只有从数据库取出来的中文是乱的。
原因:字符集问题通常出在三处中的至少一处,JSP 页面没指定编码、数据库连接 URL 没带编码参数、数据库表字符集不是 utf8。最常见的是连接 URL 里characterEncoding=utf8被删了,或者 MySQL 的my.ini里默认字符集是 latin1。
解决:先把 JSP 页面头部统一改成<%@ page contentType="text/html;charset=UTF-8" language="java" %>,再把jdbc.properties里的 URL 换成jdbc:mysql://localhost:3306/performance_db?useUnicode=true&characterEncoding=utf8&useSSL=false,最后确认数据库表字符集已经改成 utf8mb4,执行ALTER TABLE assess_indicator CONVERT TO CHARACTER SET utf8mb4;。改完重启 Tomcat,乱码问题基本消失。记住 utf8mb4 是 utf8 的超集,能存 emoji,绩效考评里的“非常好”这类中文完全够用。
5.2 MySQL 8 连不上:驱动类名过时
现象:Tomcat 启动后,项目日志里出现ClassNotFoundException: com.mysql.jdbc.Driver,或者Communications link failure,但数据库明明开着,账号密码也对。
原因:旧项目里放的是 MySQL Connector/J 5.x 的驱动包,驱动类名是com.mysql.jdbc.Driver;MySQL 8 之后官方驱动更名成com.mysql.cj.jdbc.Driver,并且在连接 URL 中还需要追加serverTimezone=Asia/Shanghai。很多 .rar 项目依赖的 lib 里没有新驱动,自然连不上。
解决:从 Maven 仓库下载mysql-connector-java 8.0.33的 jar 包,替换掉WEB-INF/lib下的旧驱动。同时更新jdbc.properties文件:
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/performance_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false改完注意删除 Tomcat 的缓存目录,因为 Tomcat 会把 lib 下的 jar 锁住,不清理的话总是加载旧类。这个问题的痕迹有两个:启动日志里的驱动类名和 Tomcat 日志里的 ClassNotFoundException,看到就基本可以确诊。
5.3 Tomcat 10 下 JSP 启动报错:javax 变 jakarta 的迁移问题
现象:用 Tomcat 10 启动项目,控制台报错java.lang.NoClassDefFoundError: javax/servlet/ServletException,或者一堆ClassNotFoundException: javax.servlet.http.HttpServletRequest,但项目代码里明明引用了这些类。
原因:Tomcat 10 开始把 Java EE 的javax.*命名空间换成了jakarta.*,旧项目编译时依赖的是javax.servlet-api的包,运行在 Tomcat 10 里,容器提供的类全变成了jakarta.*,于是运行期找不到类。这不是代码写错了,是容器的兼容性断裂。
解决:最省事的办法是换回 Tomcat 8.5 或 Tomcat 9.0,旧版源码包默认就是为这两个版本准备的,一般不需要改代码。如果你一定要用 Tomcat 10,那就得把源码里所有import javax.servlet批量替换成import jakarta.servlet,同时web.xml里的 schema 版本也要升级到 5.0 或 6.0,工作量不小。我接手这类 .rar 项目,第一件事就是确认 Tomcat 版本,8.5 是最稳的。
5.4 部署后 404:Artifact 与 context path 没配对
现象:Tomcat 正常启动了,日志也没报错,但浏览器访问http://localhost:8080/performance/login.jsp时返回 404。访问http://localhost:8080/能显示 Tomcat 默认首页,但切不到自己项目的页面。
原因:IDEA 部署 Artifact 时,Deployment 里的 Application context 没有设置为/performance,或者 Artifact 没有被加到部署列表里,导致 Tomcat 根本不知道有这个 Web 应用。有时候还会出现 Tomcat 目录下 webapps 里没有项目文件夹,因为 IDEA 是直接用 Exploded Artifact 部署的,不会复制到 webapps。
解决:打开 Run Configuration,找到 Deployment 选项卡,确认里面有这个 Artifact,并在 Application context 里填/performance。然后打开WEB-INF/web.xml,查看servlet-mapping里配置的路径,比如登录 Servlet 的 URL 是/login,那访问路径就是http://localhost:8080/performance/login。404 问题首先要区分是 Tomcat 没部署还是路径写错,检查 Tomcat 的conf/Catalina/localhost目录,如果启动了部署,这里会生成一个performance.xml临时文件,没有这个文件说明根本没部署上。
5.5 导出 Excel 报错:poi 版本与 JDK 不兼容
现象:管理员点击“导出考核结果”按钮,页面报 500 错误,控制台输出java.lang.NoSuchMethodError,指向 POI 的某个方法,比如setCellValue或createRow。
原因:源码包里带的 Apache POI 版本太低,比如 3.9,用的 JDK 8 编译后调用某些新方法时不存在;或者项目里同时存在多个 POI 版本类,互相冲突。绩效考评系统导出 Excel 几乎都用 POI,这个问题非常普遍。
解决:确认当前 JDK 版本,如果用的是 JDK 8,直接换 POI 4.1.2,这个版本对 JDK 8 最友好;如果非要留在 JDK 7,那就只能继续用 POI 3.17。替换办法是把WEB-INF/lib下的旧poi-*.jar删干净,放一个poi-4.1.2.jar、poi-ooxml-4.1.2.jar和对应的commons-collections4依赖进去。如果项目里用的是 Maven,就在pom.xml里改成上面的版本号,然后强制更新依赖。这个报错的规律是:它往往不在导入数据时发生,而是发生在点击导出按钮那一瞬间,因为 POI 类是在生成 Excel 时才第一次被加载。
6. 让这套绩效考评系统更顺手:用一份模拟数据做回归验证的最小改造
6.1 用 SQL 脚本造一套假数据,验证考核结果
跑通之后最值得做的事,是造一批可控的模拟数据来验证算分逻辑有没有被改坏。比如给同一个被考评人安排 5 个考评人,分数从 60 到 100 各不同,然后用 SQL 算一遍期望值,再和页面显示值对比。如果对不上,说明ScoreCalculator的去最高最低分逻辑或权重计算有偏差。
我常用的一条验证 SQL 长这样:
SELECT task_id, AVG(score) AS raw_avg FROM assess_score GROUP BY task_id;这条 SQL 算出来的是包含所有打分的普通平均分;在页面里的分数应该比这个平均分略有不同,因为代码去掉了最高最低分。如果你发现两边完全一样,就去检查一下ScoreCalculator是否真的执行了去极值逻辑,八成是条件scores.size() < 3刚好触发了,测试数据太少导致走了平均分分支。这也是为什么要造 5 条以上打分数据的原因。
6.2 小步改造:把 Servlet 接口保留,前端换成 Ajax 调用
老项目的 JSP 页面每次打分都要整页刷新,体验差且调试麻烦。你可以做一个小幅改造,不动后端 Java 结构,只把打分表单的提交改成 Ajax,这样在验证绩效数据时不用反复刷新页面,也方便抓取返回的 JSON 排查问题。
前端改造成这样:
fetch(contextPath + "/score/submit", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams(new FormData(document.querySelector("#scoreForm"))) }) .then(resp => { if (!resp.ok) throw new Error("提交失败"); return resp.text(); }) .then(() => { window.location.href = contextPath + "/assessor/pending.jsp"; }) .catch(err => console.error(err));后端要做的最小改动是把ScoreSubmitServlet返回值从sendRedirect改成直接写一串文本,比如{"code":0,"msg":"ok"},这只需要在doPost里加几行resp.setContentType("application/json;charset=utf-8")和resp.getWriter().write(...)即可。改完记住要把原来的sendRedirect注释掉或删掉,否则 Ajax 拿到的还是一个重定向页面而不是 JSON。
我给自己定的习惯是:凡是接手 .rar 源码,先按最小链路跑通,再决定要不要动手重构。这套绩效考评系统跑起来不难,难的是你别一上来就一头扎进代码里改样式、换框架,先让它稳定跑两天,用模拟数据验证核心算分,再谈优化。这么多 Java Web 项目做下来,我发现翻车最狠的多数不是业务逻辑,而是环境问题和乱改依赖。希望帮到你。
本文还有配套的精品资源,点击获取