104 lines
4.3 KiB
Python
104 lines
4.3 KiB
Python
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()
|
||
|
||
|