03-文件管理模块-一个看似简单的模块藏着多少细节
文件管理从一张元数据表到离线逆地理与纯前端预览文件管理这个模块刚接手时我心里的预期是上传、列表、下载三天搞定。真做起来才知道文件管理和网盘之间差的就是元数据三个字。如果只是把文件存下来它永远是一堆叫不出名字的字节只有当文件带着什么时候拍的、用什么设备、在哪、归哪个类进来它才变成一个能被检索、被理解的资料库。数巢私有云的文件模块dn_file_*我把它拆成三个支点文件夹树负责组织结构文件元数据负责结构化信息标签负责横向分类。在这三样之上又叠了 EXIF 自动抽取、离线逆地理、纯前端预览。下面把这趟ai写代码的过程从头捋一遍重点放在几个容易被忽略、但决定了好不好用的设计点上。一、三张表但标签不拆关联表三张表分工很清晰dn_file_folder文件夹经典的parent_id ancestors树结构和 RuoYi 自带的部门表同款。它比普通树表多了一个dir_path字段——存的是服务器上的真实目录路径专门给扫描导入用第五节能看到。dn_file_meta文件元数据核心表。除了文件名、大小、MIME、类型这些基本项我还特意留了一组照片属性字段camera_model、gps_lat/gps_lng、image_width/image_height、file_time以及province/city/district/address。dn_file_tag标签主数据很薄只管name和展示色color。这里有个我纠结过的取舍一个文件可以打多个标签但我没用文件-标签中间表而是直接在dn_file_meta.tags里用逗号分隔存标签1,标签2。当初也犹豫过——中间表才是教科书答案。但想清楚查询场景后我选了冗余这个系统里按标签筛文件是高频操作中间表意味着join group by而冗余存储一行就能搞定sql and concat(,, coalesce(tags, ), ,) like concat(%,, #{tagName}, ,%)前后包逗号是为了避免旅游命中旅游照这种前缀误伤。代价是标签改名要更新多行、也没法高效做每个标签各有多少文件的聚合统计。但这个系统几乎不做这类统计写入时顺手维护一下dn_file_tag主数据、保证筛选下拉不缺项就行。取舍的本质是查询模式决定了存储结构。标签查询远多于标签统计那就往查得快那头倾斜。二、上传不只是落盘顺手把元数据抽了上传前端用的是 RuoYi 现成的 bootstrap-fileinputuploadAsync:false 整批提交maxFileCount 设到 200上传时把当前选中的 folderId 一起带过去。后端收到文件后先走通用封装 FileUploadUtils.upload 落到 /profile/dn/... 目录然后做了一件很多人会漏的事——对所有文件统一调一次ExifUtils.readMeta抽元数据不管它是不是图片ExifUtils.Meta exif ExifUtils.readMeta(abs);if (exif.fileTime ! null) fileTime exif.fileTime;if (exif.cameraModel ! null) cameraModel exif.cameraModel;gpsLat exif.gpsLat; gpsLng exif.gpsLng;// ...分辨率、省市为什么所有文件都抽而不是只在图片上抽因为带元数据的远不止 jpgmp4 有拍摄时间和 GPSpdf 有创建时间。抽不到就留空异常全部静默吞掉——这点很关键否则一个损坏的缩略图就能让整批上传失败。上传完成后 file_time 默认取拍摄时间抽不到才回落到 new Date()入库时间。三、EXIF 提取metadata-extractor 的几个坑抽元数据用的是 com.drew.metadatametadata-extractor。代码不长但有好几个容易写错的地方拍摄时间优先取ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL拍照那一刻很多相机只写了TAG_DATETIME文件修改时间所以取不到就回退。设备名ExifIFD0Directory里Make品牌和Model型号经常重复比如某些机型会拼出 Canon Canon EOS...所以代码里判断model是否已包含make包含就只留model否则make model。分辨率先取 EXIF 里的宽高取不到再用JpegDirectory兜底有些 jpg 的分辨率只在 JPEG 段里。GPSGpsDirectory.getGeoLocation()直接给经纬度比自己解析度分秒省事太多。最稳的一点设计是readMeta 对任何异常都返回字段全空的对象而不是抛错。文件管理这种来者不拒的场景健壮性比精确报错重要。四、坐标变地名一个不联网的逆地理GPS 有了但 22.55, 114.06 远不如广东 / 深圳有用。这一步我本来想接高德或百度的逆地理 API转念一想不对——私有云的底色就是离线、不把数据传出去把用户照片的坐标发给第三方地图服务既违背定位也引入外部依赖。于是内置了一份 china-geo.dat全国省级行政区 数百个地级市的边界多边形数据GeoUtils 做成单例首次调用时从 classpath 加载进内存。逆地理的算法是个有意思的点射线法point-in-polygon判定落在哪个省从目标点向右发一条水平射线与省边界多边形交点数为奇数就在省内。一旦命中某省只在省内找最近的地级市按平方距离比较够用且省算力。如果落在所有省之外比如境外坐标回退到几何中心最近的省——这样像深圳靠近香港这种点不会被人误判成香港或邻省城市而是稳妥归广东。为什么不直接用全局最近城市因为最近的城市会跨行政区误判。射线法先定省、再省内找最近市层级清晰也更符合人们对归属地的直觉。五、扫描导入把现成的目录登记进来上传是把文件交给我扫描导入是你服务器上那个目录我登记一下。它递归扫指定目录自动建文件夹、建文件记录、抽 EXIF、做逆地理——但不复制原文件file_path 直接存服务器绝对路径。为了上传和扫描两套路径能共用一套下载/预览逻辑我写了个 DnFileUtils.resolveAbsolute相对路径/profile/...拼上 RuoYiConfig.getProfile()绝对路径扫描导入产生原样返回。下载和预览都走它不需要两套代码。两个小设计值得记一下去重checkFileExists(folderId, origName)同一文件夹下同名文件跳过重复点扫描不会刷出一堆重复记录。文件夹查重后递归扫到子目录先按parent name查没有才建再往下递归。踩过的一个坑现在还记着扫描完成后表格卡在正在加载左侧树也不刷新。根因是我太勤快——自定义回调里手滑调了 $.table.search()而 $.operate.post 成功后会先跑你的回调、再无条件跑自己的ajaxSuccess里面也会刷新表格。两次刷新竞争把 bootstrap-table 的遮罩锁死了。修法就一句回调里别去刷新表格框架的活交给框架。六、多条件检索把找文件做成带索引的查询列表页的筛选给了五个维度文件名、文件类型、标签、文件时间、省市。后端 selectDnFileMetaList 用 MyBatis 动态 SQL 拼几个写法是我特意定的标签就是第一节那个逗号包裹法。省市province / city / address三列OR模糊匹配输深圳能命中省为空但市为深圳的也能命中地址里含深圳的。时间范围file_time concat(#{beginTime}, 00:00:00)拼上时分秒concat写法在 H2 和 MySQL 都通用不拼时间就只能比日期。排序order by coalesce(file_time, create_time) desc——EXIF 时间为空时回落到入库时间列表永远有合理的 chronological 顺序。类型分类guessFileCat按扩展名把文件归到image/video/audio/doc/archive/installer/otherguessMimeType同理推真实 MIME。纯查表式实现没引入任何类型探测库。七、在线预览纯前端组件 一个会发 Range 的后端最后是预览。需求很朴素浏览器里直接看 PDF/图片/Office别让用户下载下来再打开。前端接的是 flyfish-file-viewer 这个纯前端预览组件在 Worker 里渲染不依赖后端转格式。引入它的 iife 包后挂载就一行​​​​​​​window.FlyfishFileViewerWebFull.mountViewer(container, {url: prefix ”/view/” row.id ”/” encodeURIComponent(name),options: { theme: light }});预览地址 /view/{id}/{fileName} 有个小心机URL 末段带原文件名含扩展名是给预览组件按类型识别用的真实文件只靠 id 从库里定位。前端永远拿不到真实路径从根上杜绝路径遍历。后端 streamFile 才是这节的重点它得像个正经的静态文件服务一样专业支持 HTTP Rangepdf.js 这类组件会按字节区间分段拉取必须返回206 Partial ContentContent-Range否则它拉到一半就主动断开连接。代码里用RandomAccessFile按start/endseek 后只写那段。预览用 inline、下载用 attachmentContent-Disposition区分开MIME 优先用记录里的mimeType避免一路octet-stream让预览组件认不出类型。一个踩出来的雷客户端拉够所需字节就断开 → 服务端写出阶段抛ClientAbortException→ 它从void接口逃逸 → RuoYi 全局异常处理器想以application/pdf的 Content-Type 把一个AjaxResult序列化出去 → 又抛HttpMessageNotWritableException。两步修支持 Range 让断开变干净写出阶段的IOException直接catch吞掉只打 debug 日志绝不让它跑到异常处理器。安全边界/view/**在 Shiro 里配成匿名anon。原因很现实——预览组件在 Worker 内fetch不携带会话 cookie若要求登录会被 302 到登录页前端表现成net::ERR_FAILED。安全不靠登录态而靠只能按 id 取文件、前端无路径这条硬约束。八、这个模块教会我的回头看文件管理真正难的不是存文件而是让文件可被找到、可被理解。几个落地的体会元数据是第一公民。把 EXIF、GPS、标签在入库那一刻就结构化好后面检索和展示都是白捡的。离线优先。EXIF、逆地理、预览都尽量不依赖外网这是私有云该有的样子。框架的钩子别抢活。扫描导入那个卡死根子是我没吃透$.operate.post的回调机制多此一举刷新了一次。下载安装包shuchao.zipwindows解压后一键运行体验 https://pan.baidu.com/s/1qdaodlVBz-hVom27OJcFcA?pwdezym​​​​​​​下一篇内容发布——怎么让不懂技术的同事也能往你的私有云上挂一个网站。

相关新闻

Keil工程中.c/.h文件创建与管理全流程详解

Keil工程中.c/.h文件创建与管理全流程详解

1. 为什么新建.c和.h文件是Keil工程里最基础也最容易出错的一步 如果你刚开始用Keil做单片机开发,或者从其他IDE转过来,可能会觉得新建一个C源文件和头文件是件小事。但恰恰是这一步,决定了你后续代码的组织结构、编译能否通过,以…

2026/11/15 20:08:43 阅读更多 →
寻味呼和浩特特色火锅:双吃锅底的鲜香破局

寻味呼和浩特特色火锅:双吃锅底的鲜香破局

在北方餐饮的辽阔版图中,呼和浩特特色火锅始终占据着一席独特的位置。它不像川渝火锅那般热烈奔放,也不似京式涮肉那样简约直接,而是以一种融合了草原豪情与蒙餐智慧的方式,自成格局。谈及一锅好火锅,人们往往先论羊肉…

2026/11/15 16:20:00 阅读更多 →
不要只問「AI 有沒有提及我」:如何建立可重複的品牌能見度測試

不要只問「AI 有沒有提及我」:如何建立可重複的品牌能見度測試

不要只問「AI 有沒有提及我」:如何建立可重複的品牌能見度測試 「我剛剛問 AI,它沒有提到我的品牌」是一個有用的起點,但還不是可用的診斷結論。不同帳戶、時間、地區、語言、模型和搜尋模式都可能產生不同回答;若不保留測試條件&…

2026/11/11 2:57:33 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/11/15 5:13:35 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/11/15 5:13:35 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/11/15 5:13:35 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/11/15 3:37:41 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/11/15 4:18:38 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/11/15 5:13:35 阅读更多 →

日新闻

周新闻

月新闻