{"record":{"id":"0fc15dc5751c24d4","repo":"ZhuLinsen/daily_stock_analysis","slug":"unsupported-type","errorCode":"unsupported_type","errorMessage":"不支持的类型: {content_type}。允许: {ALLOWED_MIME_STR}","messagePattern":"不支持的类型: (.+?)。允许: (.+?)","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"api/v1/endpoints/stocks.py","lineNumber":149,"sourceCode":")\ndef extract_from_image(\n    file: Optional[UploadFile] = File(None, description=\"图片文件（表单字段名 file）\"),\n    include_raw: bool = Query(False, description=\"是否在结果中包含原始 LLM 响应\"),\n) -> ExtractFromImageResponse:\n    \"\"\"\n    从上传的图片中提取股票代码（使用 Vision LLM）。\n\n    表单字段请使用 file 上传图片。优先级：Gemini / Anthropic / OpenAI（首个可用）。\n    \"\"\"\n    if not file or not file.filename:\n        raise HTTPException(\n            status_code=400,\n            detail={\"error\": \"bad_request\", \"message\": \"未提供文件，请使用表单字段 file 上传图片\"},\n        )\n\n    content_type = (file.content_type or \"\").split(\";\")[0].strip().lower()\n    if content_type not in ALLOWED_MIME:\n        raise HTTPException(\n            status_code=400,\n            detail={\n                \"error\": \"unsupported_type\",\n                \"message\": f\"不支持的类型: {content_type}。允许: {ALLOWED_MIME_STR}\",\n            },\n        )\n\n    try:\n        # 先读取限定大小，再检查是否还有剩余（语义清晰：超出则拒绝）\n        data = file.file.read(MAX_SIZE_BYTES)\n        if file.file.read(1):\n            raise HTTPException(\n                status_code=400,\n                detail={\n                    \"error\": \"file_too_large\",\n                    \"message\": f\"图片超过 {MAX_SIZE_BYTES // (1024 * 1024)}MB 限制\",\n                },\n            )","sourceCodeStart":131,"sourceCodeEnd":167,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/api/v1/endpoints/stocks.py#L131-L167","documentation":"Raised by /extract-from-image when the upload's Content-Type (after stripping parameters like ;charset=utf-8 and lowercasing) is not in ALLOWED_MIME, the set imported from src/services/image_stock_extractor.py (JPEG, PNG, WebP, GIF per the route description). HTTP 400 with error code unsupported_type.","triggerScenarios":"Uploading a BMP, TIFF, HEIC, SVG, or AVIF file; uploading a file renamed to .png whose real Content-Type is application/octet-stream; a client that defaults to text/plain when it cannot sniff the type.","commonSituations":"iPhone screenshots in HEIC not converted by the browser; files dragged from design tools as SVG; servers/clients that send generic application/octet-stream; double-extension files like photo.png.exe misdetected.","solutions":["Convert the image to JPEG/PNG/WebP/GIF before upload (e.g. canvas.toBlob('image/png') in JS, or convert HEIC on the client).","Ensure the client sets the correct Content-Type per part; when the browser cannot detect it, explicitly supply type in new File([blob], 'x.png', {type: 'image/png'}).","Check the echoed value in the error message ('不支持的类型: ...') to see what type actually arrived."],"exampleFix":"# before\ncurl -F 'file=@photo.heic' .../extract-from-image  # unsupported_type\n\n# after\n# convert first: heif-convert photo.heic photo.jpg\ncurl -F 'file=@photo.jpg' .../extract-from-image","handlingStrategy":"validation","validationCode":"const ALLOWED = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'];\nconst type = (file.type || '').split(';')[0].trim().toLowerCase();\nif (!ALLOWED.includes(type)) {\n  showError(`不支持的类型: ${type || '未知'}，请使用 JPEG/PNG/WebP/GIF`);\n  return;\n}","typeGuard":"function isAllowedImageType(file: File): boolean {\n  const t = (file.type || '').split(';')[0].trim().toLowerCase();\n  return ['image/jpeg', 'image/png', 'image/webp', 'image/gif'].includes(t);\n}","tryCatchPattern":null,"preventionTips":["Convert HEIC/BMP/TIFF to PNG or JPEG client-side before upload.","Set explicit type when constructing File/Blob the browser cannot sniff.","Surface the allowed list in the file-picker accept attribute: accept='image/jpeg,image/png,image/webp,image/gif'."],"tags":["upload","mime-type","image","http-400"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}