2024-09-04 05:01:33 +00:00
|
|
|
|
//
|
|
|
|
|
|
// 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]()
|
2024-09-23 02:53:09 +00:00
|
|
|
|
var allParams = parameters;
|
|
|
|
|
|
|
|
|
|
|
|
GuruAnalytics.BuiltinParametersKeys.allCases.forEach { paramKey in
|
|
|
|
|
|
if let value = allParams.removeValue(forKey: paramKey.rawValue) {
|
|
|
|
|
|
params[paramKey.rawValue] = normalizeValue(value);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-09-04 05:01:33 +00:00
|
|
|
|
var count = 0
|
2024-09-23 02:53:09 +00:00
|
|
|
|
allParams.sorted(by: { $0.key < $1.key }).forEach({ key, value in
|
2024-09-04 05:01:33 +00:00
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-09-23 02:53:09 +00:00
|
|
|
|
params[normalizedKey] = normalizeValue(value)
|
2024-09-04 05:01:33 +00:00
|
|
|
|
|
|
|
|
|
|
count += 1
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
return params
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-09-23 02:53:09 +00:00
|
|
|
|
static func normalizeValue(_ value: Any) -> EventValue {
|
2025-08-08 10:12:14 +00:00
|
|
|
|
|
|
|
|
|
|
var preprocessedValue = value
|
|
|
|
|
|
|
|
|
|
|
|
if let val = preprocessedValue as? NSNumber {
|
|
|
|
|
|
preprocessedValue = val.numricValue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-09-23 02:53:09 +00:00
|
|
|
|
let eventValue: EventValue
|
2025-08-08 10:12:14 +00:00
|
|
|
|
if let value = preprocessedValue as? String {
|
|
|
|
|
|
eventValue = Entity.EventValue(stringValue: normalizeStringValue(String(value.prefix(maxParameterStringValueLength))))
|
|
|
|
|
|
} else if let value = preprocessedValue as? Int {
|
2024-09-23 02:53:09 +00:00
|
|
|
|
eventValue = Entity.EventValue(longValue: Int64(value))
|
2025-08-08 10:12:14 +00:00
|
|
|
|
} else if let value = preprocessedValue as? Int64 {
|
2024-09-23 02:53:09 +00:00
|
|
|
|
eventValue = Entity.EventValue(longValue: value)
|
2025-08-08 10:12:14 +00:00
|
|
|
|
} else if let value = preprocessedValue as? Double {
|
2024-09-23 02:53:09 +00:00
|
|
|
|
eventValue = Entity.EventValue(doubleValue: value)
|
|
|
|
|
|
} else {
|
2025-08-08 10:12:14 +00:00
|
|
|
|
eventValue = Entity.EventValue(stringValue: normalizeStringValue(String("\(value)".prefix(maxParameterStringValueLength))))
|
2024-09-23 02:53:09 +00:00
|
|
|
|
}
|
|
|
|
|
|
return eventValue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-08 10:12:14 +00:00
|
|
|
|
static func normalizeStringValue(_ value: String) -> String {
|
|
|
|
|
|
let normalizedString = value.replacingOccurrences(of: "'", with: "''")
|
|
|
|
|
|
cdPrint("original string value: \(value)")
|
|
|
|
|
|
cdPrint("normalized string value: \(normalizedString)")
|
|
|
|
|
|
return normalizedString
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-09-04 05:01:33 +00:00
|
|
|
|
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?
|
2025-08-08 10:12:14 +00:00
|
|
|
|
///appsFlyerId
|
|
|
|
|
|
let appsflyerId: String?
|
2024-09-04 05:01:33 +00:00
|
|
|
|
|
2024-09-23 02:53:09 +00:00
|
|
|
|
///IDFV
|
|
|
|
|
|
let vendorId: String? = UIDevice().identifierForVendor?.uuidString
|
|
|
|
|
|
|
2024-09-04 05:01:33 +00:00
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
|
|
|
|
case deviceId
|
|
|
|
|
|
case uid
|
|
|
|
|
|
case adjustId
|
|
|
|
|
|
case adId
|
|
|
|
|
|
case firebaseId
|
2024-09-23 02:53:09 +00:00
|
|
|
|
case vendorId
|
2025-08-08 10:12:14 +00:00
|
|
|
|
case appsflyerId
|
2024-09-04 05:01:33 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 参数对应的值
|
|
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-09-23 02:53:09 +00:00
|
|
|
|
|
|
|
|
|
|
extension Entity {
|
|
|
|
|
|
struct Session {
|
|
|
|
|
|
let id: UUID = UUID()
|
|
|
|
|
|
|
|
|
|
|
|
var sessionId: Int {
|
|
|
|
|
|
return id.uuidString.hash
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct SessionNumber: Codable {
|
|
|
|
|
|
var number: Int
|
|
|
|
|
|
let createdAtMs: Int64
|
|
|
|
|
|
|
|
|
|
|
|
static func createNumber() -> SessionNumber {
|
|
|
|
|
|
return SessionNumber(number: 0, createdAtMs: Date().msSince1970)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|