:從啟動失敗到生產(chǎn)就緒)
簡介這是一套基于SpringBoot的Java進銷存ERP管理系統(tǒng)完整源碼面向Java初學者與中小型企業(yè)管理軟件開發(fā)者提供可直接運行、二次開發(fā)的全功能業(yè)務系統(tǒng)參考實現(xiàn)。系統(tǒng)覆蓋零售、采購、銷售、倉庫、財務及報表查詢等核心模塊支持預付款、組裝拆卸、調(diào)撥、訂單全流程管理并實現(xiàn)細粒度權(quán)限控制精確到按鈕級與多維度庫存統(tǒng)計報表。資源包共2000個文件含251個Java業(yè)務邏輯類、260個JS交互腳本、250個HTML頁面、244個CSS樣式文件、512個PNG圖標資源及2個SQL建庫腳本技術(shù)棧清晰完整MyBatisEasyUIAdminLTELog4j壓縮包大小52.83MB。目前已有746人學習下載開箱即用附帶完整目錄結(jié)構(gòu)與典型配置如start.bat、數(shù)據(jù)庫腳本、JDK1.8/Mysql5.7適配說明適合用于課程設(shè)計、畢業(yè)項目或企業(yè)輕量級ERP快速原型開發(fā)。1. Java進銷存ERP管理系統(tǒng)源碼不是拿來就能跑的“開箱即用”而是要親手擰緊每一顆螺絲的生產(chǎn)級工程你搜到這個標題大概率正站在三個岔路口剛接下小工廠的定制單老板說“要個能管進貨、賣貨、查庫存的系統(tǒng)越快上線越好”或是校招面試被問“做過什么完整項目”想拿套真實可演示的Java后臺系統(tǒng)補簡歷又或者在技術(shù)群看到別人分享“SpringBootVue進銷存源碼”點開壓縮包發(fā)現(xiàn)連數(shù)據(jù)庫腳本都缺兩份、登錄頁404、pom.xml里還寫著version2.3.0.RELEASE/version——而你本地裝的是JDK 17。這不是代碼質(zhì)量問題而是所有真實業(yè)務系統(tǒng)源碼的共性它不承諾運行只提供骨架不替代理解只放大認知差。這套“Java進銷存ERP管理系統(tǒng)源碼”本質(zhì)是一套面向中小制造/貿(mào)易企業(yè)的輕量級ERP核心模塊集合覆蓋采購訂單→入庫質(zhì)檢→銷售出庫→庫存調(diào)撥→財務應付應收→基礎(chǔ)BOM與簡單MRP推演。它不追求SAP那種千人千面的配置化但要求你必須親手配通MySQL字符集、調(diào)平MyBatis動態(tài)SQL的嵌套層級、把Shiro權(quán)限樹和前端菜單JSON對齊、甚至手動補全日志中報錯的java.lang.NoClassDefFoundError: org/apache/commons/collections4/MapUtils——因為打包者刪了lib目錄。適合能寫Controller、會看Druid監(jiān)控面板、敢改application-prod.yml里數(shù)據(jù)庫密碼的人。新手照著做能打通全流程老手能借它反向吃透ERP領(lǐng)域建模邏輯。別信“一鍵部署”信“三小時排錯”。2. 從解壓到登錄五步走通本地啟動鏈每一步都卡在真實環(huán)境的毛細血管里2.1 解壓后第一眼識別源碼包的真實技術(shù)棧與隱含約束拿到.zip或.rar文件別急著mvn clean install。先用文本編輯器打開根目錄下的pom.xml重點抓三處!-- 示例真實項目中常見的“陷阱式”依賴聲明 -- parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.5.14/version !-- 注意此版本要求JDK 8-16JDK 17會報錯 -- /parent properties java.version1.8/java.version !-- 強制鎖定JDK 8即使你裝了17也得切版本 -- mybatis-plus.version3.4.3.4/mybatis-plus.version !-- 非官方MyBatis-Plus注意Mapper掃描路徑 -- /properties提示很多源碼包的pom.xml里藏著“版本綁架”。比如java.version1.8/java.version意味著你必須用JDK 8推薦Adoptium Temurin 8u362否則編譯階段就卡在Unsupported class file major version 61。別試圖強行升級——MyBatis-Plus 3.4.x與Spring Boot 2.5.x深度耦合升Spring Boot版本會導致TableField注解失效、分頁插件不生效。再檢查src/main/resources目錄結(jié)構(gòu)必有application.yml開發(fā)配置和application-prod.yml生產(chǎn)配置模板若存在mapper/目錄且是空的說明SQL映射文件被外置——要去src/main/resources/mapper/找XML文件而非MapperScan注解指向的位置若有static/和templates/說明是前后端未分離架構(gòu)Thymeleaf渲染此時Vue前端代碼大概率在另一個壓縮包里當前Java包只負責API2.2 數(shù)據(jù)庫初始化字符集、時區(qū)、外鍵三道生死線進銷存系統(tǒng)對數(shù)據(jù)一致性極度敏感。直接執(zhí)行sql/目錄下的init.sql大概率失敗。必須按順序操作創(chuàng)建數(shù)據(jù)庫并指定字符集MySQL 5.7# 登錄MySQL后執(zhí)行 CREATE DATABASE erp_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; SET NAMES utf8mb4;為什么必須utf8mb4進銷存單據(jù)號常含emoji如客戶備注“?已付款”、生僻字如“?”、“堃”utf8僅支持3字節(jié)會截斷報錯Incorrect string value。修正SQL腳本中的時區(qū)問題 打開init.sql查找所有DEFAULT CURRENT_TIMESTAMP替換為-- 原始危險 create_time datetime DEFAULT CURRENT_TIMESTAMP, -- 替換為顯式指定時區(qū)避免服務器時區(qū)漂移 create_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,血淚經(jīng)驗某次客戶服務器時區(qū)設(shè)為UTC0而業(yè)務要求東八區(qū)時間導致所有單據(jù)創(chuàng)建時間比實際晚8小時財務對賬直接崩盤。外鍵約束必須顯式開啟尤其MySQL 8.0默認禁用-- 執(zhí)行前確認 SHOW VARIABLES LIKE foreign_key_checks; -- 若為OFF則執(zhí)行 SET FOREIGN_KEY_CHECKS 1;否則purchase_order表插入時因supplier_id外鍵未關(guān)聯(lián)supplier表而報錯Cannot add or update a child row。2.3 后端服務啟動繞過Shiro認證攔截的調(diào)試模式Spring Boot項目啟動類通常叫ErpApplication.java。但直接java -jar target/erp.jar會卡在登錄頁——因為Shiro默認攔截所有/api/**請求。調(diào)試階段需臨時關(guān)閉認證在application-dev.yml中添加shiro: filter: anon: /swagger-ui.html,/webjars/**,/v2/api-docs,/druid/**,/actuator/** authc: /** # 此行暫時注釋掉啟動時強制使用dev配置java -jar target/erp.jar --spring.profiles.activedev驗證啟動成功訪問http://localhost:8080/druidDruid監(jiān)控臺輸入默認賬號admin/admin查看SQL監(jiān)控中是否有SELECT * FROM sys_user WHERE username ?——證明MyBatis已連通數(shù)據(jù)庫若出現(xiàn)Whitelabel Error Page檢查pom.xml是否漏加spring-boot-starter-thymeleaf2.4 前端聯(lián)調(diào)當Vue頁面報404問題90%在后端跨域與靜態(tài)資源路徑若源碼包含vue-admin目錄需單獨npm run serve。但常見錯誤是Vue請求/api/login返回404。此時不是前端錯了而是后端沒配好檢查WebMvcConfigurer實現(xiàn)類常位于config/WebConfig.javaOverride public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/api/**) // 必須精確匹配前端請求路徑 .allowedOrigins(http://localhost:8080) // Vue默認端口 .allowCredentials(true) .maxAge(3600); }注意allowedOrigins不能寫*否則allowCredentialstrue會失效瀏覽器報The value of the Access-Control-Allow-Origin header in the response must not be the wildcard * when the requests credentials mode is include靜態(tài)資源路徑映射針對Thymeleaf混合架構(gòu)Configuration public class WebConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { // 將/static/映射到classpath:/static/ registry.addResourceHandler(/static/**) .addResourceLocations(classpath:/static/); // 關(guān)鍵Vue構(gòu)建后的dist目錄需映射到根路徑 registry.addResourceHandler(/**) .addResourceLocations(classpath:/static/dist/); // 確保dist目錄存在 } }3. 核心業(yè)務閉環(huán)采購入庫→銷售出庫→庫存扣減三張表如何用事務鎖死數(shù)據(jù)一致性3.1 采購入庫從PO到Stock兩次更新必須原子化進銷存最脆弱環(huán)節(jié)是“采購入庫”——用戶提交入庫單系統(tǒng)需同時更新①purchase_order表的status字段從“待入庫”→“已完成”②stock表對應商品的quantity字段增加入庫數(shù)量若用兩個獨立SQL網(wǎng)絡中斷會導致訂單完成但庫存未增造成“幽靈庫存”。正確做法是Service層加Transactional并用SELECT FOR UPDATE鎖住庫存行Service public class PurchaseService { Transactional(rollbackFor Exception.class) public void confirmReceipt(Long poId) { // 1. 鎖定采購訂單防止重復提交 PurchaseOrder po purchaseOrderMapper.selectById(poId); if (!WAIT_RECEIPT.equals(po.getStatus())) { throw new BusinessException(訂單狀態(tài)非待入庫無法確認); } // 2. 鎖定庫存記錄關(guān)鍵 Stock stock stockMapper.selectOne( new QueryWrapperStock() .eq(goods_id, po.getGoodsId()) .last(FOR UPDATE) // MySQL行鎖阻塞其他事務修改同一商品庫存 ); // 3. 更新訂單狀態(tài) po.setStatus(RECEIVED); purchaseOrderMapper.updateById(po); // 4. 更新庫存在鎖住的stock對象上操作 stock.setQuantity(stock.getQuantity() po.getQuantity()); stockMapper.updateById(stock); } }參數(shù)說明last(FOR UPDATE)是MyBatis-Plus的SQL拼接方法生成SELECT * FROM stock WHERE goods_id ? FOR UPDATE。此鎖在事務提交后才釋放確保庫存變更絕對串行。3.2 銷售出庫樂觀鎖防超賣版本號比分布式鎖更輕量銷售出庫面臨“超賣”風險A、B用戶同時下單100件庫存只剩90件若不加控制兩人均可能扣減成功。悲觀鎖SELECT FOR UPDATE會嚴重降低并發(fā)。進銷存系統(tǒng)首選樂觀鎖在stock表增加version字段BIGINT默認值1Mapper XML中寫帶版本校驗的UPDATEupdate iddecreaseStock UPDATE stock SET quantity quantity - #{quantity}, version version 1 WHERE goods_id #{goodsId} AND quantity #{quantity} AND version #{version} !-- 關(guān)鍵版本號必須匹配 -- /updateService層捕獲更新失敗int rows stockMapper.decreaseStock(goodsId, quantity, currentVersion); if (rows 0) { // 版本號不匹配說明庫存已被其他事務修改 throw new BusinessException(庫存不足請刷新后重試); }為什么不用Redis分布式鎖進銷存單據(jù)量通常1000TPSMySQL行鎖樂觀鎖組合足夠。引入Redis反而增加運維復雜度且Redis故障會導致整個出庫功能不可用。3.3 庫存流水用數(shù)據(jù)庫觸發(fā)器還是應用層記錄選后者stock_log表記錄每次庫存變動誰、何時、何原因、增減多少。有人用MySQL觸發(fā)器自動生成但強烈建議在應用層記錄// 入庫成功后顯式插入日志 StockLog log new StockLog(); log.setGoodsId(po.getGoodsId()); log.setChangeType(IN); // IN/OUT/ADJUST log.setChangeQuantity(po.getQuantity()); log.setOperator(system); log.setRemark(采購訂單 po.getPoNo() 入庫); stockLogMapper.insert(log);原因觸發(fā)器無法獲取操作人sys_user.username、無法寫入業(yè)務單據(jù)號po_no、異常時難以回滾。而應用層日志可隨主事務一起提交/回滾且便于后續(xù)對接ES做庫存溯源分析。4. 避坑指南五個讓開發(fā)者凌晨三點還在查日志的真實問題4.1 現(xiàn)象登錄成功后跳轉(zhuǎn)首頁但所有API返回401原因Shiro的SecurityManager未正確注入RememberMeManager導致Session未持久化。源碼中常遺漏配置Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager new DefaultWebSecurityManager(); securityManager.setRealm(myShiroRealm()); // realm配置正確 // ? 缺少這行 securityManager.setRememberMeManager(new CookieRememberMeManager()); return securityManager; }解決在ShiroConfig.java中補全RememberMeManager配置并確保Cookie的maxAge設(shè)為合理值如60 * 60 * 24秒。4.2 現(xiàn)象Druid監(jiān)控臺顯示SQL執(zhí)行慢但EXPLAIN顯示索引命中原因MySQL的innodb_buffer_pool_size過小默認128M導致頻繁磁盤IO。進銷存系統(tǒng)需至少2G-- 查看當前設(shè)置 SHOW VARIABLES LIKE innodb_buffer_pool_size; -- 臨時調(diào)整重啟失效 SET GLOBAL innodb_buffer_pool_size 2147483648; -- 永久生效修改my.cnf [mysqld] innodb_buffer_pool_size 2G4.3 現(xiàn)象導出Excel報表時中文亂碼字段名顯示為??原因Apache POI 4.x默認用UTF-8但Tomcat 8.5的URIEncoding未設(shè)為UTF-8導致GET請求參數(shù)亂碼。解決在application.yml中強制設(shè)置響應編碼spring: http: encoding: charset: UTF-8 force: true servlet: context-path: /并在pom.xml中確認POI版本≥4.1.2修復了Excel導出UTF-8 BOM問題。4.4 現(xiàn)象定時任務如日結(jié)庫存在Linux服務器不執(zhí)行原因源碼中用了Scheduled(cron 0 0 23 * * ?)但未啟用定時任務支持。解決在啟動類上加EnableScheduling并檢查Linux時區(qū)# 查看服務器時區(qū) timedatectl status # 若為UTC需改為Asia/Shanghai sudo timedatectl set-timezone Asia/Shanghai4.5 現(xiàn)象修改商品價格后歷史銷售單據(jù)的金額未聯(lián)動更新原因這是ERP設(shè)計常識性錯誤銷售單據(jù)必須保存當時的商品快照sale_price、tax_rate等而非實時關(guān)聯(lián)商品表。源碼中sale_order_item表若只有g(shù)oods_id外鍵沒有sale_price字段即存在致命缺陷。解決立即在sale_order_item表中添加sale_price DECIMAL(10,2)字段并在創(chuàng)建銷售單時將當前商品價格寫入該字段。永遠不要在財務單據(jù)中用JOIN實時查價格。5. 生產(chǎn)就緒 checklist從源碼到交付這12項必須人工核驗把源碼變成客戶能簽字驗收的系統(tǒng)遠不止“能登錄”那么簡單。以下是我經(jīng)手23個進銷存項目后沉淀的硬性核驗清單每項都對應過真實翻車現(xiàn)場序號核驗項檢查方法不通過后果我的實操技巧1數(shù)據(jù)庫備份腳本是否可用手動執(zhí)行mysqldump -u root -p erp_db backup.sql再mysql -u root -p erp_db backup.sql驗證恢復客戶服務器宕機后無法還原合同違約備份腳本中必須含--single-transaction --routines --triggers否則BOM表存儲過程丟失2所有日期字段是否帶時區(qū)處理查application.yml中spring.jackson.time-zoneGMT8并檢查LocalDateTime字段的JsonFormat注解跨時區(qū)訪問時單據(jù)時間錯亂8小時統(tǒng)一用ZonedDateTime替代LocalDateTime序列化時自動帶08:003日志文件是否按天滾動且保留30天查logback-spring.xml中rollingPolicy配置手動touch測試文件生成磁盤爆滿導致服務假死fileNamePattern必須含%d{yyyy-MM-dd}maxHistory設(shè)為304敏感信息是否脫敏檢查SysUser實體類中password字段是否有JsonIgnorephone字段是否有JsonSerialize(using PhoneDesensitizeSerializer.class)客戶投訴隱私泄露法律風險自定義序列化器return value.substring(0,3) **** value.substring(7)5權(quán)限控制粒度是否到按鈕級登錄不同角色賬號嘗試點擊“刪除供應商”按鈕觀察是否403客戶說“你們系統(tǒng)不安全”丟單Shiro中RequiresPermissions(supplier:delete)必須與前端v-ifhasPermission(supplier:delete)嚴格一致6打印模板是否適配A4紙用Chrome打印預覽/print/purchase?poId123檢查邊距、表格線是否完整客戶財務拒收紙質(zhì)單據(jù)模板CSS中強制page { size: A4; margin: 0.5cm; }表格用table-layout: fixed7外部接口如短信是否可熱切換修改application-prod.yml中sms.api.url不重啟服務發(fā)測試短信驗證對接第三方失敗時無法快速降級用RefreshScope注解配置類配合Nacos配置中心8批量導入Excel是否做行級錯誤反饋上傳含100條數(shù)據(jù)的Excel故意讓第5行商品編碼不存在檢查是否返回“第5行商品編碼XXX不存在”用戶無法定位錯誤數(shù)據(jù)反復試錯使用EasyExcel的AnalysisEventListener在invoke()中捕獲異常并記錄行號9單據(jù)編號是否全局唯一且可讀查purchase_order表po_no字段規(guī)則應為PO202405200001日期6位序號財務對賬時發(fā)現(xiàn)重復單號整月賬目作廢用Redis原子計數(shù)器生成序號INCR po_seq_2024052010文件上傳是否限制類型與大小嘗試上傳.exe文件檢查是否攔截上傳200MB文件觀察是否超時服務器被植入木馬或磁盤占滿application.yml中設(shè)spring.servlet.multipart.max-file-size10MBController用RequestParam MultipartFile file接收11SQL注入防護是否生效在搜索框輸入 OR 11檢查是否報錯或返回全部數(shù)據(jù)黑客拖庫客戶起訴MyBatis必須用#{}而非${}動態(tài)表名用bind標簽預處理12JVM參數(shù)是否優(yōu)化查啟動腳本start.sh中java -Xms2g -Xmx2g -XX:UseG1GC ...高并發(fā)時Full GC頻繁響應超時-Xms與-Xmx必須相等G1GC的-XX:MaxGCPauseMillis200最后說個我踩過的坑曾有個客戶要求“所有單據(jù)PDF存檔”我直接用iText生成結(jié)果生成100頁PDF耗時12秒。后來換成wkhtmltopdf命令行工具HTML模板CSS同樣100頁只要1.8秒。技術(shù)選型沒有銀彈只有場景最優(yōu)解。這套Java進銷存源碼的價值不在于它多完美而在于它逼你直面每一個生產(chǎn)細節(jié)——從MySQL的innodb_buffer_pool_size到PDF生成的毫秒級優(yōu)化。當你親手擰緊這12顆螺絲你就不再是個“會跑源碼”的人而是能扛起一個ERP交付的技術(shù)負責人。希望幫到你。本文還有配套的精品資源點擊獲取