概述
运营过程中,我们会碰到需要以App版本为维度,去定制一些App配置变量。如版本更新描述,App最低可用版本等。有时也会需要有动态变更一些配置的能力,比如客诉反馈邮箱,促销活动入口开闭等。
为此,我们设计了App全局配置表模块,即可是满足集中管理App配置,也能满足App配置动态变更的需求。
配置的管理与下发
我们需要为客户端提供一个的服务端API,用于配置表的下发,多为JSON表的形式。
下面是一个简单的实现,客户端上报版本号和请求时间,服务端可以根据版本号来选择下发的配置表。
随着配置表的演进,服务端会把配置迁移到数据库存储,再衍生出后台进行可视化的配置管理。
type AppGlobalConfigRequest struct {
BuildVersion string `json:"build_version" binding:"required"`
AppVersion string `json:"app_version" binding:"required"`
Time string `json:"time" binding:"required"`
}
func AppGlobalConfig(w http.ResponseWriter, r *http.Request) {
var reqBody map[string]interface{
}
if err := json.NewDecoder(r.Body).Decode(&reqBody); err != nil {
log.Printf("AppGlobalConfig reqBody json.NewDecoder: %v", err)
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
var reqParams AppGlobalConfigRequest
data := reqBody["data"]
dataJSONString, _ := json.Marshal(data)
if err := json.Unmarshal([]byte(dataJSONString), &reqParams); err != nil {
log.Printf("AppGlobalConfig reqParams json.Unmarshal: %v", err)
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
config := map[string]interface{
}{
"version": 2022091901,
"user_initial_trial_count": 2,
"create_date": "2022-09-19T02:58:31Z",
}
response := map[string]interface{
}{
"data": config,
}
responseString, _ := json.Marshal(response)
fmt.Fprintf(w, string(responseString))
}
配置的更新与持久化
配置的更新
为了保证配置的实效性,App初始化时,以及App从后台唤醒时,都需要检测配置的更新。
class AppConfigModule: SpaceportModuleProtocol {
var loaded = false
static func modulePriority() -> Int {
return 2895 }
func loadModule() {
AppConfigManager.shared.fetchGlobalConfig()
}
func unloadModule() {
}
func applicationWillEnterForeground(notification: Notification) {
AppConfigManager.shared.fetchGlobalConfig()
}
}
配置表模型
配置表里包含了三个字段,其中userInitialTrialCount
是配置信息,configVersion
与createDate
均为配置表的描述信息。
本地的配置表需要有默认值。当出现API请求失败,或者API配置表解析错误时,可以用默认配置保证功能正常使用。
private let defaultUserInitialTrialCount = 2
public struct GlobalConfig: Codable {
/// 配置版本号
let configVersion: Int
/// 用户初始试用次数
let userInitialTrialCount: Int
/// 生成时间 如:2022-09-19T02:58:31Z
let createDate: String
enum CodingKeys: String, CodingKey {
case configVersion = "version"
case userInitialTrialCount = "user_initial_trial_count"
case createDate = "create_date"
}
init() {
self.configVersion = 0
self.userInitialTrialCount = defaultUserInitialTrialCount
self.createDate = Date().axv.toISO8601String()
}
}
配置表持久化
配置表的持久化和一般模型的持久化基本相同,可以选择Sqlite,JSON File,甚至内存缓存的形式,可根据配置表的数据规模和响应要求来选择。
目前我们选用UserDefault来管理配置表的持久化,即用Plist文件存储的配置表信息。
为了避免配置表频繁持久化,需要对配置的版本进行筛选,低于当前环境的版本可以被抛弃。
enum AppConfig: String {
/// App全局配置 GlobalConfig
case globalConfig
}
private let appConfigUserDefaults = UserDefaultsManager(suiteName: "com.pe.AppConfig").userDefaults
extension AppConfig: UserDefaultPreference {
var userDefaults: UserDefaults {
return appConfigUserDefaults }
}
class AppConfigManager {
public static let shared = AppConfigManager()
private init() {
}
lazy var globalConfig: GlobalConfig = {
guard let config = AppConfig.globalConfig.codableObject(GlobalConfig.self) else {
return GlobalConfig()
}
return config
}() {
didSet {
AppConfig.globalConfig.save(codableObject: globalConfig)
}
}
/// 获取配置
func fetchGlobalConfig() {
Task {
do {
if let config = try await GlobalConfigRequest().request() {
if config.configVersion > globalConfig.configVersion {
globalConfig = config
logDebug("GlobalConfig Updated: \(globalConfig)", tag: .kLogTag)
}
}
} catch {
logWarn("got error: \(error)", tag: .kLogTag)
}
}
}
}
总结
配置表下发是一种常用的App配置动态管理方案。
设计方案时需要满足快速的响应速度,便捷的存取API,方便维护的模型,以及对机能的低消耗。
根据业务场景的不同,还可以扩展出配置表版本管理,多级配置表的关联使用,配置信息变更通知等不同的不同的功能点。