FLASK与JAVA的文件互传带进度条(文件互传带进度条亲测)
java:httpclient4-5-14.jar
flask:
pip install python-magic-bin pip install flask
java代码:ProgressFileUploader.java
package HttpFlask;import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;import java.io.*;
import java.nio.charset.StandardCharsets;/*** 带进度监控的文件上传 - 修正版*/
public class ProgressFileUploader {/*** 带进度回调的文件上传*/public static String uploadWithProgress(String url, File file,ProgressListener listener) throws IOException {try (CloseableHttpClient httpClient = HttpClients.createDefault()) {HttpPost httpPost = new HttpPost(url);// 使用标准的 MultipartEntityBuilderMultipartEntityBuilder builder = MultipartEntityBuilder.create();// 使用自定义的FileBody来监控上传进度ProgressFileBody fileBody = new ProgressFileBody(file,ContentType.DEFAULT_BINARY, file.getName(), listener);builder.addPart("file", fileBody);builder.addPart("comment", new StringBody("带进度监控的上传",ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8)));httpPost.setEntity(builder.build());try (CloseableHttpResponse response = httpClient.execute(httpPost)) {return EntityUtils.toString(response.getEntity());}}}/*** 进度监听接口*/public interface ProgressListener {void onProgress(long uploaded, long total);void onComplete();void onError(Exception e);}/*** 自定义FileBody实现进度监控*/static class ProgressFileBody extends FileBody {private final ProgressListener listener;private long totalLength;public ProgressFileBody(File file, ContentType contentType,String filename, ProgressListener listener) {super(file, contentType, filename);this.listener = listener;this.totalLength = file.length();}@Overridepublic void writeTo(OutputStream out) throws IOException {super.writeTo(new CountingOutputStream(out, totalLength, listener));}}/*** 计数输出流*/static class CountingOutputStream extends FilterOutputStream {private long transferred;private long total;private ProgressListener listener;public CountingOutputStream(OutputStream out, long total, ProgressListener listener) {super(out);this.total = total;this.listener = listener;this.transferred = 0;}@Overridepublic void write(byte[] b, int off, int len) throws IOException {out.write(b, off, len);transferred += len;if (listener != null) {listener.onProgress(transferred, total);}}@Overridepublic void write(int b) throws IOException {out.write(b);transferred++;if (listener != null) {listener.onProgress(transferred, total);}}@Overridepublic void close() throws IOException {super.close();if (listener != null && transferred == total) {listener.onComplete();}}}
}
java的测试代码:
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;public class FileHttpFlask {public static void main(String[] args) {try {// 示例5: 带进度监控的上传File file = new File("E:\\testWorkPlace\\testrun\\src\\HttpFlask\\down.txt");
// String url = "http://127.0.0.1:5000/api/upload/single";String url = "http://127.0.0.1:5000/api/upload/with-progress";ProgressFileUploader.uploadWithProgress(url, file,new ProgressFileUploader.ProgressListener() {@Overridepublic void onProgress(long uploaded, long total) {double percent = (double) uploaded / total * 100;System.out.printf("上传进度: %.2f%% (%d/%d bytes)%n",percent, uploaded, total);}@Overridepublic void onComplete() {System.out.println("文件上传完成!");}@Overridepublic void onError(Exception e) {System.err.println("上传出错: " + e.getMessage());}});} catch (IOException e) {e.printStackTrace();}}
}
flask代码:
from flask import Flask, request, jsonify, render_template_string, send_file
import os
import uuid
from werkzeug.utils import secure_filename
from datetime import datetime
import magic # 用于更准确的文件类型检测
from datetime import datetime
import time
# # 安装 Windows 兼容版本
# pip install python-magic-bin
app = Flask(__name__)# 配置文件上传
app.config.update(UPLOAD_FOLDER='uploads',MAX_CONTENT_LENGTH=16 * 1024 * 1024, # 16MBALLOWED_EXTENSIONS={'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif', 'doc', 'docx', 'zip'},SECRET_KEY='your-secret-key-here'
)# 用于存储上传统计信息
upload_stats = {'total_files': 0,'total_size': 0,'last_upload': None
}
# 创建上传目录
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)class FileUploader:"""文件上传工具类"""@staticmethoddef allowed_file(filename):"""检查文件扩展名"""return '.' in filename and \filename.rsplit('.', 1)[1].lower() in app.config['ALLOWED_EXTENSIONS']@staticmethoddef generate_unique_filename(original_filename):"""生成唯一文件名"""timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")unique_id = str(uuid.uuid4())[:8]safe_name = secure_filename(original_filename)return f"{timestamp}_{unique_id}_{safe_name}"@staticmethoddef get_file_info(file_path):"""获取文件详细信息"""stat = os.stat(file_path)file_type = "unknown"try:# 使用python-magic检测文件类型import magicfile_type = magic.from_file(file_path, mime=True)except:# 备用方案:使用文件扩展名ext = os.path.splitext(file_path)[1].lower()file_type = f"application/{ext[1:]}" if ext else "unknown"return {'size': stat.st_size,'created_time': datetime.fromtimestamp(stat.st_ctime).isoformat(),'modified_time': datetime.fromtimestamp(stat.st_mtime).isoformat(),'type': file_type}@app.route('/api/upload/with-progress', methods=['POST'])
def upload_with_progress():"""专门用于接收带进度监控的文件上传这个接口与 HttpClient 的 ProgressFileUploader 对应"""try:# 记录开始时间start_time = time.time()# 1. 检查文件部分if 'file' not in request.files:return jsonify({'success': False,'error': '没有文件部分'}), 400file = request.files['file']# 2. 检查是否选择了文件if file.filename == '':return jsonify({'success': False,'error': '没有选择文件'}), 400# 3. 检查文件类型if not FileUploader.allowed_file(file.filename):return jsonify({'success': False,'error': f'不允许的文件类型: {file.filename}','allowed_extensions': list(app.config['ALLOWED_EXTENSIONS'])}), 400# 4. 获取文本参数comment = request.form.get('comment', '')# 5. 模拟处理时间(为了显示进度效果)# 在实际应用中,这里可能是文件处理、验证等操作time.sleep(1)# 6. 生成唯一文件名并保存文件unique_filename = FileUploader.generate_unique_filename(file.filename)file_path = os.path.join(app.config['UPLOAD_FOLDER'], unique_filename)file.save(file_path)# 7. 获取文件信息file_info = FileUploader.get_file_info(file_path)# 8. 更新统计信息upload_stats['total_files'] += 1upload_stats['total_size'] += file_info['size']upload_stats['last_upload'] = datetime.now().isoformat()# 9. 计算处理时间processing_time = time.time() - start_time# 10. 构建响应数据response_data = {'success': True,'message': '文件上传成功(带进度监控)','upload_time': datetime.now().isoformat(),'processing_time_seconds': round(processing_time, 2),'parameters': {'comment': comment,'received_at': datetime.now().isoformat()},'file': {'id': unique_filename,'original_name': file.filename,'saved_name': unique_filename,**file_info},'server_info': {'upload_folder': app.config['UPLOAD_FOLDER'],'max_file_size': app.config['MAX_CONTENT_LENGTH']}}return jsonify(response_data), 200except Exception as e:return jsonify({'success': False,'error': f'上传失败: {str(e)}'}), 500
@app.route('/api/upload/single', methods=['POST'])
def upload_single_file():"""通用单文件上传接口也适用于带进度监控的上传"""try:if 'file' not in request.files:return jsonify({'success': False, 'error': '没有文件部分'}), 400file = request.files['file']if file.filename == '':return jsonify({'success': False, 'error': '没有选择文件'}), 400if not FileUploader.allowed_file(file.filename):return jsonify({'success': False, 'error': '不允许的文件类型'}), 400# 获取所有参数params = {}for key in request.form:params[key] = request.form.get(key)# 保存文件unique_filename = FileUploader.generate_unique_filename(file.filename)file_path = os.path.join(app.config['UPLOAD_FOLDER'], unique_filename)file.save(file_path)# 获取文件信息file_info = FileUploader.get_file_info(file_path)# 更新统计upload_stats['total_files'] += 1upload_stats['total_size'] += file_info['size']upload_stats['last_upload'] = datetime.now().isoformat()response_data = {'success': True,'message': '文件上传成功','upload_time': datetime.now().isoformat(),'parameters': params,'file': {'id': unique_filename,'original_name': file.filename,'saved_name': unique_filename,**file_info}}return jsonify(response_data), 200except Exception as e:return jsonify({'success': False, 'error': str(e)}), 500# 错误处理
@app.errorhandler(413)
def too_large(e):return jsonify({'success': False, 'error': '文件太大'}), 413@app.errorhandler(500)
def internal_error(e):return jsonify({'success': False, 'error': '服务器内部错误'}), 500if __name__ == '__main__':app.run(debug=True, host='0.0.0.0', port=5000)
测试结果: