535 lines
16 KiB
C#
535 lines
16 KiB
C#
|
|
|
|
namespace Guru
|
|
{
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using JetBrains.Annotations;
|
|
using UnityEngine;
|
|
using Firebase.RemoteConfig;
|
|
using Firebase.Extensions;
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// 运控配置管理器
|
|
/// </summary>
|
|
public class RemoteConfigManager
|
|
{
|
|
public const double DefaultUpdateHours = 2;
|
|
public const double DefaultFetchTimeout = 15;
|
|
internal const string Tag = "[Remote]";
|
|
private static bool _initOnce = false;
|
|
private static RemoteConfigManager _instance;
|
|
public static RemoteConfigManager Instance => _instance ??= new RemoteConfigManager();
|
|
|
|
private FirebaseRemoteConfig _firebaseRemote;
|
|
private static bool _isDebug = false;
|
|
private static double _fetchIntervalHours = DefaultUpdateHours;
|
|
|
|
private RemoteConfigModel _model;
|
|
internal RemoteConfigModel Model => _model ??= RemoteConfigModel.LoadOrCreate();
|
|
|
|
private static Dictionary<string, object> _defaultValues;
|
|
|
|
public static event Action<bool> OnFetchCompleted;
|
|
|
|
private Dictionary<string, Action<string,string>> _changeEvents;
|
|
|
|
private Dictionary<string, string> _staticValues;
|
|
|
|
public static void Init(Dictionary<string, object> defaults = null, double updateHours = DefaultUpdateHours, bool isDebug = false)
|
|
{
|
|
if (_initOnce) return;
|
|
Instance.InitAssets(defaults, updateHours, isDebug);
|
|
}
|
|
|
|
// 拉取所有的线上配置数据
|
|
// onFetchComplete 传参 true: 拉取成功 false: 拉取失败
|
|
public static void FetchAll(bool immediately = false)
|
|
{
|
|
if (!_initOnce)
|
|
{
|
|
LogE("Mgr not ready, call Init first.");
|
|
return;
|
|
}
|
|
Instance.FetchAllConfigs(immediately);
|
|
}
|
|
|
|
public static void AddDefaultValues(Dictionary<string, object> dict)
|
|
{
|
|
if (!_initOnce)
|
|
{
|
|
LogE("Mgr not ready, call Init first.");
|
|
return;
|
|
}
|
|
Instance.AppendDefaultValues(dict);
|
|
}
|
|
|
|
#region 初始化
|
|
|
|
private void InitAssets(Dictionary<string, object> defaults = null, double updateHours = DefaultUpdateHours, bool isDebug = false)
|
|
{
|
|
_fetchIntervalHours = updateHours;
|
|
_isDebug = isDebug;
|
|
_firebaseRemote = FirebaseRemoteConfig.DefaultInstance;
|
|
if (_firebaseRemote == null)
|
|
{
|
|
LogE("Can't find FirebaseRemoteConfig.DefaultInstance, init failed.");
|
|
return;
|
|
}
|
|
|
|
// 设置默认配置
|
|
_firebaseRemote.SetConfigSettingsAsync(new ConfigSettings()
|
|
{
|
|
FetchTimeoutInMilliseconds = (ulong)(DefaultFetchTimeout * 1000),
|
|
MinimumFetchInternalInMilliseconds = (ulong)(_fetchIntervalHours * 60 * 60 * 1000)
|
|
});
|
|
|
|
// 设置默认值
|
|
AppendDefaultValues(defaults);
|
|
_initOnce = true;
|
|
|
|
// 监听事件合集
|
|
_changeEvents = new Dictionary<string, Action<string,string>>(30);
|
|
|
|
FetchAllConfigs();
|
|
}
|
|
|
|
private void AppendDefaultValues(Dictionary<string, object> defaults)
|
|
{
|
|
if (defaults != null)
|
|
{
|
|
if(_defaultValues == null) _defaultValues = new Dictionary<string, object>(20);
|
|
string key;
|
|
object value;
|
|
for(int i = 0; i < defaults.Keys.Count; i++)
|
|
{
|
|
key = defaults.Keys.ElementAt(i);
|
|
value = defaults.Values.ElementAt(i);
|
|
_defaultValues[key] = value;
|
|
}
|
|
_firebaseRemote?.SetDefaultsAsync(_defaultValues);
|
|
}
|
|
}
|
|
|
|
|
|
private void FetchAllConfigs(bool immediately = false)
|
|
{
|
|
var span = TimeSpan.FromHours(_fetchIntervalHours);
|
|
if(_isDebug || immediately) span = TimeSpan.Zero;
|
|
_firebaseRemote.FetchAsync(span)
|
|
.ContinueWithOnMainThread(task =>
|
|
{
|
|
bool success = true;
|
|
if (task.IsFaulted || task.IsCanceled)
|
|
{
|
|
string res = task.IsFaulted? "Faulted" : "Canceled";
|
|
LogE($" --- FetchAllConfigs fails: {res}");
|
|
success = false;
|
|
}
|
|
|
|
if (success) OnFetchDataCompleted();
|
|
|
|
OnFetchCompleted?.Invoke(success);
|
|
});
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
#region Model
|
|
|
|
/// <summary>
|
|
/// 判断是否为 Config 参数
|
|
/// </summary>
|
|
/// <param name="rawStr"></param>
|
|
/// <returns></returns>
|
|
private bool IsRemoteConfigStr(string rawStr)
|
|
{
|
|
if (string.IsNullOrEmpty(rawStr)) return false;
|
|
|
|
if (rawStr.TrimStart().StartsWith("{")
|
|
&& rawStr.TrimEnd().EndsWith("}")
|
|
&& rawStr.Contains("\"enable\":"))
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 拉取成功
|
|
/// </summary>
|
|
private void OnFetchDataCompleted()
|
|
{
|
|
var values = _firebaseRemote.AllValues;
|
|
var updates = new Dictionary<string, string>(values.Count);
|
|
var configs = new Dictionary<string, string>(values.Count);
|
|
ConfigValue value;
|
|
string key, str;
|
|
for (int i = 0; i < values.Keys.Count; i++)
|
|
{
|
|
key = values.Keys.ElementAt(i);
|
|
value = values.Values.ElementAt(i);
|
|
str = value.StringValue;
|
|
|
|
updates[key] = str;
|
|
if (IsRemoteConfigStr(str))
|
|
{
|
|
configs[key] = str;
|
|
}
|
|
}
|
|
|
|
if (configs.Count > 0)
|
|
{
|
|
Model.UpdateConfigs(configs);
|
|
}
|
|
|
|
DispatchUpdateValues(updates);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 数据接口
|
|
|
|
private bool TryGetDefaultValue<T>(string key, out T value)
|
|
{
|
|
value = default(T);
|
|
if(_defaultValues != null && _defaultValues.TryGetValue(key, out var obj))
|
|
{
|
|
value = (T)obj;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
public string GetStringValue(string key, string defaultValue = "")
|
|
{
|
|
if (_firebaseRemote != null)
|
|
{
|
|
try
|
|
{
|
|
return _firebaseRemote.GetValue(key).StringValue;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
LogException(e);
|
|
}
|
|
}
|
|
|
|
if (TryGetDefaultValue<string>(key, out var value))
|
|
{
|
|
return value;
|
|
}
|
|
|
|
return defaultValue;
|
|
}
|
|
|
|
public int GetIntValue(string key, int defaultValue = 0)
|
|
{
|
|
if (_firebaseRemote != null)
|
|
{
|
|
try
|
|
{
|
|
return (int) _firebaseRemote.GetValue(key).LongValue;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
LogException(e);
|
|
}
|
|
}
|
|
|
|
if (TryGetDefaultValue<int>(key, out var value))
|
|
{
|
|
return value;
|
|
}
|
|
|
|
return defaultValue;
|
|
}
|
|
|
|
public long GetLongValue(string key, long defaultValue = 0)
|
|
{
|
|
if (_firebaseRemote != null)
|
|
{
|
|
try
|
|
{
|
|
return _firebaseRemote.GetValue(key).LongValue;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
LogException(e);
|
|
}
|
|
}
|
|
|
|
if (TryGetDefaultValue<long>(key, out var value))
|
|
{
|
|
return value;
|
|
}
|
|
|
|
return defaultValue;
|
|
}
|
|
|
|
public double GetDoubleValue(string key, double defaultValue = 0)
|
|
{
|
|
if (_firebaseRemote != null)
|
|
{
|
|
try
|
|
{
|
|
return _firebaseRemote.GetValue(key).DoubleValue;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
LogException(e);
|
|
}
|
|
}
|
|
|
|
if (TryGetDefaultValue<double>(key, out var value))
|
|
{
|
|
return value;
|
|
}
|
|
|
|
return defaultValue;
|
|
}
|
|
|
|
public bool GetBoolValue(string key, bool defaultValue = false)
|
|
{
|
|
if (_firebaseRemote != null)
|
|
{
|
|
try
|
|
{
|
|
return _firebaseRemote?.GetValue(key).BooleanValue ?? defaultValue;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
LogException(e);
|
|
}
|
|
}
|
|
|
|
if (TryGetDefaultValue<bool>(key, out var value))
|
|
{
|
|
return value;
|
|
}
|
|
|
|
return defaultValue;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取全部值
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public static Dictionary<string, ConfigValue> GetAllValues()
|
|
{
|
|
if (Instance._firebaseRemote != null)
|
|
{
|
|
try
|
|
{
|
|
return (Dictionary<string, ConfigValue>)(Instance._firebaseRemote.AllValues);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
LogException(e);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
[CanBeNull]
|
|
public static string GetStaticValue(string key)
|
|
{
|
|
if (Instance._staticValues != null && Instance._staticValues.TryGetValue(key, out var value))
|
|
{
|
|
return value;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 云控值获取
|
|
|
|
public static string GetString(string key, string defaultValue = "")
|
|
{
|
|
return Instance.GetStringValue(key, defaultValue);
|
|
}
|
|
|
|
public static int GetInt(string key, int defaultValue = 0)
|
|
{
|
|
return Instance.GetIntValue(key, defaultValue);
|
|
}
|
|
|
|
public static long GetLong(string key, long defaultValue = 0)
|
|
{
|
|
return Instance.GetLongValue(key, defaultValue);
|
|
}
|
|
|
|
public static double GetDouble(string key, double defaultValue = 0)
|
|
{
|
|
return Instance.GetDoubleValue(key, defaultValue);
|
|
}
|
|
|
|
public static float GetFloat(string key, float defaultValue = 0)
|
|
{
|
|
return (float) Instance.GetDoubleValue(key, defaultValue);
|
|
}
|
|
|
|
public static bool GetBool(string key, bool defaultValue = false)
|
|
{
|
|
return Instance.GetBoolValue(key, defaultValue);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 云控配置获取
|
|
|
|
/// <summary>
|
|
/// 注册云控配置对象
|
|
/// </summary>
|
|
/// <param name="key"></param>
|
|
/// <param name="defaultJson"></param>
|
|
public static void RegisterConfig(string key, string defaultJson)
|
|
{
|
|
Instance.Model.SetDefaultConfig(key, defaultJson); // 配置默认值
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// 获取云控配置
|
|
/// </summary>
|
|
/// <param name="key"></param>
|
|
/// <typeparam name="T"></typeparam>
|
|
/// <returns></returns>
|
|
public static T GetConfig<T>(string key) where T : IRemoteConfig<T>
|
|
{
|
|
var config = Instance.Model.Get<T>(key);
|
|
return config;
|
|
}
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
#region 监听云控值变化
|
|
|
|
/// <summary>
|
|
/// 注册值变化事件
|
|
/// </summary>
|
|
/// <param name="key"></param>
|
|
/// <param name="onValueChanged"></param>
|
|
public static void RegisterOnValueChanged(string key, Action<string,string> onValueChanged)
|
|
{
|
|
Instance.AddOnValueChangeListener(key, onValueChanged);
|
|
}
|
|
/// <summary>
|
|
/// 取消注册值变化事件
|
|
/// </summary>
|
|
/// <param name="key"></param>
|
|
/// <param name="onValueChanged"></param>
|
|
public static void UnRegisterOnValueChanged(string key, Action<string,string> onValueChanged)
|
|
{
|
|
Instance.RemoveOnValueChangeListener(key, onValueChanged);
|
|
}
|
|
|
|
|
|
private void AddOnValueChangeListener(string key, Action<string,string> onValueChanged)
|
|
{
|
|
if (_changeEvents == null) _changeEvents = new Dictionary<string, Action<string,string>>(30);
|
|
|
|
if (HasOnValueChangeListener(key))
|
|
{
|
|
_changeEvents[key] += onValueChanged;
|
|
}
|
|
else
|
|
{
|
|
_changeEvents[key] = onValueChanged;
|
|
}
|
|
}
|
|
|
|
private void RemoveOnValueChangeListener(string key, Action<string,string> onValueChanged)
|
|
{
|
|
if (_changeEvents != null && HasOnValueChangeListener(key))
|
|
{
|
|
_changeEvents[key] -= onValueChanged;
|
|
}
|
|
}
|
|
|
|
private bool HasOnValueChangeListener(string key)
|
|
{
|
|
if (_changeEvents != null)
|
|
{
|
|
return _changeEvents.ContainsKey(key);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
private void DispatchUpdateValues(Dictionary<string, string> updates)
|
|
{
|
|
Dictionary<string, string> changes = new Dictionary<string, string>(updates.Count);
|
|
|
|
if (_staticValues == null) _staticValues = new Dictionary<string, string>();
|
|
|
|
string key, value;
|
|
for (int i = 0; i < updates.Keys.Count; i++)
|
|
{
|
|
key = updates.Keys.ElementAt(i);
|
|
value = updates.Values.ElementAt(i);
|
|
|
|
if (_staticValues.TryGetValue(key, out var oldValue))
|
|
{
|
|
if (oldValue != updates[key] && _changeEvents.ContainsKey(key))
|
|
{
|
|
changes[key] = value;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
changes[key] = value;
|
|
}
|
|
}
|
|
|
|
// --------- 发送值变化事件 ------------
|
|
for (int i = 0; i < changes.Keys.Count; i++)
|
|
{
|
|
key = updates.Keys.ElementAt(i);
|
|
value = updates.Values.ElementAt(i);
|
|
|
|
if (_changeEvents.TryGetValue(key, out var callback))
|
|
{
|
|
callback?.Invoke(key, value);
|
|
}
|
|
}
|
|
_staticValues = updates;
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
#region Log
|
|
|
|
private static void LogI(string msg, params object[] args)
|
|
{
|
|
Log.I(Tag, msg, args);
|
|
}
|
|
|
|
private static void LogE(string msg, params object[] args)
|
|
{
|
|
Log.E(Tag, msg, args);
|
|
}
|
|
|
|
private static void LogW(string msg, params object[] args)
|
|
{
|
|
Log.W(Tag, msg, args);
|
|
}
|
|
|
|
private static void LogException(Exception e)
|
|
{
|
|
Log.Exception(e);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |