前一回探討了限制的方式,也驗證了可行性。 這邊接著修改原始碼。
list-videos
的 API 中,其實已經有爬這個使用者的所有影片,也有個別計算容量。 所以應該在使用者針對檔案進行異動時,比方說 convert 或是 upload。import os, json, boto3, jwt, urllib.parse, time
from decimal import Decimal # ✅ 新增這行
s3 = boto3.client("s3")
dynamodb = boto3.resource("dynamodb")
BUCKET_NAME = os.environ.get("BUCKET_NAME", "exsky-backup-media")
SECRET = os.environ.get("JWT_SECRET", "mysecret")
USAGE_TABLE = os.environ.get("USAGE_TABLE", "vlog-usage")
usage_table = dynamodb.Table(USAGE_TABLE)
def lambda_handler(event, context):
method = event.get("requestContext", {}).get("http", {}).get("method")
# --- CORS ---
if method == "OPTIONS":
return {
"statusCode": 200,
"headers": {
"Access-Control-Allow-Origin": "https://vlog.nipapa.tw",
"Access-Control-Allow-Methods": "GET,OPTIONS",
"Access-Control-Allow-Headers": "Authorization,Content-Type",
},
"body": ""
}
# --- JWT 驗證 ---
headers = event.get("headers", {}) or {}
auth = headers.get("authorization") or headers.get("Authorization") or ""
if not auth.startswith("Bearer "):
return _unauthorized("Missing token")
token = auth.split(" ")[1]
try:
decoded = jwt.decode(token, SECRET, algorithms=["HS256"])
username = decoded.get("username", "unknown")
except Exception:
return _unauthorized("Invalid token")
try:
# --- 列出影片 ---
resp = s3.list_objects_v2(Bucket=BUCKET_NAME, Prefix=f"{username}/videos/")
resp_converted = s3.list_objects_v2(Bucket=BUCKET_NAME, Prefix=f"{username}/converted/")
items = []
total_size_bytes = 0
for obj in resp.get("Contents", []):
key = obj["Key"]
if key.endswith("/") or key.startswith(f"{username}/covers/"):
continue
total_size_bytes += obj["Size"]
converted_key = f"{username}/converted/{os.path.splitext(os.path.basename(key))[0]}.mp4"
if any([x["Key"].endswith(os.path.splitext(os.path.basename(key))[0]+".mp4") for x in resp_converted.get("Contents", [])]):
key = converted_key
video_url = s3.generate_presigned_url(
"get_object",
Params={"Bucket": BUCKET_NAME, "Key": converted_key},
ExpiresIn=3600
)
else:
video_url = s3.generate_presigned_url(
"get_object",
Params={"Bucket": BUCKET_NAME, "Key": key},
ExpiresIn=3600
)
basename = os.path.basename(key)
cover_key = f"{username}/covers/{os.path.splitext(basename)[0]}.jpg"
subtitle_key = f"{username}/subtitles/{os.path.splitext(basename)[0]}.vtt.vtt"
cover_url = _try_presigned(cover_key)
subtitle_url = _try_presigned(subtitle_key, rewrite=True)
decoded_name = urllib.parse.unquote(basename)
items.append({
"key": key,
"video_url": video_url,
"cover_url": cover_url,
"subtitle_url": subtitle_url,
"size": obj["Size"],
"decodedName": decoded_name
})
# ✅ 加上 converted 檔案的容量
for obj in resp_converted.get("Contents", []):
if not obj["Key"].endswith("/"):
total_size_bytes += obj["Size"]
# ✅ 用 Decimal 表示
total_size_mb = Decimal(str(round(total_size_bytes / (1024 * 1024), 2)))
# ✅ 更新 DynamoDB 使用量紀錄(全部 Decimal)
usage_table.update_item(
Key={"username": username},
UpdateExpression="SET total_storage_mb = :s, item_count = :c, last_update = :t",
ExpressionAttributeValues={
":s": total_size_mb,
":c": Decimal(len(items)),
":t": Decimal(int(time.time()))
}
)
except Exception as e:
return _server_error(str(e))
return {
"statusCode": 200,
"headers": {"Access-Control-Allow-Origin": "https://vlog.nipapa.tw"},
"body": json.dumps({
"videos": items,
"total_storage_mb": float(total_size_mb),
"item_count": len(items)
}, ensure_ascii=False)
}
# ---- helpers ----
def _try_presigned(key, rewrite=False):
try:
s3.head_object(Bucket=BUCKET_NAME, Key=key)
url = s3.generate_presigned_url(
"get_object",
Params={"Bucket": BUCKET_NAME, "Key": key},
ExpiresIn=3600
)
if rewrite:
url = url.replace("exsky-backup-media.s3.amazonaws.com", "vlog.nipapa.tw")
return url
except Exception:
return None
def _unauthorized(msg):
return {
"statusCode": 401,
"headers": {"Access-Control-Allow-Origin": "https://vlog.nipapa.tw"},
"body": json.dumps({"error": msg})
}
def _server_error(msg):
return {
"statusCode": 500,
"headers": {"Access-Control-Allow-Origin": "https://vlog.nipapa.tw"},
"body": json.dumps({"error": msg})
}