运营过程中,我们会碰到需要以App版本为维度,去定制一些App配置变量。

概述

运营过程中,我们会碰到需要以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是配置信息,configVersioncreateDate均为配置表的描述信息。

本地的配置表需要有默认值。当出现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,方便维护的模型,以及对机能的低消耗。

根据业务场景的不同,还可以扩展出配置表版本管理,多级配置表的关联使用,配置信息变更通知等不同的不同的功能点。