
简介这是一套基于RuoYi框架搭建的Java档案管理系统设计源码面向具备一定Java与Vue基础、希望深入实践前后端分离开发的学习者与开发者可用于课程设计、毕业设计或个人技术练手。压缩包共812个文件约99.27MB以367个Java源文件、117个Vue组件、101个JavaScript脚本为主体辅以XML配置、SCSS样式、SQL脚本及Shell、BAT批处理文件覆盖后端业务逻辑、前端页面渲染、数据库建表与项目构建部署等完整环节。资源内含若依环境使用手册及多套环境配置文件目录结构清晰便于按模块拆解学习。目前已有970人学习下载读者可借此掌握RuoYi权限体系、档案管理业务实现与前后端联调思路快速搭建可运行的系统原型并在此基础上进行二次开发与功能扩展。1. 档案管理系统选型为什么我最终把 RuoYi 塞进了档案室去年帮一家事业单位的信息科做档案数字化改造需求听起来不复杂把纸质档案的目录、扫描件、借阅记录管起来能按年度、保管期限、全宗号检索借阅要留痕。他们原本想直接买成品报价单拿过来一看按并发数和存储量阶梯收费后续加个字段都要走定制流程。我当时的判断是这种需求本质上是「权限 表单 附件 流程」的组合用成熟的后台脚手架改比买成品可控得多。选 RuoYi 的原因很直接它是国内 Java 后台里文档最全、二次开发资料最多的一套基于 Spring Boot MyBatis Shiro/Spring Security前端用 Bootstrap 或 Vue 两套模板权限模型是标准的 RBAC菜单、角色、部门、数据权限都是现成的。档案管理系统的核心诉求——按部门隔离档案、按角色控制借阅审批、按数据范围限制检索结果——正好落在 RuoYi 已有的能力边界内。这份源码包适合谁适合有 Java 基础、想拿一个真实业务场景练手的中级开发者也适合小团队需要一个能快速改出档案模块的底座。不适合完全没碰过 Spring Boot 的人因为你要改的是业务层不是照着教程跑 demo。2. 拆开源码包目录结构、技术栈与档案模块的落点2.1 先看清 RuoYi 的分层再决定档案代码写在哪拿到源码包第一件事不是急着跑而是把目录结构过一遍。RuoYi 典型的多模块结构是这样的ruoyi/ ├── ruoyi-admin # 启动模块Controller 入口application.yml 在这 ├── ruoyi-framework # 核心配置Shiro/Security、拦截器、数据源 ├── ruoyi-system # 系统管理业务用户、角色、菜单、部门 ├── ruoyi-common # 工具类、常量、注解、统一返回 ├── ruoyi-quartz # 定时任务 ├── ruoyi-generator # 代码生成器 └── ruoyi-ui # 前端Vue 或 Bootstrap 模板档案模块该落在哪我的习惯是新建一个ruoyi-archive模块和ruoyi-system平级依赖ruoyi-common和ruoyi-framework。这样做的理由是档案的业务逻辑全宗、案卷、文件、借阅和系统管理用户、角色是两套领域混在ruoyi-system里后期维护会很难受。ruoyi-admin只负责把新模块的 Controller 扫描进去启动类上加ComponentScan或确保包路径在扫描范围内即可。技术栈上要留意版本差异。RuoYi 有多个分支单体版前后端不分离Thymeleaf/Bootstrap、前后端分离版Vue 后端接口、微服务版Spring Cloud。档案管理系统这种体量单体版或前后端分离版足够微服务版是过度设计。源码包里如果是前后端分离版后端返回统一用AjaxResult和TableDataInfo前端用 axios 封装这个约定要记住后面写档案接口直接复用。2.2 用代码生成器把档案表变成 CRUD省掉一半体力活RuoYi 最值钱的功能之一是代码生成器。档案管理系统里档案目录表、借阅记录表这种标准 CRUD没必要手写。先在数据库建表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 状态(0正常 1停用), create_by VARCHAR(64) DEFAULT COMMENT 创建者, create_time DATETIME DEFAULT NULL COMMENT 创建时间, 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是数据权限的关键RuoYi 的数据范围过滤全部、本部门、本部门及以下、仅本人就是靠它和sys_dept关联实现的。建完表进系统「系统工具 → 代码生成 → 导入表」选中archive_catalog预览后下载。生成的代码包含 Entity、Mapper、Service、Controller、XML 和前端页面。把 Java 文件按包路径放进ruoyi-archiveXML 放进resources/mapper/archive前端页面放进ruoyi-ui/src/views/archive。这一步做完档案目录的增删改查、导出、分页就都有了。提示生成器默认把dept_id当普通字段处理数据权限注解要自己加。在 Service 实现类的方法上加DataScope(deptAlias d)并在 Mapper XML 的查询里用${params.dataScope}拼接否则部门隔离不生效。2.3 附件上传与借阅流程档案系统的两个非标准点代码生成器解决的是标准 CRUD档案系统有两个地方它管不了扫描件上传和借阅审批。扫描件上传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; }参数说明RuoYiConfig.getProfile()是 RuoYi 配置的本地存储根路径在application.yml的ruoyi.profile里改。返回相对路径而不是绝对路径是为了后续换存储比如挂 NAS 或对象存储时不用改数据库。fondsNo从档案记录里带过来保证同一全宗的扫描件物理上聚在一起备份和迁移时直接拷目录。借阅审批RuoYi 单体版没有内置工作流别硬上 Activiti档案借阅的流程很简单申请人提交 → 部门负责人审批 → 档案管理员确认借出 → 归还登记。用状态字段 审批记录表就能实现状态机是0待审 → 1部门通过 → 2已借出 → 3已归还 / 9驳回。每次状态变更往archive_borrow_log插一条记录谁在什么时间改的、意见是什么全留痕。这比引入工作流引擎轻得多也符合档案系统「操作可追溯」的合规要求。3. 权限与数据隔离档案系统最容易翻车的地方3.1 RuoYi 的 RBAC 模型怎么映射到档案场景RuoYi 的权限模型是「用户 → 角色 → 菜单/按钮权限」加上「用户 → 部门」的数据权限。档案系统里角色划分通常是档案管理员全宗维护、借阅确认、部门兼职档案员本部门档案录入、借阅申请、普通用户检索、申请借阅、领导查看统计。菜单权限控制的是「能不能看到这个页面、能不能点这个按钮」。比如「档案删除」按钮只有档案管理员角色勾选了对应权限标识如archive:catalog:remove前端v-hasPermi[archive:catalog:remove]才会渲染。这个机制 RuoYi 已经做完了你要做的是在代码生成器生成的菜单 SQL 里把权限标识改成档案模块的命名别和系统管理的冲突。数据权限控制的是「能看到哪些数据」。这是档案系统的命门——普通用户不能检索到其他部门的档案。RuoYi 的数据范围有四种全部数据、本部门数据、本部门及以下数据、仅本人数据。在角色管理里给角色分配数据范围然后在档案查询的 Mapper 里加数据权限过滤。3.2 数据权限注解的实际写法与验证方法在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 !-- 数据权限过滤这行必须有 -- ${params.dataScope} /where ORDER BY a.create_time DESC /select${params.dataScope}是 RuoYi 在切面里动态拼进去的 SQL 片段比如「本部门数据」会拼成AND (a.dept_id 100)。注意这里用的是${}不是#{}因为拼的是 SQL 结构不是参数值但这也意味着dataScope的内容必须由框架生成不能接受外部输入否则有注入风险。验证方法用两个不同部门的账号登录分别查档案列表看返回的数据是否只包含本部门。再切换角色的数据范围为「全部数据」确认能看到所有。这一步一定要在开发阶段测我见过上线后才发现数据权限没生效、普通用户能查到全部档案的案例档案系统里这是事故。注意DataScope注解只对加了deptAlias且 XML 里有${params.dataScope}的查询生效。如果某个查询忘了加那个接口就是「越权」的。建议在代码 review 时把所有档案相关的查询方法列出来逐个确认。3.3 借阅记录的操作留痕与审计字段档案系统对「谁在什么时候做了什么」有硬性要求。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记录的是接口调用业务语义不够。借阅审批这种关键操作我还会在业务表里单独记一条archive_borrow_log字段包括借阅ID、操作类型申请/通过/驳回/借出/归还、操作人、操作时间、意见。这样查一个档案的完整流转历史直接查这张表就行不用去翻系统日志。审计字段的填充RuoYi 用 MyBatis 拦截器自动处理create_by、create_time等前提是实体类继承BaseEntity。代码生成器生成的实体默认继承别手贱去掉。如果某个字段需要自定义填充逻辑实现MetaObjectHandler接口在insertFill和updateFill里写。4. 避坑排查档案系统二次开发里我踩过的五个坑4.1 代码生成器生成的菜单 SQL 执行后菜单不显示现象把生成的菜单 SQL 在数据库执行了重新登录后左侧菜单还是没出现。原因RuoYi 的菜单有层级关系生成的 SQL 里父菜单 ID 可能写死成某个值和你系统里实际的父菜单对不上或者菜单的visible字段是1隐藏status是1停用。解决先查sys_menu表确认新菜单的parent_id指向一个存在的目录菜单menu_type是C菜单或F按钮visible为0status为0。最稳的做法是手动在「系统管理 → 菜单管理」里新建一个目录记下它的 ID再把生成 SQL 里的parent_id改成这个 ID 再执行。4.2 附件上传后能存进去但下载 404现象扫描件上传成功数据库里路径也有但点下载报 404。原因RuoYi 的本地文件访问需要配置静态资源映射。上传的文件存在ruoyi.profile指定的磁盘路径但 Web 访问路径/profile/**需要在ResourcesConfig里映射到那个磁盘路径。如果换了存储目录没同步改映射或者 Nginx 反代时没放行/profile路径就会 404。解决检查ruoyi-framework里的ResourcesConfig确认有addResourceHandler(/profile/**).addResourceLocations(file: RuoYiConfig.getProfile() /)。如果是 Nginx 前置加一段location /profile/ { alias /实际磁盘路径/; }。另外注意路径结尾的斜杠file:后面跟的目录必须以/结尾否则拼接会出错。4.3 数据权限对本部门及以下不生效现象角色数据范围设成「本部门及以下数据」但用户只能看到本部门的下级部门的看不到。原因sys_dept表里的ancestors字段没维护对。RuoYi 判断「及以下」是靠ancestors里的祖级列表做FIND_IN_SET查询如果新建部门时ancestors是空的或者没包含父级链路过滤就失效。解决检查sys_dept表每个部门的ancestors应该是从根到父级的 ID 逗号串比如0,100,101。新建部门要通过界面操作让系统自动维护别直接 INSERT。如果历史数据乱了写个脚本按parent_id递归重算一遍ancestors。4.4 档案检索按年度查询走不了索引现象档案目录到几十万条后按年度 全宗号检索明显变慢。原因archive_year和fonds_no上没建索引或者建了但查询条件里对字段做了函数操作比如YEAR(create_time) 2024导致索引失效。解决给高频查询字段建组合索引idx_fonds_year (fonds_no, archive_year)。查询条件避免在字段上套函数年度就用archive_year #{archiveYear}不要用YEAR(create_time)。如果确实要按创建时间筛年度冗余一个archive_year字段在插入时算好比函数索引可靠。4.5 前后端分离版跨域和 Token 失效现象前端本地开发时接口 401或者跨域被拦。原因前后端分离版用 JWTToken 放在请求头Authorization。跨域时浏览器先发 OPTIONS 预检如果后端没放行 OPTIONS 或者没返回正确的 CORS 头预检就失败。另外 Token 过期时间配得太短开发时频繁掉线。解决RuoYi 的SecurityConfig或ShiroConfig里确认放行了 OPTIONS 请求CORS 配置允许前端开发地址。Token 有效期在application.yml的token.expireTime里改开发阶段可以调大。生产环境别调太大配合刷新机制用。5. 从能跑到好用档案检索优化与部署前的一次自检档案系统「能跑」和「好用」之间差的是检索体验和部署稳定性。这部分说两个具体技巧。第一个是全文检索。RuoYi 默认用 MyBatis 的LIKE查询档案题名、备注少的时候够用上十万条就吃力。常见做法是引入 Elasticsearch但小系统没必要。我的折中方案是用 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参数说明IN BOOLEAN MODE支持必须包含、-必须不包含操作符比自然语言模式可控。中文分词 MySQL 默认不支持需要装 ngram 插件并设置ngram_token_size这是它的边界——如果档案题名以中文为主且检索要求高还是得上 ES 或专门的检索引擎。我一般会先问清楚数据量和检索频率几万条以内 MySQL 全文索引够用别过度设计。第二个是部署前的自检清单。档案系统涉及数据安全上线前我会强制走一遍这几项检查项验证方法不通过的后果数据权限两个部门账号交叉查询越权看到他人档案附件路径上传后下载、换目录后下载扫描件丢失或 404操作日志借阅审批后查sys_oper_log和业务日志表无法追溯合规不达标备份策略手动备份数据库和附件目录尝试恢复数据丢失无法找回默认密码检查admin等账号是否改密被弱口令登录这张表我每次部署前都过一遍尤其是数据权限和备份恢复这两项出问题就是事故级别。备份要注意数据库用mysqldump附件目录直接打包两者要能对应上——数据库里的路径和实际文件必须一致否则恢复后附件全是死链。从那以后我每次交付档案类系统都会在测试环境用两个部门的账号把数据权限交叉验证一遍再模拟一次附件目录迁移确认路径映射没问题才敢上线。档案系统的数据不像电商丢了就是丢了没有后悔药。希望帮到你。本文还有配套的精品资源点击获取