baodan/api/insurance/models/poster_case_upload.py
wsb1224 751302205a 文件 问题 严重度
1	poster/routes.py:156	下载路径硬编码,与实际存储路径不一致 → 下载 404	P0
2	poster/service.py:41	get_reviewed_products() N+1 查询	P1
3	poster/service.py:169,226	json.loads 无异常处理 → 数据损坏时崩溃	P1
4	3 个 model 文件	to_dict() 中 json.loads 无防御 → 序列化崩溃	P1
5	poster/tasks.py	~120 行死代码(线程版海报生成)	P2
6	generation/celery_tasks.py	parse_poster_task + _execute_poster_parse 死代码(~70 行)	P2
7	generation/task_service.py	对应移除 ("poster", "parse") 映射	P2
8	utils/security.py	SSRF TOCTOU:DNS 检查与请求之间的时间窗口可被 DNS rebinding 利用 → 新增 _SafeHTTPTransport 在连接时重新验证 IP	P1 安全
9	poster/image_generator.py	anchor="mm" 在旧 Pillow 默认字体上崩溃;改用 hasattr 检测	P1
前端(3 项)
#	文件	问题	严重度
10	poster-api.ts:54	downloadPoster() 返回 AxiosResponse 而非 Blob → 海报永远无法下载	P0
11	PosterStepUpload.vue	解析轮询无超时 → 无限轮询	P1
12	PosterStepPreview.vue	生成轮询无超时 → 无限轮询	P1
修改的文件总计
后端 7 个:security.py, image_generator.py, service.py, routes.py, tasks.py, celery_tasks.py, task_service.py, poster_case_upload.py, poster_record.py, poster_template_model.py

前端 3 个:poster-api.ts, PosterStepUpload.vue, PosterStepPreview.vue

未修复(确认无需修复)
manual_parser.py — 之前误判为死代码,实际被 Celery 产品小册子解析任务使用,保留不动
llm_client.py 中的 httpx 调用 — URL 来自管理员配置的系统设置,不是用户输入,SSRF 风险极低;加检查反而会阻断合法的私网 LLM 端点
2026-07-29 22:41:27 +08:00

43 lines
1.8 KiB
Python

"""海报计划书上传模型。"""
from sqlalchemy import Column, String, Text, BigInteger, TIMESTAMP, func
from insurance.db.compat import db
class PosterCaseUpload(db.Model):
"""海报计划书上传记录表。"""
__tablename__ = "poster_case_uploads"
id = Column(BigInteger, primary_key=True, autoincrement=True)
user_id = Column(String(50), nullable=False, comment="用户 ID")
product_id = Column(String(50), nullable=False, comment="产品 ID")
source_file_url = Column(String(500), nullable=False, comment="源文件地址")
parse_status = Column(String(20), default="pending", comment="解析状态: pending/parsed/failed")
parsed_data = Column(Text, nullable=True, comment="系统解析结果 JSON")
confirmed_data = Column(Text, nullable=True, comment="人工核对后最终结果 JSON")
confirmed_by = Column(String(50), nullable=True, comment="核对人")
confirmed_at = Column(TIMESTAMP, nullable=True, comment="核对时间")
created_at = Column(TIMESTAMP, server_default=func.now())
def to_dict(self):
import json
def _safe_json(text):
if not text:
return None
try:
return json.loads(text)
except (json.JSONDecodeError, TypeError):
return None
return {
"id": self.id,
"userId": self.user_id,
"productId": self.product_id,
"sourceFileUrl": self.source_file_url,
"parseStatus": self.parse_status,
"parsedData": _safe_json(self.parsed_data),
"confirmedData": _safe_json(self.confirmed_data),
"confirmedBy": self.confirmed_by,
"confirmedAt": self.confirmed_at.isoformat() if self.confirmed_at else None,
"createdAt": self.created_at.isoformat() if self.created_at else None,
}