from fastapi import FastAPI, File, UploadFile, HTTPException from fastapi.responses import JSONResponse import uvicorn import io import time import logging import numpy as np import cv2 from ..config_loader import load_config from ..pipeline import EAN13Recognizer from ..logging_utils import setup_logging config = load_config() setup_logging(config) logger = logging.getLogger(__name__) app = FastAPI(title="条形码识别端口 API", version="1.0.0") recognizer = EAN13Recognizer() @app.post("/recognize/ean13") async def recognize_ean13(file: UploadFile = File(...)): # 上传大小简易校验 t0 = time.time() content = await file.read() max_bytes = int(config["app"]["server"]["max_upload_mb"]) * 1024 * 1024 if len(content) > max_bytes: logger.warning("/recognize/ean13 上传超限: size=%d, limit=%d", len(content), max_bytes) raise HTTPException(status_code=413, detail="文件过大") # 读取为图像 data = np.frombuffer(content, dtype=np.uint8) img = cv2.imdecode(data, cv2.IMREAD_COLOR) if img is None: logger.error("/recognize/ean13 解码为图像失败, filename=%s, size=%d", getattr(file, 'filename', ''), len(content)) raise HTTPException(status_code=400, detail="无法解析为图像") logger.debug("/recognize/ean13 收到图像: shape=%s, dtype=%s", getattr(img, 'shape', None), getattr(img, 'dtype', None)) merged = recognizer.recognize_any_from_image(img) code = merged.get("ean13") or "" resp = { "code": code, "type": "EAN13" if code else "", "others": merged.get("others", []), "message": "ok" if code or merged.get("others") else "未识别", } logger.info("/recognize/ean13 识别完成: code=%s, others=%d, cost_ms=%.1f", code, len(merged.get("others", []) or []), (time.time()-t0)*1000) return JSONResponse(resp) @app.post("/api/barcode/scan") async def api_barcode_scan(file: UploadFile = File(...)): t0 = time.time() content = await file.read() max_bytes = int(config["app"]["server"]["max_upload_mb"]) * 1024 * 1024 if len(content) > max_bytes: logger.warning("/api/barcode/scan 上传超限: size=%d, limit=%d", len(content), max_bytes) raise HTTPException(status_code=413, detail="文件过大") data = np.frombuffer(content, dtype=np.uint8) img = cv2.imdecode(data, cv2.IMREAD_COLOR) if img is None: logger.error("/api/barcode/scan 解码为图像失败, filename=%s, size=%d", getattr(file, 'filename', ''), len(content)) raise HTTPException(status_code=400, detail="无法解析为图像") logger.debug("/api/barcode/scan 收到图像: shape=%s, dtype=%s", getattr(img, 'shape', None), getattr(img, 'dtype', None)) merged = recognizer.recognize_any_from_image(img) ean13 = merged.get("ean13") or "" others = merged.get("others", []) or [] # 优先返回 EAN-13;否则回退到任意码制的第一个结果 if ean13: resp = { "success": True, "barcodeType": "EAN13", "barcode": ean13, "others": others, } logger.info("/api/barcode/scan 命中 EAN13: code=%s, others=%d, cost_ms=%.1f", ean13, len(others), (time.time()-t0)*1000) return JSONResponse(resp) if isinstance(others, list) and others: first = others[0] if isinstance(others[0], dict) else None if first and first.get("code"): resp = { "success": True, "barcodeType": first.get("type", ""), "barcode": first.get("code", ""), "others": others, } logger.info("/api/barcode/scan 命中非 EAN: type=%s, code=%s, cost_ms=%.1f", first.get("type", ""), first.get("code", ""), (time.time()-t0)*1000) return JSONResponse(resp) logger.warning("/api/barcode/scan 未识别, others=%d, cost_ms=%.1f", len(others), (time.time()-t0)*1000) return JSONResponse({"success": False, "message": "无法识别,请重新上传"}, status_code=400) def main(): host = config["app"]["server"]["host"] port = int(config["app"]["server"]["port"]) logger.info("启动 FastAPI 服务器: %s:%d", host, port) uvicorn.run("app.server.main:app", host=host, port=port, reload=False) if __name__ == "__main__": main()