Celery生成PDF存Redis:如何创建带文件名的可序列化文件对象?
嘿,这个场景我太熟悉了——用Redis临时存短生命周期的PDF完全合理,没必要额外搭对象存储。这里有几个靠谱的方案,按实用程度给你列出来:
方案1:存储包含文件名和二进制内容的序列化字典
这是最直接的思路:把文件名和PDF二进制数据打包成一个字典,序列化后存入Redis。推荐用Python原生的pickle(对复杂对象支持好)或者更轻量的msgpack(跨语言友好)。
Celery任务端代码示例
import pickle from celery import Celery from redis import Redis from io import BytesIO # 初始化Celery和Redis客户端 app = Celery('pdf_tasks', broker='redis://localhost:6379/0') redis_client = Redis(host='localhost', port=6379, db=0) @app.task def generate_pdf(): # 生成PDF到BytesIO(这里替换成你的实际生成逻辑) pdf_buffer = BytesIO() # 示例:假设用ReportLab生成PDF # from reportlab.pdfgen import canvas # c = canvas.Canvas(pdf_buffer) # c.drawString(100, 750, "Hello World") # c.save() pdf_filename = "invoice_20240520_1234.pdf" pdf_data = pdf_buffer.getvalue() # 打包成包含文件名和内容的字典 pdf_payload = { "filename": pdf_filename, "content": pdf_data } # 序列化后存入Redis,设置5分钟过期时间 redis_key = f"pdf:{generate_pdf.request.id}" redis_client.setex(redis_key, 300, pickle.dumps(pdf_payload)) return redis_key
API端获取代码示例
import pickle from redis import Redis from flask import Flask, Response, request app = Flask(__name__) redis_client = Redis(host='localhost', port=6379, db=0) @app.route('/get-pdf') def get_pdf(): redis_key = request.args.get('key') if not redis_key: return "Missing key parameter", 400 payload_data = redis_client.get(redis_key) if not payload_data: return "PDF not found or expired", 404 # 反序列化获取文件名和内容 pdf_payload = pickle.loads(payload_data) filename = pdf_payload["filename"] content = pdf_payload["content"] # 返回带正确文件名的响应 return Response( content, mimetype='application/pdf', headers={ 'Content-Disposition': f'attachment; filename="{filename}"' } )
注意:如果担心
pickle的安全风险(比如Redis中存在恶意序列化数据),可以用msgpack替代——先安装msgpack-python,然后把pickle.dumps()换成msgpack.packb(),pickle.loads()换成msgpack.unpackb()即可。
方案2:用Redis Hash结构分别存储元数据和内容
如果不想序列化整个对象,可以用Redis的Hash类型,把文件名和PDF内容存在同一个Hash的不同字段里,这样单独查询元数据或内容都很灵活。
Celery任务端代码示例
@app.task def generate_pdf(): # 生成PDF逻辑同上... pdf_filename = "invoice_20240520_1234.pdf" pdf_data = pdf_buffer.getvalue() redis_key = f"pdf:{generate_pdf.request.id}" # 存入Hash的两个字段:filename和content redis_client.hset(redis_key, mapping={ "filename": pdf_filename, "content": pdf_data }) # 设置5分钟过期 redis_client.expire(redis_key, 300) return redis_key
API端获取代码示例
@app.route('/get-pdf') def get_pdf(): redis_key = request.args.get('key') if not redis_key: return "Missing key parameter", 400 # 获取Hash中的所有字段 hash_data = redis_client.hgetall(redis_key) if not hash_data: return "PDF not found or expired", 404 # 注意Redis返回的是字节串,需要解码文件名 filename = hash_data[b"filename"].decode('utf-8') content = hash_data[b"content"] # 返回响应 return Response( content, mimetype='application/pdf', headers={ 'Content-Disposition': f'attachment; filename="{filename}"' } )
方案3:把文件名编码进Redis Key(不推荐,仅作备选)
如果不想存额外元数据,可以把文件名编码进Redis的Key里,比如pdf:{task_id}:invoice_20240520_1234.pdf,然后只存PDF二进制内容。但这个方案有明显缺点:文件名中的特殊字符需要转义,后续修改文件名也很麻烦,只适合极简场景。
总结推荐
如果需要附带更多元数据(比如生成时间、文件类型),选方案1;如果需要灵活查询单个字段,选方案2。两者都能完美解决你关联文件名的需求,而且完全复用现有Redis部署,不需要额外搭建对象存储服务。
内容的提问来源于stack exchange,提问作者Stuart Buckingham
相关产品推荐
相关产品推荐

