简介:一套Java技术栈的OA办公自动化系统项目源码包,面向高校学生、初入行的Java开发者,也适合需要内部流程平台参考的企业技术团队。内容从需求分析、系统设计、流程引擎、权限管理、移动办公到报表分析、运维实施均有覆盖,可帮助读者理解OA系统从需求梳理、模块划分到上线部署的完整路径。rar压缩包共1212个文件,约17.92MB,主要包含jsp/html/js/css前端交互页面、java/class后端业务逻辑、xml与properties配置文件、jar依赖库以及gif/png素材资源,目录归类清晰,方便按模块查阅和二次开发。工作流管理、公文处理、审批流程等核心模块实现均可在源码中直接对应查看,适合用于课程设计、毕业设计或企业项目实训。已有385人学习下载,对正在准备OA相关开发任务或系统集成实践的读者有较高参考价值。
1. 为什么还要折腾OA系统项目:从“能跑”到“敢写进简历”的距离
网盘里躺着一份OA系统项目压缩包的人,大概率都是同一个心态:想找个完整的javaweb项目练手,又怕遇到烂代码。下载过的OA项目少说几十个,真正能一口气跑起来的少,能看懂审批流怎么设计的更少。这套OA系统不是那种只有登录注册的玩具项目,它把企业内部最常碰到的场景——部门管理、员工考勤、公告发布、流程审批、待办提醒——都做成了完整闭环,数据库脚本、部署文档、源码包一次给齐。对刚学完Java Web、正在准备课设或毕业设计的人来说,它是少有的“打开就能研究完整业务流”的项目:一个审批单从发起到归档,中间要经过哪几张表、哪些状态、哪些权限判断,全部摊开摆在面前。对已经工作的开发者也值得花一个晚上拆一拆,看看企业级表单场景里权限怎么控、状态怎么流转。接下来的内容,我会按“先看结构、再跑起来、后拆核心、最后动手改”的顺序,把这套资源从头到尾走一遍。
2. 先看清单再动手:这套OA系统的模块划分与数据流
2.1 解压之后先看什么:一份来自真实项目的目录结构
拿到压缩包先不要急着扔进IDEA,先花十分钟把目录结构捋清楚。这套OA系统的工程布局是典型的Maven多模块或单模块Web工程,具体取决于压缩包内的组织形式,但核心目录不会差太多:src/main/java存放Java源码,src/main/resources放配置文件,src/main/webapp放JSP页面、静态资源和WEB-INF。我第一次拆这类项目时犯过一个错误,文件多就直接全量导入IDE,结果等了五分钟还在索引,后来养成习惯:先看根目录下的pom.xml和README,再决定怎么导入。
OA-system/ ├── pom.xml # Maven 依赖与插件定义,确认打包方式 ├── sql/ │ └── oa_system.sql # 建库建表脚本,含初始管理员数据 ├── src/main/java/com/oa/ │ ├── controller/ # Servlet 或 SpringMVC 控制器层 │ ├── service/ # 业务逻辑层 │ ├── dao/ # 数据库访问层 │ ├── entity/ # 实体类 │ ├── filter/ # 登录与权限拦截器 │ └── util/ # 工具类 ├── src/main/resources/ │ ├── jdbc.properties # 数据库连接配置 │ └── mybatis-config.xml # MyBatis 配置 └── src/main/webapp/ ├── WEB-INF/web.xml # Web 应用描述符,配置 Servlet 映射 ├── index.jsp # 登录页 ├── views/ # 业务页面 └── static/ # CSS / JS / 图片用压缩包自带的SQL脚本初始化数据库是我每次拿到项目的第一动作,而不是先去读代码。这样做的原因是,你手上能有一个确定的数据基础,登录进去以后能看到真实的菜单和待办数据,而不是空表状态下去猜接口返回什么。项目里的oa_system.sql一般会创建数据库oa_system,内部包含用户表、部门表、角色表、审批相关表等,并且预置一个管理员账号,具体密码在README里有说明,拿到手先记下来,后面登录验证要用。
2.2 审批单从发起到归档:一条数据要过四张表
OA的核心不是增删改查,而是流程。以员工提交一个请假申请为例,数据会依次落在四类表里:业务表、流程实例表、任务表、日志表。业务表存的是申请本身的业务字段,比如请假类型、开始时间、结束时间、事由;流程实例表记录这条流程属于哪个模板、当前状态是什么;任务表记录当前审批到哪个人或哪个角色头上;日志表记录每一步操作是谁在什么时间点了什么按钮。
请假申请提交 -> 插入业务表 hrm_leave -> 插入流程实例表 oa_flow_instance (状态: 审批中) -> 插入任务表 oa_flow_task (审批人: 部门经理) -> 插入日志表 oa_flow_log (动作: 提交申请)这四个表的关联字段是流程实例ID,业务表通过instance_id指向流程实例表,任务表也通过instance_id找到当前流程,日志表同理。这样拆分的好处是,业务数据和流程数据互不污染,一个请假单驳回再修改后重新提交,业务表的记录可以更新,但流程实例和日志是追加式记录,追溯的时候能还原完整历史,而不是只看到最新状态。很多刚接触项目的人容易犯的错是把审批状态直接塞在业务表里加一个status字段,省事是省事,但一旦有多级审批、退回再提交这类场景,那一个字段根本记录不了流程轨迹。
2.3 权限模型:为什么有角色表却没有权限表
这套OA系统的权限设计比较经典,用角色控制菜单和操作权限。用户表里存role_id,角色表里存角色名称和对应的菜单权限码,登录的时候把用户信息和角色信息塞进Session,每次请求时通过Filter拦截,校验当前用户可访问的菜单范围。这种设计在中小型OA里够用,不引入Spring Security重框架也能把“普通员工看不到管理菜单”“部门经理只能审自己部门”这类规则跑通。
关键点在于权限校验不是写死在Servlet里,而是用一个PermissionFilter统一拦截。拦截器里维护一个 map,key是URL前缀,value是允许访问的角色码,请求进来时拿Session里的用户角色去比对,不匹配直接重定向到无权限页面。改动权限规则只需要改配置,不用动业务代码。这套设计对学习的人有参考价值,因为很多自研后台系统上线后又发现漏了权限控制,临时补拦截器找一个统一入口最划算,这套项目的Filter结构可以直接抄。
3. 把项目跑起来:从IDEA导入到MySQL初始化全流程
3.1 环境版本怎么搭配:JDK、Maven、MySQL、Tomcat四件套
任何老项目的第一次跑不起来,八成是版本不匹配。这套OA系统按javase 8的语法写的,理论上说JDK 8最稳,JDK 11也能跑,但JDK 17就要小心了,如果pom里用的是旧版Tomcat插件或旧版JSP API,编译期就会爆UnsupportedClassVersionError。我一般固定用JDK 8 + Maven 3.6.3 + Tomcat 8.5,这套组合对大部分javaweb老项目都是安全区。
MySQL版本是另一个容易翻车的点。项目里的JDBC驱动如果是com.mysql.jdbc.Driver,那么MySQL 5.7可以直接用,MySQL 8.0就必须换成com.mysql.cj.jdbc.Driver,而且URL后面要加时区参数serverTimezone=Asia/Shanghai,不然连接直接报时区错误。这属于最常见的历史遗留坑,后文避坑章节会专门展开。
3.2 导入、配置、启动:四个步骤跑通本地环境
第一步,把项目解压后用IDEA打开,选中根目录的pom.xml,选择“Open as Project”。弹窗里选“Trust Project”。这一步如果IDEA提示找不到Maven仓库里的依赖,先检查IDEA自带的Maven配置,File -> Settings -> Maven 里确认User settings file指向本地的settings.xml,而不是IDEA默认下载网络仓库。
# 如果你习惯先用命令行验证代码完整性,可以在项目根目录执行 mvn clean package -Dmaven.test.skip=true这条命令的作用是跳过测试用例直接把项目打包成war包,同时把依赖下载到本地仓库。如果执行过程里报红,看第一条错误原因,90%的情况是某个依赖版本在中央仓库已经不存在,需要在pom.xml里改成相近版本,建议优先调整Spring或MyBatis的版本号,不要动业务代码。命令行验证的意义是提前暴露依赖问题,比在IDEA里等半天部署失败再回头找原因效率高。
第二步,准备数据库。打开MySQL客户端,执行项目里的建库脚本。
mysql -uroot -p < sql/oa_system.sql脚本执行完成后,用show tables;检查核心表是否存在。注意脚本本身的编码,如果SQL文件是GBK编码而你的MySQL客户端默认UTF-8,会出现中文乱码或建表失败,用source命令导入前先设置编码:
set names utf8; source D:/workspace/OA-system/sql/oa_system.sql;set names utf8的作用是告诉MySQL客户端和服务端交互时使用UTF-8字符集,避免SQL里的中文默认值变成乱码,这一步对后面管理员账号能否正常登录影响很大。
第三步,修改数据库连接配置。
# src/main/resources/jdbc.properties jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/oa_system?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai jdbc.username=root jdbc.password=123456这里的四个参数按你的实际环境改:driver类名由MySQL版本决定,url里数据库名要和SQL脚本里的库名一致,username和password用本地数据库的账号。如果密码里有特殊字符,比如@或#,需要做URL编码,否则连接串会被解析错误,这是配置类问题里排查成本最低但出现频率不低的一种。
第四步,配置Tomcat并启动。IDEA里点击右上角Add Configuration,选Tomcat Server -> Local,Deployment选项卡里点加号选Artifact,选中项目的war包。Application context建议设为/oa,这样访问路径是http://localhost:8080/oa/,不容易跟本机其他项目冲突。启动前确认Tomcat端口没被占用,默认8080。启动成功后控制台会输出Artifact is deployed successfully和Tomcat的启动时间。
3.3 用管理员账号验证系统是否真的跑起来了
启动成功后别急着关,先做一个基础验证:打开浏览器访问登录页,用SQL脚本里预置的管理员账号登录。登录成功以后重点看四个位置:首页是否有待办事项数据、部门管理菜单是否可见、员工管理列表是否加载出预置数据、退出登录是否正常。如果登录后页面能正常打开但列表接口返回空数据,优先检查SQL脚本里INSERT语句是否真的执行成功,我遇到过脚本里的事务没提交导致部分表有数据部分表空的情况,可以重新执行一遍脚本解决。
另一个验证点是检查IDEA的Tomcat日志目录下有没有生成catalina.out,这个文件记录了应用运行时的System.out输出和异常堆栈,后续排查接口报错时最先看的就是它。项目能启动不等于能跑通业务,登录、菜单加载、列表查询这几个链路都走通才算本地环境OK,这一步别省,很多问题出在页面能打开但接口全是500的状态。
4. 核心链路拆解:审批流的状态机、表结构与权限拦截
4.1 审批流的表设计:为什么要拆成实例表和任务表两兄弟
审批流是这套OA系统最值得读的代码,也是面试聊项目时最能体现深度的点。设计上把流程数据拆成两张表:oa_flow_instance负责记录流程当前走到哪一步、整体状态是什么;oa_flow_task负责记录当前这个节点该谁处理、处理完了没有。下面给出这两张表的核心字段。
CREATE TABLE `oa_flow_instance` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `business_type` varchar(32) DEFAULT NULL COMMENT '业务类型:leave/travel/reimburse', `business_id` bigint(20) DEFAULT NULL COMMENT '业务表主键ID', `current_node` varchar(32) DEFAULT NULL COMMENT '当前节点编码:manager_audit/finance_audit', `status` tinyint(4) DEFAULT '0' COMMENT '0审批中 1已通过 2已驳回 3已撤销', `applicant_id` bigint(20) DEFAULT NULL COMMENT '申请人ID', `create_time` datetime DEFAULT NULL, `end_time` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `oa_flow_task` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `instance_id` bigint(20) DEFAULT NULL COMMENT '所属流程实例ID', `node_code` varchar(32) DEFAULT NULL COMMENT '节点编码', `assignee_id` bigint(20) DEFAULT NULL COMMENT '当前审批人ID', `task_status` tinyint(4) DEFAULT '0' COMMENT '0待处理 1已通过 2已驳回', `comment` varchar(512) DEFAULT NULL COMMENT '审批意见', `handle_time` datetime DEFAULT NULL COMMENT '处理时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;实例表记录整个流程的宏观状态,任务表记录当前节点的微观状态。实例表里有一个status字段控制整体生命周期,任务表里也有一个task_status控制节点处理情况。两级状态分开的好处是:查询“我这个月提交了几个还在审批中的申请”只需要查实例表,统计效率高;查询“当前有多少待办”只需要聚合任务表里task_status=0的记录,不需要关心发起人是谁。如果把两级状态合并到一张表,以上两种查询都要带复杂条件,而且历史任务会不断覆盖,最后表里的数据只剩最新状态,想回溯就无据可查了。
4.2 状态流转的代码实现:一个Byte值怎么推动整条流程
审批操作的核心代码在Service层,这里以“经理通过”这个动作为例拆解逻辑。
public void approve(Integer taskId, String comment) { // 1. 获取当前任务 OaFlowTask task = flowTaskMapper.selectById(taskId); if (task == null || task.getTaskStatus() != 0) { throw new BusinessException("任务不存在或已被处理"); } // 2. 更新当前任务状态 task.setTaskStatus(1); task.setComment(comment); task.setHandleTime(new Date()); flowTaskMapper.updateById(task); // 3. 获取实例并校验当前节点 OaFlowInstance instance = flowInstanceMapper.selectById(task.getInstanceId()); if (!"manager_audit".equals(instance.getCurrentNode())) { throw new BusinessException("当前节点不支持该操作"); } // 4. 判断流程是否结束 if ("manager_audit".equals(instance.getCurrentNode())) { // 经理审批通过后,如果不需要财务审核,流程直接结束 instance.setStatus(1); instance.setCurrentNode("end"); instance.setEndTime(new Date()); } flowInstanceMapper.updateById(instance); // 5. 写日志 flowLogMapper.insert(new OaFlowLog(task.getInstanceId(), "经理审批通过", comment, SecurityUtils.getCurrentUserId())); }这段代码是典型的“先检查再更新”写法,第1步锁定任务状态为待处理,避免重复审批;第2步先更新任务,第3步再更新实例,顺序不能反,因为实例的节点判断依赖任务所属的实例ID,先处理任务再推进实例,逻辑上是一致的。第4步的节点判断是整个审批流的转折点,这个项目里用简单的if判断实现了“经理通过即结束”或“经理通过后转财务审核”两种路径切换,实际运行时按current_node的值走对应分支,如果想配置多级审批链,把这个if改成查数据字典或流程配置表即可。
参数上需要注意taskStatus和status的类型都是tinyint,Java里对应Byte/Integer,在比较时用task.getTaskStatus() != 0会出现Integer缓存问题吗?不会,因为这里拿的是Byte类型,数值比较没问题,但如果你把字段类型改成Integer,就要小心Integer == 0判断的是引用而非值,必须用intValue()或equals,这是一个隐蔽的代码级别坑。
4.3 权限拦截:过滤器怎么做到“没登录就弹回登录页”
这套OA系统没有引入Spring Security,而是用Filter做登录和权限拦截。核心实现如下:
public class LoginFilter implements Filter { private static final Set<String> WHITE_LIST = new HashSet<>(Arrays.asList( "/login.jsp", "/login", "/static" )); @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; String uri = req.getRequestURI(); // 1. 白名单直接放行 for (String prefix : WHITE_LIST) { if (uri.startsWith(prefix)) { chain.doFilter(request, response); return; } } // 2. 检查Session中是否存在用户 Object user = req.getSession().getAttribute("loginUser"); if (user == null) { resp.sendRedirect(req.getContextPath() + "/login.jsp"); return; } // 3. 已登录则检查菜单权限 String roleCode = (String) req.getSession().getAttribute("roleCode"); if (!PermissionUtil.hasPermission(uri, roleCode)) { resp.sendRedirect(req.getContextPath() + "/noPermission.jsp"); return; } chain.doFilter(request, response); } }Filter的判断顺序很重要,先放行白名单,再查登录态,最后查权限,三级漏斗式校验。白名单必须包含登录页和静态资源目录,否则用户还没登录,登录页本身的CSS和JS就被拦截了,页面会裸奔。Session里存loginUser和roleCode两个属性,登录成功时由LoginServlet写入,Filter只读取不写入,权限判断逻辑收敛在PermissionUtil工具类里,后续要扩展菜单级权限、按钮级权限,只需要扩展这个工具类。配置方面,在web.xml里注册Filter并设置url-pattern为/*覆盖所有请求,注意Filter的mapping顺序,多个Filter的情况下先注册的先执行。
这里要强调一个常见误用:把权限判断写在每个Servlet里是最差的方案,代码重复且容易漏。这套OA把权限收敛到Filter和工具类的做法才是生产环境里值得仿照的结构,也是你面试时能拿出来说的一手经验。
5. 部署与改造避坑:环境差异、编码、外网访问三个重灾区
5.1 现象:Tomcat能启动,但点击菜单全是404
原因:这里90%是部署描述符里Servlet映射的路径和页面请求的路径不一致。最常见的是把项目以Exploded方式部署后,Application context配置成了/或者没带项目名,而JSP里的请求地址写的是/oa/leave/list,路径对不上自然404。
解决:打开IDEA的Run Configuration,查看Deployment选项卡里Application context是否设置为/oa,同时检查web.xml里Servlet的url-pattern是否包含/leave/*。判断路径类404最快的办法是浏览器F12看Network里请求的完整URL和返回状态,把请求路径和实际映射路径一对就知道谁错了。
5.2 现象:启动报ClassNotFoundException: com.mysql.jdbc.Driver
原因:这个异常是Driver类名不匹配导致的。项目的pom.xml或lib目录里引入的是MySQL 5.x驱动包,但本地数据库是MySQL 8.x,或者反过来。MySQL 8.0之后驱动类改名了,旧的com.mysql.jdbc.Driver被移除了。
解决:把pom.xml里的mysql-connector-java版本升到8.0.x,并把jdbc.properties里的driverClassName改成com.mysql.cj.jdbc.Driver,URL加serverTimezone=Asia/Shanghai。这里有一个关键点,8.0驱动要求必须指定serverTimezone参数,否则启动即报错,报错信息里有The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized的字样,看到这个就说明是时区问题而不是驱动问题。还有一种更隐蔽的情况,pom里版本改了但本地Maven仓库没刷新,强制mvn clean后重新reimport。
5.3 现象:页面全是中文乱码,数据库表里也是乱码
原因:三层编码断层。第一层是JSP页面本身的编码,第二层是Servlet响应时设置的ContentType编码,第三层是数据库连接串里的characterEncoding。任何一层不一致,中文就会在传输链路上变成问号或乱码。
解决:检查JSP页面头部是否有pageEncoding="UTF-8",检查Servlet里是否设置了resp.setContentType("text/html;charset=UTF-8"),检查jdbc.properties的URL里useUnicode=true&characterEncoding=utf8。另外,SQL脚本导入时建表语句里的DEFAULT CHARSET必须是utf8mb4,不要用utf8,因为utf8mb4才能存下生僻字和emoji。这三层全对齐后,乱码基本绝迹。这个问题的排查顺序建议从前端往后端走,先确定浏览器拿到的HTML源码是否包含乱码,再查数据库存储,一多半出在数据库表身上。
5.4 现象:本地跑得稳稳的,项目部署到服务器后附件上传和图片查看全部失败
原因:这是一个硬编码路径的经典翻车现场。项目里大概率有类似File savePath = new File("D:/upload/")这样的写法,或者是相对路径./upload,而Tomcat的工作目录在服务器上不是项目根目录,相对路径定位失败或没有写入权限。
解决:全局搜索代码里出现的D:/、C:/、/home/这类绝对路径,以及File.separator,把上传和读取的路径改成配置项。在jdbc.properties所在的同目录新建一个config.properties,写入file.upload.path=/data/oa/upload/,代码里通过配置读取。服务端创建目录时用if (!dir.exists()) dir.mkdirs(),避免目录不存在时写入报错。如果是Linux服务器,还要注意chmod权限,Tomcat进程用户对上传目录要具备读写权,不然启动到运行一路顺利,就挂在写入这一哆嗦上,没有任何日志提示,只有FileNotFoundException。
5.5 现象:IDEA启动项目要三分钟,日志卡在Deployment这一步
原因:Tomcat启动慢有两大类原因,一类是Spring容器初始化慢,一类是IDEA在部署时同步大量文件。大多数情况是项目里有大量静态资源或classes目录堆积了旧编译产物,IDEA的Exploded部署每次都会全量同步文件,越跑越慢。
解决:把Tomcat的Deployment方式从Exploded改成Archive会快一些;或者修改IDEA的Build Settings,关闭build process资源压缩;更直接的办法是检查target目录,定时清掉旧classes和残留文件。还有一个小技巧,IDEA设置里把Tomcat的VM options加上-Dfile.encoding=UTF-8,可以同时解决一部分因默认编码不一致导致的额外IO开销。这个题属于体感优化,不影响功能正确性,但对天天开发的体验影响很大。
6. 让这套OA项目变成简历上的谈资:三个低成本的二次开发切入点
6.1 给审批流加一个“催办”按钮:锻炼完整链路改造能力
加一个小功能是检验你确实看懂这套系统的最佳方式,催办功能就是个好切入点。改动点横跨前端按钮、Servlet接口、Service层和消息通知四层。按钮加在JSP的待办列表里,点击时携带taskId调用后端;Service里新增一个urge方法,逻辑是校验当前任务的审批人是否已超时,超时则生成一条催办消息插入消息表,并给审批人的Session里塞一个提醒标记。这个功能虽然小,但数据写入、状态判断、会话交互都有了,覆盖了OA项目最典型的开发路径。
如果你准备把这个项目写到简历里,催办功能比写十个增删改查都有说服力,因为它说明你理解了审批流的时间维度。
6.2 把循环查库优化成一条SQL:面试官最常问的慢查询问题
这套OA系统里有些列表页是循环查库实现的,比如展示请假列表时,每行数据都根据applicant_id再查一次用户表拿姓名,N条数据就是N+1次查询。这个写法在数据量小的时候没问题,但面试聊性能优化时这就是话题点。优化的做法是改成JOIN查询:
SELECT l.*, u.real_name AS applicant_name FROM hrm_leave l LEFT JOIN sys_user u ON l.applicant_id = u.id WHERE l.create_time >= ? ORDER BY l.create_time DESC改造后一次查询拿到全部关联数据,再配合MyBatis的ResultMap映射,列表页的响应时间能从秒级降到毫秒级。这个改动不需要动页面代码,只改Mapper里的SQL和XML映射,风险低收益明显。做完之后可以用EXPLAIN看执行计划确认走了索引,这条经验在面试聊项目时比任何自我介绍都有说服力。
6.3 一套自查清单:改完代码后按这个顺序过一遍
每次改完代码,我会强制自己走一遍完整的验证流程,这套顺序是从这个OA项目里总结出来的:第一步,启动Tomcat看控制台日志有没有红色异常,没有再进行下一步;第二步,用管理员账号登录,进入所有修改过的菜单页面,确认页面能正常渲染,不是白屏或500;第三步,走一条完整业务流——比如提交一个申请、审批通过、查看归档——确认状态流转正确;第四步,测试异常路径,比如提交一个非法日期的申请,看系统是否拒绝了而不是报500错;第五步,看数据库里对应表的记录是否更新正确。
这五步看起来简单,但它能挡掉我实际开发中至少一半的弱智bug。刚开始接触项目的时候,我每次改完代码就直接点调试,页面报错就看堆栈,来回折腾好几轮才处理好一个模块。从那以后我每次拿到新项目或者改完任何一段代码,都强制走一遍这条自查清单,效率反而比急着调试高不少。做这套OA系统项目也一样,按这个顺序来,你大概率能比想象中更快把它跑通,希望帮到你。
本文还有配套的精品资源,点击获取