交易会话:二次登录、保活与被踢
项目 trade-app
1. 背景与问题
用户打开这款 App,先登录的是 App 账户:拿到用户凭证,可以看行情、资讯、开户进度。要下单、看持仓和资金,还要再登录一次交易账户:输入交易密码(或用面容 / 指纹取出交易密码),由交易网关去对应的柜台校验。期货和证券是两套柜台,各有一个交易账户、一个交易会话。
这第二次登录带来四类问题:
- 应答复杂:两套柜台的登录应答各不相同,有二次认证(短信、邮箱)、强制改密、建议改密、请求过于频繁、密码过期、客户状态不允许登录等十几种分支。
- 静默登录:开启生物识别时,客户端会拿交易密码在后台登录一次做绑定;这次登录的应答不能弹任何界面。
- 保活:交易会话需要客户端定期告诉网关「还在」。
- 单设备在线:同一个交易账户只允许一台设备在线。被挤下线时,要让用户知道,但不能把用户踢出 App。
客户端这一侧由几块组成:交易登录管理器的主体、交易密码登录弹窗、期货与证券的二次认证页,外加一层对系统生物识别的封装。
2. 两级会话
| 维度 | App 账户 | 交易账户 |
|---|---|---|
| 凭证 | 用户凭证,有过期时间,可续期 | 期货、证券各一个会话号 |
| 怎么来 | 认证中心登录 | 交易网关登录应答 |
| 本地有过期时间吗 | 有,提前续期 | 没有,由网关告知是否失效 |
| 保活 | 续期 | 每 10 秒一次账户心跳 |
| 失效时 | 续期凭证失效才登出 App | 只下线交易会话,App 仍在登录态 |
交易长连接本身用的是 App 账户的凭证;交易会话是这条连接上的第二层。两层分开处理,是整个设计的出发点。
3. 整体设计:应答驱动
交易长连接收到期货或证券的应答后,不在连接层判断业务,而是把整个信封赋给交易登录管理器上对应的属性:证券登录载体、期货登录载体、重复登录载体、断线载体、登出载体、改密与重置密码载体。每个属性的观察器就是一个处理器。
一个交易会话的生命周期:
4. 关键实现
4.1 一个入口
交易登录最早散在登录弹窗里,各个页面各自调用。后来重写了交易登录,收成登录管理器上的一个入口,把各个页面上原来的登录调用都换成这个入口,旧版本的登录管理随之删除。
入口做的第一件事是防重入:1 秒内重复调用直接返回。然后按账号类型读出本地的交易账号,读不到就不登录;再根据生物识别的绑定状态决定走哪条路。
登录方式选择视图,以及面容 / 指纹登录的判断流程也在这里。生物识别的细节不在这里展开,这里只关心它对会话的影响:它也是一次普通的交易登录,只是密码来自本地。
4.2 两套柜台的应答分支
证券登录应答:
期货登录应答的分支多一些,统一写成表:
| 应答 | 处理 |
|---|---|
| 生物识别绑定那次的应答 | 只记下会话号,返回 |
| 需要二次认证 | 按应答里的类型进入短信或邮箱二次认证页,并显示接收地址 |
| 没有绑定二次认证信息 | 不进入二次认证 |
| 成功但会话号为 0 | 当作登录失败 |
| 成功 | 如果当前在二次认证页先退出,再走登录成功 |
| 二次认证失败 / 还没请求验证码就去验证 | 提示认证失败 |
| 密码错误 | 不是交易首页生物识别那次就提示;并解绑该序号对应的生物识别 |
| 需要强制改密 | 进入改密流程 |
| 客户状态不允许登录 / 用户不存在 / 其他 | 提示对应文案 |
文案不写死在客户端:交易错误码对应的文案表在启动时拉取,按柜台分别查。
4.3 二次认证与改密
期货的二次认证:请求验证码、校验验证码,证券的短信二次认证跳转,各有一个独立页面。
改密有两处细节:
- 期货强制改密通过再调一次登录完成:登录成功即表示改密成功,长连接把这次的登录应答转给改密流程。
- 证券强制改密时,如果用户离开了改密页,回来后要再次弹出强制改密框,否则「强制」就形同虚设。
4.4 保活:账户心跳
登录成功后,期货和证券各启动一个 10 秒一次的定时器,跑在全局队列上,带上交易账号和会话号;会话号为空时不发。掉线或登出时停止。
交易会话在客户端没有本地过期时间。会话是否失效完全由网关告知:通过下一节的主推,或者通过任意一条应答里的「账户未登录」「会话校验失败」。
4.5 被挤下线:认出「是不是自己」
单设备在线由网关保证,客户端不做互斥。客户端只做两件事:认出这条通知是不是针对本设备,以及怎么提示。
| 证券 | 期货 | |
|---|---|---|
| 主推 | 重复登录 | 账户断线 |
| 先排除 | 生物识别绑定那次的请求序号 | — |
| 判断依据 | 主推里的会话号是否等于本地会话号 | 平台 + 设备标识是本机,且「已断线」为真 |
| 相同时 | 本机自己重复登录,忽略 | — |
| 应答兜底 | 「账户未登录」「会话校验失败」,且平台与设备是本机 | 「账户未登录」「会话校验失败」 |
期货单点登录、多点登录与掉线处理、登录管理监听证券掉线都按这张表判断;判断「是不是本机」依赖交易信封里的平台与设备标识。
4.6 下线只下线交易
下线做的事很少:交易登录态置否、会话号置空、停掉这一侧的账户心跳。App 账户不动。
提示则看用户当前在哪:
交易首页掉线后弹出登录框:确认之后回到交易首页,并模拟点击一次对应的账户卡片,复用卡片点击时的登录逻辑,不用另写一套「重新登录」。
5. 踩过的坑
输错密码,所有设备都弹框。 同一账户在多台设备上时,一台设备输错期货交易密码,网关的失败应答其他设备也会收到,结果别的设备也弹出「密码错误」。修复是记录本机是否真的发起过登录,只有发起过才处理失败应答,并在提示后复位这个记录。
证券登录应答的设备过滤。 证券登录应答曾按平台和设备标识过滤,后来取消了证券登录应答上的设备判断。登出、掉线这类应答仍然比对设备。
静默登录的应答走进了界面分支。 开启生物识别时的绑定登录,应答和普通登录长得一样,不拦截就会走进登录成功、改密、二次认证这些界面分支。收到期货和证券登录应答时先看请求序号,是绑定那次就只记下会话号并返回。
图表页输错密码弹出两个提示。 证券或期货图表页上登录交易,输错密码时,登录管理已经提示过一次,图表页的失败回调又提示了一次。去掉图表页失败回调里的那次提示,失败文案只由登录管理给出。
切换到休眠账户后资金串号。 在交易首页点一个正常账户,弹出登录框后切到休眠账户登录,登录失败后,正常账户显示成了休眠账户的资金。原因是弹窗在登录结果出来之前就把「当前交易账号」改成了待登录的账号。修复是登录前不再改写当前账号,直接用待登录的账号发请求;另外记下上次查询的账号;登录成功之后才查资金并切换首页的账户。
请求过于频繁。 证券登录应答里有「请求过于频繁」的返回,单独认出来,提示稍候重试,而不是当成普通的登录失败。配合入口的 1 秒防重入,减少用户连点带来的重复登录。
6. 对比与取舍
| 问题 | 另一种做法 | 采用的做法 | 原因 |
|---|---|---|---|
| 应答处理 | 每个请求带回调闭包 | 应答赋给载体属性,观察器处理 | 主推和应答走同一条路;连接层不懂业务 |
| 静默登录与用户登录 | 两个不同的登录命令 | 同一个命令,靠请求序号区分 | 协议里只有一种登录 |
| 会话过期 | 本地记过期时间 | 不记,由网关告知 | 交易会话没有下发有效期 |
| 被挤下线 | 下线整个 App | 只下线交易会话 | 两级会话;行情、资讯仍可用 |
| 掉线提示 | 任何页面都弹 | 只在交易相关页面弹 | 用户在看资讯时被打断体验差 |
| 重新登录 | 单独的重登流程 | 回到首页模拟点击账户卡片 | 复用已有的登录入口 |
7. 可以改进的地方
下面每一条都是方向,不是已经做完的改动。
| 现在的做法 | 问题 | 改进方向 |
|---|---|---|
| 观察器里写处理逻辑 | 隐式,测试只能起整条链路 | 显式的会话状态枚举 + 纯函数 reducer,状态迁移可单测 |
| 心跳只发不判 | 心跳应答丢失时发现不了 | 连续若干次未收到心跳应答就标记为可疑并主动查询登录状态 |
| 期货、证券两套近似的代码 | 分支多、容易漏 | 抽象出柜台适配层,共用状态机 |
| 「是否本机发起」靠一个记录 | 并发登录时可能错位 | 用请求序号精确匹配 |
| 模拟点击账户卡片 | 依赖页面结构 | 暴露明确的「重新登录某账户」接口 |