
简介一套基于Java Web的绩效考评系统源码包面向需要学习企业级Java开发或人力资源绩效管理数字化的开发者、在校学生。资源共509个文件压缩包约982KB主要包含91个Java源码、39个JSP页面、8个JS脚本、6个CSS样式、6个图片及1个SQL数据库脚本同时带有SVN版本控制元数据便于追溯项目演进过程。系统涉及Servlet、JSP、Spring、MyBatis等常见技术栈覆盖员工管理、绩效指标设置、评价录入、自动计分与报表生成等典型模块适合用来理解前后端交互、ORM映射和权限控制设计。已有174人学习适合作为课程设计、毕业设计或入门实践参考。1. 绩效考评系统从哪来这个Java Web项目到底要解决什么绩效考评系统这类Java Web项目几乎是每个Java课程设计选题列表里的常驻选手。它不像电商系统那样业务链复杂又比单纯一张表的增删改查更有嚼头恰好卡在能把JSP、Servlet、MyBatis、MySQL、前端三件套这些核心技能串起来的位置上。网上大量流传的“绩效考评系统—java web.rar”就是这样一个打包好的工程里面通常包含了源码、SQL脚本和使用说明但能不能跑起来完全看你手上那台机器的JDK、Tomcat和数据库版本跟它匹不匹配。这个系统解决的实际问题很朴素企业里每个季度要给员工打分传统方式用Excel收集主管评分再人工统计加权结果又慢又容易出错。把它做成Web系统管理员能维护考评指标和权重主管登录后按部门看到待打分名单给每一项指标打分系统自动计算总分员工能查到自己最终成绩和排名。适合两类人一是正在做课程设计或毕业设计的学生想找一个逻辑完整、且能讲清楚代码的Java Web项目二是刚学完Java基础打算通过一个完整web项目把SSM框架、Maven、MySQL串起来练手的初学者。下面按我实际动手改这类项目的经验把技术选型、数据模型、部署流程和踩过的坑一条条讲清楚。2. 技术选型这套Java Web系统值得用什么框架来写2.1 三种主流实现方式纯JSP、SSM、Spring Boot怎么选拿到“绩效考评系统”这个需求第一件事不是写代码而是定技术栈。我在课程设计辅导和实际项目里见过三种做法最老的是纯JSPServletJDBC所有逻辑写在JSP页面里适合刚学完Servlet、想展示基础语法的场景最多见的是SSMSpring SpringMVC MyBatis三层结构清晰面试时能聊的东西也多很多网上下载的rar包就是这套还有近两年的Spring Boot Thymeleaf或Vue分离开发爽但需要注意有些学校老师对Spring Boot不熟答辩时反而问不出来。方案学习成本代码量答辩友好度生产可用性纯JSPServlet低大一般低SSM中中高中Spring Boot低小看老师偏好高我的建议是如果是下载现成rar包先解压看清是哪种结构别急着换框架如果自己从零写SSM是性价比最高的选择。Spring生态里的IOC和AOP在绩效考评里能用到实处Service层用事务管理批量打分权限AOP拦截未登录用户这些都能作为答辩时的讲点。Spring Boot虽然省事但这类项目往往需要部署在Tomcat的webapps目录下让老师检查打成war包还得额外处理内嵌Tomcat冲突新手容易翻车。2.2 前端页面怎么做JSPJSTL还是前后端分离绩效考评系统的页面数量其实很少登录页、管理员后台部门管理、指标管理、任务下发、主管打分页、员工结果查询页。用JSPJSTL做服务端渲染是最稳的方案不需要解决跨域问题Session天然可用适合课程设计和内部管理系统。具体做法是在JSP页面里通过${sessionScope.user}获取登录用户用c:forEach遍历出待打分列表。JSTL标签库的引入注意别漏了依赖JavaWeb老项目里经常出现The absolute uri: http://java.sun.com/jsp/jstl/core cannot be resolved原因就是jstl和standard两个jar包没放进WEB-INF/lib或者Maven依赖没引入。如果追求页面好看可以引入AdminLTE或Bootstrap模板但核心逻辑还是放在JSP的服务端标签里别为了前后端分离硬拆。2.3 项目结构Maven多模块还是单体war包很多人下载的rar包不是Maven工程而是一个传统Web工程里面就是src文件夹、WebRoot或者web目录连.classpath和.project都是Eclipse遗留的。这种老工程导入IDEA时需要花点功夫。这里先讲从零推荐的结构再讲怎么兼容老的。performance-review-system/ ├── pom.xml ├── src/main/java │ └── com/company/prs │ ├── controller/ # SpringMVC控制器 │ ├── service/ # 业务接口和实现 │ ├── dao/ # MyBatis Mapper接口 │ ├── entity/ # 数据库实体 │ └── common/ # 工具类和拦截器 ├── src/main/resources │ ├── jdbc.properties # 数据库连接配置 │ ├── spring-mvc.xml │ └── spring-mybatis.xml └── src/main/webapp ├── WEB-INF │ ├── web.xml │ └── jsp/ # 页面文件 ├── static/ # CSS/JS/图片 └── index.jsp单体war包足够不需要Maven多模块。多模块适合团队协作和人较多的大型系统绩效考评这种级别拆开了反而增加理解成本。如果你是下载的rar包大概率看到的是扁平目录那就按第4章的方式在IDEA里重新创建Artifact。2.4 pom.xml与web.xml里的关键配置Maven工程的核心就两个文件依赖管理在pom.xml容器配置在web.xml。SSM项目里我一般这样配依赖properties spring.version5.3.20/spring.version mybatis.version3.5.10/mybatis.version /properties dependencies dependency groupIdorg.springframework/groupId artifactIdspring-context/artifactId version${spring.version}/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version${spring.version}/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version${mybatis.version}/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version5.1.49/version /dependency /dependencies逻辑说明Spring 5.3配JDK8没问题MySQL驱动选5.1.49兼容MySQL 5.7如果你本地是MySQL 8.0以上这里要换成8.0.33并改驱动类为com.mysql.cj.jdbc.Driver这是项目跑不通最常见的原因之一。MyBatis 3.5.10对应Java8完全够用。web.xml主要配置Spring容器、DispatcherServlet和字符过滤器filter filter-nameencodingFilter/filter-name filter-classorg.springframework.web.filter.CharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param /filter filter-mapping filter-nameencodingFilter/filter-name url-pattern//url-pattern /filter-mapping servlet servlet-namedispatcher/servlet-name servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class init-param param-namecontextConfigLocation/param-name param-valueclasspath:spring-mvc.xml/param-value /init-param load-on-startup1/load-on-startup /servlet servlet-mapping servlet-namedispatcher/servlet-name url-pattern//url-pattern /servlet-mapping字符过滤器必须放在所有过滤器的第一个。很多下载来的rar项目没有加这个filter导致页面提交中文到数据库变成问号这就是很有代表性的坑在第5章会说。3. 核心数据模型与服务层绩效考评的业务逻辑怎么落地3.1 数据库表设计五张核心表的字段与关系绩效考评系统不要一上来就设计十几张表先抓住“谁评价谁、用什么指标评价、结果存哪”这条主线。我常用的方案是五张表sys_user用户跟员工信息合并、sys_dept部门、eval_indicator考评指标、eval_task考评批次任务、eval_score评分明细。如果还想做绩效等级分布再加一张eval_rank表也是后话。CREATE TABLE sys_dept ( id INT NOT NULL AUTO_INCREMENT, dept_name VARCHAR(50) NOT NULL COMMENT 部门名称, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE sys_user ( id INT NOT NULL AUTO_INCREMENT, username VARCHAR(50) NOT NULL, password VARCHAR(100) NOT NULL, real_name VARCHAR(50) DEFAULT NULL, role TINYINT DEFAULT 2 COMMENT 0-管理员 1-主管 2-员工, dept_id INT DEFAULT NULL, PRIMARY KEY (id), KEY idx_dept (dept_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE eval_indicator ( id INT NOT NULL AUTO_INCREMENT, indicator_name VARCHAR(50) NOT NULL COMMENT 指标名称, weight INT DEFAULT 10 COMMENT 权重总和100, dept_id INT NOT NULL COMMENT 适用部门, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE eval_task ( id INT NOT NULL AUTO_INCREMENT, task_name VARCHAR(100) NOT NULL, start_date DATE DEFAULT NULL, end_date DATE DEFAULT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE eval_score ( id INT NOT NULL AUTO_INCREMENT, task_id INT NOT NULL, user_id INT NOT NULL COMMENT 被考评人, scorer_id INT NOT NULL COMMENT 考评人, indicator_id INT NOT NULL, score DECIMAL(5,1) DEFAULT NULL, comment VARCHAR(255) DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_task_user_scorer_indicator (task_id, user_id, scorer_id, indicator_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;逻辑说明sys_user里的role字段用数字0/1/2代替字符串查询时用switch映射比直接存“管理员”三个字更规范。eval_score表加唯一索引uk_task_user_scorer_indicator避免同一个考评人对同一个被考评人的同一个指标重复打分。score字段用DECIMAL(5,1)而不用double因为数据库里浮点比较容易出诡异误差。3.2 批量生成考评任务循环、事务和去重绩效考评系统里“下发任务”是一个关键操作管理员选择某个部门、某个时间段系统自动为每个主管生成待打分记录。新手常见做法是写一个for循环嵌套另一个for循环直接往eval_score表里插插完才发现有重复数据。我在Service层一般这样做Override Transactional(rollbackFor Exception.class) public void createScoreTask(Integer taskId, Integer deptId) { // 1. 查该部门所有员工 ListSysUser users userDao.selectByDeptId(deptId); // 2. 查该部门适用的所有指标 ListEvalIndicator indicators indicatorDao.selectByDeptId(deptId); // 3. 找出该部门的主管列表 ListSysUser managers userDao.selectManagersByDeptId(deptId); // 4. 先清掉这个任务下已经有的临时数据保证可重复执行 scoreDao.deleteByTaskId(taskId); // 5. 每个主管对每个员工、每个指标生成一条空记录 for (SysUser manager : managers) { for (SysUser user : users) { if (manager.getId().equals(user.getId())) { continue; // 主管不给自己打分或根据需求决定 } for (EvalIndicator indicator : indicators) { EvalScore score new EvalScore(); score.setTaskId(taskId); score.setUserId(user.getId()); score.setScorerId(manager.getId()); score.setIndicatorId(indicator.getId()); score.setScore(null); scoreDao.insert(score); } } } }逻辑说明最外层的Transactional保证生成过程要么全部成功要么全部回滚不会出现“生成了半截任务”的脏状态。第4步的deleteByTaskId是先清后插这样管理员重复点击“生成”按钮时不会抛唯一索引冲突这是很多人没考虑到的——页面点两次就报Duplicate entry。代码里跳过了主管自己实际业务如果要自评这里改成不需要跳过即可。在MyBatis的Mapper里批量插入不要用foreach拼一个超长INSERT数据量大一点就把数据库内存撑爆。循环里单条insert配上事务足够因为绩效考评系统的并发量很低没必要在这类小系统上加复杂的批量参数。3.3 打分保存与权重计算BigDecimal解决浮点陷阱主管在页面上对每个指标打分提交时后端做两件事存储原始分同时计算加权总分。计算总分时double是最大的坑public BigDecimal calcTotalScore(ListEvalScore scores) { BigDecimal total BigDecimal.ZERO; for (EvalScore score : scores) { Integer weight indicatorDao.selectWeightById(score.getIndicatorId()); BigDecimal weightBD BigDecimal.valueOf(weight); BigDecimal scoreBD score.getScore(); total total.add(scoreBD.multiply(weightBD)); } return total.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP); }参数说明multiply算出(指标分 × 权重)的积最后统一除以100才对。好多人先把每个指标分除以100再乘权重四舍五入误差会累计。RoundingMode.HALF_UP解决小数点后第三位四舍五入问题不要用BigDecimal(double)构造器那会引入浮点噪声正确做法是用BigDecimal.valueOf(double)或字符串构造。保存打分的Controller还要做权限校验当前登录人只能提交scorer_id等于自己的评分记录否则改个请求参数就能替别人打分。这部分用AOP或者拦截器统一处理Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session request.getSession(); SysUser user (SysUser) session.getAttribute(loginUser); if (user null) { response.sendRedirect(request.getContextPath() /login.jsp); return false; } if (/score/save.equals(request.getRequestURI()) user.getRole() 1) { response.sendError(HttpServletResponse.SC_FORBIDDEN); return false; } return true; }这段在网上很多课程设计里直接写在JSP里我建议单独抽成Java类注册到SpringMVC因为答辩时老师会问“如果我直接访问这个URL怎么拦住我”这就是给分点。4. 在IDEA里把rar包变成能跑的Web应用4.1 解压与导入直接Open还是Import Project从网上下载的绩效考评系统—java web.rar解压后大概率是一个带.classpath和.project的Eclipse工程。IDEA里不要用File → New → Project from Existing Sources那个流程容易把Maven识别搞乱。我一般这样做解压rar到某个没有中文路径的目录比如D:\project\prs打开IDEA选择File → Open选中解压后的某个pom.xml文件IDEA会问你是不是Open as Project选Maven如果没有pom.xml只有WebRoot和src就用Open选择目录IDEA会弹窗提示“Non-Project file”这时选择Web类型的模块IDEA会把它识别成一个Web项目打开Project Structure → Facets确认Web那一栏里Web Resource目录指到了web或WebRootWEB-INF/web.xml已经添加。很多人卡在第3步IDEA直接把它当普通文件夹打开了看不到Tomcat运行入口。这时要在Project Structure → Artifacts里新建Web Application Exploded把web目录指进去再把右侧编译输出拉进来。4.2 改三个配置数据库连接、字符集、Tomcat部署数据库连接配置一般在src/main/resources/jdbc.properties或者WEB-INF/classes/db.properties里。老项目里甚至直接写在JDBC工具类Java文件中你需要全局搜索DriverManager.getConnection关键字。jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/performance_db?useUnicodetruecharacterEncodingutf8 jdbc.usernameroot jdbc.password123456参数说明useUnicode和characterEncoding这两个参数必须加否则即使页面表格都设置UTF-8每个中文字符存进数据库还是会乱。我见到网上很多老项目用的是jdbc:mysql://localhost:3306/perf这种极简写法第一次跑通后一旦出现中文乱码十有八九就是缺了这个后缀。还有jdbc.password很多rar包里带了root和123456如果你本机MySQL密码不是这个记得同步改。Tomcat配置方面在Run → Edit Configurations里添加Tomcat Server → Local然后Application server选择你本地的Tomcat版本注意IDEA 2024版本创建web项目时默认的Tomcat是独立包不是IDEA自带的Deployment里把war explodedArtifact加进去Application context填/prsServer面板里的VM options在-Dfile.encodingUTF-8后面追加一句-Duser.languagezh这个参数能解决一部分Windows控制台乱码问题。4.3 从登录到出结果跑通主流程的最小操作清单假设项目已经正常启动数据库初始化脚本也执行过我通常会按这个顺序做冒烟测试# 1. 初始化数据库在项目目录下SQL脚本所在位置执行 mysql -u root -p sql/init.sql # 2. 检查Tomcat日志启动无异常后说明部署成功 tail -f logs/catalina.out # 3. 用管理员账号登录后台路径示例 # http://localhost:8080/prs/login.jsp # 管理员账号admin/admin123第一步里的init.sql通常会包含表结构和初始数据包。有些项目的SQL文件编码是GBK用命令行导入前先执行SET NAMES utf8mb4;否则注释里的中文会乱。第二步启动日志里看到Deployed web application archive或Completed WebApplicationInitialization字样就基本没问题。第三步登录后不要急着点菜单先看控制台有没有打印“Mapper method not found”之类的报错那说明MyBatis接口扫描路径配错了。最后验证主流程管理员创建一个新考评任务 → 主管账号登录 → 看到待打分列表 → 给所有指标打分并提交 → 员工账号登录 → 查看结果查询页面能看到加权总分。这一步跑通整个系统就交付了80%。5. 跑项目最常见的5个坑不乱码、不报错、不4045.1 页面中文全是问号现象系统启动后登录页上的汉字全部显示为??????或者录入员工姓名后数据库里变成问号。原因JSP文件的编码没有统一。常见三种来源JSP页面本身没有声明pageEncodingUTF-8web.xml里缺Spring的CharacterEncodingFilterMySQL连接URL缺少useUnicodetruecharacterEncodingutf8。任何一个环节掉链子中文就直接坏掉。解决先改JSP首行统一% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 languagejava %再在web.xml确认第2.4节里的encodingFilter存在且url-pattern是/最后执行ALTER DATABASE 库名 CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;把库的默认字符集也改掉。改完删掉Tomcat的缓存目录重新发布。5.2 访问数据库报Connection refused或Access denied现象Tomcat启动时报Could not get JDBC Connection或者登录时弹出Access denied for user rootlocalhost (using password: YES)。原因前者是MySQL服务没启动或者端口不是3306后者是账号密码错误。还有一类隐蔽原因下载的rar包里的驱动是com.mysql.jdbc.Driver你本机装MySQL 8.0以上版本这个驱动类在新Connector/J里已经改名连接时直接报ClassNotFoundException。解决先确认mysql -u root -p能正常进MySQL命令行再检查jdbc.properties里地址用了localhost还是127.0.0.1本机开发两个都行但如果MySQL监听了IPv6有时localhost解析会出问题直接统一改127.0.0.1。驱动版本每升一个MySQL大版本就换一次MySQL 8.0务必用com.mysql.cj.jdbc.Driver并加serverTimezoneAsia/Shanghai。5.3 部署后404或者ClassNotFoundException现象Tomcat能启动但访问http://localhost:8080/prs/显示404或者控制台报Servlet.class not found。原因IDEA里Artifact打包时没有把依赖jar包打进WEB-INF/lib。IDEA 2024版本创建web项目自带的Artifact模式有时只打包编译后的class不打包Maven依赖。解决打开Project Structure → Artifacts点Available Elements里的Library Files右键Put into WEB-INF/lib然后重新bulid。如果是非Maven老工程手动去Tomcat安装目录的lib文件夹拷贝对应的spring-webmvc.jar、mybatis.jar到项目的WEB-INF/lib下。这也是为什么我坚决建议新项目用Maven老项目也要转成Maven再跑。5.4 端口被占用和Tomcat重复启动现象运行Tomcat时报Error running Tomcat: Address localhost:8080 is already in use。原因上一个崩溃的Tomcat进程没退出或者IDEA和命令行里同时运行了多个Tomcat实例。解决Windows下打开任务管理器找到带catalina的Java进程强制结束或者用netstat -ano | findstr 8080查到PIDtaskkill /F /PID 进程号强制回收。还有一种情况是IDEA的Tomcat配置里HTTP port和JMX port冲突把JMX port改成1099换个冷门端口。5.5 启动时NoClassDefFoundError和依赖冲突现象刚点启动日志报java.lang.NoClassDefFoundError: org/springframework/core/annotation/AnnotationUtils或者MyBatis和Spring版本对不上。原因Maven依赖树里存在多个不同版本的同名jar。比如Spring核心包有5.2的有5.3的classpath按字母顺序加载导致高版本方法缺失。解决在IDEA终端里跑mvn dependency:tree查看冲突然后用exclusion排除旧版。比如Spring与Spring Security冲突时dependency groupIdorg.springframework.security/groupId artifactIdspring-security-web/artifactId version5.7.3/version exclusions exclusion groupIdorg.springframework/groupId artifactIdspring-core/artifactId /exclusion exclusion groupIdorg.springframework/groupId artifactIdspring-context/artifactId /exclusion /exclusions /dependency这说明白了一个好项目的本质依赖之间不能互踩。血泪经验是遇到这种问题不要一个个试版本直接统一把Spring全家桶调到同一个版本号用spring-boot-parent父工程管理最省心。6. 从能跑到好用给绩效考评系统加一个报表导出的小功能6.1 用Apache POI把考评结果导出为Excel课程设计里的绩效系统大多是页面展示结果真正企业用到绩效考评一定会要导出存档。用Apache POI给系统加一个导出功能工作量不大但能明显提升完成度。先加依赖dependency groupIdorg.apache.poi/groupId artifactIdpoi-ooxml/artifactId version4.1.2/version /dependency然后在Controller里写导出逻辑RequestMapping(/export) public void export(HttpServletRequest request, HttpServletResponse response, RequestParam Integer taskId) throws IOException { // 查询该任务下的所有评分汇总 ListExportRow list reportDao.selectScoreSummary(taskId); // 设置响应头告诉浏览器下载文件 response.setContentType(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet); String fileName URLEncoder.encode(考评结果_ taskId .xlsx, UTF-8); response.setHeader(Content-Disposition, attachment; filename\ fileName \); try (Workbook workbook new XSSFWorkbook(); OutputStream os response.getOutputStream()) { Sheet sheet workbook.createSheet(考评结果); sheet.createRow(0).createCell(0).setCellValue(员工姓名); sheet.getRow(0).createCell(1).setCellValue(部门); sheet.getRow(0).createCell(2).setCellValue(总分); for (int i 0; i list.size(); i) { Row row sheet.createRow(i 1); row.createCell(0).setCellValue(list.get(i).getRealName()); row.createCell(1).setCellValue(list.get(i).getDeptName()); row.createCell(2).setCellValue(list.get(i).getTotalScore().doubleValue()); } workbook.write(os); os.flush(); } }逻辑说明selectScoreSummary是需要在reportMapper里写的联表查询把sys_user、sys_dept、eval_score分组汇总。导出前记得设置Content-Disposition否则浏览器会在新页面里显示一堆乱码而不是弹下载。注意URLEncoder处理文件名中文文件名直接写会报非法的Header字符。6.2 权限改造用Filter替换散落在JSP里的判断老项目最常见的安全问题就是JSP里直接写if(user.role1)来判断菜单显示但URL照样能被人直接访问。正确做法是用一个全局过滤器的思想把需要保护的路径统一拦下来。这里分享一个最简单的改造方案WebFilter(filterName securityFilter, urlPatterns {/score/*, /admin/*}) public class SecurityFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; SysUser user (SysUser) request.getSession().getAttribute(loginUser); if (user null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } String path request.getRequestURI(); if (path.startsWith(/admin/) user.getRole() ! 0) { request.setAttribute(msg, 无权限访问); request.getRequestDispatcher(/WEB-INF/jsp/error.jsp).forward(request, response); return; } chain.doFilter(request, response); } }urlPatterns里把/score/*和/admin/*这种敏感路径保护起来比在几十个Servlet里重复写if(usernull)优雅得多。做完这个改造系统才算真正达到“能给别人演示”的程度。最后说一句我的习惯每次接手这类Maven或非Maven的Java Web项目第一件事永远是看web.xml和jdbc.properties这两个文件不调通后面所有的调优都是白做功。花十几分钟把第5章的坑逐项核对一遍比遇见报错再去搜索快得多。希望帮到你。本文还有配套的精品资源点击获取