
简介云网OA 2.2 是一款面向毕业设计与课程设计场景的在线办公自动化系统定位为可供学生实践的企业级 Web 项目覆盖文档管理、任务分配、流程审批等日常办公模块。压缩包共约 2000 个文件压缩后仅 35.68MB以 JSP 动态页面、properties 配置、JavaScript 脚本、HTML/CSS 界面文件为主同时包含少量 SQL 脚本与 Python 辅助文件便于导入开发环境后查看前后端与数据结构。目前已有 118 人学习下载适合正在选题、课程设计或准备答辩的计算机相关专业学生参考。除完整源码外资源还包含全局皮肤、日历组件、富文本编辑器等界面样式可以从中学习 OA 系统的页面布局、权限控制与功能拆分思路也能为二次开发或课设演示提供直接素材。1. 毕设级云网 OA 2.2 压缩包先想清楚你拿到的到底是一套什么系统拿到“云网OA 2.2_cloundoa毕设 课设.zip”这个压缩包的同学第一反应通常是赶紧解压、导入 IDE、跑起来。但在我接手过的几十个毕业设计 OA 项目里真正让你卡住的往往不是代码本身而是你还没搞清楚这个压缩包里的项目边界它叫“云网OA”版本号 2.2路径里带cloundoa注意不是cloud是clound这基本可以判断为教学场景下产出的、以“云网”命名的办公自动化系统。后缀括号里的“毕设 课设”说明它同时承担两重身份既是毕业设计的完整交付物也是课程设计的演示工程。这类项目的技术栈相当统一绝大多数是 JSP Servlet MySQL Tomcat 的经典组合少量会用 SSHStruts2 Spring Hibernate或 SSMSpring SpringMVC MyBatis做壳但业务模块大同小异用户登录、部门管理、员工管理、公告通知、公文流转、会议管理、日程安排、通讯录、个人设置。2.2 这个版本号暗示它经历过一轮迭代比初版多了点东西但核心骨架仍然是单体应用、服务端渲染、同步请求那一套。换句话说你要处理的不是一个高并发分布式系统而是一个“能跑通、能答辩、能演示”的教学级 OA。这个压缩包最有价值的点在于它是一次完整的交付数据库脚本、项目源码、部署说明、可能还有论文框架或演示截图。你要做的不是从零造轮子而是把一个别人写好的系统变成你能讲清楚、能改得动、能应对老师追问的东西。但在动手之前先做一件事检查压缩包的完整性。网上下的毕设包经常出现解压报错invalid zip archive: could not find eocd或者解压到一半文件损坏的情况拿到包第一件事就是验证压缩包本身没坏。2. 云网 OA 2.2 的模块拆解与数据库设计先把根基看明白2.1 从压缩包结构反推技术选型看到目录就知道怎么跑打开压缩包正常情况下你会看到以下目录或文件srcJava 源码、WebRoot或webWeb 根目录、oa.sql或db_cloud_oa.sql初始化脚本、README或部署文档、可能还有论文.docx和答辩 PPT。拿到这些后先别急着配环境你的第一步是确认技术栈因为不同技术栈的启动路径完全不同。我判断项目的常用顺序是这样的# 解压后先看 root 目录结构 unzip 云网OA\ 2.2_cloundoa*.zip -d cloud_oa cd cloud_oa ls -la find . -name *.xml | head -20 find . -name *.properties -o -name *.yml | head -20如果看到pom.xml就是 Maven 工程看到build.gradle就是 Gradle只有.classpath和.project就是 Eclipse 直接导入的纯 Web 工程很可能用 MyEclipse 开发的。如果是后者你在 IDEA 里导入时要选“Eclipse”方式然后手动配置 Tomcat 和 Artifact。# 核心判断有没有 MyBatis / Hibernate 的影子 find . -name mybatis-config.xml -o -name SqlMapConfig.xml -o -name hibernate.cfg.xml # 有没有 Spring 配置 find . -name applicationContext.xml -o -name spring-mvc.xml -o -name struts.xml2.2 版如果还停留在 JSP Servlet 直连 JDBC 的阶段你会看到一个DBUtil.java或JdbcUtils.java里面写死了一套数据库连接。如果是 SSM你会看到applicationContext.xml里挂着数据源、事务管理器spring-mvc.xml里配置组件扫描和视图解析器。这些信息直接决定你给 MySQL 建库时用哪种字符集、给 Tomcat 配哪个端口。2.2 数据库脚本的导入与版本兼容性处理数据库是这类系统的命门。你拿到的oa.sql都是导出脚本而非迁移脚本也就是说它默认你本地 MySQL 版本和开发环境兼容。如果脚本里出现ENGINEInnoDB DEFAULT CHARSETutf8mb4那你必须用 MySQL 5.7 以上否则utf8mb4的排序规则可能会报错。如果出现CREATE DATABASE语句说明脚本期望你手动建库之后选库执行如果没有默认库名可能叫cloud_oa或db_oa。-- 推荐操作顺序手动建库指定字符集再导入 CREATE DATABASE IF NOT EXISTS cloud_oa DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE cloud_oa; SOURCE /path/to/oa.sql;导入后第一件事不是看有多少张表而是验证关键数据SELECT COUNT(*) FROM users;和SELECT * FROM users LIMIT 5;。如果返回 0 行说明脚本里只有建表语句没有初始数据你需要手动造一个管理员账号否则系统连登录页都过不去。常见处理方式是在users表里直接插入一条记录密码字段如果是 MD5就预先把admin123转成 MD5 存储。提示导入脚本时如果遇到ERROR 1064或ERROR 1366优先检查是不是字符集不对或者脚本里带有 BOM 头导致第一条语句解析异常。用sed -i 1s/^\xef\xbb\xbf// oa.sql清掉 BOM 再试。2.3 核心业务表关系OA 系统最核心的 7 张表这套 OA 的模块不管怎么包装核心业务表基本上就那几张用户表users、部门表dept、公告表notice、公文表document、会议表meeting、日程表schedule、通讯录表contact。它们之间的关系你至少要能画出来——这是答辩时高频考的 ER 图。看表的两个常用手段-- 查看所有表名和注释 SELECT TABLE_NAME, TABLE_COMMENT FROM information_schema.TABLES WHERE TABLE_SCHEMA cloud_oa; -- 查看单表结构的细节字段注释、类型、默认值 SHOW FULL COLUMNS FROM users;你会注意到 2.2 版本典型的设计风格users表里有dept_id外键关联部门表但大概率没有真正的外键约束教学项目喜欢去掉外键用逻辑关联避免删除报错公文表里会有status字段草稿/待审批/已审批会议表里会有room_id、start_time、end_time时间字段用DATETIME而不是TIMESTAMP。这些设计决定了你在写 SQL 或扩功能时要不要考虑联表查询也决定了你能不能直接在工具里把某个流程跑通。3. 本地跑通云网 OA 2.2 的完整部署路径Tomcat、IDEA、MySQL 一个都不能少3.1 环境版本组合怎么选JSP 老项目最怕高版本坑这是整个项目最大的坑。毕设 OA 通常用 JDK 1.7 或 1.8 开发你如果本地装了 JDK 17 甚至 21大概率会遇到三类问题一是javax.servlet包在新 JDK 里不在默认类路径中要手动引入二是 Tomcat 10 及以上把javax.servlet换成了jakarta.servlet老项目的 web.xml 头部声明还是旧的直接部署失败三是高版本 Tomcat 对 JSP 编译器的行为有差异可能会报诡异错误。我给出的最稳妥配置组合是JDK 8 Tomcat 8.5/9.0 MySQL 5.7。如果脚本用了utf8mb4MySQL 8.0 也能用但要注意 8.0 默认的认证插件是caching_sha2_password老版本的 MySQL Connector/J5.x连不上需要换成mysql-connector-java 8.0.x或把用户认证改回mysql_native_passwordALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码; FLUSH PRIVILEGES;JDK 和 Tomcat 的版本匹配关系简单判断Tomcat 9 支持 Servlet 4.0对应的 web.xml 头声明是http://xmlns.jcp.org/xml/ns/javaee如果你的项目web.xml用的是http://java.sun.com/xml/ns/javaee且版本是 2.5 或 3.0放在 Tomcat 9 下也能正常加载因为 Tomcat 做了兼容。真正不能碰的是 Tomcat 10除非你愿意全局替换javax.*为jakarta.*——证书项目不值得这么折腾。3.2 IDEA 导入非 Maven 工程用 Artifact 而不是直接跑如果项目是普通的 Web 工程没有pom.xmlIDEA 里直接 File → Open 选择根目录后你需要手动做三件事设置项目 SDK 为 JDK 8把WebRoot或web目录标记为 Web 资源目录配置 Tomcat Server 的 Deployment 添加war exploded。war exploded模式的好处是改 JSP 不用重新打包刷新页面就能看到效果这对答辩前的演示修改特别有用。在WEB-INF/classes下如果有编译好的.class文件我建议先全删掉重新编译避免旧字节码和当前 JDK 兼容问题。然后把项目里所有依赖的 jar 包确认一遍WEB-INF/lib下必须有mysql-connector-java、jstl、standard、servlet-api有些版本不放由 Tomcat 提供缺 JSTL 的典型报错是javax.servlet.jsp.tagext.TagLibraryValidator无法解析 TLD 文件。# 一个快速诊断确认 lib 下关键 jar 是否齐全 cd 云网OA*/WebRoot/WEB-INF/lib ls | grep -E mysql|jstl|standard|jta|spring如果用了 Spring 不用 Maven你还会看到一堆spring-core、spring-webmvc等 jar版本号通常是 3.x 或 4.x这些和老项目是配套的不要随手升级。改 jar 包版本等于给自己挖坑Spring 4 配 JDK 8 完全没问题不用动。3.3 数据库连接配置与启动顺序先库后服务修改数据库连接是必然要做的事。找jdbc.properties或db.properties或直接在 Spring 配置和DBUtil.java里改jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/cloud_oa?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password123456注意useSSLfalse和serverTimezone这两个参数。MySQL 8.0 对时区要求严格不加serverTimezone会直接报The server time zone value йʱ is unrecognized。useSSLfalse是避免 MySQL 8.0 默认开启 SSL 导致连接警告甚至失败。驱动类名要是com.mysql.cj.jdbc.Driver对应 8.x 驱动而不是com.mysql.jdbc.Driver对应 5.x 驱动两者区别在很多老项目中会被忽略导致ClassNotFoundException。启动顺序也有讲究先确认 MySQL 服务已经启动再启动 Tomcat因为 Tomcat 的 Spring 容器初始化时会创建连接池和DataSource。如果 MySQL 没启动Tomcat 启动日志会刷Cannot create PoolableConnectionFactory但你不会立刻看到业务报错必须等访问页面才暴露。所以部署阶段顺序固定导库 → 改配置 → 启动 MySQL → 启动 Tomcat → 访问http://localhost:8080/项目名/。# Linux/Mac 下快速验证 Tomcat 是否正常起来 curl -I http://localhost:8080/cloud_oa_init 2/dev/null || echo Tomcat 没起来 # Windows 下看 catalina.log 里的关键行 tail -f logs/catalina.out | grep -E Deploying web application|Server startup3.4 代码里的分页与 JSP 路径坑在相对路径和编码打开项目里的WEB-INF/web.xml看欢迎页配置是/login.jsp还是/index.do。如果是后者说明系统设计了默认访问控制器你直接访问根路径会跳到登录 Controller。这种设计在答辩演示时要讲清楚因为这体现的不只是“一个 JSP 页面”而是三层结构的入口。JSP 页面里最常见的坑是编码乱码。2.2 版如果是用 GBK 编写的 JSP 文件你用 UTF-8 的 IDEA 打开会满屏乱码但项目在服务器上却正常——因为 Tomcat 8 及以上默认 URI 编码是 UTF-8而页面pageEncoding写的是 GBK。这种不一致会在你提交中文表单后出现数据乱码。正确做法是在 IDEA 的 Settings → File Encodings 里把 Global Encoding、Project Encoding、Default encoding for properties files 全部设为 UTF-8同时逐个检查 JSP 头部% page languagejava contentTypetext/html; charsetUTF-8 pageEncodingUTF-8%如果原有 JSP 是GBK声明的要么全局转码成 UTF-8注意META标签里的 charset 也要改要么在 MySQL 连接 URL 里保持characterEncodingutf8然后让 Tomcat 的 web.xml 或过滤器统一处理请求编码。常见做法是加一个 CharacterEncodingFilterfilter 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 init-param param-nameforceEncoding/param-name param-valuetrue/param-value /init-param /filter filter-mapping filter-nameencodingFilter/filter-name url-pattern/*/url-pattern /filter-mappingforceEncoding设为true意味着请求和响应都强制使用 UTF-8即使页面声明不一致也能压住大部分乱码问题。但如果数据库里的历史数据本身是 GBK 存进去的统一改成 UTF-8 后反而会读乱这时候先老老实实把数据库字符集调成跟代码一致。4. 登录逻辑与增删改查背后的设计2.2 版最常被追问的 4 个实现点4.1 登录会话管理从 LoginServlet 到 Session 再到拦截器2.2 版 OA 的登录逻辑几乎都是同一个套路用户提交用户名和密码到LoginServletServlet 调UserService.login()Service 调UserDao.findByUsernameAndPassword()成功则把user对象存到 Session然后response.sendRedirect(index.do)失败则request.setAttribute(msg, 用户名或密码错误)然后forward回login.jsp。你至少要能写出核心查询的样子public User findByUsername(String username) throws SQLException { String sql SELECT * FROM users WHERE username ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { return new User( rs.getInt(id), rs.getString(username), rs.getString(password), rs.getInt(dept_id), rs.getString(real_name) ); } } } return null; }注意这段代码用的是PreparedStatement不是Statement这是防止 SQL 注入的基本要求。如果在项目的LoginDao里看到字符串拼接 SQL例如SELECT * FROM users WHERE username username 你要么改掉它要么在答辩时主动提出“这里我做了防注入处理”并把 PreparedStatement 展示出来。这是老师必看的安全点。会话控制方面web.xml里一般会配一个过滤器或者拦截器检查 Session 是否为空。如果没有系统里所有受保护页面都能直接被打网址访问这说明项目缺了最重要的安全机制。补一个最简单的 Session 校验过滤器WebFilter(/*) public class LoginFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; if (request.getRequestURI().contains(/login.jsp) || request.getRequestURI().contains(/LoginServlet) || request.getRequestURI().endsWith(.js) || request.getRequestURI().endsWith(.css)) { chain.doFilter(req, resp); return; } HttpSession session request.getSession(false); if (session ! null session.getAttribute(user) ! null) { chain.doFilter(req, resp); } else { response.sendRedirect(request.getContextPath() /login.jsp); } } }request.getSession(false)和request.getSession()的区别是这里的关键false表示如果当前没有会话就不创建新的避免未经登录的用户每次访问都产生无用 Session也给后面的内存管理减负。4.2 新增员工和部门联动的表单提交字段校验要写在两端OA 系统里“用户管理 → 添加用户”是除了登录之外最重要的操作。正常表单提交到UserServlet的add方法Servlet 要做三件事接收参数、组装对象、调用 Service 并判断影响行数。你自己复现时代码形态基本是这样request.setCharacterEncoding(UTF-8); String username request.getParameter(username); String password request.getParameter(password); String realName request.getParameter(realName); String deptIdStr request.getParameter(deptId); Integer deptId Integer.parseInt(deptIdStr); User user new User(); user.setUsername(username); user.setPassword(MD5Util.md5(password)); // 如果项目是 MD5 存储 user.setRealName(realName); user.setDeptId(deptId); UserService userService new UserService(); boolean success userService.addUser(user); if (success) { response.sendRedirect(user.do?methodlistpage1); } else { request.setAttribute(errorMsg, 用户名已存在); request.getRequestDispatcher(/user/add.jsp).forward(request, response); }两个细节要留意。第一password是不是明文入库。很多课设项目在users表里直接存明文密码这个在答辩时非常容易被追问“安全性怎么做”。即使不改代码也要明白 MD5 和加盐的区别MD5 本身不可逆但可被彩虹表破解加盐多一层随机字符串再散列会更稳。第二deptId从字符串转int时如果前端没有做必选校验用户不选部门就会抛NumberFormatException。前端 JSP 的下拉框最好默认选中第一个部门后端在解析前加一个非空判断。前端页面还有一层更容易被忽略的校验新增用户的表单把部门下拉框的数据加载出来。如果你的项目在add.jsp里用 JSTL 循环渲染部门c:forEach items${deptList} vardept那renderAddPage这个 Servlet 方法里一定要先查出所有部门再 forward。漏了这一步新增页面部门下拉框是空的你点击提交时deptId就是null报错点在后端甚至数据库NOT NULL约束。// Servlet 中渲染新增页面的写法 ListDept deptList deptService.findAll(); request.setAttribute(deptList, deptList); request.getRequestDispatcher(/user/add.jsp).forward(request, response);这里把deptList放在request里JSP 才能用 JSTL 取到。如果写在 Session 里也能用但会造成一个隐患部门数据更新后Session 里的旧列表不会刷新直到重新登录。所以我一般遵循“一次请求的数据走 request跨请求的登录态走 session”的原则。4.3 公文审批的状态流转用 int 状态码还是用枚举在云网 OA 2.2 的模块里公文审批或者叫审批流程最能体现一个毕设的完整度。常见的设计是document表里有一个status字段有四个状态值0 草稿、1 待审批、2 已通过、3 已驳回。每次审批操作是一次UPDATEUPDATE document SET status 2, approver_id ?, approve_time NOW(), approve_comment ? WHERE id ? AND status 1;WHERE id ? AND status 1这个条件很关键它是一个乐观锁思想的简化版只有当当前状态还是待审批时才能更新为已通过防止并发审批把状态覆盖掉。毕设答辩时老师可能会问“如果两个审批人同时点了通过怎么办”你就可以从这个WHERE条件出发讲清楚。页面列表的查询通常也要跟着状态过滤String status request.getParameter(status); String sql SELECT d.*, u.real_name FROM document d LEFT JOIN users u ON d.submit_id u.id WHERE d.status ? ORDER BY d.create_time DESC;这段 SQL 里用了LEFT JOIN而不是INNER JOIN意图是即使提交人已经被删除或者外键被置空公文记录也不该跟着一起消失。老手和新手的区别就在这种条件取舍上你想在答辩中体现“读代码出了门道”这些细节就是要主动讲的内容。4.4 个人日程和会议冲突检测SQL 时间区间交集判断增加一个日程或者预约会议室时2.2 版如果没做冲突检测那算很正常的完整性缺陷。你可以顺手补一个半小时内能复现的校验这也属于“课设可升级”亮点。判断两个时间段是否重叠的 SQL 逻辑是新的开始时间小于旧的结束时间且新的结束时间大于旧的开始时间。反过来两个区间完全不重叠的条件是newStart oldEnd或newEnd oldStart。SELECT COUNT(*) FROM schedule WHERE user_id ? AND status 1 AND start_time ? -- 新结束时间 AND end_time ? -- 新开始时间参数依次是当前用户 ID、新日程结束时间、新日程开始时间。查询结果大于 0 就说明有撞期。注意status 1表示有效记录避免把已取消的日程也列入判断。这个方法也可以预填进MeetingServlet的bookRoom方法里用于会议室预约冲突检测改一下表名和字段就行。5. 升级云网 OA 2.2 的 4 个低成本增强点子答辩亮点从这里出5.1 新增操作日志模块用一个注解加一个拦截器毕设答辩时老师经常会问“这个系统怎么管理用户的登录记录”。如果原包没有日志表你可以新增一张operation_log表然后写一个 Spring AOP 切面或者简单的手写 LogFilter记录每次增删改操作的用户名、操作时间、IP、请求 URI。手动实现的思路在课设级别足够public class LogInterceptor extends HandlerInterceptorAdapter { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { HttpSession session request.getSession(); Object userObj session.getAttribute(user); if (userObj ! null) { String username ((User) userObj).getUsername(); String uri request.getRequestURI(); String ip getIpAddr(request); LogService.addLog(username, uri, ip, new Date()); } return true; } }getIpAddr有讲究如果项目前面挂了 Nginxrequest.getRemoteAddr()拿到的是127.0.0.1要取X-Forwarded-For请求头里的第一个 IP。代码里加个判断就行。这个功能扩展不需要改已有 DAO只加一张表和一个 Service演示时先添加一条数据再切到日志页面刷新效果非常直观。5.2 上传头像和附件注意MAX_FILE_SIZE与容器临时目录云网 OA 2.2 的很多版本里没有文件上传功能但你可以在“个人设置”里补一个头像上传或者在“公告附件”里补一个附件上传。使用 Servlet 3.0 原生上传能力代码量不大Part part request.getPart(avatar); String fileName part.getSubmittedFileName(); String realPath getServletContext().getRealPath(/upload); File dir new File(realPath); if (!dir.exists()) dir.mkdirs(); part.write(realPath File.separator System.currentTimeMillis() _ fileName);文件保存路径的三个关键注意事项不要直接存在WEB-INF下因为 Tomcat 重启时 redeploy 会清理掉要存在项目外部目录或webapps之外的绝对路径。文件名加上时间戳避免重名覆盖这属于极低成本但极其必要的改动。request.getPart前必须在web.xml或 Servlet 注解上配置multipart-config否则容器不会解析 multipart 请求getPart直接抛异常。5.3 用连接池替换直连 JDBCDruid 的最简接入法很多课设 OA 的数据库连接是DBUtil里DriverManager.getConnection一次请求拿一次连接关闭即断。这在 50 个并发模拟下就可能出现连接超时。你可以顺手引入 Druid 连接池改动只集中在DBUtil.java和pom.xml如果是非 Maven 就拷 jar 进WEB-INF/libpublic class DBUtil { private static DruidDataSource dataSource; static { try { Properties props new Properties(); props.load(DBUtil.class.getClassLoader().getResourceAsStream(druid.properties)); dataSource (DruidDataSource) DruidDataSourceFactory.createDataSource(props); } catch (Exception e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }配置文件druid.properties长这样driverClassNamecom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/cloud_oa?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8 usernameroot password123456 initialSize5 maxActive20 maxWait3000initialSize5是启动时预创建的连接数maxActive20是最大活跃连接数maxWait3000是拿不到连接时最多等 3 秒然后抛异常。这三个参数是线上调优最常见的旋钮并发不高时initialSize保持 5 够用答辩模拟并发时可以临时调到 50 演示监控图。接入 Druid 后访问/druid/index.html可以直接看 SQL 监控和连接池实时状态这是答辩时一个极其出彩的演示点而且零 UI 成本。5.4 数据可视化用 ECharts 做部门人数统计图最后一个低成本高感知的增强在原项目里加一个统计页面用 ECharts 显示部门人数分布和公告发布趋势。后端提供一个返回 JSON 的 Servlet前端用 ECharts 拉数据// 统计部门人数返回 JSON 数组 ListMapString, Object list userService.countGroupByDept(); Gson gson new Gson(); response.setContentType(application/json;charsetUTF-8); response.getWriter().write(gson.toJson(list));前端页面用 ECharts 官方 CDN注意毕设环境如果断网本地放一个 echarts.min.js 更稳核心配置就是一个series里的type: pie和data映射。这个增强虽然业务价值一般但视觉冲击力极强而且技术范围只涉及 Servlet JSON 前端图表库不上框架工作量可控。6. 部署到 Linux 服务器与打包发布把 zip 压回可交付的 war修完代码、答辩完最后一步是把项目打成 war 包部署到 Linux 服务器上给老师或同学远程看。这个场景在毕设验收时比本地演示更显专业。先确认打包方式。非 Maven 工程在 IDEA 里可以直接 Build Artifact → 选择目标.warMaven 工程则执行mvn clean package -DskipTests然后你会得到一个cloud_oa.war名称可能不同有两种部署到 Tomcat 的方式。最简单的做法是直接扔进 Tomcat 的webapps目录然后重启 Tomcat 让它自动解压cp cloud_oa.war /opt/tomcat/webapps/ /opt/tomcat/bin/shutdown.sh /opt/tomcat/bin/startup.sh tail -f /opt/tomcat/logs/catalina.out启动日志里出现Deployment of web application archive [cloud_oa.war] has finished就代表成功。此时访问路径是http://服务器IP:8080/cloud_oa/login.jsp。端口、数据库地址都需要改成服务器上的对应值数据库导入用mysql -uroot -p oa.sql。如果云服务器有安全组只放开 8080 端口是不够的还要确认 MySQL 的 3306 端口是否只允许内网访问不要暴露到公网。Tomcat 的server.xml里有三个端口8005 是关闭端口默认 SHUTDOWN 指令、8080 是 HTTP 端口、8009 是 AJP 端口。如果你不需要 AJP 给 Apache 用建议直接注释掉 8009因为 CVE-2020-1938Ghostcat就是通过 AJP 端口攻击的线上部署安全底线就是关闭不用的端口。最后验证访问时用curl拿不到登录页的 HTML 源码因为现在很多 OA 登录页跳转带jsessionid或需要 Session这个不是报错。真正判断部署成功的标准是浏览器能打开登录页、输入 2.2 版初始管理员账号能登录、增删改查走通一条完整链路。到这一步云网 OA 2.2 从压缩包到线上可访问就全部完成了剩下你可以拿这部分部署记录直接补进毕设文档的“系统部署”章节。本文还有配套的精品资源点击获取