二次開發(fā)實戰(zhàn):權限、附件與避坑指南)
簡介這是一套基于RuoYi框架搭建的Java檔案管理系統(tǒng)設計源碼面向具備一定Java與Vue基礎、希望深入實踐前后端分離開發(fā)的學習者與開發(fā)者可用于課程設計、畢業(yè)設計或個人技術練手。壓縮包共812個文件約99.27MB以367個Java源文件、117個Vue組件、101個JavaScript腳本為主體輔以XML配置、SCSS樣式、SQL腳本及Shell、BAT批處理文件覆蓋后端業(yè)務邏輯、前端頁面渲染、數(shù)據(jù)庫建表與項目構建部署等完整環(huán)節(jié)。資源內(nèi)含若依環(huán)境使用手冊及多套環(huán)境配置文件目錄結構清晰便于按模塊拆解學習。目前已有970人學習下載讀者可借此掌握RuoYi權限體系、檔案管理業(yè)務實現(xiàn)與前后端聯(lián)調(diào)思路快速搭建可運行的系統(tǒng)原型并在此基礎上進行二次開發(fā)與功能擴展。1. 檔案管理系統(tǒng)選型為什么我最終把 RuoYi 塞進了檔案室去年幫一家事業(yè)單位的信息科做檔案數(shù)字化改造需求聽起來不復雜把紙質(zhì)檔案的目錄、掃描件、借閱記錄管起來能按年度、保管期限、全宗號檢索借閱要留痕。他們原本想直接買成品報價單拿過來一看按并發(fā)數(shù)和存儲量階梯收費后續(xù)加個字段都要走定制流程。我當時的判斷是這種需求本質(zhì)上是「權限 表單 附件 流程」的組合用成熟的后臺腳手架改比買成品可控得多。選 RuoYi 的原因很直接它是國內(nèi) Java 后臺里文檔最全、二次開發(fā)資料最多的一套基于 Spring Boot MyBatis Shiro/Spring Security前端用 Bootstrap 或 Vue 兩套模板權限模型是標準的 RBAC菜單、角色、部門、數(shù)據(jù)權限都是現(xiàn)成的。檔案管理系統(tǒng)的核心訴求——按部門隔離檔案、按角色控制借閱審批、按數(shù)據(jù)范圍限制檢索結果——正好落在 RuoYi 已有的能力邊界內(nèi)。這份源碼包適合誰適合有 Java 基礎、想拿一個真實業(yè)務場景練手的中級開發(fā)者也適合小團隊需要一個能快速改出檔案模塊的底座。不適合完全沒碰過 Spring Boot 的人因為你要改的是業(yè)務層不是照著教程跑 demo。2. 拆開源碼包目錄結構、技術棧與檔案模塊的落點2.1 先看清 RuoYi 的分層再決定檔案代碼寫在哪拿到源碼包第一件事不是急著跑而是把目錄結構過一遍。RuoYi 典型的多模塊結構是這樣的ruoyi/ ├── ruoyi-admin # 啟動模塊Controller 入口application.yml 在這 ├── ruoyi-framework # 核心配置Shiro/Security、攔截器、數(shù)據(jù)源 ├── ruoyi-system # 系統(tǒng)管理業(yè)務用戶、角色、菜單、部門 ├── ruoyi-common # 工具類、常量、注解、統(tǒng)一返回 ├── ruoyi-quartz # 定時任務 ├── ruoyi-generator # 代碼生成器 └── ruoyi-ui # 前端Vue 或 Bootstrap 模板檔案模塊該落在哪我的習慣是新建一個ruoyi-archive模塊和ruoyi-system平級依賴ruoyi-common和ruoyi-framework。這樣做的理由是檔案的業(yè)務邏輯全宗、案卷、文件、借閱和系統(tǒng)管理用戶、角色是兩套領域混在ruoyi-system里后期維護會很難受。ruoyi-admin只負責把新模塊的 Controller 掃描進去啟動類上加ComponentScan或確保包路徑在掃描范圍內(nèi)即可。技術棧上要留意版本差異。RuoYi 有多個分支單體版前后端不分離Thymeleaf/Bootstrap、前后端分離版Vue 后端接口、微服務版Spring Cloud。檔案管理系統(tǒng)這種體量單體版或前后端分離版足夠微服務版是過度設計。源碼包里如果是前后端分離版后端返回統(tǒng)一用AjaxResult和TableDataInfo前端用 axios 封裝這個約定要記住后面寫檔案接口直接復用。2.2 用代碼生成器把檔案表變成 CRUD省掉一半體力活RuoYi 最值錢的功能之一是代碼生成器。檔案管理系統(tǒng)里檔案目錄表、借閱記錄表這種標準 CRUD沒必要手寫。先在數(shù)據(jù)庫建表CREATE TABLE archive_catalog ( id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 主鍵, fonds_no VARCHAR(50) NOT NULL COMMENT 全宗號, archive_no VARCHAR(50) NOT NULL COMMENT 檔號, title VARCHAR(200) NOT NULL COMMENT 題名, category VARCHAR(50) DEFAULT NULL COMMENT 檔案類別, retention VARCHAR(20) DEFAULT NULL COMMENT 保管期限, archive_year INT(4) DEFAULT NULL COMMENT 年度, dept_id BIGINT(20) DEFAULT NULL COMMENT 歸屬部門, status CHAR(1) DEFAULT 0 COMMENT 狀態(tài)(0正常 1停用), create_by VARCHAR(64) DEFAULT COMMENT 創(chuàng)建者, create_time DATETIME DEFAULT NULL COMMENT 創(chuàng)建時間, update_by VARCHAR(64) DEFAULT COMMENT 更新者, update_time DATETIME DEFAULT NULL COMMENT 更新時間, remark VARCHAR(500) DEFAULT NULL COMMENT 備注, PRIMARY KEY (id), UNIQUE KEY uk_archive_no (archive_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT檔案目錄表;建表時幾個字段是 RuoYi 的約定不能省create_by、create_time、update_by、update_time、remark代碼生成器會識別這些字段并自動填充。dept_id是數(shù)據(jù)權限的關鍵RuoYi 的數(shù)據(jù)范圍過濾全部、本部門、本部門及以下、僅本人就是靠它和sys_dept關聯(lián)實現(xiàn)的。建完表進系統(tǒng)「系統(tǒng)工具 → 代碼生成 → 導入表」選中archive_catalog預覽后下載。生成的代碼包含 Entity、Mapper、Service、Controller、XML 和前端頁面。把 Java 文件按包路徑放進ruoyi-archiveXML 放進resources/mapper/archive前端頁面放進ruoyi-ui/src/views/archive。這一步做完檔案目錄的增刪改查、導出、分頁就都有了。提示生成器默認把dept_id當普通字段處理數(shù)據(jù)權限注解要自己加。在 Service 實現(xiàn)類的方法上加DataScope(deptAlias d)并在 Mapper XML 的查詢里用${params.dataScope}拼接否則部門隔離不生效。2.3 附件上傳與借閱流程檔案系統(tǒng)的兩個非標準點代碼生成器解決的是標準 CRUD檔案系統(tǒng)有兩個地方它管不了掃描件上傳和借閱審批。掃描件上傳RuoYi 自帶FileUploadUtils但檔案場景要額外考慮文件類型白名單pdf、jpg、tif、單文件大小上限、存儲路徑按全宗號分目錄。我一般會重寫一個ArchiveFileService在upload方法里做校驗public String uploadArchiveFile(MultipartFile file, String fondsNo) throws IOException { // 1. 校驗擴展名檔案掃描件只允許這幾種 String ext FileTypeUtils.getExtension(file.getOriginalFilename()); ListString allow Arrays.asList(pdf, jpg, jpeg, tif, tiff); if (!allow.contains(ext.toLowerCase())) { throw new ServiceException(不允許的文件類型 ext); } // 2. 按全宗號分目錄避免單目錄文件過多 String baseDir RuoYiConfig.getProfile() /archive/ fondsNo; File dir new File(baseDir); if (!dir.exists()) { dir.mkdirs(); } // 3. 文件名用 UUID防止中文名和重名問題 String fileName UUID.randomUUID().toString().replace(-, ) . ext; File dest new File(dir, fileName); file.transferTo(dest); // 4. 返回相對路徑存庫不存絕對路徑 return /archive/ fondsNo / fileName; }參數(shù)說明RuoYiConfig.getProfile()是 RuoYi 配置的本地存儲根路徑在application.yml的ruoyi.profile里改。返回相對路徑而不是絕對路徑是為了后續(xù)換存儲比如掛 NAS 或?qū)ο蟠鎯r不用改數(shù)據(jù)庫。fondsNo從檔案記錄里帶過來保證同一全宗的掃描件物理上聚在一起備份和遷移時直接拷目錄。借閱審批RuoYi 單體版沒有內(nèi)置工作流別硬上 Activiti檔案借閱的流程很簡單申請人提交 → 部門負責人審批 → 檔案管理員確認借出 → 歸還登記。用狀態(tài)字段 審批記錄表就能實現(xiàn)狀態(tài)機是0待審 → 1部門通過 → 2已借出 → 3已歸還 / 9駁回。每次狀態(tài)變更往archive_borrow_log插一條記錄誰在什么時間改的、意見是什么全留痕。這比引入工作流引擎輕得多也符合檔案系統(tǒng)「操作可追溯」的合規(guī)要求。3. 權限與數(shù)據(jù)隔離檔案系統(tǒng)最容易翻車的地方3.1 RuoYi 的 RBAC 模型怎么映射到檔案場景RuoYi 的權限模型是「用戶 → 角色 → 菜單/按鈕權限」加上「用戶 → 部門」的數(shù)據(jù)權限。檔案系統(tǒng)里角色劃分通常是檔案管理員全宗維護、借閱確認、部門兼職檔案員本部門檔案錄入、借閱申請、普通用戶檢索、申請借閱、領導查看統(tǒng)計。菜單權限控制的是「能不能看到這個頁面、能不能點這個按鈕」。比如「檔案刪除」按鈕只有檔案管理員角色勾選了對應權限標識如archive:catalog:remove前端v-hasPermi[archive:catalog:remove]才會渲染。這個機制 RuoYi 已經(jīng)做完了你要做的是在代碼生成器生成的菜單 SQL 里把權限標識改成檔案模塊的命名別和系統(tǒng)管理的沖突。數(shù)據(jù)權限控制的是「能看到哪些數(shù)據(jù)」。這是檔案系統(tǒng)的命門——普通用戶不能檢索到其他部門的檔案。RuoYi 的數(shù)據(jù)范圍有四種全部數(shù)據(jù)、本部門數(shù)據(jù)、本部門及以下數(shù)據(jù)、僅本人數(shù)據(jù)。在角色管理里給角色分配數(shù)據(jù)范圍然后在檔案查詢的 Mapper 里加數(shù)據(jù)權限過濾。3.2 數(shù)據(jù)權限注解的實際寫法與驗證方法在ArchiveCatalogServiceImpl的查詢方法上加注解DataScope(deptAlias d, userAlias u) public ListArchiveCatalog selectArchiveCatalogList(ArchiveCatalog archiveCatalog) { return archiveCatalogMapper.selectArchiveCatalogList(archiveCatalog); }對應的 Mapper XMLselect idselectArchiveCatalogList parameterTypeArchiveCatalog resultMapArchiveCatalogResult SELECT a.id, a.fonds_no, a.archive_no, a.title, a.category, a.retention, a.archive_year, a.dept_id, a.status, d.dept_name FROM archive_catalog a LEFT JOIN sys_dept d ON a.dept_id d.dept_id where if testtitle ! null and title ! AND a.title LIKE CONCAT(%, #{title}, %) /if if testarchiveYear ! null AND a.archive_year #{archiveYear} /if !-- 數(shù)據(jù)權限過濾這行必須有 -- ${params.dataScope} /where ORDER BY a.create_time DESC /select${params.dataScope}是 RuoYi 在切面里動態(tài)拼進去的 SQL 片段比如「本部門數(shù)據(jù)」會拼成AND (a.dept_id 100)。注意這里用的是${}不是#{}因為拼的是 SQL 結構不是參數(shù)值但這也意味著dataScope的內(nèi)容必須由框架生成不能接受外部輸入否則有注入風險。驗證方法用兩個不同部門的賬號登錄分別查檔案列表看返回的數(shù)據(jù)是否只包含本部門。再切換角色的數(shù)據(jù)范圍為「全部數(shù)據(jù)」確認能看到所有。這一步一定要在開發(fā)階段測我見過上線后才發(fā)現(xiàn)數(shù)據(jù)權限沒生效、普通用戶能查到全部檔案的案例檔案系統(tǒng)里這是事故。注意DataScope注解只對加了deptAlias且 XML 里有${params.dataScope}的查詢生效。如果某個查詢忘了加那個接口就是「越權」的。建議在代碼 review 時把所有檔案相關的查詢方法列出來逐個確認。3.3 借閱記錄的操作留痕與審計字段檔案系統(tǒng)對「誰在什么時候做了什么」有硬性要求。RuoYi 自帶Log注解加在 Controller 方法上會記錄操作日志到sys_oper_logLog(title 檔案借閱, businessType BusinessType.UPDATE) PostMapping(/approve) public AjaxResult approve(RequestBody ArchiveBorrow borrow) { return toAjax(archiveBorrowService.approve(borrow)); }但sys_oper_log記錄的是接口調(diào)用業(yè)務語義不夠。借閱審批這種關鍵操作我還會在業(yè)務表里單獨記一條archive_borrow_log字段包括借閱ID、操作類型申請/通過/駁回/借出/歸還、操作人、操作時間、意見。這樣查一個檔案的完整流轉(zhuǎn)歷史直接查這張表就行不用去翻系統(tǒng)日志。審計字段的填充RuoYi 用 MyBatis 攔截器自動處理create_by、create_time等前提是實體類繼承BaseEntity。代碼生成器生成的實體默認繼承別手賤去掉。如果某個字段需要自定義填充邏輯實現(xiàn)MetaObjectHandler接口在insertFill和updateFill里寫。4. 避坑排查檔案系統(tǒng)二次開發(fā)里我踩過的五個坑4.1 代碼生成器生成的菜單 SQL 執(zhí)行后菜單不顯示現(xiàn)象把生成的菜單 SQL 在數(shù)據(jù)庫執(zhí)行了重新登錄后左側菜單還是沒出現(xiàn)。原因RuoYi 的菜單有層級關系生成的 SQL 里父菜單 ID 可能寫死成某個值和你系統(tǒng)里實際的父菜單對不上或者菜單的visible字段是1隱藏status是1停用。解決先查sys_menu表確認新菜單的parent_id指向一個存在的目錄菜單menu_type是C菜單或F按鈕visible為0status為0。最穩(wěn)的做法是手動在「系統(tǒng)管理 → 菜單管理」里新建一個目錄記下它的 ID再把生成 SQL 里的parent_id改成這個 ID 再執(zhí)行。4.2 附件上傳后能存進去但下載 404現(xiàn)象掃描件上傳成功數(shù)據(jù)庫里路徑也有但點下載報 404。原因RuoYi 的本地文件訪問需要配置靜態(tài)資源映射。上傳的文件存在ruoyi.profile指定的磁盤路徑但 Web 訪問路徑/profile/**需要在ResourcesConfig里映射到那個磁盤路徑。如果換了存儲目錄沒同步改映射或者 Nginx 反代時沒放行/profile路徑就會 404。解決檢查ruoyi-framework里的ResourcesConfig確認有addResourceHandler(/profile/**).addResourceLocations(file: RuoYiConfig.getProfile() /)。如果是 Nginx 前置加一段location /profile/ { alias /實際磁盤路徑/; }。另外注意路徑結尾的斜杠file:后面跟的目錄必須以/結尾否則拼接會出錯。4.3 數(shù)據(jù)權限對本部門及以下不生效現(xiàn)象角色數(shù)據(jù)范圍設成「本部門及以下數(shù)據(jù)」但用戶只能看到本部門的下級部門的看不到。原因sys_dept表里的ancestors字段沒維護對。RuoYi 判斷「及以下」是靠ancestors里的祖級列表做FIND_IN_SET查詢?nèi)绻陆ú块T時ancestors是空的或者沒包含父級鏈路過濾就失效。解決檢查sys_dept表每個部門的ancestors應該是從根到父級的 ID 逗號串比如0,100,101。新建部門要通過界面操作讓系統(tǒng)自動維護別直接 INSERT。如果歷史數(shù)據(jù)亂了寫個腳本按parent_id遞歸重算一遍ancestors。4.4 檔案檢索按年度查詢走不了索引現(xiàn)象檔案目錄到幾十萬條后按年度 全宗號檢索明顯變慢。原因archive_year和fonds_no上沒建索引或者建了但查詢條件里對字段做了函數(shù)操作比如YEAR(create_time) 2024導致索引失效。解決給高頻查詢字段建組合索引idx_fonds_year (fonds_no, archive_year)。查詢條件避免在字段上套函數(shù)年度就用archive_year #{archiveYear}不要用YEAR(create_time)。如果確實要按創(chuàng)建時間篩年度冗余一個archive_year字段在插入時算好比函數(shù)索引可靠。4.5 前后端分離版跨域和 Token 失效現(xiàn)象前端本地開發(fā)時接口 401或者跨域被攔。原因前后端分離版用 JWTToken 放在請求頭Authorization??缬驎r瀏覽器先發(fā) OPTIONS 預檢如果后端沒放行 OPTIONS 或者沒返回正確的 CORS 頭預檢就失敗。另外 Token 過期時間配得太短開發(fā)時頻繁掉線。解決RuoYi 的SecurityConfig或ShiroConfig里確認放行了 OPTIONS 請求CORS 配置允許前端開發(fā)地址。Token 有效期在application.yml的token.expireTime里改開發(fā)階段可以調(diào)大。生產(chǎn)環(huán)境別調(diào)太大配合刷新機制用。5. 從能跑到好用檔案檢索優(yōu)化與部署前的一次自檢檔案系統(tǒng)「能跑」和「好用」之間差的是檢索體驗和部署穩(wěn)定性。這部分說兩個具體技巧。第一個是全文檢索。RuoYi 默認用 MyBatis 的LIKE查詢檔案題名、備注少的時候夠用上十萬條就吃力。常見做法是引入 Elasticsearch但小系統(tǒng)沒必要。我的折中方案是用 MySQL 的全文索引給title和remark建FULLTEXT索引查詢用MATCH ... AGAINST。ALTER TABLE archive_catalog ADD FULLTEXT INDEX ft_title_remark (title, remark);if testkeyword ! null and keyword ! AND MATCH(a.title, a.remark) AGAINST(#{keyword} IN BOOLEAN MODE) /if參數(shù)說明IN BOOLEAN MODE支持必須包含、-必須不包含操作符比自然語言模式可控。中文分詞 MySQL 默認不支持需要裝 ngram 插件并設置ngram_token_size這是它的邊界——如果檔案題名以中文為主且檢索要求高還是得上 ES 或?qū)iT的檢索引擎。我一般會先問清楚數(shù)據(jù)量和檢索頻率幾萬條以內(nèi) MySQL 全文索引夠用別過度設計。第二個是部署前的自檢清單。檔案系統(tǒng)涉及數(shù)據(jù)安全上線前我會強制走一遍這幾項檢查項驗證方法不通過的后果數(shù)據(jù)權限兩個部門賬號交叉查詢越權看到他人檔案附件路徑上傳后下載、換目錄后下載掃描件丟失或 404操作日志借閱審批后查sys_oper_log和業(yè)務日志表無法追溯合規(guī)不達標備份策略手動備份數(shù)據(jù)庫和附件目錄嘗試恢復數(shù)據(jù)丟失無法找回默認密碼檢查admin等賬號是否改密被弱口令登錄這張表我每次部署前都過一遍尤其是數(shù)據(jù)權限和備份恢復這兩項出問題就是事故級別。備份要注意數(shù)據(jù)庫用mysqldump附件目錄直接打包兩者要能對應上——數(shù)據(jù)庫里的路徑和實際文件必須一致否則恢復后附件全是死鏈。從那以后我每次交付檔案類系統(tǒng)都會在測試環(huán)境用兩個部門的賬號把數(shù)據(jù)權限交叉驗證一遍再模擬一次附件目錄遷移確認路徑映射沒問題才敢上線。檔案系統(tǒng)的數(shù)據(jù)不像電商丟了就是丟了沒有后悔藥。希望幫到你。本文還有配套的精品資源點擊獲取