微信朋友圈图片上传接口的分片上传与断点续传 Java 实现
·
微信朋友圈图片上传接口的分片上传与断点续传 Java 实现
微信图片上传协议特征
微信客户端在上传朋友圈高清图时,采用分片上传 + 断点续传机制。其流程为:
- 预上传请求获取
upload_id; - 将文件按固定块(通常 256KB)切片,依次上传;
- 每次上传返回已接收的偏移量(
offset),用于断点恢复; - 所有分片完成后提交合并。
模拟此过程需精确处理二进制分片、偏移校验与重试逻辑。
分片上传上下文模型
定义上传任务状态,支持持久化以实现断点续传:
package wlkankan.cn.wx.upload;
import java.io.File;
import java.io.Serializable;
public class UploadContext implements Serializable {
private String uploadId;
private String mediaId;
private File sourceFile;
private long totalSize;
private long uploadedOffset;
private int chunkSize = 256 * 1024; // 256KB per chunk
public UploadContext(File file) {
this.sourceFile = file;
this.totalSize = file.length();
this.uploadedOffset = 0;
}
// getters and setters
public long getRemaining() { return totalSize - uploadedOffset; }
public boolean isCompleted() { return uploadedOffset >= totalSize; }
}
预上传接口调用
向微信服务器申请 upload_id:
package wlkankan.cn.wx.client;
import wlkankan.cn.wx.upload.UploadContext;
import com.fasterxml.jackson.databind.JsonNode;
import okhttp3.*;
public class WeChatUploadClient {
private final OkHttpClient httpClient = new OkHttpClient();
private static final String PRE_UPLOAD_URL = "https://mmsns.qpic.cn/mmsns_upload";
public String preUpload(UploadContext ctx) throws Exception {
RequestBody body = new FormBody.Builder()
.add("file_size", String.valueOf(ctx.getTotalSize()))
.add("file_type", "image/jpeg")
.build();
Request request = new Request.Builder()
.url(PRE_UPLOAD_URL)
.post(body)
.build();
try (Response resp = httpClient.newCall(request).execute()) {
if (!resp.isSuccessful()) throw new RuntimeException("Pre-upload failed");
JsonNode json = objectMapper.readTree(resp.body().string());
return json.get("upload_id").asText();
}
}
}

分片上传核心逻辑
逐块读取文件并上传,记录成功偏移:
public class ChunkUploader {
private static final String CHUNK_UPLOAD_URL = "https://mmsns.qpic.cn/mmsns_chunk";
public void uploadChunk(UploadContext ctx, WeChatUploadClient client) throws Exception {
RandomAccessFile raf = new RandomAccessFile(ctx.getSourceFile(), "r");
raf.seek(ctx.getUploadedOffset());
byte[] buffer = new byte[Math.min(ctx.getChunkSize(), (int) ctx.getRemaining())];
int read = raf.read(buffer);
if (read <= 0) {
raf.close();
return;
}
// 构造 multipart/form-data 分片
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("upload_id", ctx.getUploadId())
.addFormDataPart("offset", String.valueOf(ctx.getUploadedOffset()))
.addFormDataPart("chunk", "chunk.dat",
RequestBody.create(buffer, MediaType.get("application/octet-stream")))
.build();
Request request = new Request.Builder()
.url(CHUNK_UPLOAD_URL)
.post(requestBody)
.build();
try (Response resp = client.getHttpClient().newCall(request).execute()) {
if (resp.isSuccessful()) {
JsonNode json = objectMapper.readTree(resp.body().string());
long confirmedOffset = json.get("offset").asLong();
// 微信返回的 offset 应等于 ctx.uploadedOffset + read
if (confirmedOffset == ctx.getUploadedOffset() + read) {
ctx.setUploadedOffset(confirmedOffset);
persistContext(ctx); // 持久化进度
} else {
throw new IllegalStateException("Offset mismatch");
}
} else {
throw new RuntimeException("Chunk upload failed: " + resp.code());
}
} finally {
raf.close();
}
}
private void persistContext(UploadContext ctx) {
// 可序列化到本地文件或数据库,例如:
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream(ctx.getSourceFile().getPath() + ".upload"))) {
oos.writeObject(ctx);
} catch (Exception e) {
// log error
}
}
}
断点续传恢复机制
从本地加载未完成的上传上下文:
public class UploadResumeManager {
public UploadContext loadOrCreate(File imageFile) {
File metaFile = new File(imageFile.getPath() + ".upload");
if (metaFile.exists()) {
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(metaFile))) {
UploadContext ctx = (UploadContext) ois.readObject();
if (ctx.getTotalSize() == imageFile.length() && !ctx.isCompleted()) {
return ctx; // 继续上传
} else {
metaFile.delete(); // 文件变更或已完成,清理
}
} catch (Exception ignored) {}
}
return new UploadContext(imageFile); // 全新上传
}
}
完整上传流程编排
整合预上传、分片循环与提交:
public class TimelineImageUploader {
public String uploadImage(File image) throws Exception {
UploadResumeManager resumeMgr = new UploadResumeManager();
UploadContext ctx = resumeMgr.loadOrCreate(image);
WeChatUploadClient client = new WeChatUploadClient();
if (ctx.getUploadId() == null) {
ctx.setUploadId(client.preUpload(ctx));
}
ChunkUploader chunkUploader = new ChunkUploader();
while (!ctx.isCompleted()) {
try {
chunkUploader.uploadChunk(ctx, client);
} catch (Exception e) {
// 可加入指数退避重试
Thread.sleep(1000);
continue; // 下次循环重试当前块
}
}
// 提交并获取 mediaId
return commitUpload(ctx, client);
}
private String commitUpload(UploadContext ctx, WeChatUploadClient client) throws Exception {
// 调用微信 commit 接口(伪代码)
RequestBody body = new FormBody.Builder()
.add("upload_id", ctx.getUploadId())
.build();
// ... 发送请求并解析 mediaId
return "mock_media_id_123";
}
}
通过分片读取、偏移确认、上下文持久化三重机制,该实现在网络中断或进程崩溃后可无缝恢复上传,精准复刻微信官方客户端的上传行为。
更多推荐



所有评论(0)