# IM 个人/企业注册登录前后端对接说明 ## 1. 目标 当前客户端没有独立的企业码进入页面,登录页和注册页由用户选择“个人用户”或“企业用户”。前端提交登录和注册时必须显式带类型,后端不能再通过企业码是否为空来推断个人/企业。 核心规则: 1. 前端新增字段 `registration_type`,类型为 int,取值只能是 `0` 或 `1`。 2. `registration_type=1` 时,后端按个人注册处理,内部租户使用 `default`,数据写入 `default` 租户业务库。 3. `registration_type=0` 时,前端必须提交企业码,后端必须校验企业存在、启用且允许注册。 4. 登录新增字段 `login_type`,类型为 int,取值同样是 `0` 企业、`1` 个人。 5. 企业登录或企业注册都不允许使用 `default`。 6. `default` 是系统保留的个人租户码,不允许作为企业码输入。 一句话规则:**注册类型由 `registration_type` 决定,登录类型由 `login_type` 决定;个人落 `default`,企业必须校验企业码。** ## 2. 前端注册字段 注册页提交发码和最终注册时都带同一组注册上下文字段: | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `registration_type` | int | 是 | `0` 表示企业注册,`1` 表示个人注册 | | `tenant_code` | string | 企业注册必填 | 企业码;个人注册不填或传空字符串 | 前端规则: | `registration_type` | `tenant_code` | 后端处理 | | --- | --- | --- | | `1` | 空 | 允许提交,后端内部使用 `default` | | `1` | 非空 | 后端拒绝,避免误入库 | | `0` | 空 | 后端拒绝,提示请输入企业码 | | `0` | `default`、`Default`、`DEFAULT` | 后端拒绝 | | `0` | 其他值 | 后端在注册链路中校验 | 用户在企业注册模式输入 `default` 时,建议提示: ```text default 是系统保留企业码。个人注册请选择个人用户,企业注册请输入企业提供的企业码。 ``` ## 3. 企业码校验 不新增单独的企业码校验接口,也不要求前端在注册前调用 `/v1/im/tenants/resolve`。 后端在注册发码和最终注册链路中校验: 1. `registration_type=1` - 不要求企业码。 - 即使请求 Header 或内部路由使用 `default`,也属于服务端内部租户,不是用户输入企业码。 - 创建用户时写入 `default` 租户业务库。 2. `registration_type=0` - `tenant_code` 必填。 - `tenant_code` 大小写不敏感等于 `default` 时拒绝。 - 查询企业是否存在、启用、允许注册。 - 通过后在对应企业租户业务库创建用户。 企业不存在、停用或不允许注册时,客户端统一提示: ```text 企业不存在或不可用 ``` 客户端不要区分不存在、停用、禁用注册等内部原因。 `/v1/im/tenants/resolve` 只保留给服务发现、品牌配置、历史企业入口等场景使用;当前注册页不依赖它完成主流程。 ## 4. 前端登录字段 登录页提交 WebSocket `{login}` 时带登录上下文字段: | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `login_type` | int | basic 登录必填 | `0` 表示企业登录,`1` 表示个人登录 | | `tenant_code` | string | 企业登录必填 | 企业码;个人登录不填、传空字符串或内部传 `default` | 登录规则: | `login_type` | `tenant_code` / `hi.tenant` | 后端处理 | | --- | --- | --- | | `1` | `default` | 个人登录,允许 | | `1` | 非 `default` | 拒绝,避免个人登录误进企业租户 | | `0` | 空 | 拒绝,提示请输入企业码 | | `0` | `default`、`Default`、`DEFAULT` | 拒绝,统一提示“企业不存在或不可用” | | `0` | 其他值 | 企业登录,后端按该租户认证 | 企业用户如果在企业入口输入 `default`,后端返回策略错误,错误参数为 `what=tenant_code`、`reason=TENANT_CODE_FORBIDDEN`;客户端统一展示: ```text 企业不存在或不可用 ``` 个人登录 `{login}` 示例: ```json { "login": { "id": "login-1", "scheme": "basic", "login_type": 1, "tenant_code": "", "secret": "base64(account:password)" } } ``` 企业登录 `{login}` 示例: ```json { "login": { "id": "login-1", "scheme": "basic", "login_type": 0, "tenant_code": "acme", "secret": "base64(account:password)" } } ``` ## 5. 个人注册流程 个人注册必须显式提交 `registration_type=1`。 1. 用户选择个人注册。 2. 前端提交 `registration_type=1`,`tenant_code` 为空。 3. 注册发码、验证码校验、WebSocket `hi`、`acc` 注册链路使用服务端内部租户 `default`。 4. 服务端将用户创建到 `default` 租户对应的业务库。 发码示例: ```http POST /v1/im/auth/verification-codes X-MeetingIM-APIKey: X-IM-Schema: 1 X-Tenant-Code: default X-Request-ID: ``` ```json { "scene": "register", "registration_type": 1, "tenant_code": "", "phone": "19122421333" } ``` WebSocket 握手示例: ```json { "hi": { "id": "hi-1", "ver": "0.25", "ua": "ios/1.0.0", "platf": "ios", "lang": "zh-CN", "tenant": "default" } } ``` 最终注册 `{acc}` 示例: ```json { "acc": { "id": "acc-1", "user": "new", "scheme": "basic", "registration_type": 1, "tenant_code": "", "username": "henry", "phone": "19122421333", "secret": "base64(henry:password)", "login": true, "cred": [ { "meth": "tel", "val": "19122421333", "resp": "verification_token", "params": {"challenge_id": "challenge-id"} } ] } } ``` ## 6. 企业注册流程 企业注册必须显式提交 `registration_type=0`。 1. 用户选择企业注册。 2. 用户填写企业码,例如 `acme`。 3. 前端直接提交用户输入的企业码,不做企业码存在性或保留字拦截。 4. 获取验证码、提交注册时都带 `registration_type=0` 和 `tenant_code=acme`。 5. 后端在注册链路中校验企业是否存在且可用。 6. 校验通过后,服务端在该企业租户业务库中创建用户;校验不通过时返回“企业不存在或不可用”。 注册发码请求: ```http POST /v1/im/auth/verification-codes X-MeetingIM-APIKey: X-IM-Schema: 1 X-Tenant-Code: acme X-Request-ID: ``` ```json { "scene": "register", "registration_type": 0, "tenant_code": "acme", "phone": "19122421333" } ``` WebSocket `hi`: ```json { "hi": { "id": "hi-1", "ver": "0.25", "ua": "android/1.0.0", "platf": "android", "lang": "zh-CN", "tenant": "acme" } } ``` 最终注册 `{acc}` 示例: ```json { "acc": { "id": "acc-1", "user": "new", "scheme": "basic", "registration_type": 0, "tenant_code": "acme", "username": "henry", "phone": "19122421333", "secret": "base64(henry:password)", "login": true, "cred": [ { "meth": "tel", "val": "19122421333", "resp": "verification_token", "params": {"challenge_id": "challenge-id"} } ] } } ``` ## 7. 后端校验规则 后端负责注册链路的最终校验,前端不做企业码拦截。 1. `registration_type` 缺失或不是 `0|1` - 拒绝注册。 - 返回 `MALFORMED_REQUEST`,提示客户端升级或检查参数。 2. `registration_type=1` - 不从空企业码推断,而是由该字段明确进入个人注册。 - 内部租户码设置为 `default`。 - 拒绝用户提交的非空 `tenant_code`,避免误入库。 - 注册时写入 `default` 租户业务库。 3. `registration_type=0` - `tenant_code` 必填。 - 先 trim 首尾空格。 - 如果大小写不敏感等于 `default`,拒绝注册。 - 否则查询租户是否存在、是否启用、是否允许注册。 - 校验通过后绑定对应租户。 建议错误: ```json { "code": 30000, "reason": "MALFORMED_REQUEST", "message": "invalid registration_type or tenant_code" } ``` 企业不可用时,客户端展示: ```text 企业不存在或不可用 ``` ## 8. 缓存和本地数据隔离 客户端本地缓存、Token、会话、草稿、附件缓存必须继续按 `tenant_code + user_id` 隔离。 个人注册用户登录后,缓存键中的 `tenant_code` 使用 `default`。企业用户登录后,使用对应企业码。切换个人/企业身份时必须清理当前连接并重新 `hi`,不能在同一个 WebSocket 中切换租户。 ## 9. 验收用例 | 场景 | 预期 | | --- | --- | | `login_type=1`,`hi.tenant=default` | 个人登录成功进入 `default` 租户认证 | | `login_type=1`,`hi.tenant=acme` | 拒绝登录 | | `login_type=0`,企业码空 | 拒绝登录,提示请输入企业码 | | `login_type=0`,企业码为 `default` | 拒绝登录,提示企业不存在或不可用 | | `login_type=0`,企业码有效 | 在该企业租户认证 | | `registration_type=1`,企业码空,手机号注册 | 成功进入 `default` 租户注册链路 | | `registration_type` 缺失 | 拒绝注册,返回参数错误 | | `registration_type=1`,企业码非空 | 拒绝或忽略,建议拒绝,避免误入库 | | `registration_type=0`,企业码空 | 拒绝注册,提示请输入企业码 | | `registration_type=0`,企业码为 `default` | 拒绝注册 | | `registration_type=0`,企业码不存在 | 发码或注册失败,提示企业不存在或不可用 | | `registration_type=0`,企业码已停用 | 统一提示企业不存在或不可用 | | `registration_type=0`,企业码有效 | 注册写入该企业租户库 | | 个人用户注册成功后登录 | `tenant_code=default`,可正常登录、收发消息 | ## 10. 对客户端的明确要求 1. 登录页和注册页都必须明确区分个人和企业。 2. basic 登录必须提交 `login_type`。 3. 发码和最终注册都必须提交 `registration_type`。 4. 个人登录/注册提交类型 `1`,不要让用户输入或看到 `default`。 5. 企业登录/注册提交类型 `0` 和用户输入的企业码。 6. 企业用户手动输入 `default` 也直接提交,由后端返回错误,客户端按后端错误展示提示。 7. 不需要为了注册主流程调用 `/v1/im/tenants/resolve`。 ## 11. 对后端的明确要求 1. basic 登录链路必须以 `login_type` 作为个人/企业登录的判定来源。 2. 注册链路必须以 `registration_type` 作为个人/企业注册的判定来源。 3. 不允许仅根据企业码是否为空推断个人注册或个人登录。 4. 登录、注册、发码、验证码校验、WebSocket `hi/login/acc` 链路保持同一租户上下文。 5. 显式企业码 `default` 必须拒绝,防止用户绕过企业码校验。 6. 企业登录/注册的企业码必须校验存在、启用、允许登录/注册。 7. 日志中要能看出来源是 `personal_default` 还是 `enterprise_code`,方便排查误入库问题。