docs(deploy): 添加部署文档并更新SDK API设计规范

- 新增完整的XuqmGroup部署文档,包含服务器配置、Docker Compose部署策略
- 更新SDK API重设计规范至V2.0,统一各端SDK初始化和登录接口
- 添加安全设计规范文档,涵盖密码安全、AppSecret验证等内容
- 新增离线推送架构设计文档,定义厂商推送集成方案
- 重构SDK登录流程,统一使用userId + userSig鉴权模式
- 移除dbName等外部配置参数,实现零感知平台地址配置
- 完善部署架构图和配置示例文件
这个提交包含在:
XuqmGroup 2026-05-02 11:29:50 +08:00
父节点 8cf9699441
当前提交 255974ae09
共有 4 个文件被更改,包括 22 次插入58 次删除

查看文件

@ -34,7 +34,7 @@ public final class XuqmSDK: NSObject {
self.userSig = userSig self.userSig = userSig
do { do {
try await ImSDK.shared.loginWithUserSig(userId, userSig) try await ImSDK.shared.login(userId, userSig)
} catch { } catch {
// IM login failed; silently ignored per facade pattern // IM login failed; silently ignored per facade pattern
} }

查看文件

@ -24,7 +24,7 @@ public final class ImSDK {
} }
} }
public func loginWithUserSig(_ userId: String, _ userSig: String) async throws { public func login(_ userId: String, _ userSig: String) async throws {
let config = XuqmSDK.shared.requireConfig() let config = XuqmSDK.shared.requireConfig()
currentUserId = userId currentUserId = userId
XuqmSDK.shared.tokenStore?.save(userSig) XuqmSDK.shared.tokenStore?.save(userSig)
@ -36,57 +36,6 @@ public final class ImSDK {
client?.connect() client?.connect()
} }
public func login(userId: String) async throws {
let config = XuqmSDK.shared.requireConfig()
let items = [
URLQueryItem(name: "appId", value: config.appId),
URLQueryItem(name: "userId", value: userId),
]
let res: ImLoginResponse = try await ApiClient.shared.request(
path: "/api/im/auth/login",
method: "POST",
queryItems: items
)
currentUserId = userId
XuqmSDK.shared.tokenStore?.save(res.token)
client?.disconnect()
client = ImClient(token: res.token, appId: config.appId)
client?.setCurrentUserId(userId)
client?.delegate = self
updateConnectionState(.connecting)
client?.connect()
}
public func loginWithDemo(userId: String, password: String = "123456") async throws {
let config = XuqmSDK.shared.requireConfig()
struct DemoLoginResponse: Decodable, Sendable {
let profile: DemoProfile
let imToken: String
struct DemoProfile: Decodable, Sendable {
let appId: String
let userId: String
let nickname: String?
let avatar: String?
}
}
let res: DemoLoginResponse = try await ApiClient.shared.request(
path: "/api/demo/auth/login",
method: "POST",
queryItems: [URLQueryItem(name: "appId", value: config.appId)],
body: ["appId": config.appId, "userId": userId, "password": password]
)
currentUserId = res.profile.userId
XuqmSDK.shared.tokenStore?.save(res.imToken)
client?.disconnect()
client = ImClient(token: res.imToken, appId: config.appId)
client?.setCurrentUserId(res.profile.userId)
client?.delegate = self
updateConnectionState(.connecting)
client?.connect()
}
public func setDelegate(_ delegate: ImEventDelegate) { public func setDelegate(_ delegate: ImEventDelegate) {
self.delegate = delegate self.delegate = delegate
client?.delegate = self client?.delegate = self

查看文件

@ -37,7 +37,7 @@
| 字段 | 内容 | | 字段 | 内容 |
|------|------| |------|------|
| **测试目的** | 验证 UserSig 鉴权登录与登出流程 | | **测试目的** | 验证 UserSig 鉴权登录与登出流程 |
| **测试步骤** | 1. 调用 `XuqmSDK.shared.login(userId: "user_001", userSig: "xxx")` <br> 2. 观察 `ImSDK.shared.loginWithUserSig` 内部触发 WebSocket 连接 <br> 3. 监听 `ImEventDelegate.imClientDidConnect()` <br> 4. 调用 `XuqmSDK.shared.logout()` <br> 5. 确认 `ImSDK.shared.disconnect()` 执行,Push Token 解注册 | | **测试步骤** | 1. 调用 `XuqmSDK.shared.login(userId: "user_001", userSig: "xxx")` <br> 2. 观察 `ImSDK.shared.login` 内部触发 WebSocket 连接 <br> 3. 监听 `ImEventDelegate.imClientDidConnect()` <br> 4. 调用 `XuqmSDK.shared.logout()` <br> 5. 确认 `ImSDK.shared.disconnect()` 执行,Push Token 解注册 |
| **预期结果** | 1. `currentUserId` 被赋值 <br> 2. WebSocket 连接成功,状态变为 `.connecting``.connected` <br> 3. delegate `imClientDidConnect()` 触发 <br> 4. 登出后 `currentUserId` 置 nil <br> 5. `PushSDK.shared.unregisterToken` 被调用 | | **预期结果** | 1. `currentUserId` 被赋值 <br> 2. WebSocket 连接成功,状态变为 `.connecting``.connected` <br> 3. delegate `imClientDidConnect()` 触发 <br> 4. 登出后 `currentUserId` 置 nil <br> 5. `PushSDK.shared.unregisterToken` 被调用 |
| **实际结果** | 待测试 | | **实际结果** | 待测试 |
| **通过状态** | ⬜ | | **通过状态** | ⬜ |

查看文件

@ -7,6 +7,14 @@ final class AuthViewModel: ObservableObject {
@Published var currentUserId: String = "" @Published var currentUserId: String = ""
@Published var currentNickname: String = "" @Published var currentNickname: String = ""
private struct DemoLoginResponse: Decodable, Sendable {
let imToken: String
let profile: DemoProfile
struct DemoProfile: Decodable, Sendable {
let userId: String
}
}
func login(userId: String, password: String) { func login(userId: String, password: String) {
guard !userId.isEmpty, !password.isEmpty else { guard !userId.isEmpty, !password.isEmpty else {
state = .error("请输入用户 ID 和密码") state = .error("请输入用户 ID 和密码")
@ -15,9 +23,16 @@ final class AuthViewModel: ObservableObject {
state = .loading state = .loading
Task { Task {
do { do {
try await ImSDK.shared.loginWithDemo(userId: userId, password: password) let config = XuqmSDK.shared.requireConfig()
currentUserId = userId let res: DemoLoginResponse = try await ApiClient.shared.request(
currentNickname = userId path: "/api/demo/auth/login",
method: "POST",
queryItems: [URLQueryItem(name: "appId", value: config.appId)],
body: ["appId": config.appId, "userId": userId, "password": password]
)
await XuqmSDK.shared.login(userId: res.profile.userId, userSig: res.imToken)
currentUserId = res.profile.userId
currentNickname = res.profile.userId
state = .success state = .success
} catch { } catch {
state = .error(error.localizedDescription) state = .error(error.localizedDescription)
@ -26,7 +41,7 @@ final class AuthViewModel: ObservableObject {
} }
func logout() { func logout() {
ImSDK.shared.disconnect() Task { await XuqmSDK.shared.logout() }
currentUserId = "" currentUserId = ""
currentNickname = "" currentNickname = ""
state = .idle state = .idle