后端:

import os
from flask import Flask, request, send_file, jsonify, abort, render_template, flash, redirect, url_for

app = Flask(__name__)
app.secret_key = 'supersecretkey'  # 设置一个安全的密钥

# 配置上传文件夹和允许的扩展名
UPLOAD_FOLDER = 'uploads'
ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif', 'zip'}
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
MAX_CONTENT_LENGTH = 16 * 1024 * 1024  # 16MB大小限制

# 确保上传文件夹存在
os.makedirs(UPLOAD_FOLDER, exist_ok=True)

def allowed_file(filename):
    """检查文件扩展名是否合法"""
    return '.' in filename and \
           filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

def get_file_list():
    """获取文件列表信息"""
    files = []
    for filename in os.listdir(app.config['UPLOAD_FOLDER']):
        path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
        if os.path.isfile(path):
            files.append({
                'name': filename,
                'size': os.path.getsize(path),
                'url': f'/download/{filename}'
            })
    return files

@app.route('/')
def index():
    """首页,显示文件上传界面和文件列表"""
    files = get_file_list()
    return render_template('index.html', files=files)

# 修复1: 确保表单提交到/upload而不是根路径
@app.route('/upload', methods=['POST'])
def upload_file():
    """文件上传接口"""
    # 检查请求中是否有文件部分
    if 'file' not in request.files:
        flash('没有文件部分', 'error')
        return redirect(url_for('index'))
    
    file = request.files['file']
    
    # 如果用户没有选择文件
    if file.filename == '':
        flash('未选择文件', 'error')
        return redirect(url_for('index'))
    
    # 验证文件名和扩展名
    if not allowed_file(file.filename):
        flash('文件类型不允许', 'error')
        return redirect(url_for('index'))
    
    try:
        # 安全保存文件
        filename = os.path.join(app.config['UPLOAD_FOLDER'], file.filename)
        file.save(filename)
        flash(f'文件上传成功: {file.filename}', 'success')
        return redirect(url_for('index'))
    except Exception as e:
        flash(f'上传失败: {str(e)}', 'error')
        return redirect(url_for('index'))

@app.route('/download/<path:filename>', methods=['GET'])
def download_file(filename):
    """文件下载接口"""
    # 防止目录遍历攻击
    if '..' in filename or filename.startswith('/'):
        abort(400)
    
    file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
    
    # 检查文件是否存在
    if not os.path.isfile(file_path):
        abort(404)
    
    return send_file(file_path, as_attachment=True)

@app.route('/delete/<path:filename>', methods=['DELETE'])
def delete_file(filename):
    """文件删除接口"""
    # 防止目录遍历攻击
    if '..' in filename or filename.startswith('/'):
        return jsonify({'error': '非法文件名'}), 400
    
    file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
    
    # 检查文件是否存在
    if not os.path.isfile(file_path):
        return jsonify({'error': '文件不存在'}), 404
    
    try:
        os.remove(file_path)
        return jsonify({'message': f'文件已删除: {filename}'}), 200
    except Exception as e:
        return jsonify({'error': str(e)}), 500

# 修复2: 添加文件列表API
@app.route('/file-list', methods=['GET'])
def file_list_api():
    """返回文件列表的JSON数据"""
    try:
        files = get_file_list()
        return jsonify({'files': files}), 200
    except Exception as e:
        return jsonify({'error': str(e)}), 500

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)

templates/index.html 文件中,修复表单的 action 属性:

HTML前端:

