简介:这是一份集成 OA、HR、CRM 三大业务模块的大型协同办公系统源码包,适合作企业级管理系统开发、二次开发或源码分析的学习者使用。源码按模块化思路组织,OA 含工作流、公文、任务、会议和考勤,HR 覆盖员工信息、招聘、绩效、培训与薪酬,CRM 则包括客户、销售机会、服务、市场分析和合同管理,完整呈现多模块系统的权限划分与数据流转方式。压缩包整体约 38.19MB,目前已有 107 人学习;因上游未提供文件清单和具体类型,此处暂不罗列文件构成。阅读这份源码既能了解从流程审批到客户跟踪的完整业务闭环,也能结合数据库设计与分层架构,理解权限、组织、日志审计等关键实现,为二次开发和系统集成提供参考,有助于在真实项目中快速上手。
1. 大型协同办公系统源码:OA+HR+CRM 一体包到底能解决什么问题
很多团队拿到这套“大型协同办公系统源码(OA+HR+CRM)源码 (1).zip”,第一反应是“又是老掉牙的 SSH 框架打包货”,但真正把它在本地跑起来之后,你会发现它其实是把企业里最常用的三套管理系统——OA(办公自动化)、HR(人力资源)、CRM(客户关系管理)——塞进了一个工程里。对于需要快速搭建内部管理平台、做二次开发交付给甲方、或者拿来当毕业设计/课程设计项目的人来说,它最大的价值不是“代码能跑”,而是“一个工程里同时能看到三种业务域的完整实现”。
这个包适合两类人:一类是接外包做企业信息化项目的开发者,拿它当基础工程改品牌、改流程可以省掉从零搭建的时间;另一类是正在学习 Java Web 开发的工程师,需要看一个真实的、包含多模块、有权限控制和流程审批的项目源码。它不解决“从零构思一套 OA 系统”的问题,它解决的是“当你需要一套能演示、能部署、能二次开发的协同办公底座时,这个压缩包能给你省多久”的问题。
先说结论:这套源码的核心价值集中在三块——单体应用的多模块划分方式、基于 RBAC 的权限模型、以及 OA 审批流与 CRM 客户数据之间的读写关系。把它搞透之后,你再去看市面上动辄几十万的商业 OA 产品,会发现在核心架构上它们并没有多出什么神秘东西。
2. 看懂工程结构:先把压缩包里这三个业务域拆开讲
2.1 大型协同办公系统源码的工程分层:从 zip 到能跑的 Web 项目
解压之后第一件事不是急着导入 IDE,而是先看目录结构。大型协同办公系统源码这个包我拿到手后观察到的典型布局是 Maven 多模块工程,或者单工程多包名,具体取决于打包者用的构建工具。常见的结构是:
oa-hr-crm/ ├── pom.xml // Maven 父工程 ├── oa-common/ // 公共模块:工具类、常量、统一返回体 ├── oa-system/ // 系统管理:用户、角色、菜单、权限 ├── oa-oa/ // OA 模块:审批、公告、日程、会议 ├── oa-hr/ // HR 模块:员工档案、考勤、薪资、招聘 ├── oa-crm/ // CRM 模块:客户、商机、合同、跟进记录 ├── sql/ // 初始化脚本 └── README.md拿到 zip 后第一步不是打开 IDEA 就开始等 Maven 下载依赖,而是先确认三件事:JDK 版本要求、数据库类型(MySQL 还是 Oracle)、是否依赖 Redis 做会话共享。很多来源不明的源码压缩包在这里会埋坑——pom.xml 里写的依赖版本老到已经在中央仓库被清理,或者数据库脚本是用某个特定版本的 MySQL 导出的,直接导入新版会报排序规则错误。
正确操作顺序是:先读 README 和 sql 目录下的建库脚本,确认数据库版本,再导入 IDE。如果压缩包里没有 README,就看 pom.xml 里的<properties>节点,那里会写清楚编译版本。常见做法是先在本地用命令快速验证依赖是否能拉全:
# 在解压后的工程根目录执行,先只做编译级别的校验 mvn clean compile -DskipTests -q # 如果 parent 或 dependency 拉取失败,检查 settings.xml 是否使用了内部镜像 # 实在拉不动就换阿里云镜像: # <mirror> 配置指向 https://maven.aliyun.com/repository/public这段命令的逻辑是先用 compile 目标看整个工程的依赖能不能解析成功,不需要启动服务。-DskipTests是为了跳过测试编译,-q是安静模式,只在出错时打印信息。如果这一步过不去,后面所有模块都跑不起来。常见失败原因是网络不通和仓库地址失效,换镜像源能解决 80% 的问题。
依赖拉通后,再看数据库脚本。这个步骤极其关键,因为 OA、HR、CRM 三个模块的数据表是交织在一起的,不能只导某一张表。
2.2 OA+HR+CRM 三个模块的数据表设计:为什么它们能放同一个库
大型协同办公系统源码里最容易被人忽略但又最值得学习的,是它的数据库设计。OA 的表通常包含oa_leave(请假单)、oa_approval(审批流)、oa_notice(公告);HR 模块有hr_employee(员工主数据)、hr_attendance(考勤记录)、hr_salary(薪资项);CRM 模块则是crm_customer(客户)、crm_contract(合同)、crm_follow_record(跟进记录)。
真正的设计亮点在于它们之间的关联关系。比如crm_customer表里有一个owner_id字段,指向hr_employee表;oa_approval里的applicant_id也指向hr_employee。这意味着三个模块共享了一套员工主数据,而不是各自维护一份用户表。用 SQL 来看这种关联最直观:
-- 查某个销售名下的客户列表,关联 HR 员工表获取真实姓名 SELECT c.customer_name, c.phone, e.employee_name AS owner_name FROM crm_customer c LEFT JOIN hr_employee e ON c.owner_id = e.id WHERE e.employee_name = '张三' AND c.deleted = 0;这里的关键是deleted = 0。这套源码大概率采用逻辑删除而不是物理删除,这是企业级系统的基本素养——客户数据被误删后需要能恢复,审批记录需要留痕。你在二次开发时如果要加字段,优先考虑在扩展表里加,而不是改核心表的字段,否则后面升级合并代码会非常痛苦。
CRM 模块最体现水平的是跟进记录的写法。它通常不是往crm_customer表里塞一个“最近跟进时间”字段就完事,而是单独建一张crm_follow_record流水表,客户表只冗余一个last_follow_time作为列表排序用。这种“流水表 + 冗余字段”的设计在大型协同办公系统源码里随处可见,是值得搬走的模式。
2.3 公共模块的作用:统一返回值、分页参数和权限注解的底层逻辑
oa-common模块是这个包的技术精华所在,不要因为它没有业务逻辑就跳过。它至少提供了三类能力:统一返回体、分页封装、权限注解。统一返回体决定了前端axios拦截器怎么写,分页封装决定了每个 Manager 层的分页查询长什么样,权限注解则和 Spring MVC 的拦截器配合完成菜单级和按钮级鉴权。
// 统一返回体,所有 Controller 接口都返回这个结构 public class R<T> { private int code; // 200 成功,500 失败 private String msg; // 提示信息 private T data; // 业务数据 public static <T> R<T> ok(T data) { R<T> r = new R<>(); r.code = 200; r.msg = "ok"; r.data = data; return r; } }这段代码逻辑很简单,但它的意义在于:如果这套源码里所有 Controller 都遵守 “返回 R” 的约定,你在做二次开发时写新接口就不用跟前端对格式了。翻源码时花五分钟确认一下有多少接口真的返回了 R 而不是裸数据,这决定了前端要不要为了你新加的接口单独写一层适配。
权限注解也是公共模块里的好东西。通常是一个@RequiresPermission("crm:customer:add")这样的注解,配合 Spring AOP 切面,在方法调用前校验当前用户是否有对应权限码。你要扩展一个新功能,比如“客户批量导入”,只需要后端加一个接口方法并标上@RequiresPermission("crm:customer:import"),然后在系统管理的菜单管理里挂上这个权限码,前端控制按钮显隐,后端拦截器做二次校验。这套机制看懂了,你就能理解为什么说所有 OA 系系统的权限模型都是同一套肌肉记忆。
3. 本地跑通的最小路径:从初始化 SQL 到一键启动
3.1 用 MySQL + Tomcat 在本地跑通 OA+HR+CRM 的完整步骤
现在进入实操环节。假设你已经把大型协同办公系统源码解压到D:/oa-hr-crm,环境是 Windows + IDEA + JDK 8 + MySQL 5.7。开始之前把所有 IDE 的自动导入关掉,防止它在你没看数据库脚本之前就把代码编译失败的红字标得到处都是。
第一步是建库和数据初始化。注意先看 SQL 脚本里有没有CREATE DATABASE语句,很多打包源码会把建库语句和建表语句分开写,也有部分版本要求你手动建库后只导入表结构。
# 登录 MySQL,创建字符集为 utf8mb4 的数据库 mysql -uroot -p CREATE DATABASE IF NOT EXISTS oa_hr_crm DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci; USE oa_hr_crm; SOURCE D:/oa-hr-crm/sql/oa_hr_crm.sql;这里用utf8mb4而不是老旧基操utf8,是因为 HR 模块的员工姓名和 CRM 模块的客户备注里可能存 emoji 表情,用 utf8 在 MySQL 5.5 之后的版本会让 INSERT 直接报错。如果你看到导入脚本时报错“Unknown collation”,说明你的 MySQL 版本太老,需要把脚本里的utf8mb4_0900_ai_ci全局替换成utf8mb4_general_ci——前者是 MySQL 8.0 的默认排序规则。
注意:有些从老项目导出的 SQL 里带着DEFINER=的存储过程或视图定义,导入到本地时会因为当前用户不匹配报“ERROR 1449”,别慌,用文本编辑器把DEFINER后面的内容删掉再导入即可。
数据库就绪后,第二步是改 JDBC 连接配置。典型位置是src/main/resources/jdbc.properties或application.yml,这取决于工程用的是 Spring MVC 还是 Spring Boot。看清楚再改,不要上来就改 application.yml,结果发现工程根本没引入 Spring Boot。
# jdbc.properties 写法 jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/oa_hr_crm?useUnicode=true&characterEncoding=utf8mb4&useSSL=false&serverTimezone=GMT%2B8 jdbc.username=root jdbc.password=123456这里的serverTimezone=GMT%2B8是给 MySQL 8.0 的 JDBC 驱动用的,如果你用的驱动是 5.x 版本,这个参数不能带,带了会报“The server time zone value conflicts”。判断驱动版本看 pom.xml 里的mysql-connector-java版本号,5.1.40 以下不需要时区参数。这就是最常见的翻车点——很多人照抄新版本博客的配置,用在老工程上,结果客户端和数据库 GMT 表情互相不理解,直接 Connection refused。
第三步是启动方式。如果是 Maven 多模块工程,需要先把公共模块和系统管理模块 install 到本地仓库,否则 Tomcat 起来时会报 ClassNotFoundException:
mvn install -pl oa-common,oa-system -am -DskipTests这段命令的作用是只构建oa-common和oa-system两个模块,-am表示同时构建它们依赖的模块。如果整个工程只有一个 war 包没有子模块,那直接配置 Tomcat 的 Artifact 启动即可。启动完能看到日志里有“Initializing Spring root WebApplicationContext”就说明上下文加载成功了。
3.2 配置文件的坑:数据源、Redis、文件存储路径缺一不可
如果你运气好,上面三步走完系统就能登录了。但绝大多数打包源码会在启动时卡在配置文件上,最常见的三类问题:Redis 连不上、文件上传路径不存在、日志路径无权限。
先看 Redis。这套大型协同办公系统源码如果引入了 Redis,通常会用来存验证码、Session 或者字典缓存。配置文件的坑在于默认配置写的是127.0.0.1:6379,而你本地没有启动 Redis 服务。解决方式是两种:要么本地装一个 Redis,要么把配置里的spring.redis.host改成你不用的随机端口,让连接失败后走降级逻辑——但前提是代码里对 Redis 连接失败有 try-catch 兜底,否则启动直接抛异常。拿不准就先在本地启动 Redis:
# Mac 或 Linux 下启动 Redis redis-server --port 6379 --daemonize yes # Windows 下如果是解压版 redis-server.exe redis.windows.conf不要觉得本地开发就无所谓,Session 存 Redis 和存 Tomcat 内存里,登录冲掉重登是两种完全不同的体验。如果这套源码把登录 Session 放 Redis,你本地没启动它,每重启一次 Tomcat 就要重新登录一次,极影响调试效率。
然后是文件上传路径。OA 系统必然有附件上传,代码里会写一个上传根目录,常见的是/usr/local/upload。这个路径在你自己电脑上不存在时,上传文件会报“系统找不到指定路径”。找到配置里类似upload.path或file.upload-dir的项,改成:
upload.path=D:/oa-hr-crm/upload logging.path=D:/oa-hr-crm/logs改完之后记得手动创建这两个目录,不要寄希望于代码自己建目录——有的项目会用Files.createDirectories(),有的不会,你赌不起这个玄学。
3.3 启动欢迎页和默认账号:登录进去之后先看哪几个菜单
系统启动成功后,浏览器访问http://localhost:8080/oa-hr-crm,大概率跳到一个登录页。这时候你需要默认账号密码。一般放在 README 里,或者 SQL 脚本初始化的第一行数据里。常见是admin / 123456或admin / admin,如果数据库脚本里有加密后的密码字段,别猜,直接查表:
-- 查找用户表里第一个用户的账号密码,密码字段可能是密文 SELECT user_name, password FROM sys_user LIMIT 5;看到密码是密文不要慌,这说明系统用 MD5 或者 BCrypt 加密了。MD5 的话可以用md5("123456")比对一下密文长度和格式,BCrypt 则以$2a$、$2b$开头。如果是后者的加密策略,你拿着密文去找在线验证工具即可,一般也能确认初始密码就是 123456。
登录进去之后,优先看三个菜单:系统管理里的菜单管理(看权限树结构)、HR 模块的员工管理(看主数据)、CRM 的客户列表(看关联客户负责人)。如果在菜单管理里能新增一条菜单并绑定权限码,再用另一个测试账号登录看到这个菜单,说明这套权限模型是真实生效的,不是花架子。
4. 二次开发必改的 5 处代码:品牌信息、组织架构逻辑、审批流配置
4.1 改品牌信息的四件套:Logo、系统名称、版权信息、默认密码
企业交付项目时第一个要求永远是换皮。“大型协同办公系统源码”这个名字本身也说明它是要被拿去改造成特定企业的办公系统的。换皮工作说难不难,但漏改一处就会在验收时被甲方截图打回。我一般按四件套来走:
第一处是前端 Logo 和标题。找到系统管理模块的首页模板index.html或 Vue 项目里的siteConfig.js,全局搜索项目原始名称,全部替换成目标公司名。重点注意浏览器标签页的<title>和登录页的标题,这两个地方最容易漏。第二处是左侧菜单栏顶部的系统名称,一般在layout组件的sidebar头部。第三处是页面底部的版权信息,通常在独立的全站footer模板里。第四处是登录成功后首页的欢迎语和公司简称,有些项目会做成配置项写进数据库,有些则是硬编码在前端。
<!-- 以纯静态首页模板为例,登录页标题和底部版权信息需要同步替换 --> <div class="login-title">某某企业管理协同平台</div> <div class="copyright">Copyright © 2025 某某企业信息化项目组</div>改完前端不要忘了后端。有些项目的资源文件会有resources/application.properties里的system.name,引擎模板或者邮件通知里会引用它。如果系统带邮件发送功能,欢迎邮件的落款也是公司的名字,这些散落的点不统一改的话,每次测试发送邮件就会露馅。批量替换之前先在工程根目录搜出所有包含原始系统名的文件清单,挨个过一遍再动手。
4.2 组织架构和员工主数据:从写死到可配置的演变
大型协同办公系统源码里的组织架构一般有两种实现。老派写法是部门表sys_dept用parent_id实现树形结构,通过递归查出一棵完整组织树。新一点的会引入level和ancestors字段来加速路径查询。二次开发时最容易改动的是部门的新增、编辑和删除逻辑,因为牵扯递归遍历,很多新手在这里直接写崩。
-- 部门表核心字段示意 CREATE TABLE sys_dept ( id INT PRIMARY KEY AUTO_INCREMENT, parent_id INT DEFAULT 0, ancestors VARCHAR(255) DEFAULT '', dept_name VARCHAR(100), order_num INT, status CHAR(1) DEFAULT '0', del_flag CHAR(1) DEFAULT '0' );ancestors字段存的是从根节点到当前节点的完整路径,比如0,1,5。这样查一个部门下的所有子部门,用WHERE ancestors LIKE '%5%'就够,不需要递归查库。如果你接手的是没有ancestors字段的老表结构,不建议直接加列然后手动刷数据,可以用一条 SQL 先把所有部门的祖先路径算出来,但这属于风险操作——在生产环境跑数据订正脚本之前,必须先做全量备份。
更稳妥的方式是维护代码里构建树的工具类。一般会有一个DeptTreeUtil,输入所有部门列表,输出嵌套的树对象。你在添加新部门后,需要保证它的ancestors是正确的,否则在部门选择树上就会出现“明明设置了父部门,但上级节点里看不到它”的诡异现象。解决办法是看新增部门的后端代码里,是否在插入时重新拼接了ancestors字符串,没有就补上:
// 新增部门时拼接 ancestors dept.setAncestors(parentDept.getAncestors() + "," + parentDept.getId());这段代码的逻辑是:新部门的祖先路径 = 父部门的祖先路径 + 父部门 ID。如果不拼接,树形结构不会断裂,但查询子部门时会漏掉这条新记录,属于典型的“看起来正常实则埋雷”。
HR 模块的员工主数据更复杂一些,它会关联部门、岗位、职级三张基础表。二次开发时要加“员工工号自动生成”规则,常见做法是在员工 Service 里用@Transactional包住插入逻辑,结合数据库序列或 UUID 生成唯一工号。不要用System.currentTimeMillis()直接拼,并发插入时会重复。
4.3 审批流配置:OA 模块最值钱的部分,搞懂复制和回滚机制
OA 模块的审批流是这套系统里代码量最大、逻辑最绕的一块。常见的实现方案是预先定义审批模板表oa_approval_template,审批单实例表oa_approval_instance,再加一个审批记录表oa_approval_record。大型协同办公系统源码里,你大概率会遇到层层嵌套的状态流转逻辑。
最值得二次开发的地方是“审批层级可配置”。比如请假审批:3 天以内直属主管批,3 天以上要总监批。这块源码如果已经支持基于规则的条件表达式,你可配置空间就很大;如果写死了 if-else,你要改造就需要动审批流引擎。不管哪种,先看审批模板表里有没有condition_expr这样的字段:
-- 审批模板条件字段示例 SELECT * FROM oa_approval_template WHERE condition_expr IS NOT NULL;如果查到有数据,那就说明这套源码把“审批条件”做成了可配置项,你只需要在界面上新增规则,不需要动代码。如果没有这个字段,那就老老实实改审批 Service,在提交审批时根据请假天数和当前操作人部门手动跳转审批层级。
审批流改代码最怕的是死循环和并发跳过。比如前端的“同意并转审”按钮,连续点两次会不会同一张单被转两条任务?所以看代码时重点找有没有用数据库乐观锁(version字段)来防止重复提交。如果没有,在修改状态前先执行一行条件更新:
// 乐观锁更新审批单状态,防止并发重复提交 int count = approvalMapper.updateStatusIfVersionMatch(id, expectedVersion, newStatus, newVersion); if (count == 0) { throw new BusinessException("该审批单已被其他节点处理,请刷新后重试"); }这里的核心逻辑通过WHERE id = ? AND version = ?保证只有版本号匹配时才更新。这样两个管理员同时审批同一张单时,只会有一个成功,另一个得到版本冲突提示。不要小看这种细节,用户不会觉得自己点快了,他们只会看到“审批成功的单被判为重复提交”,如果系统没有这层防护的话。
5. 常见问题排查:启动崩溃、数据错乱、权限失效的实用清单
5.1 数据库启动时报错“表不存在”或“列不存在”的三种原因与处理
现象:启动项目后进入页面,点菜单报Table 'oa_hr_crm.xxx' doesn't exist,或者操作时报Unknown column 'yyy'。
原因:大概率是两种——SQL 脚本没有完整执行,或者脚本版本与代码的实体类不一致。很多打包源码在 SQL 脚本之外还会附一个 upgrade 增量脚本,如果只执行了主脚本而没执行增量,就会出现代码已经查询新字段但表里还没有的尴尬局面。
解决:确认表清单和实体类清单。先用连接工具可视化看一下数据库的表列表,对照src/main/resources/mapper/**下的 XML 文件里resultMap的字段。不要全量删除重导——如果你已经录入了测试数据,全库重导会让后续所有调试数据清零。更安全的做法是只补增量:
-- 例如缺失某个字段,直接 ALTER TABLE 补上 ALTER TABLE crm_customer ADD COLUMN follow_status TINYINT DEFAULT 0 COMMENT '跟进状态:0未跟进,1已跟进'; ALTER TABLE oa_approval_instance ADD COLUMN urgency_flag CHAR(1) DEFAULT '0' COMMENT '是否紧急审批';补完字段后重启服务再试。如果还是报错,打开 MyBatis 的 SQL 日志输出,看执行的 SQL 里到底引用了哪个字段,和实际表结构逐一对齐。
5.2 登录后跳转 404,但是接口有响应:权限拦截器和路由设计冲突
现象:输入账号密码后登录成功,URL 跳转到/index或/home,但页面 404。直接访问后端接口却能拿到 JSON 数据。
原因:这不是后端崩溃,是前端的路由和后端返回的菜单权限码对不上。典型场景:数据库中配置的菜单路径是crm/customerList,前端路由定义是/crm/customer,权限码匹配成功但组件路径不匹配,导致权限通过后找不到对应的 Vue 组件或 JSP 页面。
解决:先看前端菜单路由表。如果是 Vue 项目,找到动态路由加载的地方,通常是根据后端返回的菜单数据用router.addRoutes动态挂载。检查后端返回的component字段是否是一个可以解析的组件路径字符串。常见处理方式是把菜单表里的component字段统一改成相对路径:
{ "path": "/crm/customer", "component": "crm/CustomerList", "name": "客户管理" }然后在路由加载处加一层映射转换,比如() => import(@/views/${component}.vue)。如果你看到 404 但浏览器控制台没有报错,多半是路由路径大小写问题——Linux 部署环境下文件名区分大小写,而 Windows 下不区分。在本地开发没事,发到服务器就 404,这是大小写混用导致的经典翻车。
5.3 部门树出现重复节点或移位:递归删除时没有同步子部门
现象:在系统管理里删除一个部门,刷新后部门树出现“鬼影”节点,或者子部门平白无故跑到了根目录下。
原因:删除部门时只删了当前部门记录,没有处理子部门的parent_id。数据库里sys_dept表如果有外键约束,删除会直接报错;没有外键约束,子部门的parent_id指向一个不存在父节点,树形结构就乱了。
解决:先停止发版,做数据修复。把孤儿节点的父 ID 挂到被删部门的父部门下:
-- 修复前先查看孤儿部门 SELECT id, dept_name, parent_id FROM sys_dept WHERE parent_id NOT IN (SELECT id FROM sys_dept) AND parent_id != 0; -- 将这些部门挂到根部门下(或指定新父部门) UPDATE sys_dept SET parent_id = 0 WHERE parent_id NOT IN (SELECT id FROM sys_dept) AND parent_id != 0;然后更正代码删除逻辑。正确做法是:删除部门时,先查所有子部门,把它们的parent_id改成被删部门的parent_id,或者整棵子树都逻辑删除(del_flag = 1),不能硬删。
5.4 CRM 客户重复导入:唯一性校验不能只看客户名称
现象:用 Excel 导入客户数据,同一个客户的手机号被导入了两次,列表里出现两条几乎一样的数据。
原因:导入时只判断了客户名称是否重复,没判断手机号。B 端客户跟 C 端不同,客户名称可能因为称呼习惯写得不一致(“某某科技有限公司”和“某某科技公司”),但手机号是相对稳定的唯一键。大型协同办公系统源码里 CRM 模块的导入功能普遍存在这个逻辑漏洞。
解决:二开时在导入逻辑里加“手机号 + 客户名称”双重复判:
// 导入前批量查询已存在的手机号集合 Set<String> existPhones = customerMapper.selectExistingPhones(phoneList); if (existPhones.contains(phone)) { row.setErrorMsg("该手机号已存在,跳过导入"); continue; }同时建议在crm_customer表上给手机号加一个唯一索引,但要注意:历史数据里如果已经存在重复手机号,加索引会失败。你得先写 SQL 把重复数据处理掉再操作:
-- 保留最早的一条重复记录,其余逻辑删除 UPDATE crm_customer c SET c.deleted = 1 WHERE c.id NOT IN ( SELECT MIN(id) FROM crm_customer GROUP BY phone HAVING COUNT(*) > 1 );这种修复方式在业务上偏激进,最好在客户确认后再执行。
5.5 系统重启后登录失效:分布式缓存和本地缓存的取舍
现象:每次重启 Tomcat 后都要重新登录,Session 里的用户信息全部丢失。如果这套系统配了单体会话,这是正常现象;但如果你是希望“记住登录状态几天”的用户,这就是持久化配置的问题。
原因:这套系统通常用 Tomcat 内存 Session,“Session 失效”等于“要重新登录”。少数用 Spring Security 的项目会把 Session 存 Redis,但重启 Tomcat 只清应用内存,不是清 Redis,理论上登录状态还在——如果你选择存数据库或 Redis,问题就解决了。
解决:如果你是部署在测试环境,希望在开发调试阶段减少重复登录次数,可以把 Session 超时时间调长,在web.xml或配置类里把session-timeout设为 480 分钟(8 小时)。但如果你是多节点部署,生态内这套源码并没有做 Session 同步,这时只能引入 Redis 共享 Session:
<dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> </dependency>引入依赖后还需要把spring.session.store-type=redis配置项打开。注意:切到 Redis Session 后,你原来在 Session 里塞的自定义对象必须实现Serializable接口,否则存 Redis 时直接序列化异常。这也是很多项目从 Tomcat 内存切到 Redis Session 时踩的最狠的坑。
6. 把源码变成你自己的交付物:先看权限边界,再谈自定义表单引擎
以上步骤跑完,你已经拥有了一套能登录、能操作、能二次开发的协同办公系统。但“能跑”和“能交付”之间还有一道坎:源码里那些“看着不太灵活”的表单界面,往往才是客户真正关心的地方——他们要的不是系统里有什么,而是这系统能不能适配他们自己的业务流程。
这时候要做的不是去改底层的 Spring 容器配置,而是找一个更有杠杆的点:自定义表单引擎。大型协同办公系统源码里如果带了“自定义字段”、“动态表单”这类的包,那是妥妥的加分项;如果没带,你要学会把现有模块里的字段配置表用起来。
一个典型方案是设计一张表单字段扩展表:
-- 表单扩展字段配置表 CREATE TABLE form_ext_field ( id INT PRIMARY KEY AUTO_INCREMENT, module_code VARCHAR(50) COMMENT '模块标识:crm_customer / hr_employee', field_key VARCHAR(50), field_name VARCHAR(100), field_type VARCHAR(20), -- text / number / date / select required_flag CHAR(1) DEFAULT '0', enabled_flag CHAR(1) DEFAULT '1' );有了这张表,你可以在不动数据库主表的情况下,给客户登记页增加用户自定义的字段。渲染时前端遍历这张表生成表单项,提交时把扩展字段以 JSON 格式存入主表的一个扩展列里,或者独立扩展值表。这种方案的核心好处是:当客户说“我需要给客户加一个‘意向等级’字段”时,你不需要经历改表结构、改实体、改 Mapper、改前端表单的四层变更,只需要在这个配置表里加一条记录。
但请注意:这个方案边界很清晰。如果客户需要的不是一个字段,而是一套复杂的校验联动逻辑(比如:选了某个客户等级后,跟进方式必填、金额上限变化),配置表就Held不住了,这时候正常的选择是写代码硬编码。不要为了“灵活”把一切塞给配置,最后配置比代码还难维护,那才是真正的灾难。
在这个项目源码的改造实践中,我自己的习惯是:每接到一个新需求,先问自己三件事——改数据库会不会影响现有查询?加配置项会不会让界面变复杂?改代码后会不会让自动化测试挂掉?这三件事里有两件风险高,就换个实现姿势。永远不要先把困难留给未来的自己,这坑踩多了自然长记性。
希望上面这套拆解和踩坑清单能帮你少走点弯路。按这个顺序撸一遍大型协同办公系统源码,相比盲目地导入工程然后对着报错日志发呆,效率会高很多。
本文还有配套的精品资源,点击获取