Flutter应用后端PDF数据处理及Firebase存储方案咨询
解决方案:Flutter + Firebase + Python 实现司法PDF的存储与按需筛选
1. 先搞定PDF的元数据提取(Python端)
因为目标网站无API,你用Playwright下载PDF后,必须先提取每个PDF里的姓名和案件编号作为元数据——这是实现筛选的核心,不然没法从一堆PDF里快速定位目标文件。
- 用
pdfplumber或PyPDF2提取PDF文本(如果是扫描件,搭配pytesseract做OCR):
import pdfplumber import re def extract_metadata(pdf_path, year): metadata = {"year": year, "names": [], "case_numbers": []} with pdfplumber.open(pdf_path) as pdf: for page in pdf.pages: text = page.extract_text() if not text: continue # 正则匹配TRF5案件编号格式(可根据实际PDF调整) case_matches = re.findall(r"\d{7}-\d{2}\.\d{4}\.\d\.\d{2}\.\d{4}", text) metadata["case_numbers"].extend(case_matches) # 正则匹配姓名(假设姓名跟随"Nome:"标签,可根据PDF结构调整) name_matches = re.findall(r"Nome:\s*([A-Za-zÀ-ÿ\s]+)", text) metadata["names"].extend([name.strip() for name in name_matches]) # 去重避免重复数据 metadata["names"] = list(set(metadata["names"])) metadata["case_numbers"] = list(set(metadata["case_numbers"])) return metadata
- 配合Playwright的下载逻辑,每下载完一个PDF就立即提取元数据,为后续上传Firebase做准备。
2. Firebase架构设计
2.1 Cloud Storage:存储原始PDF
- 创建专属存储桶,按年份/类型分层存储,比如路径:
2020/administrative/xxx.pdf,方便管理与权限控制。 - 默认设置文件私有,通过后端生成临时下载链接,避免公开访问泄露数据。
2.2 Firestore:存储元数据索引
创建pdf_records集合,每个文档对应一个PDF,字段设计如下:
file_path:Cloud Storage中的文件路径(如2020/administrative/123.pdf)year:文档所属年份(2020-2023)orgao:固定值Seção Judiciária do Sergipeedicao:固定值Administrativanames:数组类型,存储提取到的所有姓名case_numbers:数组类型,存储提取到的所有案件编号
通过Firestore的数组包含查询,就能快速定位匹配姓名或案件编号的PDF,无需逐个读取PDF内容。
3. 用Cloud Functions实现后端逻辑
3.1 批量上传函数(Python)
将下载并提取元数据的PDF上传到Cloud Storage,同时将元数据写入Firestore:
import firebase_admin from firebase_admin import credentials, storage, firestore # 初始化Firebase(需提前下载服务账号密钥) cred = credentials.Certificate("service-account-key.json") firebase_admin.initialize_app(cred, { "storageBucket": "your-bucket-name.appspot.com" }) db = firestore.client() bucket = storage.bucket() def upload_pdf_to_firebase(pdf_local_path, metadata): # 上传PDF到Cloud Storage file_name = pdf_local_path.split("/")[-1] blob_path = f"{metadata['year']}/administrative/{file_name}" blob = bucket.blob(blob_path) blob.upload_from_filename(pdf_local_path) blob.content_type = "application/pdf" blob.patch() # 写入元数据到Firestore doc_ref = db.collection("pdf_records").document() doc_ref.set({ "file_path": blob_path, "year": metadata["year"], "orgao": "Seção Judiciária do Sergipe", "edicao": "Administrativa", "names": metadata["names"], "case_numbers": metadata["case_numbers"] })
3.2 筛选与生成下载链接的函数(Node.js)
写一个Callable Function,接收前端的筛选参数,查询Firestore并生成临时下载链接:
const functions = require("firebase-functions"); const admin = require("firebase-admin"); admin.initializeApp(); exports.getFilteredPdfs = functions.https.onCall(async (data, context) => { // 可选:验证用户身份(根据作业需求决定是否开启) if (!context.auth) { throw new functions.https.HttpsError("unauthenticated", "需验证身份"); } const { name, caseNumber } = data; let query = admin.firestore().collection("pdf_records"); // 按姓名筛选 if (name) { query = query.where("names", "array-contains", name.trim()); } // 按案件编号筛选 if (caseNumber) { query = query.where("case_numbers", "array-contains", caseNumber.trim()); } const snapshot = await query.get(); const results = []; for (const doc of snapshot.docs) { const fileRef = admin.storage().bucket().file(doc.data().file_path); // 生成1小时有效的临时下载链接 const [url] = await fileRef.getSignedUrl({ action: "read", expires: Date.now() + 3600 * 1000 }); results.push({ id: doc.id, year: doc.data().year, downloadUrl: url, names: doc.data().names, caseNumbers: doc.data().case_numbers }); } return results; });
4. Flutter前端集成
4.1 调用Cloud Functions获取筛选结果
用cloud_functions插件调用后端筛选接口:
import 'package:cloud_functions/cloud_functions.dart'; Future<List<dynamic>> fetchFilteredPdfs(String? name, String? caseNumber) async { final HttpsCallable callable = FirebaseFunctions.instance.httpsCallable('getFilteredPdfs'); final results = await callable.call({ 'name': name, 'caseNumber': caseNumber, }); return results.data; }
4.2 实现PDF下载
用flutter_downloader插件实现下载功能:
import 'package:flutter_downloader/flutter_downloader.dart'; import 'package:path_provider/path_provider.dart'; Future<void> downloadPdf(String url, String fileName) async { final directory = await getExternalStorageDirectory(); await FlutterDownloader.enqueue( url: url, savedDir: directory!.path, fileName: fileName, showNotification: true, openFileFromNotification: true, ); }
关键注意事项
- 如果PDF是扫描件(图片格式),需安装Tesseract引擎并搭配
pytesseract做OCR文本提取。 - 批量处理PDF时,建议分批次异步执行,避免单次任务超时。
- Firestore的数组包含查询是精确匹配,提取姓名时需统一格式(如全大写/小写、去除多余空格)。
- 调整Cloud Functions的内存与超时时间,确保处理大量查询时不会中断。
内容的提问来源于stack exchange,提问作者João Bosco
相关产品推荐
相关产品推荐

