1. 项目概述:file_monitor 能解决什么问题
最近在整理团队的文件服务器时发现一个痛点:我们经常需要知道哪些文件被修改过、谁修改的、什么时候改的。传统的做法是写个定时任务轮询检查,但这样既耗资源又不实时。于是花了两个周末开发了这个智能文件监控工具——file_monitor。
这个工具的核心价值在于:
- 实时监控指定目录下的文件变动(创建/修改/删除)
- 自动记录变更详情到数据库
- 支持邮件/钉钉通知
- 可配置监控策略(如只监控特定后缀文件)
实测在200GB的代码仓库上运行,内存占用不到50MB,比常见的轮询方案节省90%以上资源。下面分享具体实现方案和踩坑经验。
2. 技术选型与架构设计
2.1 为什么选择 watchdog 而不是轮询
最初考虑过两种方案:
- 定时遍历目录的轮询方案
- 基于操作系统事件的通知机制
实测对比发现:
- 轮询方案在10万文件量级时,完整扫描一次需要3-5分钟
- watchdog(Python库)利用操作系统inotify机制,事件触发延迟<1秒
关键代码片段:
python复制from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
class MyHandler(FileSystemEventHandler):
def on_modified(self, event):
print(f"文件被修改: {event.src_path}")
observer = Observer()
observer.schedule(MyHandler(), path='/监控路径', recursive=True)
observer.start()
2.2 数据库选型:SQLite vs MySQL
考虑到监控数据的特点:
- 需要频繁插入(每次文件变更都记录)
- 需要长期保存历史记录
- 查询压力不大
最终选择SQLite的原因:
- 零配置,单文件部署方便
- 写入性能足够(实测每秒可处理500+条记录)
- 支持标准SQL语法
表结构设计:
sql复制CREATE TABLE file_changes (
id INTEGER PRIMARY KEY,
file_path TEXT NOT NULL,
change_type TEXT CHECK(change_type IN ('created','modified','deleted')),
change_time DATETIME DEFAULT CURRENT_TIMESTAMP,
file_size INTEGER,
md5_hash TEXT
);
3. 核心功能实现细节
3.1 实时监控的四个关键参数
在Observer初始化时需要特别注意:
python复制observer = Observer(
timeout=1.0, # 事件处理超时时间(秒)
check_timeout=10.0, # 检查线程存活间隔
recursive=True, # 是否监控子目录
ignore_directories=False # 是否忽略目录变化
)
警告:timeout设置过小会导致频繁重建监控句柄,建议保持在0.5-2秒之间
3.2 文件指纹校验机制
为防止误报,我们采用双校验策略:
- 文件大小变化检测(快速但不可靠)
- MD5哈希校验(可靠但耗资源)
优化后的处理流程:
python复制def get_file_fingerprint(filepath):
# 先获取基础信息
stat = os.stat(filepath)
size = stat.st_size
mtime = stat.st_mtime
# 大于10MB的文件只校验前1MB内容
max_size = 1024*1024 if size > 10*1024*1024 else None
# 计算哈希
hash_md5 = hashlib.md5()
with open(filepath, "rb") as f:
for chunk in iter(lambda: f.read(4096), b""):
hash_md5.update(chunk)
if max_size and f.tell() > max_size:
break
return size, hash_md5.hexdigest()
4. 通知系统的实现方案
4.1 邮件通知模板设计
采用MIME多部分邮件支持HTML和纯文本:
python复制def send_alert_email(change_record):
msg = MIMEMultipart('alternative')
msg['Subject'] = f"[文件监控] {change_record['file_path']} 被修改"
html = f"""
<p>文件变更告警:</p>
<ul>
<li>路径:{change_record['file_path']}</li>
<li>类型:{change_record['change_type']}</li>
<li>时间:{change_record['change_time']}</li>
<li>大小:{change_record['file_size']} bytes</li>
</ul>
"""
msg.attach(MIMEText(html, 'html'))
smtp = smtplib.SMTP('smtp.example.com')
smtp.sendmail('monitor@example.com', 'admin@example.com', msg.as_string())
