Flask向Next.js(App Router)实时推进度:解决实例进度隔离问题
问题:PDF生成进度条实例隔离失败
初始代码与背景
Python后端代码(/api/generate/all)
@app.route("/api/generate/all", methods=["POST"]) async def render_code_all(): data = request.get_json() doc = SimpleDocTemplate(pdf_filename, pagesize=letter, rightMargin=0.6*inch, leftMargin=0.6*inch, topMargin=0.3*inch, bottomMargin=0.3*inch) elements = [] for practical in practicals: heading_ = Paragraph(f"Experiment - {num}", pdf_styles["Title"]) elements.append(heading_) processed += 1 progress = round((processed/total)*100, 2) print(f"Generated {num} of {total} practicals.") print(f"Progress: {progress}%") doc.title = f"{subject} | Semester {semester}" doc.build(elements) r2_client.upload_file(os.path.join(os.getcwd(), pdf_filename), pdf_filename, namespace_id) pdf_link = f"https://files.praxout.co/{pdf_filename}" os.remove(os.path.join(os.getcwd(), pdf_filename)) return jsonify({"pdf_link": pdf_link})
前端表单组件
export default function CreateOutPutForm() { const [codePDFData, setCodePDFData] = useState(null); const [loading, setLoading] = useState(false); const [progress, setProgress] = useState(0); const { toast } = useToast(); const handleSubmit = async (values: typeof formSchema._type) => { try { setLoading(true); setProgress(0); const response = await fetch(`/api/practicals/generate`, { method: "POST", body: JSON.stringify({ ...values }), }).then((res) => res.json()); if (response.pdf_link) { setCodePDFData(response.pdf_link); toast({ title: "Practical Generated", description: "The practical has been generated successfully.", }); } else { console.error("Failed to generate output."); toast({ title: "Failed to Generate Practical", description: `An error occurred while generating the practical...`, }); } } }; return ( <div className="flex w-full flex-col items-center justify-center gap-8 pb-24 "> <AutoForm formSchema={formSchema} values={values} onParsedValuesChange={setValues} onSubmit={(values) => handleSubmit(values)} > <AutoFormSubmit disabled={loading} className="w-full"> Generate Practical </AutoFormSubmit> </AutoForm> {codePDFData && !loading && ( <PDFVIewer key={codePDFData} link={codePDFData} /> )} {loading && ( <div className="w-full"> <Progress value={progress} /> </div> )} </div> ); }
前端API路由(/api/practicals/generate)
import { NextRequest, NextResponse } from "next/server"; import { neon } from "@neondatabase/serverless"; import axios from "axios"; export async function POST(req: NextRequest, res: NextResponse) { const sql = neon(process.env.DATABASE_URL!); if (!req || !req.body) { return NextResponse.json({ error: "Request body required.", status: 400 }); } const { semester, subject, num, name, enrollment_no, _class, invert, type }: GeneratePracticalOutputRequestProps = await req.json(); console.log(type) if (type === "single") { const res = await sql`SELECT DISTINCT * FROM practicals WHERE semester = ${semester} AND subject = ${subject} AND num = ${num};`; const data = res[0]; const code_template = data.code_template; const code_theme = data.code_template_highlight; const output_template = data.output_template; const output_theme = data.output_highlight; const language = data.code_language; const response = await axios.post(`${BACKEND_URI}/api/generate`, { ...all_values }, { headers: { "Authorization": process.env.BACKEND_API_KEY!, }, }); return NextResponse.json({ pdf_link: response.data.pdf_link, status: 200, success: true }); } if (type === "all") { const res = await sql`SELECT * FROM practicals WHERE semester = ${semester} AND subject = ${subject} ORDER BY num ASC;`; const response = await axios.post(`${BACKEND_URI}/api/generate/all`, { ...all_values }, { headers: { "Authorization": process.env.BACKEND_API_KEY!, }, }); return NextResponse.json({ pdf_link: response.data.pdf_link, status: 200, success: true }); } }
已尝试的初始方案
- Flask SSE:无法在后端初始化,推测是事件循环已运行导致无法创建新任务。
- SocketIO:同样无法在后端初始化,
@socketio.on("connect")事件从未触发,推测也是事件循环问题。
进度条实现与新问题
已实现进度条功能,但所有PDF生成实例共用同一个进度值,实现方式如下:
后端进度推送代码
async def emit_progress_single(progress): socketio.emit("progress", {"progress": progress}) ... for practical in practicals: .... await emit_progress_single(progress)
前端监听代码
useEffect(() => { const BACKEND_URI = process.env.NODE_ENV === "production" ? process.env.BACKEND_API_URI! : "http://127.0.0.1:25565"; const socket = io(BACKEND_URI); socket.on("connect", () => { console.log("Connected to backend socketIO server."); const socketid = socket.id; if (socketid) { setSocketId(socketid); } }); socket.connect(); socket.on(`progress`, (data) => { setProgress(data.progress); }); }, []);
尝试的隔离改进方案(无效)
前端修改代码
const [progress, setProgress] = useState(0); const [socketId, setSocketId] = useState<string>(""); useEffect(() => { const BACKEND_URI = process.env.NODE_ENV === "production" ? process.env.BACKEND_API_URI! : "http://127.0.0.1:25565"; const socket = io(BACKEND_URI); socket.on("connect", () => { console.log("Connected to backend socketIO server."); const socketid = socket.id; if (socketid) { setSocketId(socketid); } }); socket.connect(); socket.on(`progress/${socketId}`, (data) => { setProgress(data.progress); }); }, [socketId]);
后端修改代码
async def emit_progress(progress, session_id): socketio.emit(f"progress/{session_id}", {"progress": progress}, namespace=f"/progress/{session_id}", to=session_id) ... for practical in practicals: ... await emit_progress(progress, socket_id)
上述改进方案完全无法接收进度更新/事件,需要解决进度条实例隔离的问题。
内容的提问来源于stack exchange,提问作者Harjot
相关产品推荐
相关产品推荐

