186 lines
5.3 KiB
Python
186 lines
5.3 KiB
Python
"""
|
|
文件上传视图
|
|
"""
|
|
from flask import Blueprint, request, jsonify, session, current_app
|
|
from werkzeug.utils import secure_filename
|
|
from app.utils.decorators import login_required
|
|
from app.models.user import User
|
|
from app.utils.cos_client import cos_client
|
|
from config.database import db
|
|
from config.cos_config import COSConfig
|
|
import os
|
|
|
|
upload_bp = Blueprint('upload', __name__)
|
|
|
|
|
|
@upload_bp.route('/avatar', methods=['POST'])
|
|
@login_required
|
|
def upload_avatar():
|
|
"""
|
|
上传用户头像
|
|
"""
|
|
try:
|
|
# 检查是否有文件
|
|
if 'avatar' not in request.files:
|
|
return jsonify({
|
|
'success': False,
|
|
'message': '没有选择文件'
|
|
}), 400
|
|
|
|
file = request.files['avatar']
|
|
|
|
# 检查文件名
|
|
if file.filename == '':
|
|
return jsonify({
|
|
'success': False,
|
|
'message': '没有选择文件'
|
|
}), 400
|
|
|
|
# 验证文件类型
|
|
if not allowed_file(file.filename, COSConfig.ALLOWED_IMAGE_EXTENSIONS):
|
|
return jsonify({
|
|
'success': False,
|
|
'message': f'不支持的文件格式,只支持: {", ".join(COSConfig.ALLOWED_IMAGE_EXTENSIONS)}'
|
|
}), 400
|
|
|
|
# 验证文件大小
|
|
file.seek(0, 2) # 移动到文件末尾
|
|
file_size = file.tell()
|
|
file.seek(0) # 重置文件指针
|
|
|
|
if file_size > COSConfig.MAX_IMAGE_SIZE:
|
|
size_mb = COSConfig.MAX_IMAGE_SIZE / 1024 / 1024
|
|
return jsonify({
|
|
'success': False,
|
|
'message': f'文件大小超过限制,最大允许 {size_mb:.1f}MB'
|
|
}), 400
|
|
|
|
# 获取当前用户
|
|
user = User.query.get(session['user_id'])
|
|
if not user:
|
|
return jsonify({
|
|
'success': False,
|
|
'message': '用户不存在'
|
|
}), 404
|
|
|
|
# 上传到COS
|
|
upload_result = cos_client.upload_file(
|
|
file_obj=file,
|
|
folder_type='avatar',
|
|
original_filename=file.filename
|
|
)
|
|
|
|
if not upload_result['success']:
|
|
current_app.logger.error(f"COS上传失败: {upload_result['error']}")
|
|
return jsonify({
|
|
'success': False,
|
|
'message': '文件上传失败,请重试'
|
|
}), 500
|
|
|
|
# 删除旧头像(如果存在)
|
|
if user.avatar_url:
|
|
try:
|
|
# 从URL中提取文件路径
|
|
old_file_key = extract_file_key_from_url(user.avatar_url)
|
|
if old_file_key:
|
|
cos_client.delete_file(old_file_key)
|
|
current_app.logger.info(f"删除旧头像: {old_file_key}")
|
|
except Exception as e:
|
|
current_app.logger.warning(f"删除旧头像失败: {str(e)}")
|
|
|
|
# 更新用户头像URL
|
|
user.avatar_url = upload_result['url']
|
|
db.session.commit()
|
|
|
|
current_app.logger.info(f"用户 {user.username} 头像上传成功: {upload_result['file_key']}")
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'message': '头像上传成功',
|
|
'avatar_url': upload_result['url'],
|
|
'file_key': upload_result['file_key']
|
|
})
|
|
|
|
except Exception as e:
|
|
current_app.logger.error(f"头像上传异常: {str(e)}")
|
|
db.session.rollback()
|
|
return jsonify({
|
|
'success': False,
|
|
'message': '服务器内部错误'
|
|
}), 500
|
|
|
|
|
|
def allowed_file(filename, allowed_extensions):
|
|
"""
|
|
检查文件扩展名是否允许
|
|
"""
|
|
return '.' in filename and \
|
|
filename.rsplit('.', 1)[1].lower() in allowed_extensions
|
|
|
|
|
|
def extract_file_key_from_url(url):
|
|
"""
|
|
从COS URL中提取文件路径
|
|
"""
|
|
try:
|
|
if not url:
|
|
return None
|
|
|
|
# 移除域名部分,只保留文件路径
|
|
if COSConfig.BUCKET_DOMAIN in url:
|
|
return url.split(COSConfig.BUCKET_DOMAIN + '/')[-1]
|
|
|
|
return None
|
|
except Exception:
|
|
return None
|
|
|
|
|
|
@upload_bp.route('/test', methods=['GET', 'POST'])
|
|
@login_required
|
|
def test_upload():
|
|
"""
|
|
测试上传功能
|
|
"""
|
|
if request.method == 'GET':
|
|
return '''
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>测试上传</title>
|
|
<meta charset="utf-8">
|
|
</head>
|
|
<body>
|
|
<h2>测试文件上传</h2>
|
|
<form method="post" enctype="multipart/form-data">
|
|
<input type="file" name="test_file" accept="image/*" required>
|
|
<button type="submit">上传测试</button>
|
|
</form>
|
|
</body>
|
|
</html>
|
|
'''
|
|
|
|
# POST 请求处理
|
|
if 'test_file' not in request.files:
|
|
return '没有文件'
|
|
|
|
file = request.files['test_file']
|
|
if file.filename == '':
|
|
return '没有选择文件'
|
|
|
|
# 上传到COS
|
|
result = cos_client.upload_file(
|
|
file_obj=file,
|
|
folder_type='temp',
|
|
original_filename=file.filename
|
|
)
|
|
|
|
if result['success']:
|
|
return f'''
|
|
<h2>上传成功!</h2>
|
|
<p>文件路径: {result['file_key']}</p>
|
|
<p>访问URL: <a href="{result['url']}" target="_blank">{result['url']}</a></p>
|
|
<img src="{result['url']}" style="max-width: 300px;">
|
|
'''
|
|
else:
|
|
return f'上传失败: {result["error"]}'
|