From 2352b46c6b935733a57628fb42aa82686b9335d9 Mon Sep 17 00:00:00 2001 From: XuqmGroup Date: Wed, 29 Apr 2026 15:46:39 +0800 Subject: [PATCH] =?UTF-8?q?docs(sdk):=20=E6=B7=BB=E5=8A=A0=20Android=20SDK?= =?UTF-8?q?=20=E6=96=87=E6=A1=A3=E5=92=8C=20API=20=E8=AE=BE=E8=AE=A1?= =?UTF-8?q?=E8=A7=84=E8=8C=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 Android SDK 使用文档,包含模块结构、集成方式和快速开始指南 - 添加 SDK API 重设计规范,统一初始化和登录接口设计 - 补充安全设计规范,完善 UserSig 鉴权和敏感数据处理方案 - 创建平台 REST API 规范,定义服务端到服务端的调用接口 - 添加离线推送架构设计,集成各大厂商推送服务与 IM 联动方案 --- README.md | 12 ++++----- Sources/XuqmSDK/Core/ApiClient.swift | 2 +- Sources/XuqmSDK/Core/SDKConfig.swift | 26 +++++++++---------- Sources/XuqmSDK/IM/ImClient.swift | 6 ++--- Sources/XuqmSDK/IM/ImSDK.swift | 2 +- scripts/Fastfile | 37 ++++++++++++++++++++++------ 6 files changed, 49 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 4201b3f..06aab63 100644 --- a/README.md +++ b/README.md @@ -68,8 +68,6 @@ struct MyApp: App { XuqmSDK.initialize( appKey: "ak_your_app_key", appSecret: "as_your_app_secret", - apiBaseUrl: "https://api.xuqm.com", - imBaseUrl: "wss://im.xuqm.com", debug: true ) } @@ -100,7 +98,7 @@ struct LoginResponse: Decodable { // 发起请求 let response: LoginResponse = try await ApiClient.post( path: "/api/im/auth/login", - body: ["appId": appId, "userId": userId] + body: ["appKey": appKey, "userId": userId] ) ``` @@ -197,10 +195,10 @@ import XuqmSDK func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { Task { + let token = deviceToken.map { String(format: "%02x", $0) }.joined() try await PushSDK.registerToken( - appId: "ak_xxx", - userId: "user_001", - token: deviceToken + token, + userId: "user_001" ) } } @@ -217,7 +215,7 @@ func application(_ application: UIApplication, ```swift import XuqmSDK -let result = try await UpdateSDK.checkAppUpdate(appId: "ak_xxx") +let result = try await UpdateSDK.checkAppUpdate(currentVersionCode: 123) if result.needsUpdate, let info = result.info { print("最新版本: \(info.versionName)") // iOS 只能跳转 App Store diff --git a/Sources/XuqmSDK/Core/ApiClient.swift b/Sources/XuqmSDK/Core/ApiClient.swift index 16ab211..c524a14 100644 --- a/Sources/XuqmSDK/Core/ApiClient.swift +++ b/Sources/XuqmSDK/Core/ApiClient.swift @@ -32,7 +32,7 @@ public final class ApiClient: @unchecked Sendable { guard let config else { throw URLError(.badURL) } let tokenStore = await XuqmSDK.shared.tokenStore - var components = URLComponents(url: config.apiBaseURL.appendingPathComponent(path), resolvingAgainstBaseURL: false)! + var components = URLComponents(url: SDKEndpoints.apiBaseURL.appendingPathComponent(path), resolvingAgainstBaseURL: false)! if let qi = queryItems { components.queryItems = qi } var req = URLRequest(url: components.url!) diff --git a/Sources/XuqmSDK/Core/SDKConfig.swift b/Sources/XuqmSDK/Core/SDKConfig.swift index 61b2b2c..0d6871c 100644 --- a/Sources/XuqmSDK/Core/SDKConfig.swift +++ b/Sources/XuqmSDK/Core/SDKConfig.swift @@ -1,44 +1,40 @@ import Foundation public struct SDKConfig: Sendable { - public let appId: String public let appKey: String public let appSecret: String - public let apiBaseURL: URL - public let imWebSocketURL: URL public let debug: Bool public init( - appId: String, appKey: String, appSecret: String, - apiBaseURL: URL, - imWebSocketURL: URL, debug: Bool = false ) { - self.appId = appId self.appKey = appKey self.appSecret = appSecret - self.apiBaseURL = apiBaseURL - self.imWebSocketURL = imWebSocketURL self.debug = debug } } public extension SDKConfig { static func development( - appId: String, + appKey: String, appSecret: String, - appKey: String? = nil, debug: Bool = false ) -> SDKConfig { SDKConfig( - appId: appId, - appKey: appKey ?? appId, + appKey: appKey, appSecret: appSecret, - apiBaseURL: URL(string: "http://192.168.116.9:8081")!, - imWebSocketURL: URL(string: "ws://192.168.116.9:8082/ws/im")!, debug: debug ) } } + +extension SDKConfig { + var appId: String { appKey } +} + +enum SDKEndpoints { + static let apiBaseURL = URL(string: "https://dev.xuqinmin.com")! + static let imWebSocketURL = URL(string: "wss://dev.xuqinmin.com/ws/im")! +} diff --git a/Sources/XuqmSDK/IM/ImClient.swift b/Sources/XuqmSDK/IM/ImClient.swift index 68e43fc..7940fe7 100644 --- a/Sources/XuqmSDK/IM/ImClient.swift +++ b/Sources/XuqmSDK/IM/ImClient.swift @@ -12,7 +12,6 @@ public final class ImClient: NSObject, URLSessionWebSocketDelegate, @unchecked S private let subscriptionId = "sub-user-queue" private var groupSubscriptions = Set() - private let wsURLOverride: URL? private let tokenOverride: String? private let appIdOverride: String? @@ -21,8 +20,7 @@ public final class ImClient: NSObject, URLSessionWebSocketDelegate, @unchecked S private var activeAppId: String? private var activeUserId: String? - public init(wsURL: URL? = nil, token: String? = nil, appId: String? = nil) { - self.wsURLOverride = wsURL + public init(token: String? = nil, appId: String? = nil) { self.tokenOverride = token self.appIdOverride = appId super.init() @@ -37,7 +35,7 @@ public final class ImClient: NSObject, URLSessionWebSocketDelegate, @unchecked S reconnectWorkItem?.cancel() reconnectWorkItem = nil - activeWsURL = wsURLOverride + activeWsURL = SDKEndpoints.imWebSocketURL activeToken = tokenOverride activeAppId = appIdOverride diff --git a/Sources/XuqmSDK/IM/ImSDK.swift b/Sources/XuqmSDK/IM/ImSDK.swift index 665af90..4d0d51d 100644 --- a/Sources/XuqmSDK/IM/ImSDK.swift +++ b/Sources/XuqmSDK/IM/ImSDK.swift @@ -28,7 +28,7 @@ public final class ImSDK { currentUserId = userId XuqmSDK.shared.tokenStore?.save(res.token) client?.disconnect() - client = ImClient(wsURL: config.imWebSocketURL, token: res.token, appId: config.appId) + client = ImClient(token: res.token, appId: config.appId) client?.setCurrentUserId(userId) client?.delegate = delegate client?.connect() diff --git a/scripts/Fastfile b/scripts/Fastfile index f1657d4..45dcdc8 100644 --- a/scripts/Fastfile +++ b/scripts/Fastfile @@ -6,13 +6,14 @@ # # Config: add a .env.xuqm file to your project root with: # XUQM_SERVER_URL=https://update.dev.xuqinmin.com -# XUQM_APP_ID=your-app-id +# XUQM_APP_KEY=your-app-key # XUQM_API_TOKEN=your-api-token # XUQM_SCHEME=MyApp # XUQM_WORKSPACE=MyApp.xcworkspace # XUQM_BUNDLE_ID=com.example.myapp # XUQM_STORE_TARGETS=APP_STORE # comma-separated, e.g. "APP_STORE" # XUQM_AUTO_PUBLISH_AFTER_REVIEW=false +# XUQM_PUBLISH_IMMEDIATELY=false # XUQM_WEBHOOK_URL= # optional # XUQM_SCHEDULED_PUBLISH_AT= # optional, ISO datetime # # For App Store Connect API (used by upload_to_app_store): @@ -31,23 +32,24 @@ platform :ios do desc "Build, upload to XuqmGroup update service, and submit to App Store" lane :xuqm_release do server_url = ENV.fetch("XUQM_SERVER_URL") - app_id = ENV.fetch("XUQM_APP_ID") + app_key = ENV.fetch("XUQM_APP_KEY") api_token = ENV.fetch("XUQM_API_TOKEN") scheme = ENV.fetch("XUQM_SCHEME") workspace = ENV.fetch("XUQM_WORKSPACE") bundle_id = ENV.fetch("XUQM_BUNDLE_ID", "") store_targets = ENV.fetch("XUQM_STORE_TARGETS", "") auto_publish = ENV.fetch("XUQM_AUTO_PUBLISH_AFTER_REVIEW", "false") == "true" + publish_immediately = ENV.fetch("XUQM_PUBLISH_IMMEDIATELY", "false") == "true" webhook_url = ENV.fetch("XUQM_WEBHOOK_URL", "") scheduled_at = ENV.fetch("XUQM_SCHEDULED_PUBLISH_AT", "") # ── 1. Read local version ──────────────────────────────────────────── version_name = get_version_number(target: scheme) version_code = get_build_number.to_i - UI.message("Local version: #{version_name} (#{version_code})") + UI.message("Local version: #{version_name} (#{version_code}), appKey: #{app_key}") # ── 2. Check server latest ─────────────────────────────────────────── - server_version_code = xuqm_get_latest_version_code(server_url, app_id, api_token, "IOS") + server_version_code = xuqm_get_latest_version_code(server_url, app_key, api_token, "IOS") UI.message("Server latest versionCode: #{server_version_code}") if version_code <= server_version_code @@ -78,7 +80,7 @@ platform :ios do change_log = prompt(text: "Release notes: ", ci_input: "") version_id = xuqm_upload_ipa( server_url: server_url, - app_id: app_id, + app_key: app_key, api_token: api_token, ipa_path: ipa_path, version_name: version_name, @@ -134,14 +136,33 @@ platform :ios do ) end + if publish_immediately && !auto_publish && scheduled_at.empty? + xuqm_post( + url: "#{server_url}/api/v1/updates/app/#{version_id}/publish", + token: api_token, + body: {}.to_json, + ) + UI.success("Update service version published immediately.") + end + UI.success("Release complete. Version ID: #{version_id}") - UI.message(scheduled_at.empty? ? "Publish manually or wait for auto-publish." : "Will publish at #{scheduled_at}") + if scheduled_at.empty? + if auto_publish + UI.message("Will auto-publish after all store reviews pass.") + elsif publish_immediately + UI.message("Published immediately in update service.") + else + UI.message("Publish manually or wait for auto-publish.") + end + else + UI.message("Will publish at #{scheduled_at}") + end end # ── Private helpers ────────────────────────────────────────────────────── private_lane :xuqm_get_latest_version_code do |options| - uri = URI("#{options[:server_url]}/api/v1/updates/app/list?appId=#{options[:app_id]}&platform=#{options[:platform]}") + uri = URI("#{options[:server_url]}/api/v1/updates/app/list?appId=#{options[:app_key]}&platform=#{options[:platform]}") req = Net::HTTP::Get.new(uri) req["Authorization"] = "Bearer #{options[:api_token]}" resp = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") { |h| h.request(req) } @@ -155,7 +176,7 @@ platform :ios do uri = URI("#{options[:server_url]}/api/v1/updates/app/upload") request = Net::HTTP::Post::Multipart.new(uri.path, - "appId" => options[:app_id], + "appId" => options[:app_key], "platform" => "IOS", "versionName" => options[:version_name], "versionCode" => options[:version_code].to_s,