尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
开源企业文档管理系统实战:Spring Boot 3 + Vue3 前后端分离架构拆解
简介这是一款面向企业级场景的开源文档管理系统资源适合需要部署或二次开发文档平台的Java工程师与运维人员。系统以细粒度权限控制为核心支持PDF、DOC、TXT等格式解析并通过开源方式便于按业务定制。资源包共1981个文件以Java源码为主1037个另含JSP页面、前端资源、JAR依赖库及SQL脚本等整体约8.43MB模块划分清晰便于检索和定位。压缩包内展现了基于角色的权限分配、Apache POI/PDFBox等文件解析库的应用以及Maven/Gradle构建、Eclipse配置、Doxygen文档生成、ProGuard混淆等实践从源码、配置到构建脚本完整呈现可直接参照搭建和改造。对于希望深入理解权限控制、多格式支持、日志管理与代码保护的读者这套源码原貌与工程配置比零散教程更具参考价值可直接对照学习已有5228人学习下载。1. 企业文档管理系统一套能落到私有服务器上的开源办公基座如果你所在的小团队还在用网盘传文件、用聊天记录找历史版本那这套开源的「企业文档管理系统」值得花一个下午跑起来看看。它解决的痛点很直接把散落的 Office、PDF、图片统一收进一个可检索、可追溯、权限分明的文档库覆盖上传、预览、版本回滚、全文搜索和操作审计这条完整链路。项目前端是 Vue 3 Element Plus后端是 Spring Boot存储上揉合了 MySQL、对象存储和全文索引代码结构清晰拿来二次开发比从零写要省太多事。以下内容基于我实际拆解这套代码的完整过程整理先讲整体设计再给核心模块的代码讲解最后落到怎么跑、怎么踩坑、怎么改出你自己的功能。2. 技术选型与整体架构为什么是 Spring Boot Vue 3以及模块怎么分2.1 技术栈选择每一层都是当前最不折腾的组合后端选 Spring Boot 3.x在 Java 系里生态最成熟招人容易、问题在网上都能搜到答案。权限部分用 Spring Security JWT不做 Session 粘滞接口天然适合前后端分离。持久层用 MyBatis-Plus单表 CRUD 几乎不用写 SQL复杂查询再手写 XML开发速度能快不少。存储层分三块MySQL 存文档元信息、用户、角色、部门和审计日志对象存储存文件实体系统默认接 MinIO也支持本地磁盘目录方便单机开发时省去部署依赖全文检索用 Elasticsearch标题和正文文本提取后建索引搜索响应基本在毫秒级。考虑到小团队可能不想上 ES项目还留了一个 MySQL LIKE 查询的降级实现配置里一个开关就能切换。前端选 Vue 3 Vite Element Plus 是当前后台管理系统最常见的一套组合。Vite 启动速度快Element Plus 的表单、表格、树形控件直接覆盖文档管理页面的大部分需求。状态管理用 Pinia路由用 Vue Router请求统一封装了 axios 实例baseURL、token 注入、错误提示都在一个文件里管。2.2 后端目录结构与核心模块映射拿到源码后先别急着运行把目录结构过一遍后面改代码会顺手很多。后端按功能分包实际结构如下backend/ ├── src/main/java/com/example/doccenter/ │ ├── controller/ # 接口层REST 风格 │ │ ├── AuthController.java │ │ ├── DocumentController.java │ │ ├── FolderController.java │ │ ├── SearchController.java │ │ └── AdminController.java │ ├── service/ # 业务逻辑层 │ │ ├── DocumentService.java │ │ ├── FolderService.java │ │ ├── VersionService.java │ │ ├── SearchService.java │ │ ├── PermissionService.java │ │ └── AuditService.java │ ├── mapper/ # MyBatis-Plus 数据访问层 │ ├── entity/ # 数据库实体 │ │ ├── DocDocument.java │ │ ├── DocVersion.java │ │ ├── SysUser.java │ │ ├── SysRole.java │ │ └── SysDept.java │ ├── security/ # JWT 过滤器与登录认证 │ ├── config/ # MinIO、ES、WebMvc 配置 │ └── common/ # 统一返回体、异常处理、工具类 ├── src/main/resources/ │ ├── mapper/ # 复杂 SQL 的 XML 文件 │ └── application.ymlController 层很薄只做参数接收和结果包装业务逻辑全在 Service 层。DocumentService 是核心管文档的增删改查和文件流交接VersionService 负责版本记录的生成和回滚PermissionService 校验当前登录用户对某个文件夹或文档是否有权限这个类在后续二次开发中基本每个接口都会用到。前端目录结构对应着页面组织和 API 封装frontend/ ├── src/ │ ├── api/ # 按模块拆分的请求函数 │ │ ├── document.js │ │ ├── folder.js │ │ ├── auth.js │ │ └── search.js │ ├── views/ # 页面组件 │ │ ├── login/index.vue │ │ ├── document/List.vue │ │ ├── document/Preview.vue │ │ ├── document/VersionHistory.vue │ │ ├── admin/UserManage.vue │ │ └── admin/RoleManage.vue │ ├── components/ # 通用组件如 UploadDialog、FileTree │ ├── store/ # Pinia 状态 │ ├── router/index.js # 路由 权限守卫 │ └── utils/request.js # axios 封装api 目录和后端 controller 基本一一对应改后端接口时前端找文件很直观。router 里的全局前置守卫负责检查 token 和路由权限未登录直接踢回登录页。2.3 数据库设计六张核心表构成的权限模型企业文档系统和普通网盘最大的区别在权限模型上。这套系统用「部门 - 角色 - 用户 - 文档」四层模型核心表设计如下-- 部门表 CREATE TABLE sys_dept ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(64) NOT NULL, parent_id BIGINT DEFAULT 0, sort_order INT DEFAULT 0 ); -- 角色表 CREATE TABLE sys_role ( id BIGINT PRIMARY KEY AUTO_INCREMENT, code VARCHAR(32) NOT NULL UNIQUE, name VARCHAR(64) NOT NULL ); -- 用户表 CREATE TABLE sys_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(64) NOT NULL UNIQUE, password VARCHAR(128) NOT NULL, dept_id BIGINT, role_id BIGINT ); -- 文档表 CREATE TABLE doc_document ( id BIGINT PRIMARY KEY AUTO_INCREMENT, folder_id BIGINT NOT NULL, title VARCHAR(255) NOT NULL, file_name VARCHAR(255) NOT NULL, file_type VARCHAR(32), file_size BIGINT, storage_path VARCHAR(512), current_version INT DEFAULT 1, uploader_id BIGINT, created_at DATETIME, updated_at DATETIME ); -- 版本表 CREATE TABLE doc_version ( id BIGINT PRIMARY KEY AUTO_INCREMENT, doc_id BIGINT NOT NULL, version_no INT NOT NULL, storage_path VARCHAR(512) NOT NULL, file_size BIGINT, uploader_id BIGINT, remark VARCHAR(255), created_at DATETIME ); -- 权限关联表 CREATE TABLE doc_permission ( id BIGINT PRIMARY KEY AUTO_INCREMENT, target_type VARCHAR(16) NOT NULL, -- DOC 或 FOLDER target_id BIGINT NOT NULL, principal_type VARCHAR(16) NOT NULL, -- USER 或 DEPT 或 ROLE principal_id BIGINT NOT NULL, permission VARCHAR(16) NOT NULL -- READ EDIT DELETE SHARE );权限关联表是典型的扁平化授权设计一条记录代表「某个用户/部门/角色对某个文档/文件夹具备某种操作权限」。比如「研发部全体可编辑项目需求文档」就是插入一条 target_typeDOC、principal_typeDEPT、principal_id研发部ID、permissionEDIT 的记录。这套设计在扩展性上很灵活批量授权也能通过一次循环插入实现。3. 核心模块代码拆解一个文档从上传到被检索出来的完整链路3.1 登录鉴权JWT 过滤器与当前用户上下文所有接口的入口守卫是 JWT 认证过滤器。用户登录成功后拿到 token后续每次请求带上 Authorization 头过滤器解析出用户 ID 并写入请求上下文。这块代码几乎每个 Spring Boot 项目都能复用Component public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtTokenProvider tokenProvider; Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { String token resolveToken(request); if (token ! null tokenProvider.validateToken(token)) { Long userId tokenProvider.getUserId(token); SysUser user userService.getById(userId); if (user ! null) { // 把用户信息放进 Spring Security 上下文后续接口可直接获取 UsernamePasswordAuthenticationToken authentication new UsernamePasswordAuthenticationToken(user, null, getAuthorities(user)); SecurityContextHolder.getContext().setAuthentication(authentication); } } chain.doFilter(request, response); } private String resolveToken(HttpServletRequest request) { String bearer request.getHeader(Authorization); if (bearer ! null bearer.startsWith(Bearer )) { return bearer.substring(7); } return null; } }这段代码的关键点是resolveToken里对 Authorization 头的解析。很多新手翻车是直接拿前端传的原始 token 去校验忘了去掉 Bearer 前缀如果实测一直报 401优先检查这里。用户信息放进 SecurityContext 后业务代码里用SecurityUtils.getCurrentUser()就能拿到当前登录用户这比每个接口都从请求头里解析要省事得多。3.2 文档上传事务内写库、传对象存储、建 ES 索引上传接口是文档入库的主通道。注意它不只是把文件丢进存储里还要同步写元信息、记录初始版本、触发全文索引这三步得保证一致性PostMapping(/document/upload) public RLong upload(RequestParam(file) MultipartFile file, RequestParam(folderId) Long folderId, RequestParam(value tags, required false) String tags) { Long userId SecurityUtils.getCurrentUserId(); // 1. 校验目标文件夹是否有上传权限 permissionService.checkPermission(folderId, EDIT, userId); // 2. 生成存储路径按日期分目录避免单目录文件过多 String objectKey storageService.generateObjectKey(file.getOriginalFilename()); // 3. 文件实体写入 MinIO返回可访问的 storagePath String storagePath storageService.upload(file, objectKey); // 4. 写元信息并创建初始版本 DocDocument doc new DocDocument(); doc.setFolderId(folderId); doc.setTitle(FileUtils.getBaseName(file.getOriginalFilename())); doc.setFileName(file.getOriginalFilename()); doc.setFileType(FileUtils.getExtension(file.getOriginalFilename())); doc.setFileSize(file.getSize()); doc.setStoragePath(storagePath); doc.setCurrentVersion(1); doc.setUploaderId(userId); documentService.save(doc); DocVersion version new DocVersion(); version.setDocId(doc.getId()); version.setVersionNo(1); version.setStoragePath(storagePath); version.setFileSize(file.getSize()); version.setUploaderId(userId); versionService.save(version); // 5. 异步触发全文索引不阻塞上传响应 searchService.indexAsync(doc.getId()); return R.ok(doc.getId()); }这里有个值得学习的细节generateObjectKey按日期分目录生成存储路径常见格式如2025/06/01/uuid.docx。这样设计的好处是避免对象存储单目录文件数过多也方便运维按日期归档清理。indexAsync用了 Spring 的Async注解上传响应不用等 ES 索引完成用户体验更好代价是极端情况下索引会短暂延迟可接受。保存版本表这一步不能省。没有版本表的系统用户覆盖上传旧文件后连后悔药都没有有了初始版本记录后续任何一次覆盖都能追溯和回滚。这也是企业文档管理和普通网盘的关键差异。3.3 文档预览Office 转 PDF 与前端预览组件在线预览是企业文档管理最实用的功能也是坑最多的环节。系统的做法是PDF 和图片直接返回文件流Office 文档通过 LibreOffice headless 模式转成 PDF再走 PDF 预览通道public String convertToPdf(String inputPath, String outputDir) { // 用 LibreOffice headless 命令把 Office 文档转成 PDF String outputPath outputDir / UUID.randomUUID() .pdf; ListString command Arrays.asList( libreoffice, --headless, --convert-to, pdf:writer_pdf_Export, --outdir, outputDir, inputPath ); ProcessBuilder pb new ProcessBuilder(command); pb.redirectErrorStream(true); Process process pb.start(); try { // 超时控制60 秒转不完直接杀掉避免拖垮应用 boolean finished process.waitFor(60, TimeUnit.SECONDS); if (!finished) { process.destroyForcibly(); throw new RuntimeException(Office 转 PDF 超时); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); } return outputPath; }实际执行这个命令时要确保部署机器装了 LibreOffice 和中文字体否则转出来的 PDF 中文全是方块。另外waitFor(60, TimeUnit.SECONDS)这行超时控制非常重要——不设超时的话一个损坏的 Word 文件可能让线程池里的线程耗尽接口大面积超时。这个坑在后面的避坑章节会详细展开。前端预览 PDF 用的是pdf.js整个预览组件封装在Preview.vue里接受文档 ID 作为参数后端接口按需返回文件流。Vue 3 组合式写法下核心逻辑大致是// frontend/src/views/document/Preview.vue const route useRoute() const pdfUrl ref() const loading ref(true) async function loadPreview() { // 请求预览接口后端返回 PDF 文件流或重定向地址 const res await documentApi.preview(route.query.id) pdfUrl.value res.data.url loading.value false } onMounted(loadPreview)对象存储的访问地址默认是内网地址前端在浏览器里直接访问会翻车。解决办法有两种一是后端统一走网关代理转发二是 MinIO 生成带签名和过期时间的临时 URL。这套系统后端在返回 url 前调用了storageService.getAccessibleUrl()内部生成预签名 URL让前端拿到的是可直接访问的临时链接既绕开内网问题也避免了存储桶被公开访问的安全隐患。3.4 版本管理覆盖上传生成新版本可一键回滚版本回滚的设计逻辑清晰每次覆盖上传只更新当前版本指针旧文件保留在版本表里。用户点击「历史版本」时后端把该文档的版本列表返回点「回滚」则把当前版本指针指到目标版本PostMapping(/document/{docId}/rollback) public RVoid rollback(PathVariable Long docId, RequestParam Integer targetVersionNo) { Long userId SecurityUtils.getCurrentUserId(); // 回滚也是编辑操作权限校验不能少 permissionService.checkPermission(docId, EDIT, userId); DocVersion target versionService.getVersion(docId, targetVersionNo); if (target null) { throw new BizException(目标版本不存在); } // 当前文档的存储路径指向目标版本的存储路径 DocDocument doc documentService.getById(docId); doc.setStoragePath(target.getStoragePath()); doc.setFileSize(target.getFileSize()); doc.setCurrentVersion(targetVersionNo); documentService.updateById(doc); // 审计日志记录谁在什么时间回滚了哪个文档 auditService.log(userId, ROLLBACK, String.valueOf(docId), 回滚到版本 targetVersionNo); return R.ok(); }这里有一个值得注意的设计选择回滚时没有新建版本号而是让当前版本号直接跳到目标版本。如果追求严格审计可以改成「回滚操作本身也生成一个新版本」但实际使用下来业务方往往更关心「当前线上是哪个版本」而不是保留「回滚」这个动作的中间快照。不过auditService.log这步记录了你回滚过这件事查询审计时不会丢。3.5 全文检索ES 索引与实际搜索逻辑搜索接口的设计分两步文档上传时提取标题和正文文本建索引查询时用关键词去 ES 匹配返回文档 ID 列表后再回 MySQL 查详情。ES 索引的文档结构简化如下PostMapping(/document/index/{docId}) public void indexDocument(PathVariable Long docId) { DocDocument doc documentService.getById(docId); // 文本提取Office 用 TikaPDF 用 PDFBox纯文本直接读 String content textExtractor.extract(doc.getStoragePath()); MapString, Object esDoc new HashMap(); esDoc.put(docId, doc.getId()); esDoc.put(title, doc.getTitle()); esDoc.put(content, content); esDoc.put(tags, doc.getTags()); esDoc.put(folderId, doc.getFolderId()); esDoc.put(updatedAt, doc.getUpdatedAt()); esIndexClient.index(doc_index, esDoc); }搜索时如果只是关键词匹配标题可以直接在 MySQL 里 Like 解决但要全文搜文件正文内容就必须靠 ES。文本提取那块是个体力活textExtractor内部根据文件类型分发Tika 能处理绝大多数 Office 格式PDF 用 PDFBox纯文本直接读。要注意的是中文分词ES 里的 analyzer 配置成ik_max_word才能实现合理的中文切词否则搜索「企业文档」匹配不到「企业文档管理系统」这种长词。这套索引流程是异步的文档多时可以用批量任务补索引项目里提供了/admin/reindex接口做全量重建二次开发时可以直接复用。4. 项目部署与运行从本地开发环境到 Docker 编排一次跑通4.1 后端本地启动依赖服务用 Docker 起应用用 Maven 跑先启动基础设施。项目根目录准备了docker-compose.yml把 MySQL、MinIO、Elasticsearch 三个依赖容器化单机开发不用手动安装任何服务version: 3.8 services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: doccenter ports: - 3306:3306 volumes: - mysql_data:/var/lib/mysql minio: image: minio/minio command: server /data --console-address :9001 environment: MINIO_ROOT_USER: admin MINIO_ROOT_PASSWORD: admin123 ports: - 9000:9000 - 9001:9001 volumes: - minio_data:/data elasticsearch: image: docker.elastic.co/elasticsearch/elasticsearch:8.11.2 environment: - discovery.typesingle-node - ES_JAVA_OPTS-Xms512m -Xmx512m - xpack.security.enabledfalse ports: - 9200:9200 volumes: mysql_data: minio_data:在项目根目录执行docker compose up -d三个依赖服务就都起来了。接着导入数据库脚本初始化 SQL 在根目录sql/init.sql里包含了建表和基础数据mysql -h127.0.0.1 -uroot -proot123 doccenter sql/init.sql后端启动前修改application.yml里的数据库账号密码和 MinIO 地址。本地开发默认配置改成下面这样即可server: port: 8080 spring: datasource: url: jdbc:mysql://127.0.0.1:3306/doccenter?useUnicodetruecharacterEncodingutf8 username: root password: root123 doccenter: storage: type: minio minio: endpoint: http://127.0.0.1:9000 access-key: admin secret-key: admin123 bucket: doc-files search: enabled: true es-endpoint: http://127.0.0.1:9200然后执行mvn spring-boot:run看到 Started Application 日志说明后端启动成功。项目默认开放 8080 端口启动时如果端口被占用检查是不是本地有其他服务在跑。4.2 前端启动与代理配置前端是标准的 Vite 项目依赖安装和启动命令没有特殊之处cd frontend npm install npm run dev默认开发端口 5173Vite 代理配置在vite.config.js里把/api开头的请求转发到后端 8080// frontend/vite.config.js export default defineConfig({ plugins: [vue()], server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })启动后浏览器访问http://localhost:5173用初始化 SQL 里预设的管理员账号登录账号密码都是admin。登录后进入主界面左侧是部门文件夹树中间是文档列表右上角是搜索框。先创建文件夹再上传一个 Office 或 PDF 文件验证预览、下载、版本记录这些主流程是否正常。4.3 生产部署前后端构建加反代生产环境按常规做法构建前端npm run build生成静态文件用 Nginx 托管并配置反向代理到后端后端mvn package打 JAR 包运行静态资源也可以直接由 Spring Boot 托管但生产环境更推荐 Nginx 统一入口。一个精简的 Nginx 配置参考如下server { listen 80; server_name your-host; # 前端静态文件 root /opt/doccenter/frontend/dist; location / { try_files $uri $uri/ /index.html; } # 后端 API 代理 location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # 文件预览流需要支持 Range 和较长的超时时间 location /api/preview/ { proxy_pass http://127.0.0.1:8080; proxy_buffering off; proxy_read_timeout 300s; } }proxy_buffering off这行是针对 PDF 预览的特殊处理。pdf.js 预览大文件时依赖 Range 请求分段加载如果 Nginx 默认开启缓冲大文件预览容易在翻页时卡住或白屏。预览接口单独配一个 location 并关闭缓冲是实际部署中很关键的一步。5. 避坑记录与常见问题排查上线前必须知道的事5.1 现象Office 转 PDF 后中文变成乱码或方块第一次部署预览功能上传了一个带中文的 Word 文档转出来的 PDF 在浏览器里显示全是方框英文数字正常中文乱成一片。原因排查下来是容器或服务器系统缺少中文字体。LibreOffice 渲染中文需要系统里有对应字体包基础镜像通常只带英文字体渲染 CJK 字符时找不到字形就输出方框。解决方法是安装字体包。Debian 系服务器执行apt install fonts-noto-cjkAlpine 容器用apk add font-noto-cjk装完后重启应用即可。注意不要只装一个中文字体实际项目中可能混有宋体、黑体等多种版本建议直接在 Dockerfile 里装好fonts-noto-cjk一次性覆盖常见场景。5.2 现象预览接口偶发超时随后整个应用卡死某个下午预览接口突然大面积超时重启后恢复过一阵又复现。一开始以为是网络问题查了很久才定位到是 LibreOffice 转换进程堆积。原因是convertToPdf里没有超时控制一个损坏的 Word 文件让 LibreOffice 进程挂住不退出。每来一个预览请求就新起一个转换进程进程不释放CPU 和内存被占满应用整体假死。解决分两层代码层给process.waitFor()加 60 秒超时超时后destroyForcibly()杀掉进程部署层限制 LibreOffice 的并发转换数量或者用信号量控制在同时最多跑两个转换任务。从那以后我每次改转换类代码都会强制跑一遍「上传一个伪装的损坏 Office 文件」的用例确认超时兜底生效才敢继续。5.3 现象内网能访问文件外网预览一直报 404开发环境一切正常把服务部署到公网服务器后文档列表能看到但点击预览时图片和 PDF 全部 404。浏览器访问的文件地址是指向 MinIO 的内网 IP外网用户自然访问不到。原因是文件上传时存储的storagePath拼接的是 MinIO 的内网 endpoint。不管文件存在哪最终给前端返回的访问 URL 必须基于公网可访问的域名反代或者用预签名 URL。解决方法是检查storageService.getAccessibleUrl()的拼接逻辑确认外网环境配置了doccenter.storage.minio.public-endpoint这个参数指向 Nginx 反代出来的公网地址。MinIO 的签名 URL 默认用的是请求里的 Host如果没有在配置里显式指定公网地址生成的签名 URL 也会带着内网 IP这块要格外注意。5.4 现象删除文档后搜索还能搜到残留结果后台管理员删除了某份合同第二天搜索时这个文档居然还在结果列表里。查数据库记录确实删了但 ES 索引没有同步删除搜索接口从 ES 拿到 docId回 MySQL 查不到详情兜底逻辑又把标题返了回去。原因判断是删除操作只删了数据库记录没触发索引删除。异步索引只管新增和更新删除时掉了链子。解决方法是删除文档时显式调用一次searchService.deleteIndex(docId)并做好失败重试。这类跨存储组件的一致性问题如果不想在业务代码里到处处理就加一个简单的对账定时任务每天凌晨扫描数据库中不存在的 docId去 ES 里批量清理。我用的是第一种方案配合审计日志里的删除记录改起来最直接。5.5 现象后端接口正常但前端始终提示 401登录页输入正确账号密码接口返回成功跳转到首页后所有请求全部 401。浏览器 F12 看请求头发现 Authorization 字段丢了。原因是前端 axios 响应拦截器里确实存了 token但请求拦截器读取 token 时取错了字段名和登录后store.user.token的命名对不上导致每次请求都带了个 undefined 过去。解决方法是把 token 读写统一收敛到utils/auth.js固定用localStorage.getItem(doccenter_token)做存取前后端约定好字段名别在拦截器里临时加逻辑。踩完这个坑之后我把项目里所有 localStorage 的操作做了次统一收口再也没出现过 token 时有时无的玄学问题。6. 二次开发思路从零实现一个「文档收藏」功能看扩展路径拿到源码熟悉核心模块后最自然的练习是从一个轻量功能入手比如给文档加一个「收藏/星标」入口。这个功能涉及完整的后端到前端链路能帮你快速理解系统每个层怎么配合。数据库先加一张收藏表。参考现有表的命名风格CREATE TABLE doc_favorite ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, doc_id BIGINT NOT NULL, created_at DATETIME, UNIQUE KEY uk_user_doc (user_id, doc_id) );后端新增一个FavouriteController加两个接口一个是收藏/取消收藏的切换接口一个是查询当前用户收藏列表的接口。收藏列表要分页返回字段可以直接复用DocumentService里的列表查询方法把收藏表的 docId 列表当过滤条件传进去。权限校验这里有个细节收藏列表里应该只显示你当前有 READ 权限的文档所以接口里要再调一次permissionService.filterByPermission()把没权限的文档过滤掉避免泄露敏感文件信息。前端在文档列表操作栏加一个星标按钮点击后调收藏接口同时更新列表状态。如果想把「我的收藏」做成独立标签页就参照现有FolderView的写法新增一个FavouriteView.vue路由注册到/favourite左侧菜单加对应入口。整个功能加下来大概半天时间但你能把数据库、后端接口、前端页面、权限过滤、导航配置整条链路串起来。改完这个功能建议跑一遍现有测试特别是权限相关的用例。项目里针对权限模块写了不少单元测试比如「无权限用户不能下载某文档」「部门管理员可以修改本部门文档」这些测试是为了防止改动时把权限逻辑改漏了。我习惯在跑测试之前先手工验证一遍主流程上传、预览、回滚、搜索四件事都正常再提交。这个项目比较难得的地方是它把企业文档管理中最核心的权限、版本、预览、检索四个能力都做了出来代码量控制在了一个人可以读懂的范围。有机会你也可以把它当成一个骨架把缺的业务能力慢慢补上去。如果你正准备在公司内部搭一套私有文档系统或者想学一套完整的 Spring Boot Vue 前后端分离工程这份源码很适合直接拿来做底子。希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

