功能深度解析(上):账号体系、个人资料与 avatar 的三次迭代
海狸IM 功能深度解析上账号体系、个人资料与 avatar 的三次迭代写在前面为什么从账号和资料讲起IM 产品第一眼看到的是聊天窗口但底层第一条链路永远是谁在用、资料长什么样、头像和媒体怎么存。海狸 2.0 是自建账号 好友模型 双端客户端没有企业通讯录树也没有官方 SaaS 公共服。用户在你部署的beaver-server上注册拿到userId之后才能加好友、进群、走 OAuth、收 Webhook 通知。把账号和资料讲清楚后面聊消息、开放接入才不会飘。尤其avatar / fileUrl 我来回改了三次——这不是小事它直接决定了 OAuth 能不能对外透头像、Webhook 能不能塞外链图片。这篇会把来龙去脉写透。1. 账号体系自建用户完整注册登录链路1.1 产品模型海狸的账号就是服务端user模块里的UserModel核心字段包括userId、nickName、avatar、email/phone等。注册完成后即拥有全局唯一的userId后续好友、群、OAuth、机器人身份都挂在这个 id 上。服务端还区分用户类型普通用户 / 推送 Bot / 智能机器人 Robot2.0 开放接入里群 Webhook 用的 Bot 也走同一套 user 表只是userType不同——这块在 51 号下篇展开。1.2 注册与登录方式2.0能力PCFlutter说明邮箱注册✅✅邮箱验证码手机注册✅✅短信验证码需服务端配置短信通道邮箱 密码登录✅✅办公场景最常用手机 密码登录✅✅邮箱验证码登录✅✅免密找回 / 重置密码✅✅邮箱或手机验证扫码登录✅✅OAuth 或 PC 快捷登录场景1.3 一条典型的注册链路从用户视角邮箱注册可以概括为打开客户端 → 选邮箱注册 → 收验证码 → 设密码 → 注册成功 → 自动或手动登录 → 拿到 Token / 建立 WS 连接 → 进入消息主页服务端侧auth模块校验验证码 →user模块创建 UserModel → 返回登录态。之后客户端会拉个人资料、走 datasync 同步好友与会话摘要——账号一旦建好后面所有模块都认这个 userId。1.4 扫码登录在 2.0 里的位置扫码不只是「图省事」还是OAuth2 第三方接入的标配交互PC 或第三方 Web 展示二维码用户用海狸 App 扫在beaver-oauth授权页确认服务端完成 Token 签发。二维码状态一般经历待扫 → 已扫 → 已确认 / 过期。具体接口见文档站 OAuth 章节和 51 号下篇的开放能力是一套链路。1.5 账号安全与设备管理能力PCFlutter说明修改登录密码✅ 设置中心Flutter 2.1 对齐 PC已登录设备列表✅展示 UA、在线状态远程踢设备下线✅踢掉其他端退出登录✅✅2.0 在PC 独立设置中心把改密、设备管理做全了移动端能登录、能聊天安全类设置弱一些这在 2.1 规划里会对齐。设备与会话安全可以单独再开一篇本篇点到为止。1.6 2.0 明确不做的❌ 企业组织架构 / 部门通讯录树❌ 绑定微信 / 钉钉等第三方公共 IM 账号❌ 官方运营的公共账号池要的是私有化自建账号不是 SaaS 大号体系。加人靠搜用户名或群成员这是产品定位不是缺功能。2. 个人资料能改什么、双端怎么改2.1 资料字段项PCFlutter说明头像✅✅上传后全局展示昵称✅✅会话列表、气泡、好友详情个性签名✅✅好友详情可见性别等扩展字段✅✅资料页编辑查看他人资料✅✅好友详情、群成员改完昵称或头像后好友端、群成员列表、OAuth 对外接口读到的都是新版本。UserModel 带version字段资料变更会递增供 datasync 做增量同步。2.2 用户设置PC 的「换电脑不丢配置」PC 2.0独立设置中心有一项容易被忽略的能力部分用户设置 JSON 写入服务端。典型场景你在公司电脑自定义了快捷键回家用自己 PC 登录同一账号设置从服务端拉下来不用重配。Flutter 侧有通用设置通知、缓存等和 PC 完全对齐的项还在迭代。这类「账号 设置跟用户走」的设计和后面换设备 datasync 补消息是同一产品思路数据在服务端有权威副本客户端是缓存 展示。3. 头像与文件从上传到落库的真实路径3.1 客户端怎么传头像双端流程一致可以概括成四步选图 → 调 file 服务 upload → 响应里拿 fileUrl → 调 user 接口更新 avatar 字段PC 端beaver-desktop个人资料页上传成功后直接把uploadResult.fileUrl赋给表单里的avatar保存时提交给updateUserInfo。Flutter 端profile/bloc.dartuploadFileApi返回fileUrl再updateUserInfo({fileName: fileUrl})——字段名历史原因还叫fileName值实际是完整 URL。3.2 file 服务返回什么file_api.api里上传接口的响应结构typeFileRes{FileKeystringjson:fileKeyFileURLstringjson:fileUrl,optionalOriginalNamestringjson:originalNameFileInfo*FileInfojson:fileInfo,optional}也就是说上传后服务端既知道 fileKey内部键也会拼出 fileUrl对外可访问地址。预览路由是GET /api/file/preview/:fileKey但落库到用户表、消息体里2.0 选的是存 URL。3.3 服务端 UserModel 长什么样beaver-server/app/user/user_models/user_module.gotypeUserModelstruct{UserIDstringgorm:size:64;uniqueIndex json:userIdUserTypeint8gorm:default:1;index json:userTypeNickNamestringgorm:size:32;index json:nickNameAvatarstringgorm:size:256;default:https://.../file/preview/xxx.png// 头像URLAbstractstringgorm:size:128 json:abstract// ...Versionint64gorm:not null;default:0;index json:version}注释写得很直白Avatar 就是 URL。默认值也是一个可访问的 preview 地址保证新用户注册后头像不为空。3.4 聊天消息里的媒体同一套 fileUrl消息体定义在common/models/ctype/msg.go图片、视频、文件、语音等统一用fileUrl注释均为「完整 URL」typeImageMsgstruct{FileUrlstringjson:fileUrl// 图片完整 URL// ...}typeVoiceMsgstruct{FileUrlstringjson:fileUrl// 语音文件完整 URLDurationintjson:duration,omitempty}客户端自己发的图upload → 填自己的 preview URL。后面 Webhook 机器人发的图外部系统直接填 https 外链。协议层都认fileUrl这是 2.0 刻意保持的一致。4. 开发反思avatar 我改了三次这是做海狸 IM 过程中印象最深的一段设计反复单独拎出来说——因为背后不是「技术洁癖」而是产品阶段变了约束就变了。4.1 第一次数据库 avatar 直接存完整 URL最早的做法最直觉UserModel.Avatar 存 https 开头的完整地址。好处客户端img :srcavatar零转换会话列表、资料页、消息 Sender 同一套渲染管理后台beaver-manager用户 360 视图直接展示以后做 OAuth用户信息接口把 avatar 原样返回第三方 OA 也能直接展示当时想法很简单IM 里头像就是给人看的少一层转换少一类 bug。4.2 第二次改成文件唯一 id想把鉴权做严跑通双端聊天、file 服务上线之后我又动念头能不能库里只存 fileKey / fileId访问时走带登录态的 preview 接口动机很实在私有化客户有的要求文件链接不能永久裸奔审计、过期、权限都想挂在 file 网关头像和消息媒体走同一套「id → 鉴权 → 取流」架构上更「干净」那段时间客户端改过一版资料里存 id展示前再拼 preview 路径或换临时 URL。从纯后端视角这更「正确」。4.3 第三次又改回完整 URL——开放能力逼的第三次改回去不是因为 id 方案错了而是 2.0 要上OAuth2和群 Webhook 推送两条链路都和「外部世界」打交道OAuth 侧第三方 OA 调用户信息接口期望 JSON 里avatar是能直接img的地址。若只给 fileKeyOA 还得对接你的 file API、处理登录态——接入文档会厚一倍集成方会直接放弃。Webhook 侧bot_public.api里机器人发 Markdown 配图、发图片、发链接卡片字段全是urltypeBotImageContent{URLstringjson:url// 图片完整 URL}typeBotMarkdownContent{Imagestringjson:image,optional// 配图 URL}typeBotLinkContent{ImageURLstringjson:imageUrl,optional// 链接配图 URL}Jenkins、Prometheus、自研脚本不会先调你的 upload 拿 fileKey。它们手里只有 Grafana 截图链接、CDN 地址。若用户表和消息协议强制只认 id集成方就得写「下载外链 → 再 upload 到你这」——告警 5 分钟一条的场景file 服务很快被 proxy 打满。所以 2.0 最终取舍是层级存什么原因UserModel.avatar完整 URL双端渲染简单OAuth 可直接透出消息体 fileUrl完整 URL客户端 upload 后填 URLWebhook 可塞外链file 服务fileKey 存储自有文件仍走 uploadpreview 路由保留鉴权加强若要做在 preview 网关加策略例如短期签名而不是逼全世界改 id用一张图概括三次迭代┌─────────────┐ │ 完整 URL │ ← 第一版简单直给 └──────┬──────┘ ↓ 想要文件鉴权 ┌─────────────┐ │ fileKey/id │ ← 第二版网关统一控 └──────┬──────┘ ↓ OAuth Webhook 要对外互操作 ┌─────────────┐ │ 完整 URL │ ← 第三版回到 URL鉴权后置 └─────────────┘体会IM 一旦做开放接入内部一致性和外部互操作经常打架。没有银弹只有当前版本的 trade-off。以后若加强文件安全我会在file preview 层做签名或 Referer 校验而不是让 Jenkins 先学一遍海狸 fileKey 协议。5. 资料改完其他端怎么知道用户改头像或昵称后在线端通过WebSocket tableUpdates感知变更离线端靠datasync 增量补。好友详情、会话列表里的 Sender 信息会随 userversion更新——你不需要手动「刷新好友资料」。这条链路和 46 / 47 号文里的消息 sync 是同一套思路WS 通知有变更 → 客户端按模块拉增量。资料模块属于「用户域」不是聊天 seq但产品体验一致换手机登录头像还是新的。好友关系本身走getSyncFriends、getSyncFriendVerifies等接口52 号讲好友与私聊时会展开本篇只建立概念账号和资料是 IM 的地基sync 保证地基在多端一致。6. 本篇小结与下篇预告本篇讲了什么海狸 2.0自建账号的注册登录、安全设备、扫码在 OAuth 里的位置个人资料双端能改什么、PC 设置云端同步file 上传 → avatar / fileUrl 落库的真实路径与源码字段avatar 三次迭代的来龙去脉最终为 OAuth 与 Webhook 回到完整 URL刻意没写的留给后面计划篇主题51下篇OAuth2 登录 群 Webhook52好友与私聊会话、消息类型、已读撤回53群聊禁言、入群审批、 成员54表情与动态55音视频、通知中心56beaver-manager 运营与审核「有没有、双端支不支持」仍以48 号文表格为准系列文是一块讲透 开发体会不是把 48 复制粘贴拆成十篇。相关链接项目源码后端源码https://github.com/wsrh8888/beaver-server移动端Flutter源码https://github.com/wsrh8888/beaver-flutterPC端源码https://github.com/wsrh8888/beaver-desktop.git后台管理系统源码https://github.com/wsrh8888/beaver-manager开放平台门户源码https://github.com/wsrh8888/beaver-openOAuth 授权登录页源码https://github.com/wsrh8888/beaver-oauth学习资源在线文档https://wsrh8888.github.io/beaver-docs/核心教学视频本地搭建教程合集https://space.bilibili.com/269553626/lists/6075764?typeseason服务器部署教程合集https://space.bilibili.com/269553626/lists/6075828?typeseason