fix(external-entry): preserve legacy private room links

This commit is contained in:
Codex
2026-07-27 11:28:27 +08:00
parent b21ef2f6ec
commit 01c55f5630
5 changed files with 89 additions and 36 deletions
+3 -1
View File
@@ -19,7 +19,7 @@ https://<APP_HOST>/external-entry?<参数>
| 参数 | 可选值或格式 | 用途 | | 参数 | 可选值或格式 | 用途 |
| --- | --- | --- | | --- | --- | --- |
| `target` | `chat``tip``private-zone` | 指定进入的页面;不传或值无效时进入聊天页。 | | `target` | `chat``tip``private-zone` | 指定进入的页面;不传或值无效时进入聊天页。历史值 `private-room` 兼容进入 `private-zone`,新链接不得继续使用。 |
| `character` | `elio``maya``nayeli` | 指定角色;不传或值无效时使用 Elio。 | | `character` | `elio``maya``nayeli` | 指定角色;不传或值无效时使用 Elio。 |
| `psid` | string | 传入 Facebook Page-scoped User ID。 | | `psid` | string | 传入 Facebook Page-scoped User ID。 |
| `mode` | `promotion` | 开启聊天促销模式。 | | `mode` | `promotion` | 开启聊天促销模式。 |
@@ -27,6 +27,8 @@ https://<APP_HOST>/external-entry?<参数>
`mode=promotion` 只有在 `target=chat` 且同时提供有效的 `promotion_type` 时生效。 `mode=promotion` 只有在 `target=chat` 且同时提供有效的 `promotion_type` 时生效。
标准私密空间参数是 `target=private-zone`。前端仅为已经发布的旧链接保留 `target=private-room` 兼容;`private_zone` 等其他拼写仍按无效目标处理并回退聊天页。
## PSID 与登录状态流程 ## PSID 与登录状态流程
PSID 保存、登录状态判断和 Facebook Identity 绑定逻辑见 [PSID 与登录状态流程图](./psid-login-flow.md)。 PSID 保存、登录状态判断和 Facebook Identity 绑定逻辑见 [PSID 与登录状态流程图](./psid-login-flow.md)。
+56 -33
View File
@@ -1,43 +1,66 @@
# 外部入口链接清单 # 外部入口链接清单
[返回外部入口接入说明](./README.md) 这份清单可以独立发送和直接点击。所有新链接统一使用标准参数 `target=private-zone`;历史参数 `target=private-room` 仅用于兼容已经发出的旧链接。
以下链接可以直接点击打开。PSID 示例统一使用 `27511427698460020` ## 使用规则
- `target=chat`:进入聊天页。
- `target=tip`:进入对应角色的打赏页。
- `target=private-zone`:进入对应角色的私密空间。
- `character` 可使用 `elio``maya``nayeli`;不传或值无效时使用 Elio。
- `mode=promotion` 只在 `target=chat``promotion_type``voice``image``private` 时生效。
- PSID 示例统一使用 `27511427698460020`;不要在入口中放登录 Token、Page Access Token 或 App Secret。
## 测试环境 ## 测试环境
| 入口 | 链接 | | 入口 | 可点击链接 | 预期落地页面 |
| --- | --- | | --- | --- | --- |
| 普通聊天 | [打开普通聊天](https://frontend-test.banlv-ai.com/external-entry?target=chat) | | 普通聊天Elio | [打开普通聊天](https://frontend-test.banlv-ai.com/external-entry?target=chat) | `/characters/elio/chat` |
| Maya 聊天 | [打开 Maya 聊天](https://frontend-test.banlv-ai.com/external-entry?target=chat&character=maya) | | Maya 聊天 | [打开 Maya 聊天](https://frontend-test.banlv-ai.com/external-entry?target=chat&character=maya) | `/characters/maya/chat` |
| Nayeli 聊天 | [打开 Nayeli 聊天](https://frontend-test.banlv-ai.com/external-entry?target=chat&character=nayeli) | | Nayeli 聊天 | [打开 Nayeli 聊天](https://frontend-test.banlv-ai.com/external-entry?target=chat&character=nayeli) | `/characters/nayeli/chat` |
| 携带 PSID 的聊天 | [打开 PSID 聊天示例](https://frontend-test.banlv-ai.com/external-entry?target=chat&psid=27511427698460020) | | 携带 PSID 的 Elio 聊天 | [打开 PSID 聊天示例](https://frontend-test.banlv-ai.com/external-entry?target=chat&psid=27511427698460020) | `/characters/elio/chat` |
| 语音促销 | [打开语音促销](https://frontend-test.banlv-ai.com/external-entry?target=chat&mode=promotion&promotion_type=voice) | | 语音促销 | [打开语音促销](https://frontend-test.banlv-ai.com/external-entry?target=chat&mode=promotion&promotion_type=voice) | `/characters/elio/chat`,保存语音促销意图 |
| 图片促销 | [打开图片促销](https://frontend-test.banlv-ai.com/external-entry?target=chat&mode=promotion&promotion_type=image) | | 图片促销 | [打开图片促销](https://frontend-test.banlv-ai.com/external-entry?target=chat&mode=promotion&promotion_type=image) | `/characters/elio/chat`,保存图片促销意图 |
| 私密文本促销 | [打开私密文本促销](https://frontend-test.banlv-ai.com/external-entry?target=chat&mode=promotion&promotion_type=private) | | 私密文本促销 | [打开私密文本促销](https://frontend-test.banlv-ai.com/external-entry?target=chat&mode=promotion&promotion_type=private) | `/characters/elio/chat`,保存私密文本促销意图 |
| 咖啡打赏 | [打开咖啡打赏](https://frontend-test.banlv-ai.com/external-entry?target=tip) | | Elio 咖啡打赏 | [打开 Elio 咖啡打赏](https://frontend-test.banlv-ai.com/external-entry?target=tip&character=elio) | `/characters/elio/tip` |
| Maya 咖啡打赏 | [打开 Maya 咖啡打赏](https://frontend-test.banlv-ai.com/external-entry?target=tip&character=maya) | | Maya 咖啡打赏 | [打开 Maya 咖啡打赏](https://frontend-test.banlv-ai.com/external-entry?target=tip&character=maya) | `/characters/maya/tip` |
| Nayeli 咖啡打赏 | [打开 Nayeli 咖啡打赏](https://frontend-test.banlv-ai.com/external-entry?target=tip&character=nayeli) | | Nayeli 咖啡打赏 | [打开 Nayeli 咖啡打赏](https://frontend-test.banlv-ai.com/external-entry?target=tip&character=nayeli) | `/characters/nayeli/tip` |
| 私密空间 | [打开私密空间](https://frontend-test.banlv-ai.com/external-entry?target=private-zone) | | Elio 私密空间 | [打开 Elio 私密空间](https://frontend-test.banlv-ai.com/external-entry?target=private-zone&character=elio) | `/characters/elio/private-zone` |
| Maya 私密空间 | [打开 Maya 私密空间](https://frontend-test.banlv-ai.com/external-entry?target=private-zone&character=maya) | | Maya 私密空间 | [打开 Maya 私密空间](https://frontend-test.banlv-ai.com/external-entry?target=private-zone&character=maya) | `/characters/maya/private-zone` |
| Nayeli 私密空间 | [打开 Nayeli 私密空间](https://frontend-test.banlv-ai.com/external-entry?target=private-zone&character=nayeli) | | Nayeli 私密空间 | [打开 Nayeli 私密空间](https://frontend-test.banlv-ai.com/external-entry?target=private-zone&character=nayeli) | `/characters/nayeli/private-zone` |
## 旧链接兼容验证
以下链接仅用于验证已经发出的 `private-room` 旧链接仍能正确进入私密空间。新投放链接不要再使用这些地址。
| 旧入口 | 可点击链接 | 预期落地页面 |
| --- | --- | --- |
| Elio 旧私密空间 | [验证 Elio 旧链接](https://frontend-test.banlv-ai.com/external-entry?target=private-room&character=elio) | `/characters/elio/private-zone` |
| Maya 旧私密空间 | [验证 Maya 旧链接](https://frontend-test.banlv-ai.com/external-entry?target=private-room&character=maya) | `/characters/maya/private-zone` |
| Nayeli 旧私密空间 | [验证 Nayeli 旧链接](https://frontend-test.banlv-ai.com/external-entry?target=private-room&character=nayeli) | `/characters/nayeli/private-zone` |
## 正式环境 ## 正式环境
| 入口 | 链接 | 正式环境仅列标准链接,不再传播 `private-room` 旧参数。
| --- | --- |
| 普通聊天 | [打开普通聊天](https://cozsweet.com/external-entry?target=chat) |
| Maya 聊天 | [打开 Maya 聊天](https://cozsweet.com/external-entry?target=chat&character=maya) |
| Nayeli 聊天 | [打开 Nayeli 聊天](https://cozsweet.com/external-entry?target=chat&character=nayeli) |
| 携带 PSID 的聊天 | [打开 PSID 聊天示例](https://cozsweet.com/external-entry?target=chat&psid=27511427698460020) |
| 语音促销 | [打开语音促销](https://cozsweet.com/external-entry?target=chat&mode=promotion&promotion_type=voice) |
| 图片促销 | [打开图片促销](https://cozsweet.com/external-entry?target=chat&mode=promotion&promotion_type=image) |
| 私密文本促销 | [打开私密文本促销](https://cozsweet.com/external-entry?target=chat&mode=promotion&promotion_type=private) |
| 咖啡打赏 | [打开咖啡打赏](https://cozsweet.com/external-entry?target=tip) |
| Maya 咖啡打赏 | [打开 Maya 咖啡打赏](https://cozsweet.com/external-entry?target=tip&character=maya) |
| Nayeli 咖啡打赏 | [打开 Nayeli 咖啡打赏](https://cozsweet.com/external-entry?target=tip&character=nayeli) |
| 私密空间 | [打开私密空间](https://cozsweet.com/external-entry?target=private-zone) |
| Maya 私密空间 | [打开 Maya 私密空间](https://cozsweet.com/external-entry?target=private-zone&character=maya) |
| Nayeli 私密空间 | [打开 Nayeli 私密空间](https://cozsweet.com/external-entry?target=private-zone&character=nayeli) |
如需在其他入口携带 PSID,在链接末尾追加 `&psid=27511427698460020` | 入口 | 可点击链接 | 预期落地页面 |
| --- | --- | --- |
| 普通聊天(Elio | [打开普通聊天](https://cozsweet.com/external-entry?target=chat) | `/characters/elio/chat` |
| Maya 聊天 | [打开 Maya 聊天](https://cozsweet.com/external-entry?target=chat&character=maya) | `/characters/maya/chat` |
| Nayeli 聊天 | [打开 Nayeli 聊天](https://cozsweet.com/external-entry?target=chat&character=nayeli) | `/characters/nayeli/chat` |
| 携带 PSID 的 Elio 聊天 | [打开 PSID 聊天示例](https://cozsweet.com/external-entry?target=chat&psid=27511427698460020) | `/characters/elio/chat` |
| 语音促销 | [打开语音促销](https://cozsweet.com/external-entry?target=chat&mode=promotion&promotion_type=voice) | `/characters/elio/chat`,保存语音促销意图 |
| 图片促销 | [打开图片促销](https://cozsweet.com/external-entry?target=chat&mode=promotion&promotion_type=image) | `/characters/elio/chat`,保存图片促销意图 |
| 私密文本促销 | [打开私密文本促销](https://cozsweet.com/external-entry?target=chat&mode=promotion&promotion_type=private) | `/characters/elio/chat`,保存私密文本促销意图 |
| Elio 咖啡打赏 | [打开 Elio 咖啡打赏](https://cozsweet.com/external-entry?target=tip&character=elio) | `/characters/elio/tip` |
| Maya 咖啡打赏 | [打开 Maya 咖啡打赏](https://cozsweet.com/external-entry?target=tip&character=maya) | `/characters/maya/tip` |
| Nayeli 咖啡打赏 | [打开 Nayeli 咖啡打赏](https://cozsweet.com/external-entry?target=tip&character=nayeli) | `/characters/nayeli/tip` |
| Elio 私密空间 | [打开 Elio 私密空间](https://cozsweet.com/external-entry?target=private-zone&character=elio) | `/characters/elio/private-zone` |
| Maya 私密空间 | [打开 Maya 私密空间](https://cozsweet.com/external-entry?target=private-zone&character=maya) | `/characters/maya/private-zone` |
| Nayeli 私密空间 | [打开 Nayeli 私密空间](https://cozsweet.com/external-entry?target=private-zone&character=nayeli) | `/characters/nayeli/private-zone` |
## 验收判断
打开入口后,浏览器地址最终应与“预期落地页面”一致。普通聊天和私密空间必须分别落在 `/chat``/private-zone`,不能再进入同一个页面。
如需在其他入口携带 PSID,在已有查询参数的链接末尾追加 `&psid=27511427698460020`
+1
View File
@@ -6,6 +6,7 @@
* `/external-entry?target=chat&mode=promotion&promotion_type=voice` * `/external-entry?target=chat&mode=promotion&promotion_type=voice`
* `/external-entry?target=chat&character=maya` * `/external-entry?target=chat&character=maya`
* `/external-entry?target=tip` * `/external-entry?target=tip`
* `/external-entry?target=private-zone&character=nayeli`
* *
* 页面不直接 `redirect()`,而是把数据交给 Client 组件先写入本地存储, * 页面不直接 `redirect()`,而是把数据交给 Client 组件先写入本地存储,
* 再通过 `router.replace()` 清理 URL 并跳转到最终页面。 * 再通过 `router.replace()` 清理 URL 并跳转到最终页面。
@@ -14,12 +14,15 @@ describe("external entry navigation", () => {
expect(resolveExternalEntryTarget({})).toBe(ROUTES.chat); expect(resolveExternalEntryTarget({})).toBe(ROUTES.chat);
}); });
it("resolves only canonical targets", () => { it("resolves canonical targets and the published private-room alias", () => {
expect(resolveExternalEntryTarget({ target: "chat" })).toBe(ROUTES.chat); expect(resolveExternalEntryTarget({ target: "chat" })).toBe(ROUTES.chat);
expect(resolveExternalEntryTarget({ target: "tip" })).toBe(ROUTES.tip); expect(resolveExternalEntryTarget({ target: "tip" })).toBe(ROUTES.tip);
expect(resolveExternalEntryTarget({ target: "private-zone" })).toBe( expect(resolveExternalEntryTarget({ target: "private-zone" })).toBe(
ROUTES.privateZone, ROUTES.privateZone,
); );
expect(resolveExternalEntryTarget({ target: "private-room" })).toBe(
ROUTES.privateZone,
);
}); });
it("rejects removed aliases and unsupported targets", () => { it("rejects removed aliases and unsupported targets", () => {
@@ -49,6 +52,28 @@ describe("external entry navigation", () => {
}), }),
).toBe(getCharacterRoutes("elio").privateZone); ).toBe(getCharacterRoutes("elio").privateZone);
}); });
it.each([
["chat", "elio", getCharacterRoutes("elio").chat],
["chat", "maya", getCharacterRoutes("maya").chat],
["chat", "nayeli", getCharacterRoutes("nayeli").chat],
["tip", "elio", getCharacterRoutes("elio").tip],
["tip", "maya", getCharacterRoutes("maya").tip],
["tip", "nayeli", getCharacterRoutes("nayeli").tip],
["private-zone", "elio", getCharacterRoutes("elio").privateZone],
["private-zone", "maya", getCharacterRoutes("maya").privateZone],
["private-zone", "nayeli", getCharacterRoutes("nayeli").privateZone],
["private-room", "elio", getCharacterRoutes("elio").privateZone],
["private-room", "maya", getCharacterRoutes("maya").privateZone],
["private-room", "nayeli", getCharacterRoutes("nayeli").privateZone],
])(
"routes target=%s character=%s to %s",
(target, character, expectedDestination) => {
expect(resolveExternalEntryDestination({ target, character })).toBe(
expectedDestination,
);
},
);
}); });
describe("external entry facebook identity binding", () => { describe("external entry facebook identity binding", () => {
+3 -1
View File
@@ -129,7 +129,9 @@ function resolveTarget(
if (target === "chat") return ROUTES.chat; if (target === "chat") return ROUTES.chat;
if (target === "tip") return ROUTES.tip; if (target === "tip") return ROUTES.tip;
if (target === "private-zone") return ROUTES.privateZone; if (target === "private-zone" || target === "private-room") {
return ROUTES.privateZone;
}
return null; return null;
} }