FastAPI在Swagger可用但React Native调用Heroku部署接口失败排查
问题修复方案
你遇到的两个报错对应明确的根因:
- 返回带
loc字段的422参数校验错误:React Native端异步时序错误,发请求时imagedata字段为空,FastAPI校验必填参数失败,未进入业务逻辑。 - 切换
multipart/form-data后始终进入异常分支:接口定义为接收JSON格式入参,未适配form-data格式,参数解析直接失败。
React Native端修复
原有代码选图后同时执行base64转码和接口请求,而FileReader转码是异步操作,onloadend回调触发时才会完成imgUrl赋值,不等转码完成就发请求,此时imgUrl还是初始空值。另外readAsDataURL返回的结果带data:image/xxx;base64,前缀,直接传给后端会导致base64解码失败。
修复后代码:
// 封装base64转码为Promise,便于同步等待结果 const getImg = async (imageUrl) => { const response = await fetch(imageUrl); const imageBlob = await response.blob(); return new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsDataURL(imageBlob); reader.onloadend = () => { // 去除Data URL前缀,仅保留纯base64字符串 const base64data = reader.result.split(',')[1]; resolve(base64data); }; reader.onerror = reject; }); }; const chooseFromLibrary = async () => { try { const image = await ImagePicker.openPicker({ width: 3000, height: 4000, cropping: false, }); // 等待base64转码完成后再执行后续逻辑 const base64Str = await getImg(image.path); setImgUrl(base64Str); // 直接传入转码结果发请求,不依赖state异步更新 const result = await sendOcrRequest(base64Str); console.log('OCR返回结果:', result); } catch (err) { console.error('流程执行失败:', err); } }; const sendOcrRequest = async (base64Img) => { const response = await fetch( 'https://receipt-ocr-heroku.herokuapp.com/receiptOcr', { method: 'POST', headers: { Accept: 'application/json', 'Content-Type': 'application/json', }, body: JSON.stringify({ imagedata: base64Img, }), }, ); return response.json(); };
FastAPI端修复
原有代码使用裸except直接吞掉所有异常,无法定位具体错误;同时使用了已被NumPy弃用的np.fromstring方法;未对带前缀的base64字符串做兼容处理。
修复后核心代码:
import cv2 import base64 import numpy as np from processModule import * from fastapi import FastAPI, Body, HTTPException from fastapi.middleware.cors import CORSMiddleware app = FastAPI() # CORS配置保持全放行即可 app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"] ) @app.post("/receiptOcr") async def main(imagedata: str = Body(...)): try: # 兼容处理带Data URL前缀的base64入参 if ',' in imagedata: imagedata = imagedata.split(',')[1] img_bytes = base64.b64decode(imagedata) # 替换已弃用的fromstring方法 nparr = np.frombuffer(img_bytes, np.uint8) img = cv2.imdecode(nparr, cv2.IMREAD_COLOR) if img is None: raise ValueError("base64解析后不是合法图片") resize_ratio = 500 / img.shape[0] original = img.copy() resizeImg = opencv_resize(img, resize_ratio) grayImg = cv2.cvtColor(resizeImg, cv2.COLOR_BGR2GRAY) ret, binImg = cv2.threshold( grayImg, 150, 255, cv2.THRESH_BINARY, cv2.THRESH_OTSU) contours, hierarchy = cv2.findContours( binImg, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) largest_contours = sorted( contours, key=cv2.contourArea, reverse=True)[:10] receipt_contour = get_receipt_contour(largest_contours) scanned = wrap_perspective( original.copy(), contour_to_rect(receipt_contour, resize_ratio)) result = bw_scanner(scanned) ret, img = cv2.threshold(result, 120, 255, cv2.THRESH_BINARY) receiptResize = opencv_resize(img, 0.5) wordArr = wordDetectionAndExtraction(receiptResize) totalPrice = index_containing_substring( wordArr, 'total', 'sub') return {"totalPrice": totalPrice} except Exception as e: # 打印具体错误信息便于排查,不要无差别吞错 print(f"接口处理异常: {str(e)}") return {"totalPrice": '0', "error_msg": str(e)}
其他注意事项
- 无需修改Content-Type为
multipart/form-data,保持application/json即可匹配接口定义。 - Heroku默认请求体大小上限为30MB,若原图分辨率过高导致base64字符串超长,会请求失败,可适当压缩图片尺寸后再上传。
- React Native的state更新是异步操作,不要在
setState后立刻调用依赖该state值的逻辑,优先通过参数传递即时值,避免时序问题。
内容的提问来源于stack exchange,提问作者random
相关产品推荐
相关产品推荐

