thinkingdata-ruby 1.2.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|