用户信息托管

IM 服务端只保存一层很薄的用户资料(userId / name / avatarUrl / 状态位),没有独立的“用户中心”产品。是否把 IM 当作用户资料的权威源,是应用架构选择;本页给出两种托管模型、真实字段与写入路径、key 规范、同步 API 与限制。

资料模型​

字段类型写入路径客户端可见
userIdstring注册时确定,不可修改(改 ID = 新用户)是
namestringuser.register / user.update / 客户端改本人资料 / profile.clean 回退是(空则回落为 userId)
avatarUrlstringuser.register / user.update(客户端改资料接口不接受 avatarUrl)Open API 可见;客户端 GET profile 不返回
createdAtint64注册时写入是
bannedbooluser.ban / 管理端封禁是
mutedUntiluint64user.chat.mute / 群禁言不在此字段是
deactivatedbooluser.deactivate / user.reactivate通过 1016 体现
两个容易误解的点

客户端 GET /api/apps/{appkey}/users/{userId}/profile 只返回 userId/name/createdAt,不返回 avatarUrl;头像要由业务侧自己分发或走 Open API 读取。

user.update(服务端)不触发 user.profile_changed 回调;只有客户端自助修改本人 name 时才触发。需要审计资料变更时,请在自己的同步层记录。

两种托管模型​

模型权威源适合同步方向
A. 自托管用户表IM 服务端独立 IM 应用、没有既有账号体系注册/改资料都调 Open API;业务库按需拉取
B. 外部用户系统为主你的业务库 / IdP已有账号体系、需要与组织架构/SSO 对齐业务库 → 调 user.register/user.update 同步到 IM;IM 只做展示副本

推荐把「B」作为长期模型:IM 不提供目录同步连接器、LDAP/SCIM、组织架构或用户导入向导;同步逻辑必须在你自己的服务里实现。

同步时序(外部为主)​

  1. 初始化:批量导入

    按 user.batch.get 校验已存在用户(上限 100/次),缺失的用 user.register 创建;user.register 对已存在 userId 是幂等的(更新 name/avatarUrl)。

  2. 增量:业务库变更时推送

    改昵称/头像 → user.update;封禁/注销 → user.ban / user.deactivate。保留业务侧事件 ID 以便补偿。

  3. 反向:客户端自助改昵称

    客户端只能改 name;如需同步回业务库,订阅 user.profile_changed 回调(载荷 {userId,changed:{name},timestamp})后写回。

  4. 审核回滚

    接内容审核时,审核拒绝可调 user.profile.moderation.sync(status=rejected + fields)清空 name/avatarUrl;清空后 name 回落为 userId。

  5. 对账

    IM 用户没有 updatedAt,无法按时间戳做增量对账;用业务库版本号 + 定期全量比对(batch.get 100/次)兜底。

key 规范​

项规则证据 / 说明
userId 长度1–64 字符(UTF-8 字符数)openapi.rs / user.register;push deliveries 亦限制 ≤64
appkey 长度1–64 字符admin_ext.rs
应用内唯一同一 appkey 下唯一;不同应用可有同名 userId内部键为 {appkey}:{userId}
禁止冒号业务约定不得包含 :内部键用冒号拼接;当前实现未显式拒绝,含冒号会造成键歧义/越权风险
字符集除长度外未校验建议限定 [A-Za-z0-9_-],避免 URL/日志/导出歧义
name 长度1–64 字符user.register / user.update / 客户端改资料
avatarUrl未校验长度/格式建议 https CDN URL,并在客户端做兜底头像
不要用邮箱/手机号原样当 userId

邮箱含 @ 尚可,但手机号/邮箱中的 : 或超长值会触发键冲突或 1001。: 是当前实现未拦截的真实风险,业务侧必须先做映射(如 u_ + 内部 ID)。

Open API​

能力接口关键限制 / 语义
注册 / 更新user.register / user.updateregister 幂等(存在即更新 name/avatarUrl);update 对封禁用户返回 1016
查询user.get / user.batch.getbatch 上限 100;不存在的 userId 在 missing 中返回
清理托管资料user.profile.cleanname 回退为 userId、avatarUrl 清空;保留账号与关系
审核回滚user.profile.moderation.syncstatus=approved / rejected;rejected 时按 fields 清空 name/avatarUrl
标签user.tag.set/get/batch.set全量覆盖,≤20 个,每个 ≤32 字符;批量 ≤100 条
订阅关系user.subscription.set/get全量覆盖,≤100 个 userId
状态user.ban / user.deactivate / user.reactivate / user.deactivated.list注销保留记录但禁止登录/收发并踢下线
删除user.delete物理删除(开发/测试环境用);生产建议注销而非删除
所属群user.groups.listpage/size 分页;缺省返回前 5000 个

注册 + 查询 + 批量(Node SDK)​

JavaScript
import { AppClient } from '@ni-paas-im/server-sdk';

const app = new AppClient({
  appKey: process.env.IM_APP_KEY,
  appSecret: process.env.IM_APP_SECRET,
  baseUrl: 'http://127.0.0.1:8080',
});

await app.registerUser({ userId: 'u_10086', name: '张三', avatarUrl: 'https://cdn.example.com/u/10086.png' });
await app.updateUser({ userId: 'u_10086', name: '张三(产品)' });

const { users, missing } = await app.batchGetUsers({ userIds: ['u_10086', 'u_99999'] });
console.log(users, missing); // u_99999 在 missing

审核回滚(curl,Open API 需签名)​

Shell
APP_KEY="demo"; APP_SECRET="YOUR_APP_SECRET"; BASE_URL="http://127.0.0.1:8080"
NONCE="$(openssl rand -hex 16)"; TIMESTAMP="$(date +%s%3N)"
SIGNATURE="$(printf '%s' "$APP_SECRET$NONCE$TIMESTAMP" | openssl sha1 | awk '{print $2}')"

curl -sS -X POST "$BASE_URL/docs/platform-chat-api/user/profile/moderation/sync" \
  -H "content-type: application/json" \
  -H "App-Key: $APP_KEY" -H "Nonce: $NONCE" \
  -H "Timestamp: $TIMESTAMP" -H "Signature: $SIGNATURE" \
  -d '{"userId":"u_10086","status":"rejected","reason":"nickname_violation","fields":{"name":"","avatarUrl":""}}'

客户端读取路径​

  • Web SDK:client.getUserProfile(userId) → GET /api/apps/{appkey}/users/{userId}/profile,返回 {userId,name,createdAt}。
  • Web SDK:client.updateMyUserProfile({name}) → POST /api/apps/{appkey}/users/{myUserId}/profile;只能改自己的 name,长度 1–64,成功后触发 user.profile_changed。
  • 其它端(Android/Flutter/Electron/Native)按 sdk-imlib.json 的 getUserProfile / updateMyUserProfile 语义实现;头像不在客户端写入路径。

错误码与限制​

码名称场景
1001INVALID_PARAMETERuserId/name 超长、空值、字段缺失
1004USER_NOT_FOUND查询/更新/清理/审核同步的目标不存在
1016USER_BANNED封禁用户不可更新资料/签发 token;注销用户签发 token 亦返回 1016
1021SERVICE_NOT_ACTIVATED相关服务未开通
1003FORBIDDEN客户端改他人资料(仅本人)
限制项值
userId1–64 字符,应用内唯一,禁止冒号(业务约定)
name1–64 字符
batch.get≤100 个 userId
tags≤20 个,每个 ≤32 字符;batch ≤100 条
subscriptions≤100 个 userId
avatarUrl服务端不校验长度/格式
资料更新时间无 updatedAt 字段;无法按时间戳增量对账

相关页面​