thinkingdata-ruby 1.2.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +59 -0
- data/CHANGELOG.md +22 -14
- data/Gemfile +7 -7
- data/LICENSE +201 -201
- data/README.md +11 -202
- data/demo/demo.rb +104 -142
- data/lib/thinkingdata-ruby/td_analytics.rb +495 -0
- data/lib/thinkingdata-ruby/{batch_consumer.rb → td_batch_consumer.rb} +142 -120
- data/lib/thinkingdata-ruby/td_debug_consumer.rb +73 -0
- data/lib/thinkingdata-ruby/td_errors.rb +38 -0
- data/lib/thinkingdata-ruby/td_logger_consumer.rb +77 -0
- data/lib/thinkingdata-ruby/td_version.rb +3 -0
- data/lib/thinkingdata-ruby.rb +5 -5
- data/thinkingdata-ruby.gemspec +16 -16
- metadata +13 -12
- data/lib/thinkingdata-ruby/debug_consumer.rb +0 -61
- data/lib/thinkingdata-ruby/errors.rb +0 -35
- data/lib/thinkingdata-ruby/logger_consumer.rb +0 -57
- data/lib/thinkingdata-ruby/tracker.rb +0 -402
- data/lib/thinkingdata-ruby/version.rb +0 -3
@@ -1,57 +0,0 @@
|
|
1
|
-
require 'logger'
|
2
|
-
require 'thinkingdata-ruby/errors'
|
3
|
-
|
4
|
-
module TDAnalytics
|
5
|
-
# 将数据写入本地文件, 需配合 LogBus 将数据上传到服务器
|
6
|
-
# 由于 LogBus 有完善的失败重传机制,因此建议用户首先考虑此方案
|
7
|
-
class LoggerConsumer
|
8
|
-
# LoggerConsumer 构造函数
|
9
|
-
# log_path: 日志文件存放目录
|
10
|
-
# mode: 日志文件切分模式,可选 daily/hourly
|
11
|
-
# prefix: 日志文件前缀,默认为 'tda.log', 日志文件名格式为: tda.log.2019-11-15
|
12
|
-
def initialize(log_path='.', mode='daily', prefix:'tda.log')
|
13
|
-
case mode
|
14
|
-
when 'hourly'
|
15
|
-
@suffix_mode = '%Y-%m-%d-%H'
|
16
|
-
when 'daily'
|
17
|
-
@suffix_mode = '%Y-%m-%d'
|
18
|
-
else
|
19
|
-
raise IllegalParameterError.new("#{mode} is unsupported for LoggerConsumer. Replaced it by daily or hourly")
|
20
|
-
end
|
21
|
-
|
22
|
-
raise IllegalParameterError.new("prefix couldn't be empty") if prefix.nil? || prefix.length == 0
|
23
|
-
|
24
|
-
@current_suffix = Time.now.strftime(@suffix_mode)
|
25
|
-
|
26
|
-
@full_prefix = "#{log_path}/#{prefix}."
|
27
|
-
|
28
|
-
_reset
|
29
|
-
end
|
30
|
-
|
31
|
-
def add(msg)
|
32
|
-
unless Time.now.strftime(@suffix_mode) == @current_suffix
|
33
|
-
@logger.close
|
34
|
-
@current_suffix = Time.now.strftime(@suffix_mode)
|
35
|
-
_reset
|
36
|
-
end
|
37
|
-
@logger.info(msg.to_json)
|
38
|
-
end
|
39
|
-
|
40
|
-
# 关闭 logger
|
41
|
-
def close
|
42
|
-
@logger.close
|
43
|
-
end
|
44
|
-
|
45
|
-
private
|
46
|
-
|
47
|
-
# 重新创建 logger 对象. LogBus 判断新文件会同时考虑文件名和 inode,因此默认的切分方式会导致数据重传
|
48
|
-
def _reset
|
49
|
-
@logger = Logger.new("#{@full_prefix}#{@current_suffix}")
|
50
|
-
@logger.level = Logger::INFO
|
51
|
-
@logger.formatter = proc do |severity, datetime, progname, msg|
|
52
|
-
"#{msg}\n"
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
end
|
57
|
-
end
|
@@ -1,402 +0,0 @@
|
|
1
|
-
require 'securerandom'
|
2
|
-
require 'thinkingdata-ruby/errors'
|
3
|
-
require 'thinkingdata-ruby/version'
|
4
|
-
|
5
|
-
module TDAnalytics
|
6
|
-
# TDAnalytics::Tracker 是数据上报的核心类,使用此类上报事件数据和更新用户属性.
|
7
|
-
# 创建 Tracker 类需要传入 consumer 对象,consumer 决定了如何处理格式化的数据(存储在本地日志文件还是上传到服务端).
|
8
|
-
#
|
9
|
-
# ta = TDAnalytics::Tracker.new(consumer)
|
10
|
-
# ta.track('your_event', distinct_id: 'distinct_id_of_user')
|
11
|
-
#
|
12
|
-
# TDAnalytics 提供了三种 consumer 实现:
|
13
|
-
# LoggerConsumer: 数据写入本地文件
|
14
|
-
# DebugConsumer: 数据逐条、同步的发送到服务端,并返回详细的报错信息
|
15
|
-
# BatchConsumer: 数据批量、同步的发送到服务端
|
16
|
-
#
|
17
|
-
# 您也可以传入自己实现的 Consumer,只需实现以下接口:
|
18
|
-
# add(message): 接受 hash 类型的数据对象
|
19
|
-
# flush: (可选) 将缓冲区的数据发送到指定地址
|
20
|
-
# close: (可选) 程序退出时用户可以主动调用此接口以保证安全退出
|
21
|
-
class Tracker
|
22
|
-
|
23
|
-
LIB_PROPERTIES = {
|
24
|
-
'#lib' => 'ruby',
|
25
|
-
'#lib_version' => TDAnalytics::VERSION,
|
26
|
-
}
|
27
|
-
|
28
|
-
# SDK 构造函数,传入 consumer 对象
|
29
|
-
#
|
30
|
-
# 默认情况下,除参数不合法外,其他 Error 会被忽略,如果您希望自己处理接口调用中的 Error,可以传入自定义的 error handler.
|
31
|
-
# ErrorHandler 的定义可以参考 thinkingdata-ruby/errors.rb
|
32
|
-
#
|
33
|
-
# uuid 如果为 true,每条数据都会被带上随机 UUID 作为 #uuid 属性的值上报,该值不会入库,仅仅用于后台做数据重复检测
|
34
|
-
def initialize(consumer, error_handler = nil, uuid: false)
|
35
|
-
@error_handler = error_handler || ErrorHandler.new
|
36
|
-
@consumer = consumer
|
37
|
-
@super_properties = {}
|
38
|
-
@uuid = uuid
|
39
|
-
end
|
40
|
-
|
41
|
-
# 设置公共事件属性,公共事件属性是所有事件都会带上的属性. 此方法会将传入的属性与当前公共属性合并.
|
42
|
-
# 如果希望跳过本地格式校验,可以传入值为 true 的 skip_local_check 参数
|
43
|
-
def set_super_properties(properties, skip_local_check = false)
|
44
|
-
unless skip_local_check || _check_properties(:track, properties)
|
45
|
-
@error_handler.handle(IllegalParameterError.new("Invalid super properties"))
|
46
|
-
return false
|
47
|
-
end
|
48
|
-
properties.each do |k, v|
|
49
|
-
if v.is_a?(Time)
|
50
|
-
@super_properties[k] = _format_time(v)
|
51
|
-
else
|
52
|
-
@super_properties[k] = v
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
# 清除公共事件属性
|
58
|
-
def clear_super_properties
|
59
|
-
@super_properties = {}
|
60
|
-
end
|
61
|
-
|
62
|
-
# 上报事件. 每个事件都包含一个事件名和 Hash 对象的时间属性. 其参数说明如下:
|
63
|
-
# event_name: (必须) 事件名 必须是英文字母开头,可以包含字母、数字和 _, 长度不超过 50 个字符.
|
64
|
-
# distinct_id: (可选) 访客 ID
|
65
|
-
# account_id: (可选) 账号ID distinct_id 和 account_id 不能同时为空
|
66
|
-
# properties: (可选) Hash 事件属性。支持四种类型的值:字符串、数值、Time、boolean
|
67
|
-
# time: (可选)Time 事件发生时间,如果不传默认为系统当前时间
|
68
|
-
# ip: (可选) 事件 IP,如果传入 IP 地址,后端可以通过 IP 地址解析事件发生地点
|
69
|
-
# skip_local_check: (可选) boolean 表示是否跳过本地检测
|
70
|
-
def track(event_name: nil, distinct_id: nil, account_id: nil, properties: {}, time: nil, ip: nil,first_check_id:nil, skip_local_check: false)
|
71
|
-
begin
|
72
|
-
_check_name event_name
|
73
|
-
_check_id(distinct_id, account_id)
|
74
|
-
unless skip_local_check
|
75
|
-
_check_properties(:track, properties)
|
76
|
-
end
|
77
|
-
rescue TDAnalyticsError => e
|
78
|
-
@error_handler.handle(e)
|
79
|
-
return false
|
80
|
-
end
|
81
|
-
|
82
|
-
data = {}
|
83
|
-
data[:event_name] = event_name
|
84
|
-
data[:distinct_id] = distinct_id if distinct_id
|
85
|
-
data[:account_id] = account_id if account_id
|
86
|
-
data[:time] = time if time
|
87
|
-
data[:ip] = ip if ip
|
88
|
-
data[:first_check_id] = first_check_id if first_check_id
|
89
|
-
data[:properties] = properties
|
90
|
-
|
91
|
-
_internal_track(:track, data)
|
92
|
-
end
|
93
|
-
|
94
|
-
# 上报事件数据可进行更新. 每个事件都包含一个事件名和事件ID以及 Hash 对象的时间属性. 其参数说明如下:
|
95
|
-
# event_name: (必须) 事件名 必须是英文字母开头,可以包含字母、数字和 _, 长度不超过 50 个字符.
|
96
|
-
# event_id:(必须) event_name + event_id 会作为一条事件的唯一键
|
97
|
-
# distinct_id: (可选) 访客 ID
|
98
|
-
# account_id: (可选) 账号ID distinct_id 和 account_id 不能同时为空
|
99
|
-
# properties: (可选) Hash 事件属性。支持四种类型的值:字符串、数值、Time、boolean
|
100
|
-
# time: (可选)Time 事件发生时间,如果不传默认为系统当前时间
|
101
|
-
# ip: (可选) 事件 IP,如果传入 IP 地址,后端可以通过 IP 地址解析事件发生地点
|
102
|
-
# skip_local_check: (可选) boolean 表示是否跳过本地检测
|
103
|
-
def track_overwrite(event_name: nil,event_id: nil, distinct_id: nil, account_id: nil, properties: {}, time: nil, ip: nil, skip_local_check: false)
|
104
|
-
begin
|
105
|
-
_check_name event_name
|
106
|
-
_check_event_id event_id
|
107
|
-
_check_id(distinct_id, account_id)
|
108
|
-
unless skip_local_check
|
109
|
-
_check_properties(:track_overwrite, properties)
|
110
|
-
end
|
111
|
-
rescue TDAnalyticsError => e
|
112
|
-
@error_handler.handle(e)
|
113
|
-
return false
|
114
|
-
end
|
115
|
-
|
116
|
-
data = {}
|
117
|
-
data[:event_name] = event_name
|
118
|
-
data[:event_id] = event_id
|
119
|
-
data[:distinct_id] = distinct_id if distinct_id
|
120
|
-
data[:account_id] = account_id if account_id
|
121
|
-
data[:time] = time if time
|
122
|
-
data[:ip] = ip if ip
|
123
|
-
data[:properties] = properties
|
124
|
-
_internal_track(:track_overwrite, data)
|
125
|
-
end
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
# 上报事件数据可进行覆盖. 每个事件都包含一个事件名和事件ID以及 Hash 对象的时间属性. 其参数说明如下:
|
130
|
-
# event_name: (必须) 事件名 必须是英文字母开头,可以包含字母、数字和 _, 长度不超过 50 个字符.
|
131
|
-
# event_id:(必须) event_name + event_id 会作为一条事件的唯一键
|
132
|
-
# distinct_id: (可选) 访客 ID
|
133
|
-
# account_id: (可选) 账号ID distinct_id 和 account_id 不能同时为空
|
134
|
-
# properties: (可选) Hash 事件属性。支持四种类型的值:字符串、数值、Time、boolean
|
135
|
-
# time: (可选)Time 事件发生时间,如果不传默认为系统当前时间
|
136
|
-
# ip: (可选) 事件 IP,如果传入 IP 地址,后端可以通过 IP 地址解析事件发生地点
|
137
|
-
# skip_local_check: (可选) boolean 表示是否跳过本地检测
|
138
|
-
def track_update(event_name: nil,event_id: nil, distinct_id: nil, account_id: nil, properties: {}, time: nil, ip: nil, skip_local_check: false)
|
139
|
-
begin
|
140
|
-
_check_name event_name
|
141
|
-
_check_event_id event_id
|
142
|
-
_check_id(distinct_id, account_id)
|
143
|
-
unless skip_local_check
|
144
|
-
_check_properties(:track_update, properties)
|
145
|
-
end
|
146
|
-
rescue TDAnalyticsError => e
|
147
|
-
@error_handler.handle(e)
|
148
|
-
return false
|
149
|
-
end
|
150
|
-
|
151
|
-
data = {}
|
152
|
-
data[:event_name] = event_name
|
153
|
-
data[:event_id] = event_id
|
154
|
-
data[:distinct_id] = distinct_id if distinct_id
|
155
|
-
data[:account_id] = account_id if account_id
|
156
|
-
data[:time] = time if time
|
157
|
-
data[:ip] = ip if ip
|
158
|
-
data[:properties] = properties
|
159
|
-
_internal_track(:track_update, data)
|
160
|
-
end
|
161
|
-
|
162
|
-
# 设置用户属性. 如果出现同名属性,则会覆盖之前的值.
|
163
|
-
# distinct_id: (可选) 访客 ID
|
164
|
-
# account_id: (可选) 账号ID distinct_id 和 account_id 不能同时为空
|
165
|
-
# properties: (可选) Hash 用户属性。支持四种类型的值:字符串、数值、Time、boolean
|
166
|
-
def user_set(distinct_id: nil, account_id: nil, properties: {}, ip: nil)
|
167
|
-
begin
|
168
|
-
_check_id(distinct_id, account_id)
|
169
|
-
_check_properties(:user_set, properties)
|
170
|
-
rescue TDAnalyticsError => e
|
171
|
-
@error_handler.handle(e)
|
172
|
-
return false
|
173
|
-
end
|
174
|
-
|
175
|
-
_internal_track(:user_set,
|
176
|
-
distinct_id: distinct_id,
|
177
|
-
account_id: account_id,
|
178
|
-
properties: properties,
|
179
|
-
ip: ip,
|
180
|
-
)
|
181
|
-
end
|
182
|
-
|
183
|
-
# 设置用户属性. 如果有重名属性,则丢弃, 参数与 user_set 相同
|
184
|
-
def user_set_once(distinct_id: nil, account_id: nil, properties: {}, ip: nil)
|
185
|
-
begin
|
186
|
-
_check_id(distinct_id, account_id)
|
187
|
-
_check_properties(:user_setOnce, properties)
|
188
|
-
rescue TDAnalyticsError => e
|
189
|
-
@error_handler.handle(e)
|
190
|
-
return false
|
191
|
-
end
|
192
|
-
|
193
|
-
_internal_track(:user_setOnce,
|
194
|
-
distinct_id: distinct_id,
|
195
|
-
account_id: account_id,
|
196
|
-
properties: properties,
|
197
|
-
ip: ip,
|
198
|
-
)
|
199
|
-
end
|
200
|
-
|
201
|
-
# 追加用户的一个或多个列表类型的属性
|
202
|
-
def user_append(distinct_id: nil, account_id: nil, properties: {})
|
203
|
-
begin
|
204
|
-
_check_id(distinct_id, account_id)
|
205
|
-
_check_properties(:user_append, properties)
|
206
|
-
rescue TDAnalyticsError => e
|
207
|
-
@error_handler.handle(e)
|
208
|
-
return false
|
209
|
-
end
|
210
|
-
|
211
|
-
_internal_track(:user_append,
|
212
|
-
distinct_id: distinct_id,
|
213
|
-
account_id: account_id,
|
214
|
-
properties: properties,
|
215
|
-
)
|
216
|
-
end
|
217
|
-
|
218
|
-
# 删除用户属性, property 可以传入需要删除的用户属性的 key 值,或者 key 值数组
|
219
|
-
def user_unset(distinct_id: nil, account_id: nil, property: nil)
|
220
|
-
properties = {}
|
221
|
-
if property.is_a?(Array)
|
222
|
-
property.each do |k|
|
223
|
-
properties[k] = 0
|
224
|
-
end
|
225
|
-
else
|
226
|
-
properties[property] = 0
|
227
|
-
end
|
228
|
-
|
229
|
-
begin
|
230
|
-
_check_id(distinct_id, account_id)
|
231
|
-
_check_properties(:user_unset, properties)
|
232
|
-
rescue TDAnalyticsError => e
|
233
|
-
@error_handler.handle(e)
|
234
|
-
return false
|
235
|
-
end
|
236
|
-
|
237
|
-
_internal_track(:user_unset,
|
238
|
-
distinct_id: distinct_id,
|
239
|
-
account_id: account_id,
|
240
|
-
properties: properties,
|
241
|
-
)
|
242
|
-
end
|
243
|
-
|
244
|
-
# 累加用户属性, 如果用户属性不存在,则会设置为 0,然后再累加
|
245
|
-
# distinct_id: (可选) 访客 ID
|
246
|
-
# account_id: (可选) 账号ID distinct_id 和 account_id 不能同时为空
|
247
|
-
# properties: (可选) Hash 数值类型的用户属性
|
248
|
-
def user_add(distinct_id: nil, account_id: nil, properties: {})
|
249
|
-
begin
|
250
|
-
_check_id(distinct_id, account_id)
|
251
|
-
_check_properties(:user_add, properties)
|
252
|
-
rescue TDAnalyticsError => e
|
253
|
-
@error_handler.handle(e)
|
254
|
-
return false
|
255
|
-
end
|
256
|
-
|
257
|
-
_internal_track(:user_add,
|
258
|
-
distinct_id: distinct_id,
|
259
|
-
account_id: account_id,
|
260
|
-
properties: properties,
|
261
|
-
)
|
262
|
-
end
|
263
|
-
|
264
|
-
# 删除用户,用户之前的事件数据不会被删除
|
265
|
-
def user_del(distinct_id: nil, account_id: nil)
|
266
|
-
begin
|
267
|
-
_check_id(distinct_id, account_id)
|
268
|
-
rescue TDAnalyticsError => e
|
269
|
-
@error_handler.handle(e)
|
270
|
-
return false
|
271
|
-
end
|
272
|
-
|
273
|
-
_internal_track(:user_del,
|
274
|
-
distinct_id: distinct_id,
|
275
|
-
account_id: account_id,
|
276
|
-
)
|
277
|
-
end
|
278
|
-
|
279
|
-
# 立即上报数据,对于 BatchConsumer 会触发上报
|
280
|
-
def flush
|
281
|
-
return true unless defined? @consumer.flush
|
282
|
-
ret = true
|
283
|
-
begin
|
284
|
-
@consumer.flush
|
285
|
-
rescue TDAnalyticsError => e
|
286
|
-
@error_handler.handle(e)
|
287
|
-
ret = false
|
288
|
-
end
|
289
|
-
ret
|
290
|
-
end
|
291
|
-
|
292
|
-
# 退出前调用,保证 Consumer 安全退出
|
293
|
-
def close
|
294
|
-
return true unless defined? @consumer.close
|
295
|
-
ret = true
|
296
|
-
begin
|
297
|
-
@consumer.close
|
298
|
-
rescue TDAnalyticsError => e
|
299
|
-
@error_handler.handle(e)
|
300
|
-
ret = false
|
301
|
-
end
|
302
|
-
ret
|
303
|
-
end
|
304
|
-
|
305
|
-
private
|
306
|
-
|
307
|
-
# 出现异常的时候返回 false, 否则 true
|
308
|
-
def _internal_track(type, properties: {}, event_name: nil, event_id:nil, account_id: nil, distinct_id: nil, ip: nil,first_check_id: nil, time: Time.now)
|
309
|
-
if account_id == nil && distinct_id == nil
|
310
|
-
raise IllegalParameterError.new('account id or distinct id must be provided.')
|
311
|
-
end
|
312
|
-
|
313
|
-
if type == :track || type == :track_update || type == :track_overwrite
|
314
|
-
raise IllegalParameterError.new('event name is empty') if event_name == nil
|
315
|
-
properties = {'#zone_offset': time.utc_offset / 3600.0}.merge(LIB_PROPERTIES).merge(@super_properties).merge(properties)
|
316
|
-
end
|
317
|
-
|
318
|
-
# 格式化 Time 类型
|
319
|
-
properties.each do |k, v|
|
320
|
-
if v.is_a?(Time)
|
321
|
-
properties[k] = _format_time(v)
|
322
|
-
end
|
323
|
-
end
|
324
|
-
|
325
|
-
data = {
|
326
|
-
'#type' => type,
|
327
|
-
'#time' => _format_time(time),
|
328
|
-
'properties' => properties,
|
329
|
-
}
|
330
|
-
|
331
|
-
data['#event_name'] = event_name if (type == :track || type == :track_update || :track_overwrite)
|
332
|
-
data['#event_id'] = event_id if (type == :track_update || type == :track_overwrite)
|
333
|
-
data['#account_id'] = account_id if account_id
|
334
|
-
data['#distinct_id'] = distinct_id if distinct_id
|
335
|
-
data['#ip'] = ip if ip
|
336
|
-
data['#first_check_id'] = first_check_id if first_check_id
|
337
|
-
data['#uuid'] = SecureRandom.uuid if @uuid
|
338
|
-
|
339
|
-
ret = true
|
340
|
-
begin
|
341
|
-
@consumer.add(data)
|
342
|
-
rescue TDAnalyticsError => e
|
343
|
-
@error_handler.handle(e)
|
344
|
-
ret = false
|
345
|
-
end
|
346
|
-
|
347
|
-
ret
|
348
|
-
end
|
349
|
-
|
350
|
-
# 将 Time 类型格式化为数数指定格式的字符串
|
351
|
-
def _format_time(time)
|
352
|
-
time.strftime("%Y-%m-%d %H:%M:%S.#{((time.to_f * 1000.0).to_i % 1000).to_s.rjust(3, "0")}")
|
353
|
-
end
|
354
|
-
|
355
|
-
def _check_event_id(event_id)
|
356
|
-
raise IllegalParameterError.new("the event_id or property cannot be nil") if event_id.nil?
|
357
|
-
true
|
358
|
-
end
|
359
|
-
|
360
|
-
# 属性名或者事件名检查
|
361
|
-
def _check_name(name)
|
362
|
-
raise IllegalParameterError.new("the name of event or property cannot be nil") if name.nil?
|
363
|
-
|
364
|
-
unless name.instance_of?(String) || name.instance_of?(Symbol)
|
365
|
-
raise IllegalParameterError.new("#{name} is invalid. It must be String or Symbol")
|
366
|
-
end
|
367
|
-
true
|
368
|
-
end
|
369
|
-
|
370
|
-
# 属性类型检查
|
371
|
-
def _check_properties(type, properties)
|
372
|
-
unless properties.instance_of? Hash
|
373
|
-
return false
|
374
|
-
end
|
375
|
-
|
376
|
-
properties.each do |k, v|
|
377
|
-
_check_name k
|
378
|
-
next if v.nil?
|
379
|
-
unless v.is_a?(Integer) || v.is_a?(Float) || v.is_a?(Symbol) || v.is_a?(String) || v.is_a?(Time) || !!v == v || v.is_a?(Array)
|
380
|
-
raise IllegalParameterError.new("The value of properties must be type in Integer, Float, Symbol, String, Array,and Time")
|
381
|
-
end
|
382
|
-
|
383
|
-
if type == :user_add
|
384
|
-
raise IllegalParameterError.new("Property value for user add must be numbers") unless v.is_a?(Integer) || v.is_a?(Float)
|
385
|
-
end
|
386
|
-
if v.is_a?(Array)
|
387
|
-
v.each_index do |i|
|
388
|
-
if v[i].is_a?(Time)
|
389
|
-
v[i] = _format_time(v[i])
|
390
|
-
end
|
391
|
-
end
|
392
|
-
end
|
393
|
-
end
|
394
|
-
true
|
395
|
-
end
|
396
|
-
|
397
|
-
# 检查用户 ID 合法性
|
398
|
-
def _check_id(distinct_id, account_id)
|
399
|
-
raise IllegalParameterError.new("account id or distinct id must be provided.") if distinct_id.nil? && account_id.nil?
|
400
|
-
end
|
401
|
-
end
|
402
|
-
end
|