使用最新LinkedIn API发布图片帖失败,无错误返回
问题
我可以正常发布纯文本帖,但图片帖无法成功发布,且未收到任何错误提示。已遵循LinkedIn官方API文档,但响应中未包含文档提及的x-linkedin-id。以下是我的Next.js后端代码:
import dbConnect from "../../../lib/dbConnect"; import { getSession } from "next-auth/react"; import formidable from "formidable"; const fs = require("fs").promises; export const config = { api: { bodyParser: false }, }; export default async function handler(req, res) { const { method } = req; await dbConnect(); switch (method) { case "POST": try { const session = await getSession({ req }); if (!session || !session.accessToken) { console.error("No session or accessToken found."); return res.status(401).json({ error: "Not authorized" }); } const accessToken = session.accessToken; console.log("accessToken>>>", accessToken); const linkedinId = "myLinkedInID"; const data = await new Promise(function (resolve, reject) { const form = new formidable.IncomingForm(); form.uploadDir = "/tmp"; form.keepExtensions = true; form.multiples = true; form.parse(req, async function (err, fields, files) { if (err) return reject(err); resolve({ fields, files }); }); }); req.body = data.fields; let body = undefined; const fileArray = Object.values(data.files); switch (req.body.type) { case "text": body = createTextPost(req.body.content, linkedinId); break; case "singleImage": const tempAsset = await uploadFileToLinkedIn( fileArray[0], accessToken, "image", linkedinId ); body = createSingleImagePost(req.body.content, tempAsset, linkedinId); break; default: return res.status(400).json({ error: "Invalid content type" }); } const url = "https://api.linkedin.com/rest/posts"; const response = await fetch(url, { method: "POST", headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json", "X-Restli-Protocol-Version": "2.0.0", "LinkedIn-Version": "202307", }, body: JSON.stringify(body), }); console.log("Status>>>>", response.ok); if (response.ok) { console.log("headers>>>>", response.headers); const linkedinId = response.headers.get("x-linkedin-id"); console.log("x-linkedin-id>>>>", linkedinId); if (response.bodyUsed) { const data = await response.json(); console.error("data>>>>", data); } } else { const text = await response.text(); } res.status(200).json({ success: true }); } catch (error) { console.error("LinkedIn Error", error); res.status(500).json({ error: "Internal Server Error" }); } break; } function createTextPost(content, linkedinId) { return { author: `urn:li:person:${linkedinId}`, commentary: content, visibility: "PUBLIC", distribution: { feedDistribution: "MAIN_FEED", targetEntities: [], thirdPartyDistributionChannels: [], }, lifecycleState: "PUBLISHED", isReshareDisabledByAuthor: false, }; } function createSingleImagePost(content, media, linkedinId) { return { author: `urn:li:person:${linkedinId}`, commentary: content, visibility: "PUBLIC", distribution: { feedDistribution: "MAIN_FEED", targetEntities: [], thirdPartyDistributionChannels: [], }, content: { media: { altText: "testing for alt tags", id: media, }, }, lifecycleState: "PUBLISHED", isReshareDisabledByAuthor: false, }; } async function registerUpload(accessToken, owner, fileSize, mediaType) { const registerUrl = "https://api.linkedin.com/rest/images?action=initializeUpload"; const body = { initializeUploadRequest: { owner: owner, }, }; const response = await fetch(registerUrl, { method: "POST", headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json", "X-Restli-Protocol-Version": "2.0.0", "LinkedIn-Version": "202307", }, body: JSON.stringify(body), }); if (!response.ok) { response .text() .then((data) => { console.error("Failed to register:", data); }) .catch((error) => { console.error("Error parsing response:", error); }); throw new Error("Failed to register upload."); } return await response.json(); } async function uploadMedia(accessToken, uploadUrl, fileContent, mimetype) { var type = "jpg"; if (mimetype == "image/jpeg") { type = "jpeg"; } else if (mimetype == "image/png" || mimetype == "png") { type = "png"; } else if (mimetype == "image/gif" || mimetype == "gif") { type = "gif"; } else if (mimetype == "image/svg+xml" || mimetype == "svg") { type = "svg"; } else if (mimetype == "application/pdf" || mimetype == "pdf") { type = "pdf"; } const obj = { data: fileContent, mimeType: `application/${mimetype}`, }; try { const response = await fetch(uploadUrl, { method: "PUT", headers: { Authorization: `Bearer ${accessToken}`, "X-Restli-Protocol-Version": "2.0.0", "LinkedIn-Version": "202307", "Content-Type": `multipart/form-data`, }, body: fileContent, }); if (!response.ok) { const data = await response.text(); console.error("Failed to upload media:", data); throw new Error("Failed to upload media."); } } catch (error) { console.log("Upload Media Error>>>>", error); } } async function uploadFileToLinkedIn(element, accessToken, mediaType, linkedinId) { const owner = `urn:li:person:${linkedinId}`; const fileSize = element.size; let fileContent = await fs.readFile(element.filepath); let type = element.mimetype.split("/")[1]; const registerResponse = await registerUpload( accessToken, owner, fileSize, mediaType ); const uploadUrl = registerResponse.value.uploadUrl; const asset = registerResponse.value.image; try { await uploadMedia(accessToken, uploadUrl, fileContent, type); } catch (error) { console.log("Upload Media FUnction>>>>", error); } return asset; } }
问题排查与修复方案
1. 媒体上传的Content-Type完全错误
你的uploadMedia函数存在两个关键错误:
- 将图片上传的Content-Type设为
multipart/form-data,这是表单上传的类型,直接上传图片应使用对应图片的MIME类型(如image/jpeg、image/png)。 - 错误构造了
application/${mimetype}的MIME类型,完全不符合图片格式要求。
修复代码:
修改uploadMedia函数,直接使用文件原始MIME类型,去掉多余类型判断:
async function uploadMedia(accessToken, uploadUrl, fileContent, actualMimeType) { try { const response = await fetch(uploadUrl, { method: "PUT", headers: { Authorization: `Bearer ${accessToken}`, "X-Restli-Protocol-Version": "2.0.0", "LinkedIn-Version": "202307", "Content-Type": actualMimeType, // 使用文件原始MIME类型 }, body: fileContent, }); if (!response.ok) { const data = await response.text(); console.error("Failed to upload media:", data); throw new Error("Failed to upload media."); } } catch (error) { console.log("Upload Media Error>>>>", error); throw error; // 抛出错误让上层感知上传失败 } }
同时修改uploadFileToLinkedIn中的调用,传入正确的MIME类型:
// 替换原type变量定义 const actualMimeType = element.mimetype; // ... await uploadMedia(accessToken, uploadUrl, fileContent, actualMimeType);
2. 图片帖子的Content结构不符合API要求
LinkedIn API要求图片帖子的content.media是数组类型,而非单个对象。你当前直接传递单个media对象,导致API无法正确识别。
修复代码:
修改createSingleImagePost函数:
function createSingleImagePost(content, media, linkedinId) { return { author: `urn:li:person:${linkedinId}`, commentary: content, visibility: "PUBLIC", distribution: { feedDistribution: "MAIN_FEED", targetEntities: [], thirdPartyDistributionChannels: [], }, content: { media: [ // 改为数组格式 { altText: "testing for alt tags", id: media, } ], }, lifecycleState: "PUBLISHED", isReshareDisabledByAuthor: false, }; }
3. 未等待图片处理完成就发帖
LinkedIn上传图片后需要一段时间处理文件,状态变为READY才能用于发帖。你当前上传后直接返回asset,可能媒体仍处于PROCESSING状态,导致发帖失败。
修复代码:
添加轮询函数等待媒体就绪:
async function waitForMediaReady(accessToken, assetUrn) { const checkUrl = `https://api.linkedin.com/rest/images/${assetUrn.split(":").pop()}`; let retries = 10; let interval = 2000; // 每2秒检查一次 while (retries > 0) { const response = await fetch(checkUrl, { headers: { Authorization: `Bearer ${accessToken}`, "X-Restli-Protocol-Version": "2.0.0", "LinkedIn-Version": "202307", }, }); if (response.ok) { const data = await response.json(); if (data.status === "READY") { return true; } else if (data.status === "FAILED") { throw new Error("Media processing failed"); } } retries--; await new Promise(resolve => setTimeout(resolve, interval)); } throw new Error("Media processing timed out"); }
在uploadFileToLinkedIn中调用该函数:
await uploadMedia(accessToken, uploadUrl, fileContent, actualMimeType); await waitForMediaReady(accessToken, asset); // 等待媒体处理完成
4. 响应处理逻辑错误
你在处理posts接口响应时,if (response.bodyUsed)的判断逻辑完全反转——bodyUsed为true表示body已被读取,此时再读取会报错。此外,即使response.ok,也应读取响应内容获取post的ID或其他信息。
修复代码:
if (response.ok) { console.log("headers>>>>", response.headers); const linkedinId = response.headers.get("x-linkedin-id"); console.log("x-linkedin-id>>>>", linkedinId); // 直接读取响应内容 const data = await response.json(); console.log("Post response data>>>>", data); } else { const text = await response.text(); console.error("Error response>>>>", text); // 开启错误日志便于排查 throw new Error(`Post creation failed: ${text}`); }
5. 错误处理不彻底
uploadMedia的catch块仅打印错误但未抛出,导致上层无法感知上传失败,仍会使用无效asset发帖。该问题已在第1点修复中通过throw error解决。
内容的提问来源于stack exchange,提问作者Anirudh
相关产品推荐
相关产品推荐

