164 lines
6.0 KiB
Python
164 lines
6.0 KiB
Python
"""
|
||
S3画像アップロードユーティリティ
|
||
チェックイン時の画像をS3にアップロードし、URLを生成します
|
||
"""
|
||
|
||
import boto3
|
||
import logging
|
||
import uuid
|
||
from datetime import datetime
|
||
from django.conf import settings
|
||
from botocore.exceptions import ClientError, NoCredentialsError
|
||
import base64
|
||
import io
|
||
import requests
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
class S3ImageUploader:
|
||
def __init__(self):
|
||
"""S3クライアントを初期化"""
|
||
try:
|
||
# AWS認証情報の確認
|
||
aws_access_key = getattr(settings, 'AWS_ACCESS_KEY_ID', None)
|
||
aws_secret_key = getattr(settings, 'AWS_SECRET_ACCESS_KEY', None)
|
||
|
||
if not aws_access_key or not aws_secret_key:
|
||
logger.warning("AWS credentials not configured, S3 upload will be disabled")
|
||
self.s3_client = None
|
||
self.bucket_name = None
|
||
return
|
||
|
||
self.s3_client = boto3.client(
|
||
's3',
|
||
aws_access_key_id=aws_access_key,
|
||
aws_secret_access_key=aws_secret_key,
|
||
region_name=getattr(settings, 'AWS_S3_REGION_NAME', 'us-west-2')
|
||
)
|
||
self.bucket_name = getattr(settings, 'AWS_STORAGE_BUCKET_NAME', 'sumasenrogaining')
|
||
logger.info(f"S3 client initialized for bucket: {self.bucket_name}")
|
||
except Exception as e:
|
||
logger.error(f"Failed to initialize S3 client: {e}")
|
||
self.s3_client = None
|
||
self.bucket_name = None
|
||
|
||
def upload_checkin_image(self, image_data, event_code, zekken_number, cp_number):
|
||
"""
|
||
チェックイン画像をS3にアップロード
|
||
|
||
Args:
|
||
image_data: 画像データ(URLまたはBase64)
|
||
event_code: イベントコード
|
||
zekken_number: ゼッケン番号
|
||
cp_number: チェックポイント番号
|
||
|
||
Returns:
|
||
str: S3のURL、失敗時は元のimage_data
|
||
"""
|
||
if not self.s3_client or not image_data:
|
||
logger.warning("S3 client not available or no image data, returning original")
|
||
return image_data
|
||
|
||
try:
|
||
# 画像データを取得
|
||
image_binary = self._get_image_binary(image_data)
|
||
if not image_binary:
|
||
logger.error("Failed to get image binary data, returning original")
|
||
return image_data
|
||
|
||
# S3キーを生成: {event_code}/{zekken_number}/{cp_number}.jpg
|
||
s3_key = f"{event_code}/{zekken_number}/{cp_number}.jpg"
|
||
|
||
# S3にアップロード
|
||
self.s3_client.put_object(
|
||
Bucket=self.bucket_name,
|
||
Key=s3_key,
|
||
Body=image_binary,
|
||
ContentType='image/jpeg',
|
||
ACL='public-read' # 公開読み取り可能
|
||
)
|
||
|
||
# S3 URLを生成
|
||
aws_region = getattr(settings, 'AWS_REGION', 'us-west-2')
|
||
s3_url = f"https://{self.bucket_name}.s3.{aws_region}.amazonaws.com/{s3_key}"
|
||
|
||
logger.info(f"Successfully uploaded image to S3: {s3_url}")
|
||
return s3_url
|
||
|
||
except ClientError as e:
|
||
logger.error(f"S3 upload failed: {e}, returning original URL")
|
||
return image_data
|
||
except Exception as e:
|
||
logger.error(f"Unexpected error during S3 upload: {e}, returning original URL")
|
||
return image_data
|
||
|
||
def _get_image_binary(self, image_data):
|
||
"""
|
||
画像データからバイナリデータを取得
|
||
|
||
Args:
|
||
image_data: 画像URL(HTTP)またはBase64エンコードされた画像データ
|
||
|
||
Returns:
|
||
bytes: 画像のバイナリデータ
|
||
"""
|
||
try:
|
||
if isinstance(image_data, str):
|
||
# HTTPURLの場合
|
||
if image_data.startswith('http'):
|
||
return self._download_image_from_url(image_data)
|
||
# Base64の場合
|
||
elif self._is_base64(image_data):
|
||
return base64.b64decode(image_data)
|
||
# data:image/jpeg;base64,の形式の場合
|
||
elif image_data.startswith('data:image'):
|
||
base64_data = image_data.split(',')[1]
|
||
return base64.b64decode(base64_data)
|
||
|
||
logger.error(f"Unsupported image data format: {type(image_data)}")
|
||
return None
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error processing image data: {e}")
|
||
return None
|
||
|
||
def _download_image_from_url(self, url):
|
||
"""URLから画像をダウンロード"""
|
||
try:
|
||
response = requests.get(url, timeout=30)
|
||
response.raise_for_status()
|
||
return response.content
|
||
except Exception as e:
|
||
logger.error(f"Failed to download image from URL {url}: {e}")
|
||
return None
|
||
|
||
def _is_base64(self, data):
|
||
"""文字列がBase64かどうかをチェック"""
|
||
try:
|
||
if isinstance(data, str):
|
||
base64.b64decode(data, validate=True)
|
||
return True
|
||
except Exception:
|
||
return False
|
||
return False
|
||
|
||
def generate_s3_url(self, event_code, zekken_number, cp_number):
|
||
"""
|
||
S3 URLを生成(アップロード済みの画像用)
|
||
|
||
Args:
|
||
event_code: イベントコード
|
||
zekken_number: ゼッケン番号
|
||
cp_number: チェックポイント番号
|
||
|
||
Returns:
|
||
str: S3のURL
|
||
"""
|
||
aws_region = getattr(settings, 'AWS_REGION', 'us-west-2')
|
||
s3_key = f"{event_code}/{zekken_number}/{cp_number}.jpg"
|
||
return f"https://{self.bucket_name}.s3.{aws_region}.amazonaws.com/{s3_key}"
|
||
|
||
|
||
# グローバルインスタンス
|
||
s3_uploader = S3ImageUploader()
|