同辦公系統(tǒng)源碼解析:OA+HR+CRM三合一部署與二次開(kāi)發(fā))
簡(jiǎn)介大型協(xié)同辦公系統(tǒng)源碼整合了OA、HR、CRM三大核心模塊面向需要構(gòu)建企業(yè)信息化管理平臺(tái)的開(kāi)發(fā)團(tuán)隊(duì)、系統(tǒng)集成商以及對(duì)Java/.NET等企業(yè)級(jí)開(kāi)發(fā)感興趣的進(jìn)階學(xué)習(xí)者。系統(tǒng)覆蓋工作流、公文管理、任務(wù)與會(huì)議管理、考勤以及員工信息、招聘、績(jī)效、薪酬福利再到客戶、銷售機(jī)會(huì)、服務(wù)與合同管理等完整業(yè)務(wù)鏈路。壓縮包大小約38.19MB內(nèi)部文件總數(shù)與類型明細(xì)暫未在展示信息中列出主要內(nèi)容以源碼工程文件、配置及說(shuō)明文檔為主。已有107人學(xué)習(xí)下載。借助源碼可深入理解MVC分層架構(gòu)下的模塊解耦與數(shù)據(jù)流設(shè)計(jì)掌握企業(yè)級(jí)權(quán)限控制、流程引擎和報(bào)表統(tǒng)計(jì)等難點(diǎn)實(shí)現(xiàn)并能根據(jù)實(shí)際需求進(jìn)行定制化二次開(kāi)發(fā)是提升企業(yè)級(jí)開(kāi)發(fā)實(shí)戰(zhàn)能力的有力參考。1. 大型協(xié)同辦公系統(tǒng)源碼OAHRCRM 三合一先把它拆成三個(gè)項(xiàng)目來(lái)理解如果你正在找一套能跑起來(lái)的 OAHRCRM 源碼第一件事是把“協(xié)同辦公”這個(gè)虛詞丟掉把它當(dāng)成三個(gè)系統(tǒng)合在一個(gè)倉(cāng)庫(kù)里理解OA管流程審批HR管人的全生命周期CRM管客戶和銷售機(jī)會(huì)。這套源碼就是典型的企業(yè)內(nèi)部管理全家桶適合兩類人一類是做 Java 或 .NET 課程設(shè)計(jì)、畢業(yè)設(shè)計(jì)想要一套完整業(yè)務(wù)系統(tǒng)的學(xué)生另一類是公司要搭內(nèi)部管理工具想基于現(xiàn)成源碼做二次開(kāi)發(fā)的程序員。它真正值錢的部分不是考勤打卡而是審批工作流與業(yè)務(wù)數(shù)據(jù)的串聯(lián)關(guān)系。別一上來(lái)想把每個(gè)模塊都吃透先跑通再按住一條業(yè)務(wù)線往下鉆。2. 先認(rèn)清系統(tǒng)邊界OA、HR、CRM 三個(gè)模塊的分工與源碼落點(diǎn)2.1 OA 模塊是整個(gè)系統(tǒng)的骨架工作流才是核心很多第一次接觸這類源碼的人都把注意力放在考勤、公告這種“存在感強(qiáng)”的功能上。實(shí)際上 OA 模塊里最有分析價(jià)值的是工作流引擎。一個(gè)流程要跑通至少涉及四部分流程模板、流程實(shí)例、審批節(jié)點(diǎn)、審批記錄。模板管“流程長(zhǎng)什么樣”實(shí)例管“這一單走到哪”節(jié)點(diǎn)管“當(dāng)前該誰(shuí)批”記錄管“誰(shuí)在什么時(shí)候批過(guò)”。公文管理、請(qǐng)假審批、費(fèi)用報(bào)銷本質(zhì)都是給這四類數(shù)據(jù)喂記錄。工作流在源碼里常見(jiàn)的落點(diǎn)是workflow、flow或oa這類包。我拆過(guò)類似項(xiàng)目典型表有wf_process_define、wf_process_instance、wf_node、wf_approval_record??吹竭@幾張表基本就能畫出業(yè)務(wù)閉環(huán)。會(huì)議管理也別小看它不是一個(gè)簡(jiǎn)單日歷而是會(huì)議室資源、參會(huì)人、會(huì)議紀(jì)要三張表在聯(lián)動(dòng)適合用來(lái)練手關(guān)聯(lián)查詢。你可能聽(tīng)過(guò)泛微 OA、致遠(yuǎn) OA、藍(lán)凌 OA 這些商業(yè)產(chǎn)品它們的底層思路都差不多流程模板化、審批節(jié)點(diǎn)化、表單可配置。這套源碼的價(jià)值就是讓你能看到這些商業(yè)系統(tǒng)背后的通用邏輯并且能自己改。商業(yè)產(chǎn)品改一條審批流要提工單源碼項(xiàng)目里你直接改數(shù)據(jù)庫(kù)記錄或 Java 代碼差別很大。2.2 HR 與 CRM 模塊的價(jià)值在狀態(tài)流轉(zhuǎn)不在增刪改查HR 模塊里員工信息管理是最基礎(chǔ)的招聘、績(jī)效、培訓(xùn)才見(jiàn)真功夫。招聘管理不是存簡(jiǎn)歷而是維護(hù)一個(gè)候選人狀態(tài)機(jī)待篩選、面試中、待錄用、已入職、已淘汰。狀態(tài)之間靠流程或手工動(dòng)作切換每次切換都要留記錄否則后面統(tǒng)計(jì)招聘漏斗就無(wú)從談起???jī)效考核的實(shí)質(zhì)是一個(gè)周期管理先有考核計(jì)劃再有指標(biāo)最后有評(píng)分結(jié)果。薪酬模塊再基于這些結(jié)果計(jì)算這就把人事和薪酬串起來(lái)了。CRM 模塊同理??蛻舯肀旧碇皇巧矸葑C級(jí)別的靜態(tài)數(shù)據(jù)真正決定系統(tǒng)價(jià)值的是銷售機(jī)會(huì)表。機(jī)會(huì)表里最重要的字段是stage從線索到成交劃分好幾個(gè)階段。銷售漏斗的統(tǒng)計(jì) SQL跑的就是這個(gè)字段。合同管理則要維護(hù)合同與機(jī)會(huì)的關(guān)聯(lián)用opportunity_id直接指向機(jī)會(huì)表而不是靠客戶名模糊匹配。很多人在 CRM 上有一個(gè)巨大誤區(qū)以為客戶管理就是通訊錄。實(shí)際上免費(fèi) CRM 和自建系統(tǒng)的核心差別恰恰在銷售機(jī)會(huì)的階段自定義和數(shù)據(jù)權(quán)限控制上。自建系統(tǒng)的階段字段可以按公司流程改不綁定廠商邏輯商業(yè)免費(fèi) CRM 大多只能在你固定的幾個(gè)階段里打轉(zhuǎn)。如果你想搞清楚“免費(fèi) CRM 與私人網(wǎng)站/自建系統(tǒng)的區(qū)別到底在哪”讀一套這類源碼是最直接的答案。2.3 先識(shí)別技術(shù)棧再動(dòng)手看包、看配置、看 SQL下載源碼后最怕的事是環(huán)境裝了一晚上最后發(fā)現(xiàn)技術(shù)棧不匹配。所以我拿到包的第一時(shí)間不看代碼先做技術(shù)棧識(shí)別。如果根目錄有pom.xml是 Maven 管理的 Java 項(xiàng)目絕大多數(shù)是 Spring MVC 或 Spring Boot如果有一堆.csproj或.sln是 .NET如果有package.json且沒(méi)有 Java 后端目錄那可能是前后端分離的前端工程。老式 JSP 項(xiàng)目則通常能看到WebContent或web目錄。再看數(shù)據(jù)庫(kù)腳本。MySQL 腳本大量用反引號(hào)包表名Oracle 腳本會(huì)有SEQUENCE和TRIGGERSQL Server 腳本則常見(jiàn)IDENTITY關(guān)鍵字。配置文件里com.mysql.jdbc.Driver和oracle.jdbc.OracleDriver一眼就能區(qū)分。這一步做扎實(shí)后面啟動(dòng)少走一半彎路。三個(gè)模塊的源碼落點(diǎn)通常有規(guī)律可循模塊核心功能源碼常見(jiàn)落點(diǎn)典型數(shù)據(jù)表OA工作流、公文、會(huì)議、考勤workflow/oa/flow/wf_process_instanceoa_meetingHR員工、招聘、績(jī)效、培訓(xùn)、薪酬hr/person/recruit/hr_employeehr_recruit_candidatehr_performance_planCRM客戶、機(jī)會(huì)、服務(wù)、合同crm/customer/contract/crm_customercrm_opportunitycrm_contract這個(gè)表不是標(biāo)準(zhǔn)答案但對(duì)照著找方向不會(huì)錯(cuò)。拿到壓縮包后我建議先在 IDE 里按包名把所有 Controller 類列出來(lái)一個(gè)模塊對(duì)應(yīng)一組 Controller比對(duì)著文檔猜快得多。3. 從下載到跑通初始化數(shù)據(jù)庫(kù)、改連接串與兩種啟動(dòng)姿勢(shì)3.1 解壓之后先做四件事別急著雙擊這一步很多人跳過(guò)去直接 IDE 打開(kāi)結(jié)果卡在各種小問(wèn)題上。我拿到此類壓縮包的標(biāo)準(zhǔn)動(dòng)作是第一解壓到無(wú)中文無(wú)空格的路徑第二在根目錄找README、install.txt、doc第三把項(xiàng)目里所有*.sql文件列出來(lái)第四打開(kāi)配置文件看數(shù)據(jù)庫(kù)類型。路徑帶空格看似無(wú)關(guān)痛癢但對(duì)老項(xiàng)目的 Tomcat 腳本和某些框架的資源加載來(lái)說(shuō)就是隱患。中文路徑更危險(xiǎn)容易觸發(fā)編碼問(wèn)題。mkdir -p /opt/oa unzip 大型協(xié)同辦公系統(tǒng)源碼OAHRCRM源碼 (1).zip -d /opt/oa/ find /opt/oa -maxdepth 2 -type f | head -60 find /opt/oa -name *.sql -o -name application.yml -o -name *.properties -o -name web.xml | sort這段命令里壓縮包文件名我加了引號(hào)防止括號(hào)被 Shell 解釋成特殊符號(hào)。maxdepth 2限制掃描深度避免把依賴包全部列出來(lái)前 60 行足夠看清項(xiàng)目輪廓。第二次find把數(shù)據(jù)庫(kù)腳本和配置挑出來(lái)判斷這套源碼是哪個(gè)年代的產(chǎn)物。如果看到web.xml大概率是傳統(tǒng) Servlet 項(xiàng)目只看到application.yml則是 Spring Boot。接下來(lái)優(yōu)先讀 SQL 文件頭部注釋。很多源碼作者習(xí)慣在腳本開(kāi)頭寫“先建庫(kù)、再導(dǎo)表、后導(dǎo)數(shù)據(jù)”的順序。如果只有一個(gè)full.sql就直接整文件導(dǎo)入如果拆成schema.sql和data.sql順序不要反。表結(jié)構(gòu)沒(méi)建好就導(dǎo)數(shù)據(jù)外鍵會(huì)報(bào)錯(cuò)先導(dǎo)數(shù)據(jù)再建表數(shù)據(jù)會(huì)被清空。這都是老生常談的翻車點(diǎn)。3.2 建庫(kù)建用戶字符集盡量一次到位老 OA 很多默認(rèn)utf8但業(yè)務(wù)數(shù)據(jù)里會(huì)出現(xiàn)生僻字、表情符號(hào)utf8mb4更穩(wěn)。我一般這樣建庫(kù)CREATE DATABASE IF NOT EXISTS oa_hr_crm DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; CREATE USER IF NOT EXISTS oa_userlocalhost IDENTIFIED BY Oa2024; GRANT ALL PRIVILEGES ON oa_hr_crm.* TO oa_userlocalhost; FLUSH PRIVILEGES;然后導(dǎo)入腳本mysql -u root -p oa_hr_crm schema.sql mysql -u root -p oa_hr_crm data.sql這里有個(gè)很實(shí)用的小習(xí)慣導(dǎo)入完成后執(zhí)行SHOW TABLES;數(shù)一下表數(shù)量。比如這套系統(tǒng)包含 OA、HR、CRM 三大模塊表數(shù)量通常在七十到上百?gòu)?。記下這個(gè)數(shù)字后面啟動(dòng)成功再核對(duì)一次能快速判斷是不是某張關(guān)鍵表沒(méi)導(dǎo)進(jìn)來(lái)。如果 MySQL 8 環(huán)境導(dǎo)入老腳本報(bào)錯(cuò)多半是字符集排序規(guī)則或utf8寫法問(wèn)題把utf8_unicode_ci統(tǒng)一改成utf8mb4_general_ci基本能過(guò)。如果項(xiàng)目用的是 Oracle 或 SQL Server思路也一樣只是連接串寫法不同。Oracle 要寫成jdbc:oracle:thin:127.0.0.1:1521:orclSQL Server 是jdbc:sqlserver://127.0.0.1:1433;DatabaseNameoa_hr_crm。老源碼如果帶.sql腳本一般會(huì)按目標(biāo)數(shù)據(jù)庫(kù)給出多套別導(dǎo)錯(cuò)版本。3.3 配置連接串、端口和上下文路徑連接配置因框架而異。Spring Boot 看application.yml老項(xiàng)目看jdbc.properties、db.properties或context.xml。重點(diǎn)改三項(xiàng)URL、用戶名、密碼。下面是一份典型配置server: port: 8080 servlet: context-path: /oa spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/oa_hr_crm?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue username: oa_user password: Oa2024driver-class-name要跟 MySQL 版本對(duì)齊5.x 用com.mysql.jdbc.Driver8.x 用com.mysql.cj.jdbc.Driver。URL 里的serverTimezoneAsia/Shanghai必須寫否則 JDBC 8 驅(qū)動(dòng)啟動(dòng)就報(bào)時(shí)區(qū)錯(cuò)誤。127.0.0.1比localhost穩(wěn)避免某些環(huán)境的 IPv6 解析問(wèn)題。context-path: /oa意味著登錄地址是http://localhost:8080/oa/login很多人在這里被坑以為直接訪問(wèn) 8080 就行結(jié)果 404。另外JDK 版本也是重災(zāi)區(qū)。老源碼很多基于 JDK 8 寫的你拿 JDK 17 去編譯大概率會(huì)遇到j(luò)avax.servlet和jakarta.servlet的包名切換問(wèn)題或者依賴版本不兼容。我一般直接用 JDK 8 Tomcat 9 組合跑這類資源等跑通了再考慮升級(jí)。啟動(dòng)方式取決于項(xiàng)目形態(tài)。Maven 多模塊項(xiàng)目先編譯再運(yùn)行mvn clean package -DskipTests java -jar target/oa-*.jar老式 war 包則放進(jìn) Tomcat 的 webapps 目錄后啟動(dòng)cp target/oa.war /opt/apache-tomcat-9.0/webapps/ /opt/apache-tomcat-9.0/bin/startup.sh兩種方式的區(qū)別是war 包方式還要注意 Tomcat 版本。老源碼基于javax.servlet的用 Tomcat 9新項(xiàng)目用jakarta.servlet的才上 Tomcat 10/11。搞反了你會(huì)看到大批ClassNotFoundException。啟動(dòng)前先lsof -i:8080或 Windows 下netstat -ano | findstr 8080確認(rèn)端口沒(méi)被占日志出現(xiàn)Started或Server startup in才算跑起來(lái)。4. 三大模塊的核心實(shí)現(xiàn)審批工作流、招聘狀態(tài)機(jī)與銷售機(jī)會(huì)推進(jìn)4.1 審批工作流用節(jié)點(diǎn)表把流程變成可配置工作流是 OA 的靈魂也是這套源碼里最值得讀的部分。我拆過(guò)的同類型項(xiàng)目工作流實(shí)現(xiàn)通常不是復(fù)雜的狀態(tài)機(jī)引擎而是“節(jié)點(diǎn)表 實(shí)例表 記錄表”的組合。節(jié)點(diǎn)表里每個(gè)節(jié)點(diǎn)聲明自己的審批方式和下一跳public class ProcessNode { private String nodeId; // 節(jié)點(diǎn)編碼如 apply、dept_leader、hr_confirm private String nodeName; // 節(jié)點(diǎn)名稱用于審批頁(yè)展示 private String approverType; // 固定用戶 FIXED_USER / 角色 ROLE / 發(fā)起人主管 LEADER private String passType; // 通過(guò)方式all 會(huì)簽any 或簽 private Integer sortNo; // 節(jié)點(diǎn)順序 private String nextNodeId; // 審批通過(guò)后流向 }這段類的價(jià)值在于流程模板可以通過(guò)配置組裝而不需要寫死在 Java 代碼里。要增加一個(gè)總監(jiān)審批節(jié)點(diǎn)就往節(jié)點(diǎn)表插一條記錄把上一節(jié)點(diǎn)的nextNodeId改成新節(jié)點(diǎn)編碼即可。會(huì)簽all要求所有審批人同意或簽any一人同意就放行這兩個(gè)字段決定了審批的通過(guò)語(yǔ)義。很多 OA 產(chǎn)品里的“會(huì)簽/非會(huì)簽”設(shè)置底層就是這樣一個(gè)字段。流程實(shí)例表通常還要記錄當(dāng)前節(jié)點(diǎn)每次審批通過(guò)后把current_node_id更新為下一個(gè)節(jié)點(diǎn)。這一步如果用樂(lè)觀鎖實(shí)現(xiàn)就能避免兩個(gè)人同時(shí)審批導(dǎo)致的狀態(tài)錯(cuò)亂。讀源碼時(shí)我建議用一條“請(qǐng)假審批”把整個(gè)調(diào)用鏈跟一遍發(fā)起→生成實(shí)例→查當(dāng)前節(jié)點(diǎn)→寫審批記錄→更新節(jié)點(diǎn)。跑通這條鏈OA 模塊就懂了大半。4.2 HR 模塊用周期管理把招聘和績(jī)效串起來(lái)招聘管理設(shè)計(jì)的核心是候選人狀態(tài)不是簡(jiǎn)歷附件。候選人從投遞到入職會(huì)經(jīng)歷多個(gè)狀態(tài)每次狀態(tài)變化都要留下操作人和時(shí)間否則統(tǒng)計(jì)招聘渠道轉(zhuǎn)化率時(shí)沒(méi)有任何可信度。我用一個(gè)簡(jiǎn)化字段表來(lái)說(shuō)明CREATE TABLE hr_recruit_candidate ( id INT PRIMARY KEY AUTO_INCREMENT, position_id INT NOT NULL COMMENT 應(yīng)聘職位, channel VARCHAR(20) COMMENT 渠道BOSS/LIEBAO/獵頭/內(nèi)推, status VARCHAR(20) NOT NULL COMMENT PENDING/INTERVIEW/OFFER/HIRED/REJECTED, resume_uri VARCHAR(255), create_time DATETIME DEFAULT CURRENT_TIMESTAMP );position_id把候選人和招聘職位關(guān)聯(lián)status控制篩選列表channel用于統(tǒng)計(jì)渠道質(zhì)量。很多課程設(shè)計(jì)項(xiàng)目會(huì)漏掉channel導(dǎo)致后面完全無(wú)法分析哪個(gè)渠道有效???jī)效模塊同理核心是hr_performance_plan表定義考核周期為月、季度還是年度。薪酬計(jì)算時(shí)按plan_id去取考核結(jié)果而不是直接對(duì)著一張打分表求和這樣才能支撐跨周期的薪酬追溯。HR 模塊還有個(gè)容易忽略的點(diǎn)員工離職不是刪除記錄。正規(guī)做法是在員工表加status字段離職員工保留歷史數(shù)據(jù)同時(shí)禁止登錄。很多源碼在這一點(diǎn)上處理得很草率離職就直接DELETE結(jié)果工資歷史、審批歷史全斷了。你讀到這類邏輯時(shí)可以把它當(dāng)作一個(gè)改進(jìn)點(diǎn)記下來(lái)。4.3 CRM 模塊銷售機(jī)會(huì)階段推進(jìn)與數(shù)據(jù)權(quán)限CRM 模塊里最值得學(xué)的是銷售機(jī)會(huì)階段的推進(jìn)邏輯和跟進(jìn)人數(shù)據(jù)權(quán)限。先看表結(jié)構(gòu)CREATE TABLE crm_opportunity ( id INT PRIMARY KEY AUTO_INCREMENT, customer_id INT NOT NULL, owner_user_id INT NOT NULL COMMENT 跟進(jìn)人, stage VARCHAR(20) NOT NULL COMMENT LEAD/TALK/QUOTE/WON/LOST, amount DECIMAL(12,2) DEFAULT 0, expect_close_date DATE, must_check TINYINT DEFAULT 0 COMMENT 階段變更是否需審批1是 );銷售漏斗的統(tǒng)計(jì)就是按stage分組對(duì)amount求和。跟進(jìn)人字段owner_user_id極其關(guān)鍵它天然實(shí)現(xiàn)了“我只看到我負(fù)責(zé)的客戶”這一數(shù)據(jù)權(quán)限。如果系統(tǒng)還要支持上級(jí)看下屬數(shù)據(jù)通常在 Service 層再加一層查詢條件用dept_id去過(guò)濾。很多新人在 CRM 里反復(fù)改頁(yè)面卻不動(dòng)owner_user_id結(jié)果一個(gè)銷售能看到全公司的客戶這屬于典型的權(quán)限漏洞。合同表與機(jī)會(huì)表的關(guān)聯(lián)建議用opportunity_id直接外鍵而不是靠客戶 ID 或合同名稱。這樣在列表頁(yè)顯示“所屬商機(jī)”時(shí)就無(wú)需再對(duì)比字符串。階段變更最好同步寫日志表記錄從哪個(gè)階段到哪個(gè)階段、誰(shuí)改的、為什么改。CRM 的價(jià)值就在這些留痕數(shù)據(jù)里沒(méi)有留痕后面所有銷售復(fù)盤都是空談。5. 避坑與排查五條實(shí)測(cè)踩坑記錄5.1 ClassNotFoundException 與數(shù)據(jù)庫(kù)連接拒絕現(xiàn)象Tomcat 啟動(dòng)后項(xiàng)目訪問(wèn)報(bào)ClassNotFoundException或后臺(tái)日志拋Communications link failure。原因前者多為缺依賴 jar后者是數(shù)據(jù)庫(kù)連不上。連不上的細(xì)根因常見(jiàn)于 JDBC 驅(qū)動(dòng)版本與 MySQL 版本不匹配或者 URL 里沒(méi)寫時(shí)區(qū)。localhost解析成 IPv6 時(shí)數(shù)據(jù)庫(kù)監(jiān)聽(tīng)了 IPv4 地址同樣會(huì)拒絕。解決連接 URL 統(tǒng)一寫成jdbc:mysql://127.0.0.1:3306/oa_hr_crm?useSSLfalseserverTimezoneAsia/Shanghai。缺類時(shí)在 Maven 項(xiàng)目里檢查pom.xml引用的驅(qū)動(dòng)版本傳統(tǒng)項(xiàng)目查看WEB-INF/lib下有沒(méi)有對(duì)應(yīng) jar。真機(jī)排錯(cuò)順序是先 ping 目標(biāo)端口再用命令行客戶端連一次最后才懷疑代碼。5.2 登錄頁(yè)白屏或 404現(xiàn)象項(xiàng)目能啟動(dòng)但訪問(wèn)登錄頁(yè) 404或頁(yè)面出來(lái)了靜態(tài)資源全是 404。原因上下文路徑不一致。Spring Boot 配置了context-path: /oa訪問(wèn)時(shí)卻沒(méi)帶應(yīng)用名傳統(tǒng) Tomcat 部署又習(xí)慣把 war 解壓到 ROOT 下。靜態(tài)資源 404 則是 Spring MVC 沒(méi)配置資源映射老框架默認(rèn)不處理/static/**。解決啟動(dòng)成功后先看日志里 context path 是什么按它拼 URL。靜態(tài)資源 404 就在 Spring 配置里加ResourceHandler把/static/**映射到classpath:/static/。我自己的固定動(dòng)作是啟動(dòng)后直接訪問(wèn)http://localhost:8080/oa/login.jsp或/oa/login確認(rèn)第一個(gè) HTTP 狀態(tài)碼再順著往下查。5.3 中文亂碼改了數(shù)據(jù)庫(kù)還亂現(xiàn)象頁(yè)面顯示正常存入數(shù)據(jù)庫(kù)是問(wèn)號(hào)或頁(yè)面亂碼數(shù)據(jù)庫(kù)正常。原因Tomcat URI 編碼、請(qǐng)求過(guò)濾器編碼、JDBC 連接字符集、數(shù)據(jù)庫(kù)表字符集四個(gè)環(huán)節(jié)只要有一個(gè)不一致就會(huì)亂。老 JSP 項(xiàng)目尤其常見(jiàn)pageEncoding和contentType不一致的問(wèn)題。解決一次性把鏈路齊活Tomcat 的 Connector 加URIEncodingUTF-8Spring Boot 設(shè)置server.servlet.encoding.forcetrueJDBC URL 帶characterEncodingutf8數(shù)據(jù)庫(kù)字符集確認(rèn)是utf8mb4。檢查時(shí)用SHOW VARIABLES LIKE character_set%看結(jié)果不要憑猜。從那以后但凡遇到亂碼我先跑這條檢查鏈不再單獨(dú)折騰數(shù)據(jù)庫(kù)。5.4 日期時(shí)間差 8 小時(shí)現(xiàn)象頁(yè)面上顯示的日期比實(shí)際少 8 小時(shí)或后臺(tái)存儲(chǔ)比實(shí)際多 8 小時(shí)。原因JVM 時(shí)區(qū)、數(shù)據(jù)庫(kù)時(shí)區(qū)、JDBC 驅(qū)動(dòng)時(shí)區(qū)三處不統(tǒng)一。老代碼用new Date()寫入datetime字段時(shí)驅(qū)動(dòng)可能按 UTC 轉(zhuǎn)換讀取時(shí)又按系統(tǒng)時(shí)區(qū)轉(zhuǎn)換來(lái)回一折騰就偏了。解決統(tǒng)一到 UTC8。Linux 執(zhí)行timedatectl set-timezone Asia/ShanghaiJava 啟動(dòng)參數(shù)加-Duser.timezoneAsia/ShanghaiSQL 連接串帶serverTimezoneAsia/ShanghaiMySQL 會(huì)話執(zhí)行SET time_zone 08:00。注意datetime與timestamp的行為不同前者不隨會(huì)話時(shí)區(qū)轉(zhuǎn)換后者會(huì)排查時(shí)先確認(rèn)字段類型再動(dòng)手。5.5 審批流程卡住不流轉(zhuǎn)現(xiàn)象審批人點(diǎn)了同意單據(jù)仍在當(dāng)前節(jié)點(diǎn)或直接消失無(wú)反饋。原因我遇到的三類最多下一節(jié)點(diǎn)審批人解析為空比如按部門主管找人但部門主管沒(méi)配置并行分支沒(méi)有合并機(jī)制某個(gè)分支走到頭就誤判完成字段版本號(hào)沒(méi)更新更新影響 0 行導(dǎo)致事務(wù)回滾但頁(yè)面提示成功。解決先查審批記錄表有沒(méi)有新寫入的審批動(dòng)作。如果沒(méi)有說(shuō)明保存階段就異常去看后臺(tái)日志如果有再去流程實(shí)例表比對(duì)current_node_id。想要精確定位可以自己寫一條條件更新語(yǔ)句update wf_process_instance set current_node_id #{next} where id #{id} and current_node_id #{current}影響行數(shù)為 0 就說(shuō)明當(dāng)前節(jié)點(diǎn)的舊值和你以為的不一致。把工作流日志調(diào)到 DEBUG節(jié)點(diǎn)轉(zhuǎn)移的逐行日志會(huì)直接告訴你卡點(diǎn)。6. 二次開(kāi)發(fā)落地新增一條審批流并驗(yàn)證改動(dòng)的四步法想判斷自己是不是真的讀懂了這套源碼用最小改動(dòng)驗(yàn)證最有效。我每次拿到新系統(tǒng)都挑“新增一條請(qǐng)假審批流”來(lái)做因?yàn)檫@條業(yè)務(wù)線能覆蓋流程模板、菜單權(quán)限、發(fā)起表單和節(jié)點(diǎn)推進(jìn)四個(gè)基本點(diǎn)。第一步往流程模板表插入一條模板節(jié)點(diǎn)按“提交申請(qǐng)→部門主管審批→HR復(fù)核→結(jié)束”設(shè)定審批人類型分別對(duì)應(yīng)發(fā)起人本人、直屬主管、HR 角色。以實(shí)際庫(kù)中模板表字段為準(zhǔn)常見(jiàn)寫法是INSERT INTO wf_process_template (template_key, template_name, first_node_id) VALUES (leave_simple, 簡(jiǎn)易請(qǐng)假流程, apply_node);template_key是流程編碼first_node_id指向起始節(jié)點(diǎn)編碼。第二步加菜單入口表單字段只留開(kāi)始時(shí)間、結(jié)束時(shí)間、事由。第三步給 Controller 或 Action 寫一個(gè) submit 方法調(diào)用工作流引擎發(fā)起接口創(chuàng)建流程實(shí)例并把當(dāng)前節(jié)點(diǎn)置為apply_node。第四步也是最容易漏的清理權(quán)限。菜單權(quán)限、按鈕權(quán)限、數(shù)據(jù)權(quán)限三處都要給對(duì)應(yīng)角色授權(quán)否則流程起了審批人卻看不到待辦。每完成一步用一條真實(shí)請(qǐng)假單走驗(yàn)證提交后看實(shí)例表current_node_id是否為dept_leader換非主管賬號(hào)登錄是否無(wú)待辦主管同意后節(jié)點(diǎn)是否跳到hr_confirm。三句話能答上來(lái)說(shuō)明這條鏈路真的通了而不是頁(yè)面看著能點(diǎn)。動(dòng)手前我還會(huì)強(qiáng)制做兩件事備份數(shù)據(jù)庫(kù)保存 SQL 基線把這次改動(dòng)涉及的表和字段寫進(jìn)改動(dòng)記錄。因?yàn)槎伍_(kāi)發(fā)最大的翻車現(xiàn)場(chǎng)不是代碼寫不出來(lái)而是改崩了還不知道該回退到哪。從那以后我每拆一套新源碼第一天就寫一份《環(huán)境清單》數(shù)據(jù)庫(kù)版本、Java/Tomcat 版本、配置文件改過(guò)的每一行、啟動(dòng)命令是什么改動(dòng)前導(dǎo)一次基線。這套 OAHRCRM 源碼模塊多、表關(guān)聯(lián)繞但把這條最小閉環(huán)走通后面接手任何模塊都不慌。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取