almost finish migrate new circumstances

This commit is contained in:
2025-08-24 19:44:36 +09:00
parent 1ba305641e
commit fe5a044c82
67 changed files with 1194889 additions and 467 deletions

View File

@ -664,3 +664,378 @@ end
---
この詳細機能設計書により、外部システム連携の実装、運用、保守に必要な全ての技術的詳細が文書化されています。
---
## 14. 🆕 管理者向け機能拡張 (2025年8月実装)
### 14.1 一括写真アップロード・自動チェックイン機能
#### 機能概要
複数の写真を一括でアップロードし、EXIF情報から自動的にチェックイン処理を実行する機能です。
#### 技術仕様
**実装ファイル**: `rog/views_apis/api_bulk_upload.py`
**主要クラス・関数**:
```python
def bulk_upload_photos(request):
"""
一括写真アップロード処理
処理フロー:
1. アップロードファイルの検証
2. EXIF情報抽出GPS座標、撮影時刻
3. 近接チェックポイント検索
4. 自動チェックイン処理
5. S3ストレージへの画像保存
"""
```
**依存ライブラリ**:
- `piexif`: EXIF情報抽出
- `PIL`: 画像処理
- `boto3`: S3連携
**処理フロー図**:
```
写真アップロード → EXIF抽出 → GPS検証 → チェックポイント検索 → 自動チェックイン
↓ ↓ ↓ ↓ ↓
ファイル検証 座標・時刻 位置精度確認 距離計算 DB記録
↓ ↓ ↓ ↓ ↓
S3アップロード メタデータ タイムスタンプ ポイント照合 確定処理
```
#### GPS精度検証ロジック
```python
def validate_gps_proximity(gps_coords, checkin_time, event_code):
"""
GPS座標の妥当性検証
検証項目:
- チェックポイントとの距離50m以内
- 撮影時刻の妥当性(イベント期間内)
- 重複チェックイン防止同一CP 30分以内
"""
max_distance = 50 # メートル
min_interval = 30 # 分
```
#### エラーハンドリング
```python
class BulkUploadError(Exception):
"""一括アップロード専用例外クラス"""
pass
# エラーパターン
- GPS情報なし: "GPS情報が見つかりません"
- 距離超過: "チェックポイントから50m以上離れています"
- 時刻異常: "撮影時刻がイベント期間外です"
- ファイル形式: "対応していないファイル形式です"
```
---
### 14.2 通過審査管理機能
#### 機能概要
チェックインの確定・否認を管理し、審査状況を追跡する機能です。
#### データベース拡張
**テーブル**: `rog_gpsCheckin`
**新規フィールド**:
```sql
ALTER TABLE rog_gpsCheckin ADD COLUMN validation_status VARCHAR(20) DEFAULT 'PENDING';
ALTER TABLE rog_gpsCheckin ADD COLUMN validation_comment TEXT;
ALTER TABLE rog_gpsCheckin ADD COLUMN validated_at TIMESTAMP;
ALTER TABLE rog_gpsCheckin ADD COLUMN validated_by VARCHAR(255);
```
**ステータス定義**:
```python
VALIDATION_STATUS_CHOICES = [
('PENDING', '審査待ち'),
('APPROVED', '承認'),
('REJECTED', '却下'),
('AUTO_APPROVED', '自動承認'),
]
```
#### API実装
**実装ファイル**: `rog/views_apis/api_bulk_upload.py`
```python
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def confirm_checkin_validation(request):
"""
チェックイン確定・否認処理
パラメータ:
- checkin_id: 対象チェックインID
- action: APPROVED/REJECTED
- comment: 審査コメント
"""
```
#### 審査ワークフロー
```
写真アップロード → AUTO_APPROVED → 管理者審査 → APPROVED/REJECTED
↓ ↓ ↓ ↓
自動処理 システム判定 手動確認 最終決定
↓ ↓ ↓ ↓
即座反映 暫定得点 審査待ち 確定得点
```
---
### 14.3 全参加者ランキング表示機能
#### 機能概要
イベント全参加者の得点と審査状況をクラス別に一覧表示する機能です。
#### 実装仕様
**実装ファイル**: `rog/views_apis/api_admin_validation.py`
**主要関数**:
```python
def get_event_participants_ranking(request):
"""
全参加者ランキング取得
集計項目:
- 確定得点APPROVED
- 未確定得点PENDING
- 確定率(確定数/総数)
- クラス別順位
"""
```
#### SQLクエリ最適化
```sql
SELECT
e.zekken_number,
e.team_name,
e.class_name,
SUM(CASE WHEN g.validation_status = 'APPROVED' THEN l.point_value ELSE 0 END) as confirmed_points,
SUM(CASE WHEN g.validation_status = 'PENDING' THEN l.point_value ELSE 0 END) as pending_points,
COUNT(g.id) as total_checkins,
COUNT(CASE WHEN g.validation_status = 'APPROVED' THEN 1 END) as confirmed_checkins
FROM rog_entry e
LEFT JOIN rog_gpsCheckin g ON e.zekken_number = g.zekken_number
LEFT JOIN rog_location2025 l ON g.cp_number = l.cp_number
WHERE e.event_code = %s
GROUP BY e.zekken_number, e.team_name, e.class_name
ORDER BY confirmed_points DESC;
```
#### キャッシュ戦略
```python
from django.core.cache import cache
def get_cached_ranking(event_code):
"""
ランキングデータのキャッシュ
キャッシュキー: f"ranking_{event_code}"
有効期限: 300秒5分
更新トリガー: チェックイン確定時
"""
cache_key = f"ranking_{event_code}"
cached_data = cache.get(cache_key)
if cached_data is None:
cached_data = calculate_ranking(event_code)
cache.set(cache_key, cached_data, 300)
return cached_data
```
---
### 14.4 管理画面UI拡張
#### フロントエンド実装
**実装ファイル**: `supervisor/html/index.html`
**新機能**:
1. **表示モード切り替え**: 個別表示 ⇄ ランキング表示
2. **一括操作**: 確定・否認ボタン
3. **ファイルアップロード**: ドラッグ&ドロップ対応
4. **リアルタイム更新**: AJAX通信
#### JavaScript実装概要
```javascript
class AdminValidationManager {
constructor() {
this.apiBaseUrl = '/api';
this.currentEventCode = '';
this.displayMode = 'individual';
}
// 一括写真アップロード
async handleBulkPhotoUpload(files) {
const formData = new FormData();
files.forEach(file => formData.append('photos', file));
formData.append('event_code', this.currentEventCode);
const response = await fetch(`${this.apiBaseUrl}/bulk-upload-photos/`, {
method: 'POST',
headers: this.getAuthHeaders(),
body: formData
});
return await response.json();
}
// チェックイン確定・否認
async confirmCheckin(checkinId, action) {
const response = await fetch(`${this.apiBaseUrl}/confirm-checkin-validation/`, {
method: 'POST',
headers: this.getAuthHeaders(),
body: JSON.stringify({
checkin_id: checkinId,
action: action,
comment: action === 'REJECTED' ? '手動確認により却下' : '手動確認により承認'
})
});
return await response.json();
}
}
```
#### レスポンシブデザイン
```css
/* 管理画面専用スタイル */
.admin-panel {
display: grid;
grid-template-columns: 1fr 3fr;
gap: 20px;
}
.ranking-table {
overflow-x: auto;
max-height: 70vh;
}
.validation-buttons {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
@media (max-width: 768px) {
.admin-panel {
grid-template-columns: 1fr;
}
.validation-buttons {
flex-direction: column;
}
}
```
---
### 14.5 セキュリティ対策
#### 認証・認可
```python
from rest_framework.permissions import IsAuthenticated
from django.contrib.auth.decorators import user_passes_test
def is_admin_user(user):
"""管理者権限チェック"""
return user.is_authenticated and user.is_superuser
@user_passes_test(is_admin_user)
def admin_only_view(request):
"""管理者専用ビュー"""
pass
```
#### ファイルアップロード検証
```python
def validate_upload_file(file):
"""
アップロードファイル検証
検証項目:
- ファイルサイズ10MB以下
- ファイル形式JPEG/PNG
- EXIF情報の存在
- マルウェアスキャン
"""
MAX_FILE_SIZE = 10 * 1024 * 1024 # 10MB
ALLOWED_TYPES = ['image/jpeg', 'image/png']
if file.size > MAX_FILE_SIZE:
raise ValidationError('ファイルサイズが制限を超えています')
if file.content_type not in ALLOWED_TYPES:
raise ValidationError('対応していないファイル形式です')
```
#### レート制限
```python
from django_ratelimit.decorators import ratelimit
@ratelimit(key='user', rate='10/h', method=['POST'])
def bulk_upload_photos(request):
"""
レート制限: 1時間あたり10回まで
"""
pass
```
---
### 14.6 監視・ログ機能
#### 操作ログ
```python
import logging
logger = logging.getLogger('admin_operations')
def log_admin_action(user, action, target, details=None):
"""
管理者操作ログ記録
ログ項目:
- 操作者
- 操作内容
- 対象データ
- タイムスタンプ
- 詳細情報
"""
logger.info(f"Admin Action: {user.username} performed {action} on {target}", extra={
'user_id': user.id,
'action': action,
'target': target,
'details': details,
'timestamp': timezone.now().isoformat()
})
```
#### パフォーマンス監視
```python
from django.db import connection
from django.conf import settings
def monitor_query_performance():
"""
クエリパフォーマンス監視
"""
if settings.DEBUG:
query_count = len(connection.queries)
slow_queries = [q for q in connection.queries if float(q['time']) > 0.1]
if slow_queries:
logger.warning(f"Slow queries detected: {len(slow_queries)} queries > 0.1s")
```
---