阿里EDAS示例工程上云实战:本地启动、部署配置与灰度避坑指南

阿里EDAS示例工程上云实战:本地启动、部署配置与灰度避坑指南

简介:这份资源是阿里EDAS(企业级分布式应用服务平台)的入门示例工程,面向希望学习微服务架构与云端应用部署的Java开发者,尤其适合刚接触EDAS、需要动手实践的中初级人员。压缩包共15个文件,以8个xml配置文…

📅 2026/10/21 4:35:54
Redis哨兵实战:高可用架构部署、故障转移与踩坑经验

Redis哨兵实战:高可用架构部署、故障转移与踩坑经验

Redis 哨兵这套东西,我最早接触是在一个电商促销活动前夜。当时主从架构跑得好好的,结果主节点凌晨挂了,运维手动切了四十分钟,老板在群里连发十几条消息。从那以后我就把哨兵当成了高可用方案的标配。这篇文章不讲教科书定义&…

📅 2026/10/21 4:35:54
C# WinForm Panel 边框颜色改不动?三种自绘方案一次讲清

C# WinForm Panel 边框颜色改不动?三种自绘方案一次讲清

简介:面向C# WinForm开发者的Panel控件美化方案,解决默认边框颜色与宽度不可直接调整的问题。资源通过继承Panel并重写OnPaint方法,以Graphics绘制自定义边框,提供BorderColor与BorderWidth两个扩展属性,示例代码清晰易…

📅 2026/10/21 4:35:54
MORE NEWS

更多资讯

📰

施耐德平台API设备列表获取与同步实战指南

正在做设备数据对接的朋友,大概率都遇到过这种场景:上级要求把施耐德平台里的设备清单同步到自己的系统中,但平台后台只能一页一页手动导出,设备一多根本扛不住。更麻烦的是后续要定时刷新、做增量比对,手动操作完全是…

📰

GPU到国产AI加速卡迁移实战:推理服务适配与性能调优全记录

今年我们接了个有点棘手的活儿:把线上一个日调用量千万级的NLP推理服务,从国际主流的GPU平台平滑迁到国产AI加速卡上。任务听起来不复杂——换个硬件,模型重新导出一下不就行了?实际上从立项到灰度上线,我们整整折腾了…

📰

用Coze工作流搭建情绪共鸣短视频自动化生产线,实战30万涨粉全拆解

做过短视频的朋友应该都有这种感觉:刷到那种一条视频几百万赞、几十万评论的账号,点进去看内容,发现画面不算精致、文案也不算长,评论区里却全是“这就是我”“谁懂啊”“说到心坎里了”的共鸣留言。这类内容就是最近两年流量表现…

📰

基于YOLO的钢材表面缺陷检测系统设计与PyQt5界面实战

做工业视觉的人对钢材表面缺陷检测这个问题应该不陌生。热轧钢板在产线上高速通过,表面随时可能出现划痕、斑块、氧化皮、夹杂物这类缺陷,靠人工盯着屏幕看,时间一长就是视觉疲劳,漏检率根本控不住。我最近完整搭了一套基于深度学…

📰

SQL注入与XSS实战拆解:从攻击链到防御编码的完整指南

半夜两点收到告警短信,说是某业务后台的登录接口出现大量异常请求。我打开日志一看,一排排admin or 11和admin --整整齐齐,就像有人在拿着字典挨个试。那会儿我就知道,又有人盯上这个老项目的登录接口了。这类事情干得多了以后&am…

📰

AI写论文实战:千笔AI写作全流程测评与避坑指南

最近私信里被问得最多的一个词,就是“AI写论文”。打开任何学生聚集的社区,都能看到有人安利各种工具,其中“千笔AI写作”这个名字反复出现,评论区清一色的“收藏了”“真的好用”。我花了两周时间,拿真实选题跑了完整…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