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)