// // DBEntities.swift // Alamofire // // Created by mayue on 2022/11/4. // import Foundation import CryptoSwift internal enum Entity { } extension Entity { struct EventRecord: Codable { enum Priority: Int, Codable { case EMERGENCE = 0 case HIGH = 5 case DEFAULT = 10 case LOW = 15 } enum TransitionStatus: Int, Codable { case idle = 0 case instransition = 1 } let recordId: String let eventName: String let eventJson: String ///单位毫秒 let timestamp: Int64 let priority: Priority let transitionStatus: TransitionStatus init(eventName: String, event: Entity.Event, priority: Priority = .DEFAULT, transitionStatus: TransitionStatus = .idle) { let now = Date() let eventJson = event.asString ?? "" if eventJson.isEmpty { cdPrint("[WARNING] error for convert event to json") } self.recordId = "\(eventName)\(eventJson)\(now.timeIntervalSince1970)\(Int.random(in: Int.min...Int.max))".md5() self.eventName = eventName self.eventJson = eventJson self.timestamp = event.timestamp self.priority = priority self.transitionStatus = transitionStatus } init(recordId: String, eventName: String, eventJson: String, timestamp: Int64, priority: Int, transitionStatus: Int) { self.recordId = recordId self.eventName = eventName self.eventJson = eventJson self.timestamp = timestamp self.priority = .init(rawValue: priority) ?? .DEFAULT self.transitionStatus = .init(rawValue: transitionStatus) ?? .idle } enum CodingKeys: String, CodingKey { case recordId case eventName case eventJson case timestamp case priority case transitionStatus } static func createTableSql(with name: String) -> String { return """ CREATE TABLE IF NOT EXISTS \(name)( \(CodingKeys.recordId.rawValue) TEXT UNIQUE NOT NULL PRIMARY KEY, \(CodingKeys.eventName.rawValue) TEXT NOT NULL, \(CodingKeys.eventJson.rawValue) TEXT NOT NULL, \(CodingKeys.timestamp.rawValue) INTEGER, \(CodingKeys.priority.rawValue) INTEGER, \(CodingKeys.transitionStatus.rawValue) INTEGER); """ } func insertSql(to tableName: String) -> String { return "INSERT INTO \(tableName) VALUES ('\(recordId)', '\(eventName)', '\(eventJson)', '\(timestamp)', '\(priority.rawValue)', '\(transitionStatus.rawValue)')" } } } extension Entity { struct Event: Codable { ///客户端中记录此事件的时间(采用世界协调时间,毫秒为单位) let timestamp: Int64 let event: String let userInfo: UserInfo let param: [String: EventValue] let properties: [String: String] let eventId: String enum CodingKeys: String, CodingKey { case timestamp case userInfo = "info" case event, param, properties case eventId } init(timestamp: Int64, event: String, userInfo: UserInfo, parameters: [String : Any], properties: [String : String]) throws { guard let normalizedEvent = Self.normalizeKey(event), normalizedEvent == event else { cdPrint("drop event because of illegal event name: \(event)") cdPrint("standard: https://developers.google.com/android/reference/com/google/firebase/analytics/FirebaseAnalytics.Event") throw NSError(domain: "cunstrcting event error", code: 0, userInfo: [NSLocalizedDescriptionKey : "illegal event name: \(event)"]) } self.eventId = UUID().uuidString.lowercased() self.timestamp = timestamp self.event = normalizedEvent self.userInfo = userInfo self.param = Self.normalizeParameters(parameters) self.properties = properties } static let maxParametersCount = 25 static let maxKeyLength = 40 static let maxParameterStringValueLength = 128 static func normalizeParameters(_ parameters: [String : Any]) -> [String : EventValue] { var params = [String : EventValue]() var count = 0 parameters.sorted(by: { $0.key < $1.key }).forEach({ key, value in guard count < maxParametersCount else { cdPrint("too many parameters") cdPrint("standard: https://developers.google.com/android/reference/com/google/firebase/analytics/FirebaseAnalytics.Event") return } guard let normalizedKey = normalizeKey(key), normalizedKey == key else { cdPrint("drop event parameter because of illegal key: \(key)") cdPrint("standard: https://developers.google.com/android/reference/com/google/firebase/analytics/FirebaseAnalytics.Event") return } if let value = value as? String { params[normalizedKey] = Entity.EventValue(stringValue: String(value.prefix(maxParameterStringValueLength))) } else if let value = value as? Int { params[normalizedKey] = Entity.EventValue(longValue: Int64(value)) } else if let value = value as? Int64 { params[normalizedKey] = Entity.EventValue(longValue: value) } else if let value = value as? Double { params[normalizedKey] = Entity.EventValue(doubleValue: value) } else { params[normalizedKey] = Entity.EventValue(stringValue: String("\(value)".prefix(maxParameterStringValueLength))) } count += 1 }) return params } static func normalizeKey(_ key: String) -> String? { var mutableKey = key while let first = mutableKey.first, !first.isLetter { _ = mutableKey.removeFirst() } var normalizedKey = "" var count = 0 mutableKey.forEach { c in guard count < maxKeyLength, c.isAlphabetic || c.isDigit || c == "_" else { return } normalizedKey.append(c) count += 1 } return normalizedKey.isEmpty ? nil : normalizedKey } } ///用户信息 struct UserInfo: Codable { ///中台ID。只在未获取到uid时可以为空 let uid: String? ///设备ID(用户的设备ID,iOS取用户的IDFV或UUID,Android取androidID) let deviceId: String? ///adjust_id。只在未获取到adjust时可以为空 let adjustId: String? ///广告 ID/广告标识符 (IDFA) let adId: String? ///用户的pseudo_id let firebaseId: String? enum CodingKeys: String, CodingKey { case deviceId case uid case adjustId case adId case firebaseId } } // 参数对应的值 struct EventValue: Codable { let stringValue: String? // 事件参数的字符串值 let longValue: Int64? // 事件参数的整数值 let doubleValue: Double? // 事件参数的小数值。注意:APP序列化成JSON时,注意不要序列化成科学计数法 init(stringValue: String? = nil, longValue: Int64? = nil, doubleValue: Double? = nil) { self.stringValue = stringValue self.longValue = longValue self.doubleValue = doubleValue } enum CodingKeys: String, CodingKey { case stringValue = "s" case longValue = "i" case doubleValue = "d" } } } extension Entity { struct SystemTimeResult: Codable { let data: Int64 } }