IA 迁移与别名策略
旧版文档是扁平目录(/docs/*.html),新版按 IA 分层(/docs/guides/**、/docs/platform-chat-api/**)。为了不让外部收藏、搜索引擎与旧链接 404,生成器维护一份唯一别名清单:既产出跳转页,也产出 redirects.json 供静态服务器返回真正的 301。本页的别名表由生成器代码直接渲染,不是手写清单。
策略
- canonical 唯一:每个主题只有一个规范地址(新 IA);旧地址只作为别名存在,不再生成内容页。
- 两类别名:
legacy= 历史真实存在的/docs/*.html;compat= 为常用页面提供的稳定短链(如/docs/push),不是历史路径。 - 生成期校验:别名目标必须是本次真实生成的页面;不允许链式跳转;别名生成物不得与页面路径冲突,否则构建直接失败。
- 双通道兼容:静态页面内是
<meta http-equiv="refresh">+<link rel="canonical">+location.replace();服务器读取redirects.json后返回 HTTP 301。 - 不删旧地址:禁用别名等于制造 404;如需下线,先确认没有外部引用,再改为指向新页面的说明页。
| 生成物 | 作用 | 消费者 |
|---|---|---|
docs-site/*.html + **/index.html | 跳转页(meta refresh + canonical) | 静态托管 / 浏览器 |
docs-site/redirects.json | 机器可读别名表(from/to/status/kind) | 静态服务器、反向代理、CI 链接检查 |
generated-manifest.json#compatPaths | 本次构建的全部别名 URL | 回归/验收脚本 |
| 本页别名表 | 人工可读的迁移说明 | 文档读者 |
静态服务器如何实现真正的 301
生成器只写文件,不控制 HTTP 状态码。生产环境请让服务器读取 redirects.json;下面两个片段足以落地:
nginx
# 由 CI 把 redirects.json 转成 map(jq -r '.redirects[] | "\(.from) \(.to);"')
map $uri $im_alias_target {
include /etc/nginx/im-aliases.map;
}
server {
listen 443 ssl;
location /docs/ {
if ($im_alias_target) { return 301 $im_alias_target; }
try_files $uri $uri/index.html $uri.html =404;
}
}JavaScript
// 最小静态服务器(Node):先查别名,再回落到文件
import { readFileSync, existsSync, statSync } from 'node:fs';
import { join, extname } from 'node:path';
import { createServer } from 'node:http';
const root = 'docs-site';
const { redirects } = JSON.parse(readFileSync(join(root, 'redirects.json'), 'utf8'));
const alias = new Map(redirects.map((entry) => [entry.from, entry.to]));
createServer((req, res) => {
const path = decodeURIComponent(new URL(req.url, 'http://x').pathname);
if (alias.has(path)) {
res.writeHead(301, { location: alias.get(path) });
res.end();
return;
}
const rel = path.replace(/^\/docs\/?/, '');
const candidates = [join(root, rel), join(root, rel, 'index.html'), extname(rel) ? null : join(root, rel + '.html')].filter(Boolean);
const file = candidates.find((candidate) => existsSync(candidate) && statSync(candidate).isFile());
if (!file) { res.writeHead(404).end('not found'); return; }
res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
res.end(readFileSync(file));
}).listen(8080);别名表(63 条)
旧版扁平地址(legacy,54 条)
| 旧地址 | 新地址(canonical) | 状态 |
|---|---|---|
/docs/quickstart.html | /docs/guides/realtime-chat/client-setup | 301 |
/docs/concepts.html | /docs/guides | 301 |
/docs/message-flow.html | /docs/guides/realtime-chat/intro-chat/message-flow | 301 |
/docs/guides-basic.html | /docs/guides/realtime-chat/intro-chat/im-feature-basic | 301 |
/docs/guides-advanced.html | /docs/guides/realtime-chat/intro-chat/im-feature-advanced | 301 |
/docs/server-integration.html | /docs/guides/rest-apis/im-server-integration | 301 |
/docs/glossary.html | /docs/guides/glossary/imglossary | 301 |
/docs/faq.html | /docs/faq | 301 |
/docs/changelog.html | /docs/changelog | 301 |
/docs/migration.html | /docs/guides/realtime-chat/migration | 301 |
/docs/console-guide.html | /docs/console-guide | 301 |
/docs/demo-guide.html | /docs/demo-guide | 301 |
/docs/samples.html | /docs/samples | 301 |
/docs/api.html | /docs/platform-chat-api | 301 |
/docs/api-reference.html | /docs/platform-chat-api | 301 |
/docs/api-user.html | /docs/platform-chat-api/user | 301 |
/docs/api-message.html | /docs/platform-chat-api/message | 301 |
/docs/api-group.html | /docs/platform-chat-api/group | 301 |
/docs/api-chatroom.html | /docs/platform-chat-api/chatroom | 301 |
/docs/api-supergroup.html | /docs/platform-chat-api/supergroup | 301 |
/docs/api-conversation.html | /docs/platform-chat-api/conversation | 301 |
/docs/api-friend.html | /docs/platform-chat-api/friend | 301 |
/docs/api-blacklist.html | /docs/platform-chat-api/blacklist | 301 |
/docs/api-presence.html | /docs/platform-chat-api/presence | 301 |
/docs/api-sensitive-word.html | /docs/platform-chat-api/sensitive-word | 301 |
/docs/api-callback.html | /docs/platform-chat-api/callback | 301 |
/docs/api-service.html | /docs/platform-chat-api/service | 301 |
/docs/api-stats.html | /docs/platform-chat-api/stats | 301 |
/docs/api-agent.html | /docs/platform-chat-api/agent | 301 |
/docs/api-bot.html | /docs/platform-chat-api/bot | 301 |
/docs/api-push.html | /docs/platform-chat-api/push | 301 |
/docs/errors.html | /docs/platform-chat-api/code | 301 |
/docs/callbacks.html | /docs/platform-chat-api/callback | 301 |
/docs/protocol.html | /docs/protocol | 301 |
/docs/sdk-web.html | /docs/web-imlib | 301 |
/docs/sdk-web-imlib.html | /docs/web-imlib | 301 |
/docs/sdk-web-imkit.html | /docs/web-imkit | 301 |
/docs/sdk-flutter.html | /docs/flutter-imlib | 301 |
/docs/sdk-flutter-imlib.html | /docs/flutter-imlib | 301 |
/docs/sdk-flutter-imkit.html | /docs/flutter-imkit | 301 |
/docs/sdk-native.html | /docs/native-imlib | 301 |
/docs/sdk-native-imlib.html | /docs/native-imlib | 301 |
/docs/sdk-native-imkit.html | /docs/native-imkit | 301 |
/docs/sdk-android.html | /docs/android-imlib | 301 |
/docs/sdk-android-imkit.html | /docs/android-imkit | 301 |
/docs/sdk-electron.html | /docs/electron-imlib | 301 |
/docs/sdk-macos.html | /docs/macos | 301 |
/docs/sdk-macos-imlib.html | /docs/macos-imlib | 301 |
/docs/sdk-macos-imkit.html | /docs/macos-imkit | 301 |
/docs/sdk-ios.html | /docs/ios-imlib | 301 |
/docs/sdk-ios-imkit.html | /docs/ios-imkit | 301 |
/docs/sdk-ios-objc.html | /docs/ios-imlib/objc | 301 |
/docs/sdk-harmony.html | /docs/harmony-imlib | 301 |
/docs/demo.html | /docs/demo-guide | 301 |
稳定短链(compat,9 条)
| 短链 | 新地址(canonical) | 用途 | 状态 |
|---|---|---|---|
/docs/quickstart | /docs/guides/realtime-chat/client-setup | 客户端集成总览的短链 | 301 |
/docs/macos-desktop | /docs/macos | macOS 桌面端文档分区短链 | 301 |
/docs/ios-objc | /docs/ios-imlib/objc | iOS Objective-C 接入短链 | 301 |
/docs/push | /docs/guides/push | 推送通道与投递 | 301 |
/docs/translation | /docs/guides/translation | 翻译能力 | 301 |
/docs/user-profile | /docs/guides/user-profile-hosting | 用户信息托管 | 301 |
/docs/server-sdk | /docs/guides/server-sdk/node | App Server SDK(Node) | 301 |
/docs/limits | /docs/platform-chat-api/limits | API 语义与限制 | 301 |
/docs/ia-migration | /docs/guides/ia-migration | 本策略页自身的短链 | 301 |
如何新增一条别名
- 只改一个地方
在文档平台的别名清单中登记(历史路径 / 稳定短链两类);不要手写跳转 HTML。
- 重新生成
重新发布文档中心;生成器会校验目标页面存在、非链式、无路径冲突。
- 跑检查
发布前执行零漂移校验、链接可达检查与编码检查。
- 验证 301
curl -I http://127.0.0.1:8080/docs/quickstart.html。注意:im-server 内置静态托管返回的是 200 跳转页,生产 301 需按上文配置服务器。
内置演示服务器的状态码
内置 im-server 的 /docs 静态托管会直接返回跳转页 HTML(HTTP 200 + meta refresh),不是 301。这不影响浏览器跳转,但搜索引擎与缓存层应以生产服务器的 301 为准。
校验与回归
redirects.json每次构建重写,内容确定性(按 from 排序),可被--check逐字节校验。- 别名页面不进入搜索索引(只索引 canonical 页面),避免搜索结果重复。
compatPaths在生成清单与 API 快照中可查,便于回归脚本断言“旧地址仍然存在”。