From 67a469f380eb9af8a9fc3b6d6d4bb70197dcd857 Mon Sep 17 00:00:00 2001 From: Kevin Date: Tue, 10 Mar 2026 16:02:12 +0800 Subject: [PATCH] feat(api): add tencent cos storage for memoir images Made-with: Cursor --- api/requirements.txt | 3 +++ api/services/memoir_images/storage.py | 30 ++++++++++++++++++++++++++ api/tests/test_memoir_image_storage.py | 30 ++++++++++++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 api/services/memoir_images/storage.py create mode 100644 api/tests/test_memoir_image_storage.py diff --git a/api/requirements.txt b/api/requirements.txt index 205e521..bda6e32 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -47,6 +47,9 @@ tencentcloud-sdk-python>=3.0.1000 openai +# Tencent COS for memoir image storage +cos-python-sdk-v5>=1.9.30 + # Payment - WeChat Pay & Alipay wechatpayv3>=0.3.0 python-alipay-sdk>=3.0.0 \ No newline at end of file diff --git a/api/services/memoir_images/storage.py b/api/services/memoir_images/storage.py new file mode 100644 index 0000000..ea41b8e --- /dev/null +++ b/api/services/memoir_images/storage.py @@ -0,0 +1,30 @@ +import os + +from qcloud_cos import CosConfig, CosS3Client + + +class TencentCosStorageService: + def __init__(self, secret_id: str, secret_key: str, region: str, bucket: str, base_url: str): + self.bucket = bucket + self.base_url = base_url.rstrip("/") + config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key) + self.client = CosS3Client(config) + + def upload_bytes(self, image_bytes: bytes, key: str, content_type: str) -> str: + self.client.put_object( + Bucket=self.bucket, + Body=image_bytes, + Key=key, + ContentType=content_type, + ) + return f"{self.base_url}/{key}" + + @classmethod + def from_env(cls) -> "TencentCosStorageService": + return cls( + secret_id=os.getenv("TENCENT_COS_SECRET_ID", ""), + secret_key=os.getenv("TENCENT_COS_SECRET_KEY", ""), + region=os.getenv("TENCENT_COS_REGION", ""), + bucket=os.getenv("TENCENT_COS_BUCKET", ""), + base_url=os.getenv("TENCENT_COS_BASE_URL", ""), + ) diff --git a/api/tests/test_memoir_image_storage.py b/api/tests/test_memoir_image_storage.py new file mode 100644 index 0000000..9e771c1 --- /dev/null +++ b/api/tests/test_memoir_image_storage.py @@ -0,0 +1,30 @@ +import unittest +from unittest.mock import Mock, patch + +from api.services.memoir_images.storage import TencentCosStorageService + + +class MemoirImageStorageTest(unittest.TestCase): + @patch("api.services.memoir_images.storage.CosS3Client") + def test_upload_bytes_returns_persistent_cos_url(self, client_cls): + client = Mock() + client_cls.return_value = client + storage = TencentCosStorageService( + secret_id="id", + secret_key="key", + region="ap-shanghai", + bucket="memoir-1250000000", + base_url="https://memoir-1250000000.cos.ap-shanghai.myqcloud.com", + ) + + url = storage.upload_bytes( + image_bytes=b"png-bytes", + key="memoirs/u1/c1/0-demo.png", + content_type="image/png", + ) + + self.assertEqual( + url, + "https://memoir-1250000000.cos.ap-shanghai.myqcloud.com/memoirs/u1/c1/0-demo.png", + ) + client.put_object.assert_called_once()