PHP 通过 LinkedIn API 发布带图片帖子,需要完成三个独立请求:先用 Images API 初始化上传,取得一次性 `uploadUrl` 和 `urn:li:image`;再把图片二进制上传到该 URL;最后调用 Posts API,把 Image URN 放入 `content.media.id`,同时提交作者、正文、可见性、分发方式和发布状态。图片的公网 URL 不能代替 Image URN,上传成功也不会自动创建帖子。
个人发帖通常需要 `w_member_social`,组织主页发帖需要 `w_organization_social`,且当前成员必须拥有允许的主页角色。所有 REST 请求要携带 Bearer Token、`Linkedin-Version` 和 `X-Restli-Protocol-Version: 2.0.0`。成功创建帖子返回 HTTP 201,帖子 ID 位于 `x-restli-id` 响应头;PHP 客户端必须保留响应头,不能只解析 JSON 正文。
第一步是 initialize,声明图片 owner。第二步是 upload,把原始字节发送到返回的上传地址。
第三步是 create post,引用初始化返回的 Image URN。每一步都有独立状态和错误,不能用一个布尔值概括。
应用数据库至少记录 draft、upload_initialized、binary_uploaded、published 和 failed,支持安全重试与人工排查。
互联网上很多 PHP 示例仍使用旧的 ugcPosts、assets 注册或 share 接口。LinkedIn 官方当前说明 Posts API 取代 ugcPosts API。
旧字段如 mediaCategory、registerUploadRequest 和 originalUrl 不应与新 Posts API 示例混搭。
实现前固定所用文档版本和 API 版本头,升级时按迁移指南验证载荷。
个人作者使用成员 URN,组织作者使用 `urn:li:organization:{id}`。初始化图片和创建帖子的 owner、author 应属于同一发布主体。
不能用个人拥有的图片 URN 创建组织帖子,或反向混用。跨主体资源通常会导致授权或所有权错误。
数据库把账号类型、作者 URN 与令牌身份绑定,不允许调用者任意提交 author 字符串。
`w_member_social` 允许代表经过身份验证的成员发布内容。组织发布使用 `w_organization_social`。
组织权限还要结合主页角色,官方列出的可发布角色包括管理员、内容管理员等授权角色。
OAuth Scope 出现在 Token 中不代表对任意组织都有权限,发布前查询并验证目标组织授权。
令牌存储在服务端 Secret 管理或加密数据库,不能传到浏览器、日志、异常页面或源码仓库。
每个用户或组织连接保存到期时间与授权主体。到期前刷新或重新授权,不能无限重试 401。
撤销连接时删除本地令牌,并停止所有等待发布的 Job。
`Linkedin-Version` 使用平台要求的 YYYYMM 版本值。不要把教程写作时的月份永久硬编码在散落代码中。
集中配置版本,发布日志记录实际值。升级版本时跑契约测试与沙箱验证。
`X-Restli-Protocol-Version` 使用 2.0.0,并在初始化、查询资源和创建帖子等 API 请求中保持一致。
向 Images API 的 initializeUpload action 发送 POST,JSON 中包含 `initializeUploadRequest.owner`。
成功响应的 `value` 内含 uploadUrl、uploadUrlExpiresAt 和 image。image 就是后续帖子使用的 URN。
立即保存这三个值,并验证 URN 以 `urn:li:image:` 开头。缺字段时不能进入上传步骤。
uploadUrl 是一次性或短期授权地址,响应给出过期时间。Job 排队过久时地址可能失效。
上传前检查过期时间并留安全余量;过期后重新初始化,不能继续向旧地址重试。
日志不打印完整上传地址,因为其中可能包含授权查询参数。
读取本地图片流,把原始字节发送到 uploadUrl。不要把 base64 字符串或 multipart 字段名想当然地套用。
上传请求的具体方法、Content-Type 和头部按初始化与官方文档执行。HTTP 客户端不要自动把流转换成 JSON。
大图使用流式 Body,避免一次把全部内容载入 PHP 内存。
Posts API 的图片内容引用 LinkedIn 管理的 Image URN,而不是网页上的 `https` 图片地址。
把 URL 放进 commentary 只会成为正文文字或链接,不会变成原生图片媒体。
如果目标是可点击链接预览,应创建 article 内容,而不是把图片帖子和链接卡片概念混合。
Images API 不支持同步上传选项,因此上传 HTTP 成功后,资源仍可能需要处理。
稳妥实现可用 Image URN 查询资源状态,等待可用后再创建帖子,并设置最大轮询次数。
不要用固定长时间 sleep;采用短退避,状态失败时保存平台错误并停止发帖。
向 Posts API 发送 POST。author 为成员或组织 URN,commentary 为帖子正文,visibility 通常按业务选择 PUBLIC。
distribution 指定主 Feed 和目标实体,lifecycleState 设置 PUBLISHED。`content.media.id` 使用已上传 Image URN。
媒体还可以带 altText,为无障碍访问提供准确图片说明。
基础字段包括 author、commentary、visibility、distribution、content、lifecycleState 和 isReshareDisabledByAuthor。
不要发送未经文档支持的旧字段。JSON 使用真正布尔值和数组,不能全部序列化成字符串。
服务端先构造数组,再由 JSON 编码器生成载荷,避免手拼 JSON 引号和 Unicode 转义错误。
把 LinkedIn 调用封装为专用 Client,提供 initializeImageUpload、uploadImageBytes、getImage 和 createImagePost 方法。
统一添加授权与版本头,但上传到预签名 URL 时不要盲目转发 Bearer Token,严格按上传端要求发送。
每个方法返回类型化结果,包含状态码、响应头、解析正文和请求 ID。
Laravel 项目可用 Http facade 发送 JSON 和流式请求。初始化与创建帖子设置 Bearer Token 和 REST 头。
上传二进制可使用 `withBody($stream, $mimeType)`,确保流在重试前能够重新定位或重新打开。
调用 `throw()` 前先按安全规则记录状态码和平台请求 ID,不记录令牌与上传 URL。
若使用 cURL,初始化和创建帖子设置 JSON Content-Type、正确 Content-Length,并保留响应头。
可通过 header callback 收集 `x-restli-id`。不能假设它存在于响应 JSON。
文件上传使用文件句柄和明确大小,完成后关闭资源;网络异常时读取 errno 与 HTTP 状态分别处理。
Posts API 成功创建返回 201,帖子 ID在 `x-restli-id` 头。部分 HTTP 库的头名称大小写可能不同。
通过客户端的无大小写 Header API 读取,保存完整 URN,不自行截取最后数字作为唯一标识。
201 但缺少 ID 应视为不确定结果,先查询或人工确认,不能立即再次创建导致重复帖子。
为每个发布任务生成内部 idempotency_key,绑定作者、正文哈希、图片哈希和计划时间。
在本地数据库建立唯一约束,只有一个 Worker能进入发布状态。平台调用前后都写事务状态。
网络在提交后断开时,结果可能已经发布。重试前根据已保存资源和平台记录核验。
图片上传与处理可能超过普通 Web 请求时间,控制器只创建发布任务并返回任务 ID。
队列 Job 执行三阶段流程,按账号设置并发和速率限制。同一草稿使用 WithoutOverlapping 或唯一 Job。
状态页面展示处理中、已发布、需重新授权和失败,不让用户反复点击生成重复任务。
400 多为载荷、URN 或版本问题,不应原样无限重试。401 需要检查令牌到期,403 检查 Scope 与主页角色。
404 可能是资源、作者或版本失效。429 按平台限流信息退避。5xx 和网络错误可有限重试。
每类错误映射稳定内部代码,界面提供可操作提示,原始响应仅在受限日志保存。
初始化失败可以重试;上传 URL 过期则重新初始化。二进制上传失败时要判断地址是否仍有效。
创建帖子超时最危险,因为平台可能已接收。优先查询或标记 `publish_unknown`,不要盲目重发。
使用指数退避和随机抖动,避免大量定时帖子在服务恢复时同时冲击 API。
上传前验证真实 MIME、尺寸、像素数量和文件大小。扩展名不足以证明文件类型。
解码一次图片,拒绝损坏、超大像素或包含不支持格式的数据。重新编码可清理不必要元数据。
不能让用户提交服务器任意路径或内网 URL,由后端抓取会引入目录遍历或 SSRF。
手机图片可能依赖 EXIF Orientation。上传前按方向旋转并移除定位等敏感 EXIF。
转换色彩空间后生成兼容格式,避免平台预览与本地不同。透明背景需要在目标格式中验证。
保留源图哈希与发布版本,后续审计能确定实际使用哪张图片。
altText 描述图片传递的信息,不重复“图片”字样,也不堆关键词。
装饰图片可以简短,图表应说明关键趋势。文本海报应概括核心文字。
长度与字符限制按当前 API 文档校验,不能让超长替代文本导致整帖失败。
图片帖子使用 `content.media`,点击通常围绕媒体展示,并不等同于外部链接卡片。
需要标题、描述、来源 URL 和缩略图时,使用 `content.article`,缩略图同样可先通过 Images API 获得 URN。
不要指望在图片媒体中增加 originalUrl 就让整张图跳转外站,新旧 API 行为不同。
commentary 可包含文字 URL,但呈现方式由 LinkedIn 客户端决定。它不会把图片资产自动转为可点击外链。
若外链是核心内容,选择 article post 并提供 source、title、description 和 thumbnail。
发帖前预览目标类型,避免业务方期待链接卡片却得到普通图片帖。
单图帖子使用 Images API 加 Posts API 的 media。多图自然帖子应使用官方 MultiImage API 流程。
不能把多个 Image URN 随意塞进单图 media 字段。轮播广告又是不同的赞助内容能力。
在应用领域模型中区分 single_image、multi_image、article 和 video,分别验证载荷。
令牌属于某成员,组织帖子仍依赖该成员在目标 Page 的角色。角色变化后已有连接可能失去发布能力。
发布前或定期同步组织权限,不在 UI 显示无权账号。403 时提示管理员重新检查角色。
不要通过共享个人账号解决组织授权,审计与撤销都会失控。
visibility 决定内容可见范围,distribution 控制 Feed 分发和目标实体。二者不是同一开关。
普通公开自然帖子使用文档支持的组合。定向、暗帖或赞助内容需要对应产品权限和字段。
服务端只暴露业务允许选项,不把任意 JSON 透传给平台。
发布前校验正文长度、空内容、禁止词、提及格式和媒体版权。组织内容可进入人工审批。
审批记录绑定正文哈希、图片哈希、作者 URN 和发布类型。审批后任何变化都重新审核。
定时发布在执行前再次确认账号授权和内容未被撤回。
social_posts 保存内部 ID、author_urn、type、commentary、status、scheduled_at、platform_post_urn 和版本。
social_media 保存文件哈希、本地路径、MIME、image_urn、upload_url_expires_at 与处理状态。
social_publish_attempts 保存阶段、状态码、平台请求 ID、错误类别和时间,不保存秘密。
账号连接、草稿、图片和任务全部绑定 tenant_id。每次加载发布任务重新验证租户所有权。
author URN 从已授权连接派生,不接收前端任意值。Image URN 也只能引用该草稿已上传资产。
队列 Job只传内部 ID,执行时在服务端重新读取令牌与权限。
数据库令牌使用应用级加密,并由专用密钥保护。日志序列化模型时排除 token 字段。
生产、预发布与开发使用不同 LinkedIn 应用和回调地址,不能共享真实组织令牌。
密钥轮换需要重新加密或支持历史解密键,确保连接不会静默损坏。
按应用、成员和组织维度记录调用量。429 后尊重平台返回的限制信息。
初始化、状态查询和发帖都消耗请求,轮询不能过密。队列调度加入全局与账号级漏斗。
达到内部预算时延迟非紧急帖子,并在 UI 显示预计执行时间。
记录每阶段耗时、成功率、HTTP 状态、重试次数和上传字节。用 trace_id 贯穿三次请求。
告警区分授权大面积失效、版本头过期、上传失败和 Posts API 故障。
不要把完整 commentary、图片 URL、Bearer Token 或预签名上传地址放入普通指标标签。
使用 Mock HTTP Client 验证 initialize 请求的 owner、必要头和 JSON 结构。
验证上传阶段发送原始二进制且不携带错误的 JSON Content-Type。验证创建帖引用同一 Image URN。
模拟 201 并只在响应头提供 x-restli-id,确保客户端能正确保存。
覆盖初始化缺 uploadUrl、上传地址过期、图片处理失败、创建帖 401、403、429 和超时未知结果。
验证不可重试错误只执行一次,可重试错误遵守上限,未知结果不会直接重新发帖。
验证令牌、上传 URL 和图片二进制没有出现在日志快照。
在受控测试账号发布一张小图,确认平台显示正文、图片和 altText,并能通过返回 URN 查询。
分别测试个人与组织作者,不能用一种成功推断另一种授权也正确。
API 版本升级前重复集成测试,检查字段、响应头和资源状态变化。
测试帖子应带可识别前缀和运行 ID,完成后按平台能力删除或记录待人工清理。
不能让 CI 在真实公司主页持续发布测试内容。生产发布测试需要独立审批。
测试图片和本地临时文件按保留策略删除,避免个人信息残留。
最常见错误是直接把图片 URL 放进帖子、遗漏上传二进制步骤,或把 uploadUrl 当作 Image URN。
其次是 owner 与 author 不一致、缺版本头、使用旧 ugcPosts 载荷、忽略主页角色和不读取响应头。
还有把图片帖当链接卡片、创建超时后盲目重试、在日志泄露预签名 URL 等工程问题。
确认使用当前 Images API 与 Posts API,版本头集中配置,旧接口字段已移除。
确认个人和组织 Scope、角色、author 与 owner 一致性经过验证。
确认初始化、二进制上传、状态确认和创建帖子分阶段持久化,支持幂等恢复。
确认 201 的 x-restli-id 被读取,未知提交结果不会制造重复帖子。
确认图片验证、altText、Token 加密、日志脱敏、速率限制和契约测试完整。
PHP 发布 LinkedIn 图片帖的关键不是一段 cURL,而是正确实现 Images API 与 Posts API 的三阶段状态机。
先初始化获得 uploadUrl 和 Image URN,再上传原始图片字节,最后把 URN 放进 `content.media.id` 创建帖子。个人与组织使用不同权限,owner 和 author 必须匹配。
生产实现还要保存响应头中的帖子 URN,处理上传过期、资源状态、未知提交、幂等、限流和令牌安全。明确区分原生图片帖与 article 链接卡片,才能得到符合预期且可维护的发布结果。