docs: 添加 SDK API 重设计、安全设计规范和测试进度跟踪文档

- 新增 SDK API 重设计规范文档,统一各端 SDK 初始化、登录、消息接口
- 新增安全设计规范文档,涵盖密码安全、AppSecret 验证、令牌存储等安全要点
- 新增 Bug 跟踪记录文档,记录已修复问题和开放问题
- 新增测试进度跟踪文档,记录各模块测试覆盖情况和验证结果
这个提交包含在:
XuqmGroup 2026-05-02 11:45:43 +08:00
父节点 72a03a65d6
当前提交 20fc57ac97
共有 3 个文件被更改,包括 6 次插入99 次删除

查看文件

@ -148,7 +148,7 @@ val service = RetrofitFactory.create(MyApiService::class.java)
- 会话置顶/免打扰/已读/草稿/删除同步服务端
- IM 连接状态提示
- SDK 登录态恢复后自动重连
- IM 登录态更新后自动重连,不在 SDK 侧做静默续签
- 重复调用登录会覆盖当前 IM 会话并自动重连,SDK 侧不做生命周期检测或维护
- 群聊支持 `@userId` 提及,并写入 `mentionedUserIds`
- 关系链支持好友申请、接受/拒绝、黑名单
- 支持图片 / 视频 / 音频 / 文件消息,文件通过独立文件服务上传后再发 IM

查看文件

@ -108,13 +108,13 @@
---
### TC-08 UserSig 登录态更新测试(新增)
### TC-08 UserSig 匹配重登测试(新增)
| 字段 | 内容 |
|------|------|
| **测试目的** | 验证业务侧重新签发登录态后,SDK 能覆盖旧会话并重连 |
| **测试步骤** | 1. 登录后保持 WebSocket 连接 <br> 2. 业务服务端重新签发新的 UserSig <br> 3. 重新调用 `XuqmSDK.login()` <br> 4. 观察 `ImSDK.onSdkLogin``ImEventListener.onConnected()` <br> 5. 确认旧会话被替换 |
| **预期结果** | 1. 新登录态生效 <br> 2. WebSocket 使用新登录态重连 <br> 3. 不存在 SDK 侧定时续签逻辑 <br> 4. 旧会话被覆盖 <br> 5. 无内存泄漏 |
| **测试目的** | 验证 `userId + userSig` 匹配时,SDK 能覆盖当前会话并重连 |
| **测试步骤** | 1. 登录后保持 WebSocket 连接 <br> 2. 使用同一 `userId` 与匹配的 UserSig 重新调用 `XuqmSDK.login()` <br> 3. 观察 `ImSDK.onSdkLogin``ImEventListener.onConnected()` <br> 4. 确认当前会话被替换 |
| **预期结果** | 1. 匹配的 UserSig 生效 <br> 2. WebSocket 使用当前登录态重连 <br> 3. SDK 侧无生命周期检测或维护机制 <br> 4. 当前会话被覆盖 <br> 5. 无内存泄漏 |
| **实际结果** | 待测试 |
| **通过状态** | ⬜ |
@ -143,5 +143,5 @@
| TC-05 | 会话列表/置顶/静音测试 | ⬜ 待测试 |
| TC-06 | Push 设备注册测试 | ⬜ 待测试 |
| TC-07 | 版本更新检查测试 | ⬜ 待测试 |
| TC-08 | UserSig 登录态更新测试 | ⬜ 待测试 |
| TC-08 | UserSig 匹配重登测试 | ⬜ 待测试 |
| TC-09 | 多厂商 Push 检测测试 | ⬜ 待测试 |

查看文件

@ -1,93 +0,0 @@
package com.xuqm.sdk.auth
import android.os.Handler
import android.os.Looper
import android.util.Log
import java.util.concurrent.atomic.AtomicBoolean
/**
* UserSig 静默续签定时器
*
* [login] 时启动 [logout] 时停止
* 当检测到 UserSig 距离过期不足 5 分钟时触发 [UserSigRefreshListener.onUserSigRefreshRequired] 回调
* 业务层应在回调中异步获取新的 userSig然后调用 [com.xuqm.sdk.XuqmSDK.login] 重新登录以刷新定时器
*/
class UserSigRefresher {
private val mainHandler = Handler(Looper.getMainLooper())
private val isRunning = AtomicBoolean(false)
private var refreshListener: UserSigRefreshListener? = null
private var currentExpiryTimeMs: Long = 0L
/**
* UserSig 续签监听器
*/
fun interface UserSigRefreshListener {
/**
* UserSig 即将过期时触发到期前 5 分钟
* 业务层应在此回调中异步获取新的 userSig然后调用 XuqmSDK.login() 重新登录
*/
fun onUserSigRefreshRequired()
}
/**
* 设置续签监听器
*/
fun setRefreshListener(listener: UserSigRefreshListener?) {
this.refreshListener = listener
}
/**
* 启动续签检测定时器
*
* @param expiryTimeMs UserSig 过期时间戳毫秒
*/
fun start(expiryTimeMs: Long) {
stop()
currentExpiryTimeMs = expiryTimeMs
isRunning.set(true)
scheduleCheck()
}
/**
* 停止续签检测定时器
*/
fun stop() {
isRunning.set(false)
mainHandler.removeCallbacksAndMessages(null)
}
private fun scheduleCheck() {
if (!isRunning.get()) return
val now = System.currentTimeMillis()
val timeUntilExpiry = currentExpiryTimeMs - now
val timeUntilRefresh = timeUntilExpiry - REFRESH_THRESHOLD_MS
if (timeUntilRefresh <= 0) {
notifyRefreshRequired()
if (isRunning.get()) {
mainHandler.postDelayed({ scheduleCheck() }, CHECK_INTERVAL_MS)
}
} else {
mainHandler.postDelayed(
{ if (isRunning.get()) scheduleCheck() },
timeUntilRefresh.coerceAtMost(CHECK_INTERVAL_MS),
)
}
}
private fun notifyRefreshRequired() {
runCatching {
refreshListener?.onUserSigRefreshRequired()
}.onFailure { error ->
Log.w(TAG, "UserSig refresh callback failed: ${error.message}")
}
}
companion object {
private const val TAG = "UserSigRefresher"
private const val REFRESH_THRESHOLD_MS = 5 * 60 * 1000L // 到期前 5 分钟
private const val CHECK_INTERVAL_MS = 60 * 1000L // 每分钟检查一次
}
}