docs(deploy): 添加部署文档并更新SDK API设计规范
- 新增完整的XuqmGroup部署文档,包含服务器配置、Docker Compose部署策略 - 更新SDK API重设计规范至V2.0,统一各端SDK初始化和登录接口 - 添加安全设计规范文档,涵盖密码安全、AppSecret验证等内容 - 新增离线推送架构设计文档,定义厂商推送集成方案 - 重构SDK登录流程,统一使用userId + userSig鉴权模式 - 移除dbName等外部配置参数,实现零感知平台地址配置 - 完善部署架构图和配置示例文件
这个提交包含在:
父节点
8cf9699441
当前提交
255974ae09
@ -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
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户