<!DOCTYPE html>
<html>
<head>
    <title>文件上传服务</title>
    <style>
        body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; background-color: #f5f5f5; }
        .container { background: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
        h1 { color: #333; text-align: center; margin-bottom: 30px; }
        
        .upload-box { 
            border: 2px dashed #3498db; 
            padding: 25px; 
            text-align: center; 
            margin-bottom: 30px; 
            border-radius: 8px;
            background-color: #f8f9fa;
            transition: all 0.3s ease;
        }
        .upload-box:hover { background-color: #e3f2fd; border-color: #2980b9; }
        
        .file-list { list-style: none; padding: 0; margin-top: 20px; }
        .file-item { 
            padding: 12px 15px; 
            border-bottom: 1px solid #eee; 
            display: flex; 
            justify-content: space-between;
            align-items: center;
            transition: background-color 0.2s;
        }
        .file-item:hover { background-color: #f9f9f9; }
        .file-info { display: flex; flex-direction: column; }
        .file-name { font-weight: bold; color: #2c3e50; }
        .file-size { color: #7f8c8d; font-size: 0.9em; }
        .file-actions { display: flex; gap: 15px; }
        
        .btn { 
            background: #3498db; 
            color: white; 
            padding: 10px 20px; 
            border: none; 
            border-radius: 4px; 
            cursor: pointer; 
            font-size: 1em;
            transition: background 0.3s;
        }
        .btn:hover { background: #2980b9; }
        
        .btn-download {
            background: #27ae60;
        }
        .btn-download:hover {
            background: #219653;
        }
        
        .btn-delete {
            background: #e74c3c;
        }
        .btn-delete:hover {
            background: #c0392b;
        }
        
        #fileInput { display: none; }
        .file-selector { margin-bottom: 15px; }
        .file-name-display { margin: 15px 0; color: #34495e; font-weight: bold; }
        
        .message { 
            padding: 10px; 
            margin: 15px 0; 
            border-radius: 4px; 
            text-align: center;
        }
        .success { background-color: #d4edda; color: #155724; }
        .error { background-color: #f8d7da; color: #721c24; }
        
        .header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
        .refresh-btn { background: #9b59b6; }
        .refresh-btn:hover { background: #8e44ad; }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>文件上传服务</h1>
            <button class="btn refresh-btn" onclick="refreshFileList()">刷新列表</button>
        </div>
        
        <div class="upload-box">
            <h2>上传文件</h2>
            <!-- 修复3: 设置表单action到/upload -->
            <form action="/upload" method="post" enctype="multipart/form-data" id="uploadForm">
                <div class="file-selector">
                    <button type="button" class="btn" onclick="document.getElementById('fileInput').click()">
                        选择文件
                    </button>
                </div>
                <div class="file-name-display" id="fileName">未选择文件</div>
                <input type="file" name="file" id="fileInput">
                <button type="submit" class="btn">开始上传</button>
            </form>
            
            <div id="message">
                {% with messages = get_flashed_messages(with_categories=true) %}
                    {% if messages %}
                        {% for category, message in messages %}
                            <div class="message {{ category }}">{{ message }}</div>
                        {% endfor %}
                    {% endif %}
                {% endwith %}
            </div>
        </div>

        <h2>文件列表</h2>
        <ul class="file-list" id="fileList">
            {% if files %}
                {% for file in files %}
                <li class="file-item">
                    <div class="file-info">
                        <span class="file-name">{{ file.name }}</span>
                        <span class="file-size">{{ (file.size/1024)|round(2) }} KB</span>
                    </div>
                    <div class="file-actions">
                        <a href="/download/{{ file.name }}" class="btn btn-download">下载</a>
                        <button class="btn btn-delete" onclick="deleteFile('{{ file.name }}')">删除</button>
                    </div>
                </li>
                {% endfor %}
            {% else %}
                <li class="file-item">暂无文件</li>
            {% endif %}
        </ul>
    </div>

    <script>
        // 显示选择的文件名
        document.getElementById('fileInput').addEventListener('change', function(e) {
            const fileNameDisplay = document.getElementById('fileName');
            if (this.files[0]) {
                fileNameDisplay.textContent = this.files[0].name;
                fileNameDisplay.style.color = '#27ae60';
            } else {
                fileNameDisplay.textContent = '未选择文件';
                fileNameDisplay.style.color = '#34495e';
            }
        });

        // 删除文件
        async function deleteFile(filename) {
            if (!confirm(`确定删除 "${filename}" 吗?此操作不可撤销。`)) return;
            
            try {
                const response = await fetch(`/delete/${encodeURIComponent(filename)}`, {
                    method: 'DELETE'
                });
                
                if (response.ok) {
                    // 刷新文件列表
                    refreshFileList();
                } else {
                    const error = await response.json();
                    alert('删除失败: ' + error.error);
                }
            } catch (error) {
                alert('网络错误: ' + error.message);
            }
        }

        // 刷新文件列表
        async function refreshFileList() {
            try {
                const response = await fetch('/file-list');
                const data = await response.json();
                
                const fileList = document.getElementById('fileList');
                fileList.innerHTML = '';
                
                if (data.files && data.files.length > 0) {
                    data.files.forEach(file => {
                        const listItem = document.createElement('li');
                        listItem.className = 'file-item';
                        
                        const sizeKB = (file.size / 1024).toFixed(2);
                        
                        listItem.innerHTML = `
                            <div class="file-info">
                                <span class="file-name">${file.name}</span>
                                <span class="file-size">${sizeKB} KB</span>
                            </div>
                            <div class="file-actions">
                                <a href="/download/${encodeURIComponent(file.name)}" class="btn btn-download">下载</a>
                                <button class="btn btn-delete" onclick="deleteFile('${file.name}')">删除</button>
                            </div>
                        `;
                        fileList.appendChild(listItem);
                    });
                } else {
                    const listItem = document.createElement('li');
                    listItem.className = 'file-item';
                    listItem.textContent = '暂无文件';
                    fileList.appendChild(listItem);
                }
                
                // 显示刷新成功消息
                showMessage('文件列表已刷新', 'success');
            } catch (error) {
                showMessage('刷新失败: ' + error.message, 'error');
            }
        }
        
        // 显示消息
        function showMessage(text, type) {
            const messageDiv = document.getElementById('message');
            // 清除旧消息
            while (messageDiv.firstChild) {
                messageDiv.removeChild(messageDiv.firstChild);
            }
            
            const msgElement = document.createElement('div');
            msgElement.className = `message ${type}`;
            msgElement.textContent = text;
            messageDiv.appendChild(msgElement);
            
            // 3秒后移除消息
            setTimeout(() => {
                msgElement.remove();
            }, 3000);
        }
    </script>
</body>
</html>

使用说明:

  1. 将 Python 代码保存为 app.py
  2. 创建 templates 文件夹,将 HTML 代码保存为 templates/index.html
  3. 安装依赖:pip install flask
  4. 运行应用:python app.py
  5. 访问 http://localhost:5000

创建项目结构:

project/
├── app.py
├── templates/
│   └── index.html
└── uploads/