baodan/api/insurance/models/poster_record.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

85 lines
4.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""海报生成记录模型。"""
from sqlalchemy import Column, String, Text, BigInteger, Integer, TIMESTAMP, func
from insurance.db.compat import db
class PosterRecord(db.Model):
"""海报生成记录表。"""
__tablename__ = "poster_records"
id = Column(BigInteger, primary_key=True, autoincrement=True)
user_id = Column(String(50), nullable=False, comment="用户 ID")
product_id = Column(String(50), nullable=True, comment="产品 ID")
case_upload_id = Column(BigInteger, nullable=True, comment="关联计划书上传 ID")
template_id = Column(BigInteger, nullable=True, comment="海报模板 ID")
copy_mode = Column(String(20), nullable=True, comment="文案模式: template/ai")
copy_content = Column(Text, nullable=True, comment="最终文案 JSON")
ai_raw_content = Column(Text, nullable=True, comment="AI 原始文案 JSON合规留痕")
export_url = Column(String(500), nullable=True, comment="导出文件地址")
export_format = Column(String(10), nullable=True, comment="导出格式: png/jpg")
export_size = Column(String(20), nullable=True, comment="导出尺寸")
reference_image_used = Column(String(500), nullable=True, comment="使用的参考图(合规留痕)")
prompt_used = Column(Text, nullable=True, comment="完整 prompt合规留痕")
generation_mode = Column(String(20), nullable=True, comment="生成方式: ai/fallback")
image_provider = Column(String(50), nullable=True, comment="图片供应商")
image_model = Column(String(100), nullable=True, comment="图片模型名称")
task_status = Column(String(20), default="pending", comment="任务状态: pending/queued/generating/done/failed")
task_progress = Column(db.Integer, default=0, comment="任务进度 0-100")
task_error = Column(Text, nullable=True, comment="任务错误信息")
extra_data = Column(Text, nullable=True, comment="扩展数据 JSON如 useMaskedData")
# 工作区字段migrate_022
title = Column(String(200), default="", comment="工作区名称")
workflow_step = Column(String(20), default="product", comment="当前业务步骤")
draft_status = Column(String(20), default="active", comment="active/archived")
draft_revision = Column(Integer, default=1, comment="当前草稿版本")
generated_revision = Column(Integer, default=0, comment="最新成品版本")
latest_task_id = Column(String(36), nullable=True, comment="最近任务 ID")
archived_at = Column(TIMESTAMP, nullable=True, comment="归档时间")
started_at = Column(TIMESTAMP, nullable=True, comment="任务开始时间")
finished_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,
"caseUploadId": self.case_upload_id,
"templateId": self.template_id,
"copyMode": self.copy_mode,
"copyContent": _safe_json(self.copy_content),
"aiRawContent": _safe_json(self.ai_raw_content),
"exportUrl": self.export_url,
"exportFormat": self.export_format,
"exportSize": self.export_size,
"referenceImageUsed": self.reference_image_used,
"promptUsed": self.prompt_used,
"generationMode": self.generation_mode,
"imageProvider": self.image_provider,
"imageModel": self.image_model,
"taskStatus": self.task_status,
"taskProgress": self.task_progress,
"taskError": self.task_error,
"extraData": _safe_json(self.extra_data),
"title": self.title or "",
"workflowStep": self.workflow_step or "product",
"draftStatus": self.draft_status or "active",
"draftRevision": self.draft_revision or 1,
"generatedRevision": self.generated_revision or 0,
"latestTaskId": self.latest_task_id,
"archivedAt": self.archived_at.isoformat() if self.archived_at else None,
"startedAt": self.started_at.isoformat() if self.started_at else None,
"finishedAt": self.finished_at.isoformat() if self.finished_at else None,
"createdAt": self.created_at.isoformat() if self.created_at else None,
}