From 7b2d6b4894bc11dc71a26f68e92e3a21c2157a54 Mon Sep 17 00:00:00 2001
From: huangyujie <27665451@qq.com>
Date: Thu, 26 Mar 2026 09:46:28 +0800
Subject: [PATCH] =?UTF-8?q?fix(pdd):=20goods.image.upload=20=E7=9A=84=20im?=
=?UTF-8?q?age=20=E4=BD=BF=E7=94=A8=20data:image/*;base64,=20=E5=89=8D?=
=?UTF-8?q?=E7=BC=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
拼多多常见报「图片格式错误」因仅传裸 Base64;按魔数选 jpeg/png/gif/webp/bmp 后拼接 data URI。
Made-with: Cursor
---
.../service/external/pdd/PddPopClient.java | 48 ++++++++++++++++---
1 file changed, 41 insertions(+), 7 deletions(-)
diff --git a/service/src/main/java/cn/qihangerp/service/external/pdd/PddPopClient.java b/service/src/main/java/cn/qihangerp/service/external/pdd/PddPopClient.java
index dcd076ea..72330ba3 100644
--- a/service/src/main/java/cn/qihangerp/service/external/pdd/PddPopClient.java
+++ b/service/src/main/java/cn/qihangerp/service/external/pdd/PddPopClient.java
@@ -23,7 +23,7 @@ import java.util.Map;
*
{@link #invokeGoodsAdd} — {@code pdd.goods.add}:业务字段全部在表单顶层({@code sku_list}、{@code carousel_gallery} 等为 JSON 字符串),
* 无 {@code param_json}、无 {@code version}
* {@link #invokeTopLevelBiz} — 如 {@code pdd.goods.cat.rule.get} 的 {@code cat_id}/{@code goods_id} 等顶层字段
- * {@link #invokeGoodsImageUpload} — {@code pdd.goods.image.upload}:{@code application/x-www-form-urlencoded},{@code image} 为 Base64
+ * {@link #invokeGoodsImageUpload} — {@code pdd.goods.image.upload}:urlencoded,{@code image} 为 {@code data:image/*;base64,} + Base64
* {@link #invoke} — 仅当接口要求整包 {@code param_json} 时使用(少数场景)
*
*/
@@ -97,9 +97,9 @@ public class PddPopClient {
}
/**
- * {@code pdd.goods.image.upload}:与官方 curl/SDK 一致,{@code Content-Type: application/x-www-form-urlencoded}。
- * 开放平台参数 {@code image}:类型 STRING、必填;支持 jpg/jpeg、png 等,须为图片二进制经 Base64 编码后的字符串。
- * 本方法对下载/读取得到的原始字节使用 {@link Base64#getEncoder()}(标准 Base64,无 {@code data:image/...;base64,} 前缀)。
+ * {@code pdd.goods.image.upload}:{@code application/x-www-form-urlencoded}。
+ * 参数 {@code image} 为必填 STRING:常见对接要求为 {@code data:image/jpeg;base64,} 或 {@code data:image/png;base64,}
+ * 后接标准 Base64(无换行)。仅传裸 Base64 时网关可能返回「图片格式错误」。
*/
public String invokeGoodsImageUpload(String gatewayUrl, String clientId, String clientSecret, String accessToken,
byte[] imageBytes) throws Exception {
@@ -109,14 +109,48 @@ public class PddPopClient {
if (imageBytes == null || imageBytes.length == 0) {
throw new IllegalArgumentException("image 不能为空");
}
- String b64 = Base64.getEncoder().encodeToString(imageBytes);
+ String imageParam = buildPddGoodsImageUploadParam(imageBytes);
Map params = buildBaseParams(clientId, accessToken, "pdd.goods.image.upload", false);
- params.put("image", b64);
- String logPayload = "pdd.goods.image.upload image=base64(len=" + b64.length() + ") rawBytes=" + imageBytes.length;
+ params.put("image", imageParam);
+ String logPayload = "pdd.goods.image.upload image=data:*;base64,(totalChars=" + imageParam.length() + ") rawBytes="
+ + imageBytes.length;
return postSignedAndLog(gatewayUrl, clientId, clientSecret, "pdd.goods.image.upload", params, logPayload,
Duration.ofSeconds(180));
}
+ /**
+ * 组装 {@code pdd.goods.image.upload} 的 {@code image} 字段:{@code data:{mime};base64,}{base64}。
+ */
+ static String buildPddGoodsImageUploadParam(byte[] imageBytes) {
+ String mime = guessImageMimeForPddUpload(imageBytes);
+ String b64 = Base64.getEncoder().encodeToString(imageBytes);
+ return "data:" + mime + ";base64," + b64;
+ }
+
+ /** 按文件头识别 MIME;无法识别时按 jpeg 声明(仍可能因非图片内容被平台拒绝)。 */
+ private static String guessImageMimeForPddUpload(byte[] b) {
+ if (b == null || b.length < 12) {
+ return "image/jpeg";
+ }
+ if (b[0] == (byte) 0xFF && b[1] == (byte) 0xD8 && b[2] == (byte) 0xFF) {
+ return "image/jpeg";
+ }
+ if (b[0] == (byte) 0x89 && b[1] == 'P' && b[2] == 'N' && b[3] == 'G') {
+ return "image/png";
+ }
+ if (b[0] == 'G' && b[1] == 'I' && b[2] == 'F') {
+ return "image/gif";
+ }
+ if (b[0] == 'R' && b[1] == 'I' && b[2] == 'F' && b[3] == 'F'
+ && b[8] == 'W' && b[9] == 'E' && b[10] == 'B' && b[11] == 'P') {
+ return "image/webp";
+ }
+ if (b[0] == 'B' && b[1] == 'M') {
+ return "image/bmp";
+ }
+ return "image/jpeg";
+ }
+
private static Map buildBaseParams(String clientId, String accessToken, String type,
boolean withVersion) {
long ts = System.currentTimeMillis() / 1000L;