流水线到商店的产品与构建 ingest 接口
商店是一个 WooCommerce 站点,但它的产品页和构建下载并不靠手工维护——由一条 CI 打包流水线推送进来。商店上两个 REST 端点,都是 youteacher/v1 命名空间下的 POST,构成这个 ingest 面:一个用来记录新构建出来的二进制文件放在哪里,一个用来从游戏自己的清单(manifest)出发,创建或刷新整份产品列表。两者合在一起,让一次构建跑完时商店就已经更新好了,中间不需要人工介入。
slug 就是整个映射。 流水线只知道两件事:它在构建哪个游戏(游戏的仓库名),以及它刚产出的是哪个平台。它不知道、也不携带任何 WooCommerce 产品 id。把两个系统绑在一起的约定是:产品 slug 等于游戏 slug——这个映射由商店持有,两个端点都只是按路径在 product 文章类型下查一次来定位产品。这让流水线对商店保持无状态:它报出游戏名,商店去找对应的那一行。
/set-build 记录某个平台的构建位置。 它的请求体带着游戏、一个平台,以及一个指向私有对象存储中构建产物的对象键。接受三个平台:两个供下载的桌面构建,以及一个供浏览器里直接玩的 web 构建。端点会把平台校验到那个固定集合里,按 slug 找到产品(若该游戏还没有产品则返回 404),并把这个键写进该产品上一个按平台区分的元字段。之后下载路由和游玩路由读的正是这些按平台区分的字段,所以这一次写入,就是让一个构建对买家变得可达的那一步。
/upsert-product 从游戏清单构建列表。 它的请求体是游戏 game.json 里的 store 块——标语、价格、分类、适合谁、怎么玩、功能卡片、包含什么、设置步骤——外加一个可选的图片列表。处理器按 slug 幂等:若已存在该 slug 的产品就更新,否则用该 slug 新建一个虚拟简单产品,且从不删除任何东西。它设置名称、价格、短描述和分类,并把结构化的清单字段渲染成一段带样式的描述 HTML。每个字段都是可选且带守卫的,整个处理器还被包了起来,好让一个畸形的请求体返回 JSON 错误而不是致命错误——一份坏清单永远打不垮这个端点。
图片画廊是可选的、容错的、并按内容去重的。 图片以 base64 编码随同一份请求体到达。在做任何事之前,处理器会对到来的图片算一个内容指纹;若它和上次成功旁加载(sideload)时存下的指纹相同,画廊这一步就整个跳过——用没变的美术素材重跑流水线不会产生重复媒体。当图片确实变了,每一张都被独立解码并旁加载进媒体库:单张坏图会被跳过并记进一个 warnings 列表,而不是让整批中止。第一张成功的图成为主图,其余的成为画廊。若每一张都失败,现有画廊会被原样保留而不是被清空——一次失败的上传永远不会把一份本来看着没问题的列表弄坏。
两个端点都由一个共享密钥鉴权。 每个都带一道权限检查,把请求头里提供的密钥与一个仅存在于环境中的值做恒定时间比较,且除非确实配置了密钥否则一律拒绝。这是一个机器对机器的面——流水线持有密钥,没有任何浏览器或买家会碰到这些路由——并被刻意保持狭窄:只做创建或更新,绝不破坏性删除。
相关
- youteacher-store ——这个 ingest 面所在的商店
- youteacher ——商店只是其中一块的那个平台