From 255974ae0982313333f05803948218a5c65beeb7 Mon Sep 17 00:00:00 2001 From: XuqmGroup Date: Sat, 2 May 2026 11:29:50 +0800 Subject: [PATCH] =?UTF-8?q?docs(deploy):=20=E6=B7=BB=E5=8A=A0=E9=83=A8?= =?UTF-8?q?=E7=BD=B2=E6=96=87=E6=A1=A3=E5=B9=B6=E6=9B=B4=E6=96=B0SDK=20API?= =?UTF-8?q?=E8=AE=BE=E8=AE=A1=E8=A7=84=E8=8C=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增完整的XuqmGroup部署文档,包含服务器配置、Docker Compose部署策略 - 更新SDK API重设计规范至V2.0,统一各端SDK初始化和登录接口 - 添加安全设计规范文档,涵盖密码安全、AppSecret验证等内容 - 新增离线推送架构设计文档,定义厂商推送集成方案 - 重构SDK登录流程,统一使用userId + userSig鉴权模式 - 移除dbName等外部配置参数,实现零感知平台地址配置 - 完善部署架构图和配置示例文件 --- Sources/XuqmSDK/Core/XuqmSDK.swift | 2 +- Sources/XuqmSDK/IM/ImSDK.swift | 53 +------------------ TEST_REPORT.md | 2 +- .../Sources/ViewModels/AuthViewModel.swift | 23 ++++++-- 4 files changed, 22 insertions(+), 58 deletions(-) diff --git a/Sources/XuqmSDK/Core/XuqmSDK.swift b/Sources/XuqmSDK/Core/XuqmSDK.swift index ac07ddc..1d018f3 100644 --- a/Sources/XuqmSDK/Core/XuqmSDK.swift +++ b/Sources/XuqmSDK/Core/XuqmSDK.swift @@ -34,7 +34,7 @@ public final class XuqmSDK: NSObject { self.userSig = userSig do { - try await ImSDK.shared.loginWithUserSig(userId, userSig) + try await ImSDK.shared.login(userId, userSig) } catch { // IM login failed; silently ignored per facade pattern } diff --git a/Sources/XuqmSDK/IM/ImSDK.swift b/Sources/XuqmSDK/IM/ImSDK.swift index bb4efca..6651627 100644 --- a/Sources/XuqmSDK/IM/ImSDK.swift +++ b/Sources/XuqmSDK/IM/ImSDK.swift @@ -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() currentUserId = userId XuqmSDK.shared.tokenStore?.save(userSig) @@ -36,57 +36,6 @@ public final class ImSDK { 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) { self.delegate = delegate client?.delegate = self diff --git a/TEST_REPORT.md b/TEST_REPORT.md index d836230..5994f3c 100644 --- a/TEST_REPORT.md +++ b/TEST_REPORT.md @@ -37,7 +37,7 @@ | 字段 | 内容 | |------|------| | **测试目的** | 验证 UserSig 鉴权登录与登出流程 | -| **测试步骤** | 1. 调用 `XuqmSDK.shared.login(userId: "user_001", userSig: "xxx")`
2. 观察 `ImSDK.shared.loginWithUserSig` 内部触发 WebSocket 连接
3. 监听 `ImEventDelegate.imClientDidConnect()`
4. 调用 `XuqmSDK.shared.logout()`
5. 确认 `ImSDK.shared.disconnect()` 执行,Push Token 解注册 | +| **测试步骤** | 1. 调用 `XuqmSDK.shared.login(userId: "user_001", userSig: "xxx")`
2. 观察 `ImSDK.shared.login` 内部触发 WebSocket 连接
3. 监听 `ImEventDelegate.imClientDidConnect()`
4. 调用 `XuqmSDK.shared.logout()`
5. 确认 `ImSDK.shared.disconnect()` 执行,Push Token 解注册 | | **预期结果** | 1. `currentUserId` 被赋值
2. WebSocket 连接成功,状态变为 `.connecting` → `.connected`
3. delegate `imClientDidConnect()` 触发
4. 登出后 `currentUserId` 置 nil
5. `PushSDK.shared.unregisterToken` 被调用 | | **实际结果** | 待测试 | | **通过状态** | ⬜ | diff --git a/XuqmDemo/Sources/ViewModels/AuthViewModel.swift b/XuqmDemo/Sources/ViewModels/AuthViewModel.swift index 36f2736..60e031a 100644 --- a/XuqmDemo/Sources/ViewModels/AuthViewModel.swift +++ b/XuqmDemo/Sources/ViewModels/AuthViewModel.swift @@ -7,6 +7,14 @@ final class AuthViewModel: ObservableObject { @Published var currentUserId: 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) { guard !userId.isEmpty, !password.isEmpty else { state = .error("请输入用户 ID 和密码") @@ -15,9 +23,16 @@ final class AuthViewModel: ObservableObject { state = .loading Task { do { - try await ImSDK.shared.loginWithDemo(userId: userId, password: password) - currentUserId = userId - currentNickname = userId + let config = XuqmSDK.shared.requireConfig() + 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] + ) + await XuqmSDK.shared.login(userId: res.profile.userId, userSig: res.imToken) + currentUserId = res.profile.userId + currentNickname = res.profile.userId state = .success } catch { state = .error(error.localizedDescription) @@ -26,7 +41,7 @@ final class AuthViewModel: ObservableObject { } func logout() { - ImSDK.shared.disconnect() + Task { await XuqmSDK.shared.logout() } currentUserId = "" currentNickname = "" state = .idle