using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using LitJson; using Sog; namespace ThinkingData.Analytics { public class ThinkingdataAnalytics { private const string LibVersion = "1.3.2"; private const string LibName = "tga_csharp_sdk"; private static readonly Regex KeyPattern = new Regex("^(#[a-z][a-z0-9_]{0,49})|([a-z][a-z0-9_]{0,50})$", RegexOptions.IgnoreCase); private readonly IConsumer _consumer; private readonly bool _enableUuid; private readonly Dictionary _pubicProperties; /* * 实例化tga类,接收一个Consumer的一个实例化对象 * @param consumer BatchConsumer,LoggerConsumer实例 */ public ThinkingdataAnalytics(IConsumer consumer) : this(consumer, false) { } public ThinkingdataAnalytics(IConsumer consumer, bool enableUuid) { _consumer = consumer; _enableUuid = enableUuid; _pubicProperties = new Dictionary(); ClearPublicProperties(); } /* * 公共属性只用于track接口,其他接口无效,且每次都会自动向track事件中添加公共属性 * @param properties 公共属性 */ public void SetPublicProperties(Dictionary properties) { lock (_pubicProperties) { foreach (var kvp in properties) { _pubicProperties[kvp.Key] = kvp.Value; } } } /* * 清理掉公共属性,之后track属性将不会再加入公共属性 */ public void ClearPublicProperties() { lock (_pubicProperties) { _pubicProperties.Clear(); _pubicProperties.Add("#lib", LibName); _pubicProperties.Add("#lib_version", LibVersion); _pubicProperties.Add("#zone_offset", TimeZoneInfo.Local.BaseUtcOffset.Hours); } } // 记录一个没有任何属性的事件 public void Track(string account_id, string distinct_id, string event_name) { _Add(account_id, distinct_id, "track", event_name, null, null); } /* * 用户事件属性(注册) * @param account_id 账号ID * @param distinct_id 匿名ID * @param event_name 事件名称 * @param properties 事件属性 */ public void Track(string account_id, string distinct_id, string event_name, Dictionary properties) { if (string.IsNullOrEmpty(event_name)) { throw new SystemException("The event name must be provided."); } _Add(account_id, distinct_id, "track", event_name, null, properties); } /* * 可更新事件属性 * @param account_id 账号ID * @param distinct_id 匿名ID * @param event_name 事件名称 * @param event_id 事件ID * @param properties 事件属性 */ public void TrackUpdate(string account_id, string distinct_id, string event_name, string event_id, Dictionary properties) { if (string.IsNullOrEmpty(event_name)) { throw new SystemException("The event name must be provided."); } if (string.IsNullOrEmpty(event_id)) { throw new SystemException("The event id must be provided."); } _Add(account_id, distinct_id, "track_update", event_name, event_id, properties); } /* * 可重写事件属性 * @param account_id 账号ID * @param distinct_id 匿名ID * @param event_name 事件名称 * @param event_id 事件ID * @param properties 事件属性 */ public void TrackOverwrite(string account_id, string distinct_id, string event_name, string event_id, Dictionary properties) { if (string.IsNullOrEmpty(event_name)) { throw new SystemException("The event name must be provided."); } if (string.IsNullOrEmpty(event_id)) { throw new SystemException("The event id must be provided."); } _Add(account_id, distinct_id, "track_overwrite", event_name, event_id, properties); } /* * 设置用户属性,如果已经存在,则覆盖,否则,新创建 * @param account_id 账号ID * @param distinct_id 匿名ID * @param properties 增加的用户属性 */ public void UserSet(string account_id, string distinct_id, Dictionary properties) { _Add(account_id, distinct_id, "user_set", properties); } /* * 删除用户属性 * @param account_id 账号 ID * @param distinct_id 访客 ID * @param properties 用户属性 */ public void UserUnSet(string account_id, string distinct_id, List properties) { var props = properties.ToDictionary(property => property, property => 0); _Add(account_id, distinct_id, "user_unset", props); } /** * 设置用户属性,首次设置用户的属性,如果该属性已经存在,该操作为无效. * @param account_id 账号ID * @param distinct_id 匿名ID * @param properties 增加的用户属性 */ public void UserSetOnce(string account_id, string distinct_id, Dictionary properties) { _Add(account_id, distinct_id, "user_setOnce", properties); } /** * 首次设置用户的属性。这个接口只能设置单个key对应的内容。 * @param account_id 账号ID * @param distinct_id 匿名ID * @param properties 增加的用户属性 */ public void UserSetOnce(string account_id, string distinct_id, string property, object value) { var properties = new Dictionary {{property, value}}; _Add(account_id, distinct_id, "user_setOnce", properties); } /* * 用户属性修改,只支持数字属性增加的接口 * @param account_id 账号ID * @param distinct_id 匿名ID * @param properties 增加的用户属性 */ public void UserAdd(string account_id, string distinct_id, Dictionary properties) { _Add(account_id, distinct_id, "user_add", properties); } public void UserAdd(string account_id, string distinct_id, string property, long value) { var properties = new Dictionary {{property, value}}; _Add(account_id, distinct_id, "user_add", properties); } /* * 追加用户的集合类型的一个或多个属性 * @param account_id 账号ID * @param distinct_id 匿名ID * @param properties 增加的用户属性 */ public void UserAppend(string account_id, string distinct_id, Dictionary properties) { _Add(account_id, distinct_id, "user_append", properties); } /** * 用户删除,此操作不可逆 * @param account_id 账号ID * @param distinct_id 匿名ID */ public void UserDelete(string account_id, string distinct_id) { _Add(account_id, distinct_id, "user_del", new Dictionary()); } /// 立即发送缓存中的所有日志 public void Flush() { _consumer.Flush(); } //关闭并退出 sdk 所有线程,停止前会清空所有本地数据 public void Close() { _consumer.Close(); } private static bool IsNumber(object value) { return (value is sbyte) || (value is short) || (value is int) || (value is long) || (value is byte) || (value is ushort) || (value is uint) || (value is ulong) || (value is decimal) || (value is float) || (value is double); } private static void AssertProperties(string type, IDictionary properties) { if (null == properties) { return; } foreach (var kvp in properties) { var key = kvp.Key; var value = kvp.Value; if (null == value) { continue; } if (KeyPattern.IsMatch(key)) { if (!IsNumber(value) && !(value is string) && !(value is DateTime) && !(value is bool) && !(value is IList) && !(value is JsonData)) { throw new ArgumentException( "The supported data type including: Number, String, Date, Boolean,List. Invalid property: {key}"); } // IList list = value as List; // if (list != null) // for (var i = 0; i < list.Count; i++) // { // if (list[i] is DateTime) // { // list[i] = (DateTime.Now).ToString("yyyy-MM-dd HH:mm:ss.fff"); // } // } if (type == "user_add" && !IsNumber(value)) { throw new ArgumentException("Only Number is allowed for user_add. Invalid property:" + key); } } else { throw new ArgumentException("The " + type + "'" + key + "' is invalid."); } } } private void _Add(string account_id, string distinct_id, string type, IDictionary properties) { _Add(account_id, distinct_id, type, null, null, properties); } private void _Add(string account_id, string distinct_id, string type, string event_name, string event_id, IDictionary properties) { if (string.IsNullOrEmpty(account_id) && string.IsNullOrEmpty(distinct_id)) { throw new SystemException("account_id or distinct_id must be provided. "); } var eventProperties = new Dictionary(properties); if (type == "track" || type == "track_update" || type == "track_overwrite") { if (_pubicProperties != null) lock (_pubicProperties) { foreach (var kvp in _pubicProperties) { if(eventProperties.ContainsKey(kvp.Key) == false) eventProperties.Add(kvp.Key, kvp.Value); else TraceLog.Error("try add same key ,key:" + kvp.Key + " val:" + kvp.Value); } } } var evt = new Dictionary(); if (account_id != null) { evt.Add("#account_id", account_id); } if (distinct_id != null) { evt.Add("#distinct_id", distinct_id); } if (event_name != null) { evt.Add("#event_name", event_name); } if (event_id != null) { evt.Add("#event_id", event_id); } //#uuid 只支持UUID标准格式xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx if (eventProperties.ContainsKey("#uuid")) { evt.Add("#uuid", eventProperties["#uuid"]); eventProperties.Remove("#uuid"); } else if (_enableUuid) { evt.Add("#uuid", Guid.NewGuid().ToString("D")); } AssertProperties(type, eventProperties); if (eventProperties.ContainsKey("#ip")) { evt.Add("#ip", eventProperties["#ip"]); eventProperties.Remove("#ip"); } //#first_check_id if (eventProperties.ContainsKey("#first_check_id")) { evt.Add("#first_check_id", eventProperties["#first_check_id"]); eventProperties.Remove("#first_check_id"); } // if (properties != null) // { // foreach (var kvp in properties) // { // if (kvp.Value is DateTime time) // { // eventProperties[kvp.Key] = time.ToString("yyyy-MM-dd HH:mm:ss.fff"); // } // } // } if (eventProperties.ContainsKey("#time")) { evt.Add("#time", eventProperties["#time"]); eventProperties.Remove("#time"); } else { evt.Add("#time", DateTime.Now); } //加上时区偏移 // if (eventProperties.ContainsKey("#zone_offset")) // { // evt.Add("#zone_offset", eventProperties["#zone_offset"]); // eventProperties.Remove("#zone_offset"); // } // else // { // evt.Add("#zone_offset", TimeZoneInfo.Local.BaseUtcOffset.Hours); // } evt.Add("#type", type); evt.Add("properties", eventProperties); _consumer.Send(evt); } } }