- 商品详情页核心卖点:编辑态原是纯静态文本、且 save 不提交 selling_points, 既改不了也存不了;改为可改/删/回车增,save 带 selling_points 一起 PATCH。 - 去掉详情页 name/cat/target/卖点 的「设计稿假数据」fallback —— 商品没卖点 时不再凭空显示护肤品卖点,空值显示「未填写」。 - 出图成功后 submitAndPollAsset 不再整页全量 loadData(十几个 setState 触发 全页大重渲染),只刷当前项目详情 + 轻量刷余额。 - TOS 上传对象加 Cache-Control: immutable 长效强缓存,重渲染不再重拉图。 - products 序列化器内嵌商品图/封面 preview_url(并修回错位的 read_only_fields)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
63 lines
2.3 KiB
Python
63 lines
2.3 KiB
Python
from dataclasses import dataclass
|
|
from typing import BinaryIO
|
|
from urllib.parse import quote, urlparse
|
|
|
|
import boto3
|
|
from botocore.config import Config
|
|
from django.conf import settings
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class StoredObject:
|
|
bucket: str
|
|
object_key: str
|
|
content_type: str
|
|
size_bytes: int
|
|
|
|
|
|
class TosStorage:
|
|
def __init__(self) -> None:
|
|
tos = settings.TOS
|
|
self.bucket = tos["bucket"]
|
|
self.client = boto3.client(
|
|
"s3",
|
|
endpoint_url=tos["endpoint"],
|
|
aws_access_key_id=tos["access_key_id"],
|
|
aws_secret_access_key=tos["secret_access_key"],
|
|
region_name="cn-shanghai",
|
|
config=Config(s3={"addressing_style": "virtual"}),
|
|
)
|
|
|
|
def upload_fileobj(self, *, fileobj: BinaryIO, object_key: str, content_type: str) -> StoredObject:
|
|
fileobj.seek(0, 2)
|
|
size = fileobj.tell()
|
|
fileobj.seek(0)
|
|
self.client.upload_fileobj(
|
|
fileobj,
|
|
self.bucket,
|
|
object_key,
|
|
# object_key 按 asset id 唯一、内容不可变 → 长效强缓存 + immutable:
|
|
# 浏览器/CDN 永不重拉,前端重渲染(背景图重新赋值)也命中缓存,不再"每次生成全页图闪一下重载"。
|
|
ExtraArgs={"ContentType": content_type, "CacheControl": "public, max-age=31536000, immutable"},
|
|
)
|
|
return StoredObject(
|
|
bucket=self.bucket,
|
|
object_key=object_key,
|
|
content_type=content_type,
|
|
size_bytes=size,
|
|
)
|
|
|
|
def presigned_get_url(self, *, object_key: str, expires_in: int = 3600) -> str:
|
|
return self.client.generate_presigned_url(
|
|
"get_object",
|
|
Params={"Bucket": self.bucket, "Key": object_key},
|
|
ExpiresIn=expires_in,
|
|
)
|
|
|
|
def public_url(self, *, object_key: str, bucket: str | None = None) -> str:
|
|
"""桶公读时的虚拟主机式直链:https://{bucket}.{host}/{key}。
|
|
相比预签名 URL:不签名(省 boto3 客户端/签名开销)、且 URL 稳定可被浏览器/CDN 缓存
|
|
(签名链每次都变、1h 过期,反而打不到缓存)。仅用于公开可读的预览图。"""
|
|
host = urlparse(settings.TOS["endpoint"]).netloc
|
|
return f"https://{bucket or self.bucket}.{host}/{quote(object_key, safe='/')}"
|