fix(pdd): goods.image.upload 的 image 使用 data:image/*;base64, 前缀

拼多多常见报「图片格式错误」因仅传裸 Base64;按魔数选 jpeg/png/gif/webp/bmp 后拼接 data URI。

Made-with: Cursor
This commit is contained in:
huangyujie 2026-03-26 09:46:28 +08:00
parent cc20c98168
commit 7b2d6b4894
1 changed files with 41 additions and 7 deletions

View File

@ -23,7 +23,7 @@ import java.util.Map;
* <li>{@link #invokeGoodsAdd} {@code pdd.goods.add}业务字段全部在表单顶层{@code sku_list}{@code carousel_gallery} 等为 JSON 字符串
* <b></b> {@code param_json}<b></b> {@code version}</li>
* <li>{@link #invokeTopLevelBiz} {@code pdd.goods.cat.rule.get} {@code cat_id}/{@code goods_id} 等顶层字段</li>
* <li>{@link #invokeGoodsImageUpload} {@code pdd.goods.image.upload}{@code application/x-www-form-urlencoded}{@code image} Base64</li>
* <li>{@link #invokeGoodsImageUpload} {@code pdd.goods.image.upload}urlencoded{@code image} {@code data:image/*;base64,} + Base64</li>
* <li>{@link #invoke} 仅当接口要求整包 {@code param_json} 时使用少数场景</li>
* </ul>
*/
@ -97,9 +97,9 @@ public class PddPopClient {
}
/**
* {@code pdd.goods.image.upload}与官方 curl/SDK 一致{@code Content-Type: application/x-www-form-urlencoded}
* <p>开放平台参数 {@code image}类型 STRING<b>必填</b>支持 jpg/jpegpng <b>须为图片二进制经 Base64 编码后的字符串</b>
* 本方法对下载/读取得到的原始字节使用 {@link Base64#getEncoder()}标准 Base64 {@code data:image/...;base64,} 前缀</p>
* {@code pdd.goods.image.upload}{@code application/x-www-form-urlencoded}
* <p>参数 {@code image} 为必填 STRING常见对接要求为 {@code data:image/jpeg;base64,} {@code data:image/png;base64,}
* 后接<strong>标准 Base64</strong>无换行仅传裸 Base64 时网关可能返回图片格式错误</p>
*/
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<String, String> 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<String, String> buildBaseParams(String clientId, String accessToken, String type,
boolean withVersion) {
long ts = System.currentTimeMillis() / 1000L;