Repository navigation
v0.3.0
v0.3.0
日期:2026-10-19
这是 v0.2.6 之后的商家问答。设备码、授权范围、买家注册、买入开关、支付成功条件、商品描述、店面缓存、文本压缩范围、Caddy 匹配和安装回滚都没有改。没有新错误码,也没有新依赖。compose.yaml 与 v0.2.6 相同。install.sh 与 v0.2.6 相同。
v0.2.6 的公开页没有店家可维护的问答。把问答写进店铺介绍或商品描述,会在改店名、改简介或上下架时顺带发布或清掉。这一版把问答做成两份独立资源:全店一份,每件商品一份。平台不预置问题。保存介绍、上架或下架商品都不改问答行。
新增四条商家路由:GET 和 PUT /api/v1/merchant/faqs,以及 GET 和 PUT /api/v1/catalogs/{id}/products/{product_id}/faqs。公开读取不另开 FAQ 路径。GET /api/v1/store 的形状变了:介绍未发布但全店问答已公开时,可以只返回 { faqs }。调用方不能再把这个 200 单独理解成介绍已发布。
生产应用镜像是 docker.io/<用户名>/merclink:0.3.0。正式版同时推送 0.3.0 和 latest。要换应用镜像。进程启动时会执行新迁移 018_merchant_faqs.sql。不需要换 compose.yaml。
中文
这一版能做什么
- 新表
merchant_faqs。一个商家的全店问答一行,product_id为空;一件商品的问答一行,product_id指向该商品。published是布尔,items是只含question和answer的 JSON 数组。全店按商家唯一,商品按product_id唯一。不回填,不插入默认问题。软删除商品不删这一行。 - 两条
PUT都是整份替换,正文必须是application/json。published和items都必须出现,不能多字段。还没保存过时,GET返回published: false、items: []、updated_at: null,不插入行,也不是404。写入要商家令牌,并且要有product:write。没有新增 FAQ scope。买家或缺少product:write仍是forbidden。目录不拥有该商品,或商品已软删除,商品问答是not_found。 - 问题和答案必须是单行纯文本。换行、控制字符、HTML 和 Markdown 直接拒绝,不悄悄删掉。先去掉首尾空白,再用
Array.from量长度。问题 1 到 80 个字符,答案 1 到 400 个字符。全店最多 12 条,商品最多 8 条。同一份里去掉空白后相同的问题拒绝。published: true且条目为空时拒绝,并且不写库。published: false且条目为空会清空,公开页不再显示。登录手机号和账号邮箱不能出现在问题或答案里。拒绝仍是validation_error。 faqs成为保留系统字段名,和title、description、price、stock一样。不能新建这个目录字段 key。介绍PUT、商品创建和商品PATCH带上faqs也是validation_error,不会把问答写进fields。商家商品 JSON 不增加faqs。要读写问答,走上面的四条路由。- 全店问答只看自己的
published。介绍已发布时,GET /api/v1/store原有字段都在,并附加faqs;未公开或没有条目时faqs为[]。介绍未发布但全店问答已公开且非空时,200的data只有faqs,不带回介绍草稿。两者都不可见,或商家已停用、已删除时,仍是not_found。 - 公开商品详情附加已公开的
faqs。未公开时这个数组是[]。商品还要仍在公开查询结果里:已上架、未软删除,且商家有效。下架或软删除后,公开页不再显示该商品问答;商家侧的条目和published保留。重新上架后,仍公开的问答回到详情。商品列表、首页卡片和关键词q不带问答正文,也不按问答搜索。 - 内置首页的顺序是店头、商品货架、全店问答、联系方式和执照、使用指南。内置商品详情把「查看用户 Skill」留在商品信息下面。商品问答在详情正文之后单独占一整行。有可见条目时标题是一个
h2「常见问题」。问题用h3,答案转义后放在同一条 HTML 里。默认全部收起,同一页同时只展开一条,不依赖脚本。没有可见条目时不渲染这块。 /index.md和/products/{id}.md使用同一份已公开条目。有条目时单独成## 常见问题,空则省略。商品列表 Markdown 和/llms.txt不展开问答正文。有可见条目时另加一段FAQPageJSON-LD,不替换现有OnlineStore或Product。- 店面用重复区域
data-merclink="store.faq"和data-merclink="product.faq"。区域里只许faq.question和faq.answer。这两个槽位出现在区域外,或区域里再放别的槽位,模板不能发布。有可见条目时,服务端在该区域条目前插入恰好一个<h2>常见问题</h2>。没有条目则去掉区域,也不留标题。起始模板不写死这个标题。已经接受、而且没有这些区域的自定义店面保持原样,页面不会自动补问答。商家要在模板里声明区域后再发布,自定义店面才会出现这段文字。商家 Skill 和店面 Skill 已改成这套说明。买家 Skill 不新增管理路径。
这一版不做
- 不把问答放进
merchant_profiles或products的列,也不写入目录fields。 - 不把全店问答自动附到每件商品上,不预置问题,不参与关键词搜索。
- 不把问答放进首页卡片、商品列表或店铺简介。
- 不改「如何使用 MercLink」。
- 不渲染问答里的 Markdown 或 HTML。问题里的换行不会被收成空格,而是直接拒绝。
- 不改商品描述的读写、长度和
product.description槽位。 - 不改设备码流程、scope、买家注册或支付成功条件。只有订单状态
paid表示支付成功。调用方仍不能传价格。写入问答仍用已有的product:write。 - 不改店面 HTML 的
no-store,也不改 CSS、JS 的 10 分钟新鲜期和之后最多 24 小时的后台校验。 - 不改公开图片按 id 缓存一年的规则。缺失图片仍是
404。 - 不改 Caddy 的五行
Content-Type匹配,也不改镜像标签。仍是caddy:2.11.7-alpine。 - 不改一键安装询问的字段,不安装系统包,也不默认打开买入。安装和升级未完成时的回滚与 v0.2.6 相同。
- 不新增错误码或依赖。不激活店面,不补写历史发布,也不把 Docker Hub 用户名、令牌、域名或证书写进仓库。
升级注意
问答在应用镜像和数据库迁移里。compose.yaml 和 install.sh 与 v0.2.6 相同。不要删 .env,也不要删 caddy_data。不要改已经应用过的迁移;新文件只是 018_merchant_faqs.sql。
这次迁移没有目录字段前置检查。它只建 merchant_faqs 和两个唯一索引,不回填。已有商品、介绍和订单不受影响。升级完成后,新建目录字段不能再使用 key faqs。已经存在的同名目录字段不会被这次迁移停用或搬走。
一键安装过的机器,等这个正式版发布后执行:
curl -fsSL -o /tmp/merclink-install.sh https://github.com/ptonlix/MercLink/releases/latest/download/install.sh && bash /tmp/merclink-install.sh upgradeupgrade 会下载这个 tag 的 compose.yaml,保留已有密钥和后来手工打开的买入配置,然后 pull。它不打印已保存的密码。这次 compose.yaml 没有变化。升级如果没有完成,部署文件和版本号会回到升级前;已经换上的容器要再执行 docker compose up -d 才和文件一致。新容器要能跑完 018 才会正常服务。
手工部署的机器不要再跑一键安装。把 MERCLINK_IMAGE 改为 docker.io/<用户名>/merclink:0.3.0,然后执行:
docker compose pull && docker compose up -d不要加 --build。拉取完成且进程起来之后,公开页与 v0.2.6 一致,直到商家公开一份非空问答。还没有接受发布时,重新下载的起始店面带有 store.faq 和 product.faq。已经激活的自定义店面不会因此改版。
回滚时把应用镜像改回 docker.io/<用户名>/merclink:0.2.6,再 pull。不要把 latest 当作回滚点。latest 会在这个正式版发布时移到 0.3.0。0.2.6 不读 merchant_faqs。迁移已经成功时,不要为了回滚删掉这张表;删掉会丢掉这一版写入的问答。介绍、商品、订单金额、库存和支付状态不依赖这张表。
English
Date: 2026-10-19
This is merchant-managed FAQs after v0.2.6. The device-code flow, authorization scopes, buyer registration, purchase switch, the rule that only paid means payment success, product descriptions, storefront caching, the set of compressed types, the Caddy matchers, and install rollback are unchanged. There is no new error code and no new dependency. compose.yaml is the same file as v0.2.6. install.sh is the same file as v0.2.6.
In v0.2.6 the public pages have no FAQ a merchant can maintain. Putting questions into the store profile or a product description would publish or clear them whenever the name, summary, or shelf status changes. This version stores two separate resources: one for the shop, and one for each product. The platform does not insert default questions. Saving the profile, or publishing and unpublishing a product, does not change an FAQ row.
Four merchant routes are new: GET and PUT /api/v1/merchant/faqs, and GET and PUT /api/v1/catalogs/{id}/products/{product_id}/faqs. Public reads do not gain an FAQ-only path. The shape of GET /api/v1/store changes: when the profile is unpublished and the store FAQ is public, the response can be only { faqs }. A caller can no longer treat that 200 by itself as proof that the profile is published.
The production image is docker.io/<namespace>/merclink:0.3.0. The stable release also publishes latest. The application image has to be replaced. Process startup applies the new migration 018_merchant_faqs.sql. compose.yaml does not need to change.
What this version does
- A new table,
merchant_faqs. One row is a merchant's store FAQ, withproduct_idnull. One row is a product FAQ, withproduct_idpointing at that product.publishedis a boolean.itemsis a JSON array ofquestionandansweronly. The store row is unique per merchant. The product row is unique perproduct_id. Nothing is backfilled, and no default question is inserted. Soft-deleting a product does not delete the row. - Both
PUTroutes replace the whole document. The body must beapplication/json.publishedanditemsare both required, and extra fields are rejected. Before any save,GETreturnspublished: false,items: [], andupdated_at: null. It does not insert a row, and it is not404. A write needs a merchant token withproduct:write. There is no new FAQ scope. A buyer, or a token withoutproduct:write, is stillforbidden. A product FAQ for a product the catalog does not own, or a soft-deleted product, isnot_found. - Questions and answers must be single-line plain text. Line breaks, control characters, HTML, and Markdown are rejected rather than stripped. End whitespace is removed first, then length is
Array.from(...).length. A question is 1 to 80 characters. An answer is 1 to 400 characters. A store FAQ has at most 12 items. A product FAQ has at most 8. Two questions that match after trimming are rejected.published: truewith an empty list is rejected and is not written.published: falsewith an empty list clears the set, and the public page stops showing it. The sign-in phone number and the account email cannot appear in a question or an answer. Rejection staysvalidation_error. faqsis now a reserved system field name, liketitle,description,price, andstock. It cannot be created as a catalog field key. A profilePUT, product create, or productPATCHthat includesfaqsis alsovalidation_errorand does not store the FAQ infields. Merchant product JSON does not grow afaqsfield. Read and write the FAQ through the four routes above.- A store FAQ is visible only from its own
publishedflag. When the profile is published,GET /api/v1/storekeeps the existing fields and addsfaqs. That array is[]when the FAQ is unpublished or empty. When the profile is unpublished and the store FAQ is public and non-empty,200datais onlyfaqs, with no profile draft. When neither is visible, or the merchant is disabled or deleted, the response staysnot_found. - A public product detail adds the published
faqs. The array is[]when the FAQ is not public. The product must also still be in the public query: on the shelf, not soft-deleted, and owned by an active merchant. After unpublishing or soft-deleting, the public page hides that product FAQ. The merchant copy andpublishedflag remain. Publishing the product again brings a still-public FAQ back to the detail page. The product list, home cards, and keywordqdo not include FAQ text, and search does not match it. - Built-in home order is the store header, the product shelf, the store FAQ, contact and license, then the usage guide. Built-in product detail keeps “查看用户 Skill” under the product facts. The product FAQ is its own full-width row after that detail. When items are visible, the heading is one
h2, “常见问题”. Each question is anh3, and the escaped answer stays in the same HTML. Every item starts collapsed, only one item on a page is open, and this does not depend on script. With no visible items, the block is not rendered. /index.mdand/products/{id}.mduse the same published items. A non-empty set is its own## 常见问题section. An empty set omits the section. Product-list Markdown and/llms.txtdo not expand the FAQ text. A visible set also adds oneFAQPageJSON-LD document and does not replace the existingOnlineStoreorProductdocument.- Storefront templates use repeating regions
data-merclink="store.faq"anddata-merclink="product.faq". Inside a region onlyfaq.questionandfaq.answerare allowed. Either slot outside its region, or any other slot inside the region, makes the template unpublishable. When items are visible, the server inserts exactly one<h2>常见问题</h2>before the items in that region. An empty set removes the region and leaves no heading. The starter template does not hard-code that heading. An accepted custom storefront that does not declare the region stays as it is, and the page does not grow an FAQ on its own. The merchant declares the region and publishes again before a custom storefront shows the text. The merchant Skill and the storefront Skill now describe this. The buyer Skill does not gain a management path.
What this version does not do
- It does not store FAQs on
merchant_profilesorproducts, and it does not write them into catalogfields. - It does not copy the store FAQ onto every product, insert default questions, or include FAQ text in keyword search.
- It does not put FAQ text on home cards, the product list, or the store profile.
- It does not change “如何使用 MercLink”.
- It does not render Markdown or HTML inside a question or an answer. A line break in a question is rejected, not collapsed to a space.
- It does not change how product descriptions are read or written, their length rule, or the
product.descriptionslot. - It does not change the device-code flow, scopes, buyer registration, or the payment-success rule. Only order status
paidmeans payment succeeded. The caller still cannot send a price. FAQ writes keep using the existingproduct:writescope. - It does not change
no-storeon storefront HTML, or the 10-minute freshness window and later 24-hour background revalidation for CSS and JS. - It does not change the one-year cache for a public image id. A missing image is still
404. - It does not change Caddy's five
Content-Typelines or the image tag. It remainscaddy:2.11.7-alpine. - It does not change what one-click install asks for, it does not install system packages, and it does not turn purchase on by default. Rollback of an unfinished install or upgrade stays as it was in v0.2.6.
- It does not add an error code or a dependency. It does not activate a storefront, backfill old releases, or store a Docker Hub username, token, domain, or certificate in the repository.
Upgrade note
FAQs ship in the application image and in a database migration. compose.yaml and install.sh match v0.2.6. Do not delete .env or caddy_data. Do not edit a migration that has already been applied. The new file is only 018_merchant_faqs.sql.
This migration has no catalog-field precondition. It only creates merchant_faqs and two unique indexes. It does not backfill. Existing products, profiles, and orders stay as they are. After the upgrade, a new catalog field cannot use the key faqs. An existing catalog field with that key is not retired or copied by this migration.
After this stable release is published, a host installed by the script should run:
curl -fsSL -o /tmp/merclink-install.sh https://github.com/ptonlix/MercLink/releases/latest/download/install.sh && bash /tmp/merclink-install.sh upgradeupgrade downloads compose.yaml for this tag, keeps generated secrets and any later purchase settings, then pulls. It does not print stored passwords. This compose.yaml is unchanged. If this upgrade does not finish, the deploy files and version pin return to their previous values. Containers already recreated from the new image match those files only after another docker compose up -d. The new container serves traffic only after 018 finishes.
Do not run a fresh one-click install over a deployment that was created by hand. Set MERCLINK_IMAGE to docker.io/<namespace>/merclink:0.3.0, then run:
docker compose pull && docker compose up -dDo not add --build. After the pull, once the process is up, public pages stay as they were in v0.2.6 until a merchant publishes a non-empty FAQ. When no release has been accepted, a newly downloaded starter contains store.faq and product.faq. An already active custom storefront is not republished by this upgrade.
To roll back, set the application image to docker.io/<namespace>/merclink:0.2.6 and pull again. Do not use latest as the rollback point. latest moves to 0.3.0 when this stable release is published. 0.2.6 does not read merchant_faqs. After the migration has succeeded, do not drop that table just to roll back. Dropping it discards FAQs written by this version. Profiles, products, order amounts, stock, and payment status do not depend on the table.