183 lines
7.2 KiB
Python
183 lines
7.2 KiB
Python
from flask import Flask, request, jsonify, send_from_directory, make_response, send_file
|
|
from flask_cors import CORS
|
|
import boto3
|
|
from botocore.exceptions import NoCredentialsError, ClientError, EndpointConnectionError
|
|
import os
|
|
from dotenv import load_dotenv
|
|
import logging
|
|
import datetime
|
|
import pytz
|
|
from botocore.client import Config
|
|
import csv
|
|
import pandas as pd
|
|
|
|
# 配置日志
|
|
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
|
|
|
|
load_dotenv() # 从.env文件加载环境变量
|
|
|
|
app = Flask(__name__, static_url_path='', static_folder='.')
|
|
CORS(app, resources={r"/*": {"origins": "*", "methods": "GET,POST,PUT,DELETE,OPTIONS"}}) # 添加 CORS 支持
|
|
|
|
aws_access_key_id = os.getenv('AWS_ACCESS_KEY_ID')
|
|
aws_secret_access_key = os.getenv('AWS_SECRET_ACCESS_KEY')
|
|
region_name = os.getenv('AWS_REGION')
|
|
bucket_name = os.getenv('S3_BUCKET_NAME')
|
|
|
|
# 打印环境变量 (仅用于调试,生产环境中请移除)
|
|
print(f"AWS_ACCESS_KEY_ID: {aws_access_key_id}")
|
|
print(f"AWS_SECRET_ACCESS_KEY: {'*' * len(aws_secret_access_key) if aws_secret_access_key else 'Not set'}")
|
|
print(f"AWS_REGION: {region_name}")
|
|
print(f"S3_BUCKET_NAME: {bucket_name}")
|
|
|
|
s3_client = boto3.client(
|
|
's3',
|
|
aws_access_key_id=aws_access_key_id,
|
|
aws_secret_access_key=aws_secret_access_key,
|
|
region_name=region_name,
|
|
config=Config(signature_version='s3v4') # 使用 S3v4 签名版本
|
|
)
|
|
|
|
# 跟踪学生提交信息
|
|
submissions_file = 'submissions.csv'
|
|
|
|
# 创建或者加载提交文件
|
|
if not os.path.exists(submissions_file):
|
|
with open(submissions_file, 'w', newline='') as file:
|
|
writer = csv.writer(file)
|
|
writer.writerow(['ID', '学生姓名', '学号', '提交的文件'])
|
|
|
|
|
|
def add_submission(student, student_id, filename):
|
|
with open(submissions_file, 'a', newline='') as file:
|
|
writer = csv.writer(file)
|
|
writer.writerow([datetime.datetime.now().isoformat(), student, student_id, filename])
|
|
|
|
|
|
def generate_presigned_url(object_key, content_type, expiration=3600):
|
|
try:
|
|
current_time = datetime.datetime.now(pytz.UTC)
|
|
logging.info(f"Current UTC time before generating URL: {current_time}")
|
|
|
|
response = s3_client.generate_presigned_url('put_object',
|
|
Params={
|
|
'Bucket': bucket_name,
|
|
'Key': object_key,
|
|
'ContentType': content_type # 使用实际文件的 Content-Type
|
|
},
|
|
ExpiresIn=expiration,
|
|
HttpMethod='PUT'
|
|
)
|
|
logging.info(f"Generated presigned URL: {response}")
|
|
return response
|
|
except (NoCredentialsError, ClientError, EndpointConnectionError) as e:
|
|
logging.error(f"Error generating presigned URL: {str(e)}", exc_info=True)
|
|
return None
|
|
|
|
|
|
@app.route('/generate-url', methods=['GET'])
|
|
def get_presigned_url():
|
|
student = request.args.get('student')
|
|
student_id = request.args.get('student_id')
|
|
filename = request.args.get('filename')
|
|
content_type = request.args.get('content_type', 'application/octet-stream')
|
|
logging.info(
|
|
f"Received request for student: {student}, student_id: {student_id}, filename: {filename}, content_type: {content_type}")
|
|
|
|
if not student or not filename or not student_id:
|
|
logging.warning("Missing student, student_id or filename parameter")
|
|
return jsonify({'error': 'Student, student_id and filename parameters are required'}), 400
|
|
|
|
folder_name = 'sure_homework_define_by_qin'
|
|
object_key = f'{folder_name}/{student}-{filename}'
|
|
|
|
url = generate_presigned_url(object_key, content_type) # 包含 content_type
|
|
if not url:
|
|
logging.error("Failed to generate presigned URL")
|
|
return jsonify({'error': 'Failed to generate presigned URL'}), 500
|
|
|
|
add_submission(student, student_id, filename)
|
|
|
|
logging.info(f"Generated URL: {url}")
|
|
return jsonify({'url': url, 'content_type': content_type})
|
|
|
|
|
|
@app.route('/')
|
|
def serve_index():
|
|
return send_from_directory('.', 'index.html')
|
|
|
|
|
|
@app.route('/health')
|
|
def health_check():
|
|
logging.info("Health check initiated")
|
|
try:
|
|
local_time = datetime.datetime.now()
|
|
utc_time = datetime.datetime.now(pytz.UTC)
|
|
logging.info(f"Local time: {local_time}, UTC time: {utc_time}")
|
|
|
|
logging.info("Attempting to list S3 buckets")
|
|
response = s3_client.list_buckets()
|
|
logging.info(f"Successfully listed buckets: {[bucket['Name'] for bucket in response['Buckets']]}")
|
|
return jsonify({
|
|
'status': 'healthy',
|
|
'message': 'AWS credentials are valid',
|
|
'local_time': local_time.isoformat(),
|
|
'utc_time': utc_time.isoformat()
|
|
}), 200
|
|
except NoCredentialsError:
|
|
logging.error("AWS credentials not found", exc_info=True)
|
|
return jsonify({'status': 'unhealthy', 'message': 'AWS credentials not found'}), 500
|
|
except ClientError as e:
|
|
error_code = e.response['Error']['Code']
|
|
error_message = e.response['Error']['Message']
|
|
logging.error(f"AWS client error: {error_code} - {error_message}", exc_info=True)
|
|
return jsonify({'status': 'unhealthy', 'message': f'AWS client error: {error_code} - {error_message}'}), 500
|
|
except Exception as e:
|
|
logging.error(f"Unexpected error during health check: {str(e)}", exc_info=True)
|
|
return jsonify({'status': 'unhealthy', 'message': f'Unexpected error: {str(e)}'}), 500
|
|
|
|
|
|
@app.route('/download-submissions')
|
|
def download_submissions():
|
|
df = pd.read_csv(submissions_file)
|
|
output_file = 'submissions.xlsx'
|
|
df.to_excel(output_file, index=False)
|
|
return send_file(output_file, as_attachment=True)
|
|
|
|
|
|
@app.before_request
|
|
def before_request_func():
|
|
if request.method == 'OPTIONS':
|
|
return _build_cors_preflight_response()
|
|
|
|
|
|
def _build_cors_preflight_response():
|
|
response = make_response()
|
|
response.headers.add("Access-Control-Allow-Origin", "*")
|
|
response.headers.add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
|
response.headers.add("Access-Control-Allow-Headers", "Content-Type")
|
|
return response
|
|
|
|
|
|
if __name__ == '__main__':
|
|
local_time = datetime.datetime.now()
|
|
utc_time = datetime.datetime.now(pytz.UTC)
|
|
logging.info(f"Application starting. Local time: {local_time}, UTC time: {utc_time}")
|
|
|
|
try:
|
|
logging.info("Validating AWS credentials on startup")
|
|
sts = boto3.client('sts',
|
|
aws_access_key_id=aws_access_key_id,
|
|
aws_secret_access_key=aws_secret_access_key,
|
|
region_name=region_name)
|
|
response = sts.get_caller_identity()
|
|
logging.info(f"AWS credentials validated successfully. Account ID: {response['Account']}")
|
|
except Exception as e:
|
|
logging.error(f"Failed to validate AWS credentials: {str(e)}", exc_info=True)
|
|
# 如果你想在凭证验证失败时退出程序,取消注释下面两行
|
|
# import sys
|
|
# sys.exit(1)
|
|
|
|
app.run(debug=True)
|
|
|