first commit

This commit is contained in:
superlishunqin 2024-07-03 03:02:24 +08:00
commit 1760f36f7b
15 changed files with 665 additions and 0 deletions

4
.env Normal file
View File

@ -0,0 +1,4 @@
AWS_ACCESS_KEY_ID=AKIAZQ3DT3KLI6N5LQUM
AWS_SECRET_ACCESS_KEY=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
AWS_REGION=ap-northeast-1
S3_BUCKET_NAME=sure-ae-upload

8
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

10
.idea/AWS-sure.iml generated Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/venv/lib/python3.10/site-packages" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -0,0 +1,23 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<Languages>
<language minSize="272" name="Python" />
</Languages>
</inspection_tool>
<inspection_tool class="PyPep8Inspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="ignoredErrors">
<list>
<option value="E265" />
<option value="E231" />
<option value="E262" />
<option value="E225" />
<option value="E402" />
<option value="E271" />
<option value="E302" />
</list>
</option>
</inspection_tool>
</profile>
</component>

View File

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

4
.idea/misc.xml generated Normal file
View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.11 (AWS-sure)" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/AWS-sure.iml" filepath="$PROJECT_DIR$/.idea/AWS-sure.iml" />
</modules>
</component>
</project>

182
app.py Normal file
View File

@ -0,0 +1,182 @@
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)

31
generate_presigned_url.py Normal file
View File

@ -0,0 +1,31 @@
import boto3
from botocore.exceptions import NoCredentialsError, ClientError
# 使用获取的访问密钥ID和秘密访问密钥来初始化S3客户端
aws_access_key_id = 'AKIAZQ3DT3KLN5WGXZOR' # 替换为你的访问密钥ID
aws_secret_access_key = '5UZb8SovTrbroT7yU1pBzaR5myLn+NMA+c87RvLH' # 替换为你的秘密访问密钥
region_name = 'ap-northeast-1' # 替换为你的S3存储桶所在区域
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
)
# 创建一个模拟“文件夹”的空对象
def create_s3_folder(bucket_name, folder_name):
try:
s3_client.put_object(Bucket=bucket_name, Key=(folder_name + '/'))
print(f"Folder {folder_name} created in bucket {bucket_name}")
except NoCredentialsError:
print("Credentials not available")
except ClientError as e:
print(f"Error: {e}")
# Bucket name and folder name
bucket_name = 'sure-ae-upload'
folder_name = 'sure_homework_define_by_qin'
# Create folder
create_s3_folder(bucket_name, folder_name)

BIN
image.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 493 KiB

316
index.html Normal file
View File

