update checkin image issue

This commit is contained in:
2025-09-03 07:56:40 +09:00
parent 4901b44f4a
commit a0e024b77d
5 changed files with 260 additions and 15 deletions

1
rog/utils/__init__.py Normal file
View File

@ -0,0 +1 @@
# Python package marker

View File

@ -0,0 +1,153 @@
"""
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:
self.s3_client = boto3.client(
's3',
aws_access_key_id=getattr(settings, 'AWS_ACCESS_KEY', None),
aws_secret_access_key=getattr(settings, 'AWS_SECRET_ACCESS_KEY', None),
region_name=getattr(settings, 'AWS_REGION', 'us-west-2')
)
self.bucket_name = getattr(settings, 'S3_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: 画像URLHTTPまたは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()

View File

@ -15,6 +15,7 @@ from datetime import datetime
import uuid
import time
from django.http import JsonResponse
from rog.utils.s3_image_uploader import s3_uploader
logger = logging.getLogger(__name__)
@ -570,19 +571,34 @@ def checkin_from_rogapp(request):
# トランザクション開始
with transaction.atomic():
# S3に画像をアップロードし、S3 URLを取得
s3_image_url = image_url
if image_url:
try:
s3_image_url = s3_uploader.upload_checkin_image(
image_data=image_url,
event_code=entry.event.event_name,
zekken_number=entry.zekken_number,
cp_number=cp_number
)
logger.info(f"[CHECKIN] S3 upload - Original: {image_url[:50]}..., S3: {s3_image_url}")
except Exception as e:
logger.error(f"[CHECKIN] S3 upload failed, using original URL: {e}")
s3_image_url = image_url
# serial_numberを自動生成既存の最大値+1
max_serial = GpsLog.objects.filter(
zekken_number=entry.zekken_number,
event_code=entry.event.event_name
).aggregate(max_serial=Max('serial_number'))['max_serial'] or 0
# チェックポイント登録
# チェックポイント登録S3 URLを使用
checkpoint = GpsLog.objects.create(
serial_number=max_serial + 1,
zekken_number=entry.zekken_number,
event_code=entry.event.event_name,
cp_number=cp_number,
image_address=image_url,
image_address=s3_image_url, # S3 URLを保存
checkin_time=timezone.now(),
create_at=timezone.now(),
update_at=timezone.now(),
@ -638,7 +654,8 @@ def checkin_from_rogapp(request):
"bonus_points": bonus_points,
"scoring_breakdown": scoring_breakdown,
"validation_status": "pending",
"requires_manual_review": bool(gps_coordinates.get('accuracy', 0) > 10) # 10m以上は要審査
"requires_manual_review": bool(gps_coordinates.get('accuracy', 0) > 10), # 10m以上は要審査
"image_url": s3_image_url # S3画像URLを返す
})
except Exception as e: