2025-07-04 19:07:35 +08:00

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"]}'