@ -0,0 +1,316 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>秀儿文件提交系统</title>
<style>
body {
font-family: 'Helvetica Neue', Arial, sans-serif;
background-color: #f0f2f5;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.container {
background-color: #ffffff;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
padding: 30px;
width: 100%;
max-width: 400px;
}
.image-container {
margin-bottom: 20px;
border-radius: 8px;
overflow: hidden;
}
.image-container img {
width: 100%;
height: auto;
display: block;
}
h1 {
color: #333;
font-size: 24px;
margin-bottom: 20px;
text-align: center;
}
.form-field {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
color: #555;
font-weight: 500;
}
input[type="text"] {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
}
.file-input-wrapper {
position: relative;
overflow: visible;
display: inline-block;
width: 100%;
}
.file-input-button {
background-color: #f0f0f0;
border: 1px solid #ddd;
color: #333;
padding: 10px 15px;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
display: inline-block;
width: 100%;
text-align: center;
box-sizing: border-box;
transition: background-color 0.3s, border-color 0.3s;
}
.file-input-button:hover {
background-color: #e0e0e0;
border-color: #ccc;
}
.file-input-button input[type=file] {
font-size: 100px;
position: absolute;
left: 0;
top: 0;
opacity: 0;
cursor: pointer;
width: 100%;
height: 100%;
}
#file-name {
margin-top: 5px;
font-size: 14px;
color: #666;
}
button[type="submit"] {
background-color: #1890ff;
color: white;
border: none;
padding: 12px 20px;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
width: 100%;
transition: background-color 0.3s;
}
button[type="submit"]:hover {
background-color: #40a9ff;
}
.progress-container {
margin-top: 20px;
display: none;
}
.progress {
height: 10px;
background-color: #f0f0f0;
border-radius: 5px;
overflow: hidden;
}
.progress-bar {
height: 100%;
background-color: #1890ff;
width: 0;
transition: width 0.3s ease;
}
#progress-percentage {
font-size: 14px;
color: #666;
margin-top: 5px;
}
#status {
margin-top: 15px;
font-size: 14px;
color: #666;
}
#download-link {
display: inline-block;
margin-top: 20px;
text-decoration: none;
color: #1890ff;
font-size: 14px;
}
.upload-stats {
margin-top: 10px;
font-size: 14px;
color: #666;
}
.upload-stats span {
display: inline-block;
margin-right: 15px;
}
</style>
</head>
<body>
<div class="container">
<div class="image-container">
<img src="image.jpg" alt="描述性文本">
</div>
<h1>秀儿文件提交系统</h1>
<form id="upload-form">
<div class="form-field">
<label for="student">学生姓名:</label>
<input type="text" id="student" name="student" required>
</div>
<div class="form-field">
<label for="student-id">学号:</label>
<input type="text" id="student-id" name="student-id" required>
</div>
<div class="form-field">
<label for="file">选择文件:</label>
<div class="file-input-wrapper">
<div class="file-input-button">
选择文件
<input type="file" id="file" name="file" required>
</div>
</div>
<div id="file-name">未选择文件</div>
</div>
<button type="submit">提交 (截止日期: 2024.07.03)</button>
</form>
<div id="status"></div>
<div class="progress-container" id="progress-container">
<div class="progress">
<div class="progress-bar" id="progress-bar"></div>
</div>
<div id="progress-percentage">0%</div>
<div class="upload-stats">
<span id="upload-speed">速度: 0 KB/s</span>
<span id="upload-size">0 KB / 0 KB</span>
<span id="upload-time">剩余时间: 计算中</span>
</div>
</div>
<a href="/download-submissions" id="download-link">下载统计表格</a>
</div>
<script>
document.getElementById('upload-form').addEventListener('submit', async (e) => {
e.preventDefault();
const student = document.getElementById('student').value;
const studentId = document.getElementById('student-id').value;
const file = document.getElementById('file').files[0];
const statusDiv = document.getElementById('status');
const progressContainer = document.getElementById('progress-container');
const progressBar = document.getElementById('progress-bar');
const progressPercentage = document.getElementById('progress-percentage');
const uploadSpeed = document.getElementById('upload-speed');
const uploadSize = document.getElementById('upload-size');
const uploadTime = document.getElementById('upload-time');
if (!student || !studentId || !file) {
alert('请提供学生姓名、学号,并选择一个文件。');
return;
}
statusDiv.textContent = '准备上传...';
progressContainer.style.display = 'block';
try {
const response = await fetch(`/generate-url?student=${encodeURIComponent(student)}&student_id=${encodeURIComponent(studentId)}&filename=${encodeURIComponent(file.name)}&content_type=${encodeURIComponent(file.type)}`, {
method: 'GET',
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(`生成预签名URL失败: ${errorData.error}`);
}
const data = await response.json();
if (!data.url) {
throw new Error('服务器未返回URL');
}
statusDiv.textContent = '正在上传文件...';
const totalSize = file.size;
let uploadedSize = 0;
let startTime = Date.now();
const updateProgress = (additionalProgress = 0) => {
uploadedSize += additionalProgress;
const percentComplete = (uploadedSize / totalSize) * 100;
const elapsedTime = (Date.now() - startTime) / 1000; // 秒
const speed = uploadedSize / elapsedTime;
const remainingSize = totalSize - uploadedSize;
const estimatedRemainingTime = speed > 0 ? remainingSize / speed : 0;
progressBar.style.width = percentComplete + '%';
progressPercentage.textContent = percentComplete.toFixed(2) + '%';
uploadSpeed.textContent = `速度: ${formatSize(speed)}/s`;
uploadSize.textContent = `${formatSize(uploadedSize)} / ${formatSize(totalSize)}`;
uploadTime.textContent = `剩余时间: ${formatTime(estimatedRemainingTime)}`;
};
// 模拟进度更新
const progressInterval = setInterval(() => {
if (uploadedSize < totalSize) {
updateProgress(totalSize / 100); // 每次更新1%的进度
}
}, 200);
// 实际上传
const uploadResponse = await fetch(data.url, {
method: 'PUT',
body: file,
headers: {
'Content-Type': data.content_type
}
});
clearInterval(progressInterval);
if (!uploadResponse.ok) {
throw new Error(`Upload failed: ${uploadResponse.statusText}`);
}
// 确保进度达到100%
updateProgress(totalSize - uploadedSize);
statusDiv.textContent = '文件上传成功';
alert('文件上传成功');
} catch (error) {
console.error('错误:', error);
statusDiv.textContent = `错误: ${error.message}`;
alert('发生错误: ' + error.message);
}
});
function formatSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
function formatTime(seconds) {
if (isNaN(seconds) || !isFinite(seconds)) {
return '计算中';
}
if (seconds < 60) return Math.round(seconds) + ' ';
const minutes = Math.floor(seconds / 60);
const remainingSeconds = Math.round(seconds % 60);
return `${minutes} 分 ${remainingSeconds} 秒`;
}
// 添加文件名显示功能
document.getElementById('file').addEventListener('change', function(e) {
const fileName = e.target.files[0] ? e.target.files[0].name : '未选择文件';
document.getElementById('file-name').textContent = fileName;
});
</script>
</body>
</html>

16
main.py Normal file
View File

@ -0,0 +1,16 @@
# 这是一个示例 Python 脚本。
# 按 ⌃R 执行或将其替换为您的代码。
# 按 双击 ⇧ 在所有地方搜索类、文件、工具窗口、操作和设置。
def print_hi(name):
# 在下面的代码行中使用断点来调试脚本。
print(f'Hi, {name}') # 按 ⌘F8 切换断点。
# 按间距中的绿色按钮以运行脚本。
if __name__ == '__main__':
print_hi('PyCharm')
# 访问 https://www.jetbrains.com/help/pycharm/ 获取 PyCharm 帮助

13
submissions.csv Normal file
View File

@ -0,0 +1,13 @@
ID,学生姓名,学号,提交的文件
2024-07-02T15:38:06.844568,嗷呜呜汪汪汪钦,12345,fastgpt.txt
2024-07-03T02:01:37.364487,王中王火腿肠,123,mao.png
2024-07-03T02:02:02.087326,王中王火腿肠,123,《移动互联开发》软件设计考查材料.rar
2024-07-03T02:31:23.019179,秀儿,000,SUS 304 Stainless Steel.png
2024-07-03T02:31:55.184382,秀儿,000,阅读1.pdf
2024-07-03T02:35:50.030507,嘿!,324,计算机网络_数据链路层.ppt
2024-07-03T02:37:23.232007,CC,34534,"性学观止 (贺兰特·凯查杜里安, 胡颖翀) (Z-Library).pdf"
2024-07-03T02:40:42.161975,4324,4234,数学分析 陈纪修 第三版 上 by 陈纪修,于崇华,金路 (z-lib.org).pdf
2024-07-03T02:42:47.614487,324,4234,男人来自火星女人来自金星套装共4册 (约翰·格雷) (Z-Library).pdf
2024-07-03T02:43:01.033652,534534,543534,数学分析 陈纪修 第三版 上 by 陈纪修,于崇华,金路 (z-lib.org).pdf
2024-07-03T02:46:15.397526,CCC,AAA,"周易译注 (黄寿祺,张善文) (Z-Library).pdf"
2024-07-03T02:48:26.189052,2423,423423,阅读1.pdf
1 ID 学生姓名 学号 提交的文件
2 2024-07-02T15:38:06.844568 嗷呜呜汪汪汪钦 12345 fastgpt.txt
3 2024-07-03T02:01:37.364487 王中王火腿肠 123 mao.png
4 2024-07-03T02:02:02.087326 王中王火腿肠 123 《移动互联开发》软件设计考查材料.rar
5 2024-07-03T02:31:23.019179 秀儿 000 SUS 304 Stainless Steel.png
6 2024-07-03T02:31:55.184382 秀儿 000 阅读1.pdf
7 2024-07-03T02:35:50.030507 嘿! 324 计算机网络_数据链路层.ppt
8 2024-07-03T02:37:23.232007 CC 34534 性学观止 (贺兰特·凯查杜里安, 胡颖翀) (Z-Library).pdf
9 2024-07-03T02:40:42.161975 4324 4234 数学分析 陈纪修 第三版 上 by 陈纪修,于崇华,金路 (z-lib.org).pdf
10 2024-07-03T02:42:47.614487 324 4234 男人来自火星,女人来自金星(套装共4册) (约翰·格雷) (Z-Library).pdf
11 2024-07-03T02:43:01.033652 534534 543534 数学分析 陈纪修 第三版 上 by 陈纪修,于崇华,金路 (z-lib.org).pdf
12 2024-07-03T02:46:15.397526 CCC AAA 周易译注 (黄寿祺,张善文) (Z-Library).pdf
13 2024-07-03T02:48:26.189052 2423 423423 阅读1.pdf

BIN
submissions.xlsx Normal file

Binary file not shown.

44
upload_homework.py Normal file
View File

@ -0,0 +1,44 @@
import boto3
from botocore.exceptions import NoCredentialsError
# 使用访问密钥ID和秘密访问密钥来初始化S3客户端
aws_access_key_id = 'AKIAZQ3DT3KLN5WGXZOR' # 替换为你的访问密钥ID
aws_secret_access_key = '5UZb8SovTrbroT7yU1pBzaR5myLn+NMA+c87RvLH' # 替换为你的秘密访问密钥
region_name = 'ap-northeast-1' # 替换为你的S3存储桶所在区域
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
)
def generate_presigned_url(bucket_name, object_key, expiration=3600):
try:
response = s3_client.generate_presigned_url(
'put_object',
Params={
'Bucket': bucket_name,
'Key': object_key,
'ContentType': 'application/pdf' # 设置Content-Type
},
ExpiresIn=expiration
)
except NoCredentialsError:
print("Credentials not available")
return None
return response
# 生成每个学生的上传预签名URL
bucket_name = 'sure-ae-upload'
folder_name = 'sure_homework_define_by_qin'
students = ['alice', 'bob', 'charlie'] # 示例学生名单
for student in students:
object_key = f'{folder_name}/{student}-assignment.pdf' # 动态生成路径
url = generate_presigned_url(bucket_name, object_key)
if url:
print(f"The presigned URL for {student} is: {url}")