@haluo/util 2.0.33 → 2.1.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.
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/modules/app-call/configs.d.ts +9 -0
- package/dist/modules/app-call/configs.js +39 -0
- package/dist/modules/app-call/core.d.ts +53 -0
- package/dist/modules/app-call/core.js +166 -0
- package/dist/modules/app-call/extensions.d.ts +186 -0
- package/dist/modules/app-call/extensions.js +1226 -0
- package/dist/modules/app-call/index.d.ts +16 -0
- package/dist/modules/app-call/index.js +84 -0
- package/dist/modules/app-call/offline.d.ts +12 -0
- package/dist/modules/app-call/offline.js +183 -0
- package/dist/modules/app-call/partner-bridge.d.ts +5 -0
- package/dist/modules/app-call/partner-bridge.js +68 -0
- package/dist/modules/app-call/types.d.ts +53 -0
- package/dist/modules/app-call/types.js +4 -0
- package/dist/modules/open-app/index.d.ts +2 -7
- package/dist/modules/tools/index.d.ts +3 -3
- package/dist/modules/track/index.d.ts +122 -0
- package/dist/modules/track/index.js +368 -0
- package/dist/modules/track/types.d.ts +108 -0
- package/dist/modules/track/types.js +4 -0
- package/dist/modules/upload/aliOss.d.ts +16 -1
- package/dist/modules/upload/aliOss.js +46 -8
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +27 -2
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Track 埋点模块
|
|
3
|
+
* 统一封装各项目的埋点上报逻辑
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* // 最简初始化(所有配置都有默认值)
|
|
7
|
+
* import { createTracker } from '@haluo/util/dist/modules/track'
|
|
8
|
+
*
|
|
9
|
+
* const tracker = createTracker({})
|
|
10
|
+
* // 或指定 appName
|
|
11
|
+
* const tracker2 = createTracker({ appName: 'emoto' })
|
|
12
|
+
*
|
|
13
|
+
* // 使用
|
|
14
|
+
* tracker.track('EVENT_ID', { key: 'value' })
|
|
15
|
+
* tracker.trackError({ err: 'error message' })
|
|
16
|
+
*/
|
|
17
|
+
export * from './types';
|
|
18
|
+
/** 默认埋点上报URL */
|
|
19
|
+
const DEFAULT_LOG_URL = 'https://log-center.58moto.com/am/log/v1/json';
|
|
20
|
+
/** 默认应用名称 */
|
|
21
|
+
const DEFAULT_APP_NAME = 'moto';
|
|
22
|
+
/** 默认错误上报事件ID */
|
|
23
|
+
const ERROR_EVENT_ID = 'S_00000000000082';
|
|
24
|
+
/** 默认错误应用类型(H5异常) */
|
|
25
|
+
const DEFAULT_ERROR_APPTYPE = '2012';
|
|
26
|
+
/**
|
|
27
|
+
* 内置的简易 httpClient
|
|
28
|
+
* 使用 fetch 实现 POST 请求
|
|
29
|
+
*/
|
|
30
|
+
const defaultHttpClient = {
|
|
31
|
+
post: (url, data) => {
|
|
32
|
+
if (typeof window === 'undefined' || !window.fetch) {
|
|
33
|
+
console.warn('[Tracker] 当前环境不支持 fetch');
|
|
34
|
+
return Promise.resolve();
|
|
35
|
+
}
|
|
36
|
+
// 构建 form-urlencoded 格式的请求体
|
|
37
|
+
const formBody = Object.keys(data)
|
|
38
|
+
.map(key => encodeURIComponent(key) + '=' + encodeURIComponent(data[key]))
|
|
39
|
+
.join('&');
|
|
40
|
+
return fetch(url, {
|
|
41
|
+
method: 'POST',
|
|
42
|
+
headers: {
|
|
43
|
+
'Content-Type': 'application/x-www-form-urlencoded'
|
|
44
|
+
},
|
|
45
|
+
body: formBody
|
|
46
|
+
}).then(res => res.json()).catch(err => {
|
|
47
|
+
console.error('[Tracker] 埋点请求失败:', err);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* 从 localStorage 安全获取并解析 JSON
|
|
53
|
+
*/
|
|
54
|
+
function safeGetLocalStorage(key, defaultValue) {
|
|
55
|
+
try {
|
|
56
|
+
if (typeof window === 'undefined' || !window.localStorage) {
|
|
57
|
+
return defaultValue;
|
|
58
|
+
}
|
|
59
|
+
const value = window.localStorage.getItem(key);
|
|
60
|
+
if (!value || value === 'undefined') {
|
|
61
|
+
return defaultValue;
|
|
62
|
+
}
|
|
63
|
+
return JSON.parse(value);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return defaultValue;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* 获取用户代理字符串
|
|
71
|
+
*/
|
|
72
|
+
function getUserAgent() {
|
|
73
|
+
if (typeof window === 'undefined' || !window.navigator) {
|
|
74
|
+
return '';
|
|
75
|
+
}
|
|
76
|
+
return window.navigator.userAgent.toLowerCase();
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* 检测平台类型
|
|
80
|
+
*/
|
|
81
|
+
function detectPlatform(ua) {
|
|
82
|
+
const isAndroid = ua.indexOf('android') > -1;
|
|
83
|
+
const isHarmonyos = ua.indexOf('harmonyos') > -1;
|
|
84
|
+
return isAndroid || isHarmonyos ? 'android' : 'ios';
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* 检测是否在客户端环境
|
|
88
|
+
*/
|
|
89
|
+
function isClientEnv() {
|
|
90
|
+
return typeof window !== 'undefined' && !!window.isClient;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* 检测是否为开发环境
|
|
94
|
+
*/
|
|
95
|
+
function isDevelopment() {
|
|
96
|
+
return process.env.NODE_ENV === 'development';
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* 获取全局 AppCall 对象
|
|
100
|
+
*/
|
|
101
|
+
function getGlobalAppCall() {
|
|
102
|
+
if (typeof window === 'undefined')
|
|
103
|
+
return null;
|
|
104
|
+
return window.AppCall || null;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* 内置的 getChannel 方法
|
|
108
|
+
* 根据 UA 检测渠道:微信、QQ、微博等
|
|
109
|
+
*/
|
|
110
|
+
function defaultGetChannel() {
|
|
111
|
+
if (typeof window === 'undefined' || !window.navigator) {
|
|
112
|
+
return 'other';
|
|
113
|
+
}
|
|
114
|
+
const ua = window.navigator.userAgent.toLowerCase();
|
|
115
|
+
const isAndroid = ua.indexOf('android') > -1;
|
|
116
|
+
// 区分出微信、QQ、微博平台、其他
|
|
117
|
+
if (ua.indexOf('micromessenger') > -1) {
|
|
118
|
+
// 在微信中打开
|
|
119
|
+
return 'weixin';
|
|
120
|
+
}
|
|
121
|
+
else if (ua.indexOf('weibo') > -1) {
|
|
122
|
+
// 在新浪微博客户端打开
|
|
123
|
+
return 'weibo';
|
|
124
|
+
}
|
|
125
|
+
else if (ua.indexOf(' qq') > -1 && ua.indexOf('mqqbrowser') < 0 && !isAndroid) {
|
|
126
|
+
// ios在QQ空间打开
|
|
127
|
+
return 'qq';
|
|
128
|
+
}
|
|
129
|
+
else if (ua.indexOf(' qq') > -1 && ua.indexOf('mqqbrowser') > -1 && isAndroid) {
|
|
130
|
+
// 安卓在QQ空间打开
|
|
131
|
+
return 'qq';
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
// 使用 AppCall 获取包名,或返回 other
|
|
135
|
+
const appCall = getGlobalAppCall();
|
|
136
|
+
return appCall?.getAppPackage?.() || 'other';
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Track 类
|
|
141
|
+
* 提供埋点上报功能
|
|
142
|
+
*/
|
|
143
|
+
class Tracker {
|
|
144
|
+
config;
|
|
145
|
+
constructor(config) {
|
|
146
|
+
this.config = config;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* 获取当前版本号
|
|
150
|
+
* 优先级:window.AppCall.getAppVersion() > 传入的 appCall > config.version
|
|
151
|
+
*/
|
|
152
|
+
getVersion() {
|
|
153
|
+
const { version, appCall } = this.config;
|
|
154
|
+
// 优先使用全局 window.AppCall 获取版本号
|
|
155
|
+
const globalAppCall = getGlobalAppCall();
|
|
156
|
+
if (globalAppCall?.getAppVersion) {
|
|
157
|
+
const appVersion = globalAppCall.getAppVersion();
|
|
158
|
+
if (appVersion)
|
|
159
|
+
return appVersion;
|
|
160
|
+
}
|
|
161
|
+
// 其次使用传入的 appCall
|
|
162
|
+
if (appCall?.getAppVersion) {
|
|
163
|
+
const appVersion = appCall.getAppVersion();
|
|
164
|
+
if (appVersion)
|
|
165
|
+
return appVersion;
|
|
166
|
+
}
|
|
167
|
+
return version || '';
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* 获取渠道号
|
|
171
|
+
* 优先使用自定义 getChannel,否则使用内置的默认方法
|
|
172
|
+
*/
|
|
173
|
+
getChannel() {
|
|
174
|
+
if (this.config.getChannel) {
|
|
175
|
+
return this.config.getChannel();
|
|
176
|
+
}
|
|
177
|
+
// 使用内置的 getChannel 方法
|
|
178
|
+
return defaultGetChannel();
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* 检查是否命中AB测试
|
|
182
|
+
*/
|
|
183
|
+
checkAbTest(eId) {
|
|
184
|
+
if (!this.config.enableAbTest || !this.config.getAbFlags) {
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
try {
|
|
188
|
+
const abFlags = this.config.getAbFlags();
|
|
189
|
+
// 支持数组和对象两种格式
|
|
190
|
+
const flagList = Array.isArray(abFlags) ? abFlags : Object.values(abFlags);
|
|
191
|
+
return flagList.find(item => item?.indexList?.includes(eId));
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
return undefined;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* 构建埋点请求数据
|
|
199
|
+
*/
|
|
200
|
+
buildPostData(params) {
|
|
201
|
+
const { eId, eventContent } = params;
|
|
202
|
+
const deviceData = safeGetLocalStorage('deviceData', {});
|
|
203
|
+
const userData = safeGetLocalStorage('user', {});
|
|
204
|
+
const ua = getUserAgent();
|
|
205
|
+
const platform = detectPlatform(ua);
|
|
206
|
+
const isClient = isClientEnv();
|
|
207
|
+
return {
|
|
208
|
+
deviceid: deviceData.deviceId || '',
|
|
209
|
+
plateform: isClient ? platform : 'h5',
|
|
210
|
+
subplateform: 'h5',
|
|
211
|
+
version: this.getVersion(),
|
|
212
|
+
channel: this.getChannel(),
|
|
213
|
+
client: '',
|
|
214
|
+
os: '',
|
|
215
|
+
useragent: ua,
|
|
216
|
+
logs: [
|
|
217
|
+
{
|
|
218
|
+
uid: String(userData.uid || '0'),
|
|
219
|
+
eventid: eId,
|
|
220
|
+
eventcontent: eventContent || '{}',
|
|
221
|
+
begintime: Date.now(),
|
|
222
|
+
pmenu: '',
|
|
223
|
+
menu: '',
|
|
224
|
+
net: deviceData.network || '',
|
|
225
|
+
lon: '',
|
|
226
|
+
lat: '',
|
|
227
|
+
areacode: '',
|
|
228
|
+
address: ''
|
|
229
|
+
}
|
|
230
|
+
]
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* 发送埋点请求
|
|
235
|
+
*/
|
|
236
|
+
sendRequest(postData) {
|
|
237
|
+
const logUrl = this.config.logUrl || DEFAULT_LOG_URL;
|
|
238
|
+
const appName = this.config.appName || DEFAULT_APP_NAME;
|
|
239
|
+
// 优先使用传入的 httpClient,否则使用内置的默认实现
|
|
240
|
+
const httpClient = this.config.httpClient || defaultHttpClient;
|
|
241
|
+
return httpClient.post(logUrl, {
|
|
242
|
+
json: JSON.stringify(postData),
|
|
243
|
+
appName
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* 核心埋点方法
|
|
248
|
+
* @param params 埋点参数
|
|
249
|
+
*/
|
|
250
|
+
async trackUser(params) {
|
|
251
|
+
const { eId, eventContent } = params;
|
|
252
|
+
// 开发环境不发送埋点
|
|
253
|
+
if (isDevelopment()) {
|
|
254
|
+
console.log('[Tracker] 开发环境,跳过埋点:', { eId, eventContent });
|
|
255
|
+
return Promise.resolve();
|
|
256
|
+
}
|
|
257
|
+
// 检查AB测试
|
|
258
|
+
if (this.config.enableAbTest) {
|
|
259
|
+
const abFlag = this.checkAbTest(eId);
|
|
260
|
+
if (abFlag?.test) {
|
|
261
|
+
// 优先使用全局 window.AppCall,其次使用传入的 appCall
|
|
262
|
+
const globalAppCall = getGlobalAppCall();
|
|
263
|
+
const appCall = globalAppCall || this.config.appCall;
|
|
264
|
+
if (appCall?.sendEventMsg) {
|
|
265
|
+
return appCall.sendEventMsg({ eId, eventContent: eventContent || '{}' });
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
// 构建并发送请求
|
|
270
|
+
const postData = this.buildPostData(params);
|
|
271
|
+
return this.sendRequest(postData);
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* 埋点上报
|
|
275
|
+
* @param eId 事件ID
|
|
276
|
+
* @param contentObj 事件内容对象
|
|
277
|
+
* @param isAbTest 是否AB测试(已废弃,保留兼容)
|
|
278
|
+
*/
|
|
279
|
+
track(eId, contentObj = {}, isAbTest = false) {
|
|
280
|
+
return this.trackUser({
|
|
281
|
+
eId,
|
|
282
|
+
eventContent: JSON.stringify(contentObj),
|
|
283
|
+
isAbTest
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* 错误上报
|
|
288
|
+
* @param contentObj 错误内容对象
|
|
289
|
+
*/
|
|
290
|
+
trackError(contentObj = {}) {
|
|
291
|
+
const errorContent = {
|
|
292
|
+
err: contentObj.err || '',
|
|
293
|
+
apptype: contentObj.apptype || DEFAULT_ERROR_APPTYPE,
|
|
294
|
+
url: contentObj.url || (typeof window !== 'undefined' ? window.location?.href : '') || '',
|
|
295
|
+
platform: contentObj.platform || 'wap',
|
|
296
|
+
...contentObj
|
|
297
|
+
};
|
|
298
|
+
return this.trackUser({
|
|
299
|
+
eId: ERROR_EVENT_ID,
|
|
300
|
+
eventContent: JSON.stringify(errorContent)
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* 更新配置
|
|
305
|
+
*/
|
|
306
|
+
updateConfig(config) {
|
|
307
|
+
this.config = { ...this.config, ...config };
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* 获取当前配置
|
|
311
|
+
*/
|
|
312
|
+
getConfig() {
|
|
313
|
+
return { ...this.config };
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* 创建 Tracker 实例
|
|
318
|
+
* @param config 配置项
|
|
319
|
+
* @returns Tracker 实例
|
|
320
|
+
*
|
|
321
|
+
* @example
|
|
322
|
+
* const tracker = createTracker({
|
|
323
|
+
* appName: 'emoto'
|
|
324
|
+
* // logUrl 可选,默认: https://log-center.58moto.com/am/log/v1/json
|
|
325
|
+
* // httpClient 可选,默认使用内置 fetch 实现
|
|
326
|
+
* })
|
|
327
|
+
*
|
|
328
|
+
* // 普通埋点
|
|
329
|
+
* tracker.track('EVENT_001', { page: 'home' })
|
|
330
|
+
*
|
|
331
|
+
* // 错误上报
|
|
332
|
+
* tracker.trackError({ err: 'Something went wrong' })
|
|
333
|
+
*/
|
|
334
|
+
export function createTracker(config) {
|
|
335
|
+
return new Tracker(config);
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* 创建简化版埋点方法
|
|
339
|
+
* 返回 postTrack 和 postTrackError 方法,与原有项目用法一致
|
|
340
|
+
*
|
|
341
|
+
* @example
|
|
342
|
+
* const { postTrack, postTrackError } = createTrackMethods({
|
|
343
|
+
* appName: 'moto'
|
|
344
|
+
* })
|
|
345
|
+
*
|
|
346
|
+
* // 等同于原来的 postJddTrack
|
|
347
|
+
* postTrack('EVENT_001', { page: 'home' })
|
|
348
|
+
*
|
|
349
|
+
* // 等同于原来的 postJddTrackError
|
|
350
|
+
* postTrackError({ err: 'error message' })
|
|
351
|
+
*/
|
|
352
|
+
export function createTrackMethods(config) {
|
|
353
|
+
const tracker = createTracker(config);
|
|
354
|
+
return {
|
|
355
|
+
/** 埋点上报方法 */
|
|
356
|
+
postTrack: (eId, contentObj = {}, isAbTest = false) => {
|
|
357
|
+
return tracker.track(eId, contentObj, isAbTest);
|
|
358
|
+
},
|
|
359
|
+
/** 错误上报方法 */
|
|
360
|
+
postTrackError: (contentObj = {}) => {
|
|
361
|
+
return tracker.trackError(contentObj);
|
|
362
|
+
},
|
|
363
|
+
/** Tracker 实例 */
|
|
364
|
+
tracker
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
export { defaultGetChannel as getChannel };
|
|
368
|
+
export default createTracker;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Track 模块类型定义
|
|
3
|
+
*/
|
|
4
|
+
/** 应用名称类型 */
|
|
5
|
+
export type AppName = 'emoto' | 'moto' | 'watch' | 'drone' | string;
|
|
6
|
+
/** 平台类型 */
|
|
7
|
+
export type Platform = 'android' | 'ios' | 'h5' | 'wap';
|
|
8
|
+
/** Track 初始化配置 */
|
|
9
|
+
export interface TrackConfig {
|
|
10
|
+
/** 应用名称,如 'emoto', 'moto', 'watch',默认: 'moto' */
|
|
11
|
+
appName?: AppName;
|
|
12
|
+
/** 埋点上报URL,默认: https://log-center.58moto.com/am/log/v1/json */
|
|
13
|
+
logUrl?: string;
|
|
14
|
+
/** 应用版本号 */
|
|
15
|
+
version?: string;
|
|
16
|
+
/** HTTP请求方法,需要支持 post(url, data) 形式 */
|
|
17
|
+
httpClient?: HttpClient;
|
|
18
|
+
/** 获取渠道号的方法 */
|
|
19
|
+
getChannel?: () => string;
|
|
20
|
+
/** AppCall实例(用于AB测试等场景) */
|
|
21
|
+
appCall?: AppCallInterface;
|
|
22
|
+
/** 是否启用AB测试逻辑 */
|
|
23
|
+
enableAbTest?: boolean;
|
|
24
|
+
/** 获取当前AB测试标记的方法 */
|
|
25
|
+
getAbFlags?: () => AbFlag[] | Record<string, AbFlag>;
|
|
26
|
+
}
|
|
27
|
+
/** HTTP客户端接口 */
|
|
28
|
+
export interface HttpClient {
|
|
29
|
+
post: (url: string, data: any) => Promise<any>;
|
|
30
|
+
}
|
|
31
|
+
/** AppCall接口(可选) */
|
|
32
|
+
export interface AppCallInterface {
|
|
33
|
+
getAppVersion?: () => string;
|
|
34
|
+
sendEventMsg?: (data: {
|
|
35
|
+
eId: string;
|
|
36
|
+
eventContent: string;
|
|
37
|
+
}) => Promise<any>;
|
|
38
|
+
}
|
|
39
|
+
/** AB测试标记 */
|
|
40
|
+
export interface AbFlag {
|
|
41
|
+
indexList: string[];
|
|
42
|
+
test?: boolean;
|
|
43
|
+
[key: string]: any;
|
|
44
|
+
}
|
|
45
|
+
/** 设备数据 */
|
|
46
|
+
export interface DeviceData {
|
|
47
|
+
deviceId?: string;
|
|
48
|
+
network?: string;
|
|
49
|
+
[key: string]: any;
|
|
50
|
+
}
|
|
51
|
+
/** 用户数据 */
|
|
52
|
+
export interface UserData {
|
|
53
|
+
uid?: string | number;
|
|
54
|
+
[key: string]: any;
|
|
55
|
+
}
|
|
56
|
+
/** 位置数据 */
|
|
57
|
+
export interface PositionData {
|
|
58
|
+
lng?: string | number;
|
|
59
|
+
lat?: string | number;
|
|
60
|
+
[key: string]: any;
|
|
61
|
+
}
|
|
62
|
+
/** 单条日志数据 */
|
|
63
|
+
export interface LogItem {
|
|
64
|
+
uid: string;
|
|
65
|
+
eventid: string;
|
|
66
|
+
eventcontent: string;
|
|
67
|
+
begintime: number;
|
|
68
|
+
pmenu: string;
|
|
69
|
+
menu: string;
|
|
70
|
+
net: string;
|
|
71
|
+
lon: string;
|
|
72
|
+
lat: string;
|
|
73
|
+
areacode: string;
|
|
74
|
+
address: string;
|
|
75
|
+
}
|
|
76
|
+
/** 埋点请求数据 */
|
|
77
|
+
export interface TrackPostData {
|
|
78
|
+
deviceid: string;
|
|
79
|
+
plateform: Platform;
|
|
80
|
+
subplateform: string;
|
|
81
|
+
version: string;
|
|
82
|
+
channel: string;
|
|
83
|
+
client: string;
|
|
84
|
+
os: string;
|
|
85
|
+
useragent: string;
|
|
86
|
+
logs: LogItem[];
|
|
87
|
+
}
|
|
88
|
+
/** 埋点参数 */
|
|
89
|
+
export interface TrackParams {
|
|
90
|
+
/** 事件ID */
|
|
91
|
+
eId: string;
|
|
92
|
+
/** 事件内容(JSON字符串) */
|
|
93
|
+
eventContent?: string;
|
|
94
|
+
/** 是否使用AB测试上报 */
|
|
95
|
+
isAbTest?: boolean;
|
|
96
|
+
}
|
|
97
|
+
/** 错误上报内容 */
|
|
98
|
+
export interface ErrorContent {
|
|
99
|
+
/** 错误信息 */
|
|
100
|
+
err: string;
|
|
101
|
+
/** 应用类型 */
|
|
102
|
+
apptype?: string;
|
|
103
|
+
/** 页面URL */
|
|
104
|
+
url?: string;
|
|
105
|
+
/** 平台 */
|
|
106
|
+
platform?: string;
|
|
107
|
+
[key: string]: any;
|
|
108
|
+
}
|
|
@@ -14,7 +14,10 @@
|
|
|
14
14
|
* darkWaterUploadImage: (params) => api.darkWaterUploadImage(params),
|
|
15
15
|
* multiTransferImage: (params) => api.multiTransferImage(params),
|
|
16
16
|
* generatePrePresignedUrl: (params) => api.generatePrePresignedUrl(params),
|
|
17
|
-
* messageWarning: (msg) => window.$message.warning(msg)
|
|
17
|
+
* messageWarning: (msg) => window.$message.warning(msg),
|
|
18
|
+
* // 可选:开启错误上报
|
|
19
|
+
* enableErrorReport: true,
|
|
20
|
+
* appName: 'emoto'
|
|
18
21
|
* })
|
|
19
22
|
*
|
|
20
23
|
* // 图片上传
|
|
@@ -41,6 +44,7 @@
|
|
|
41
44
|
* })
|
|
42
45
|
* ```
|
|
43
46
|
*/
|
|
47
|
+
import type { TrackConfig } from '../track';
|
|
44
48
|
/**
|
|
45
49
|
* 业务类型枚举
|
|
46
50
|
*/
|
|
@@ -164,6 +168,10 @@ interface ApiResponse<T = any> {
|
|
|
164
168
|
* 业务相关的API通过此接口传入,实现业务逻辑解耦
|
|
165
169
|
*/
|
|
166
170
|
export interface AliOssApiConfig {
|
|
171
|
+
/** 应用名称,用于错误上报 */
|
|
172
|
+
appName?: TrackConfig['appName'];
|
|
173
|
+
/** 是否启用错误上报,默认 false */
|
|
174
|
+
enableErrorReport?: boolean;
|
|
167
175
|
/** 获取STS,POST V4签名独有的表单元素- 必需 */
|
|
168
176
|
/** businessType: 业务编码(必传), docType: 文件格式如png(必传),size: _1024_567(可选) */
|
|
169
177
|
getSts: (params: {
|
|
@@ -194,8 +202,15 @@ export interface AliOssApiConfig {
|
|
|
194
202
|
export declare class AliOssClass {
|
|
195
203
|
private static instance;
|
|
196
204
|
private apiConfig;
|
|
205
|
+
private tracker;
|
|
197
206
|
private constructor();
|
|
198
207
|
static getInstance(apiConfig: AliOssApiConfig): AliOssClass;
|
|
208
|
+
/**
|
|
209
|
+
* 上报上传错误
|
|
210
|
+
* @param err 错误信息
|
|
211
|
+
* @param extra 额外信息
|
|
212
|
+
*/
|
|
213
|
+
private reportError;
|
|
199
214
|
/**
|
|
200
215
|
* 判断文件是否为图片(支持常见图片格式)
|
|
201
216
|
* @param {File} file - 上传的File对象
|
|
@@ -14,7 +14,10 @@
|
|
|
14
14
|
* darkWaterUploadImage: (params) => api.darkWaterUploadImage(params),
|
|
15
15
|
* multiTransferImage: (params) => api.multiTransferImage(params),
|
|
16
16
|
* generatePrePresignedUrl: (params) => api.generatePrePresignedUrl(params),
|
|
17
|
-
* messageWarning: (msg) => window.$message.warning(msg)
|
|
17
|
+
* messageWarning: (msg) => window.$message.warning(msg),
|
|
18
|
+
* // 可选:开启错误上报
|
|
19
|
+
* enableErrorReport: true,
|
|
20
|
+
* appName: 'emoto'
|
|
18
21
|
* })
|
|
19
22
|
*
|
|
20
23
|
* // 图片上传
|
|
@@ -42,6 +45,7 @@
|
|
|
42
45
|
* ```
|
|
43
46
|
*/
|
|
44
47
|
import lrz from 'lrz';
|
|
48
|
+
import { createTracker } from '../track';
|
|
45
49
|
// ==================== 常量定义 ====================
|
|
46
50
|
/** 支持的图片格式 */
|
|
47
51
|
const SUPPORTED_IMAGE_TYPES = [
|
|
@@ -187,8 +191,15 @@ function getFileSizeInMB(size) {
|
|
|
187
191
|
export class AliOssClass {
|
|
188
192
|
static instance = null;
|
|
189
193
|
apiConfig;
|
|
194
|
+
tracker = null;
|
|
190
195
|
constructor(apiConfig) {
|
|
191
196
|
this.apiConfig = apiConfig;
|
|
197
|
+
// 只有启用错误上报时才创建 tracker 实例
|
|
198
|
+
if (apiConfig.enableErrorReport) {
|
|
199
|
+
this.tracker = createTracker({
|
|
200
|
+
appName: apiConfig.appName
|
|
201
|
+
});
|
|
202
|
+
}
|
|
192
203
|
}
|
|
193
204
|
static getInstance(apiConfig) {
|
|
194
205
|
if (!AliOssClass.instance) {
|
|
@@ -196,6 +207,22 @@ export class AliOssClass {
|
|
|
196
207
|
}
|
|
197
208
|
return AliOssClass.instance;
|
|
198
209
|
}
|
|
210
|
+
/**
|
|
211
|
+
* 上报上传错误
|
|
212
|
+
* @param err 错误信息
|
|
213
|
+
* @param extra 额外信息
|
|
214
|
+
*/
|
|
215
|
+
reportError(err, extra) {
|
|
216
|
+
// 只有启用错误上报且 tracker 存在时才上报
|
|
217
|
+
if (!this.apiConfig.enableErrorReport || !this.tracker) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
this.tracker.trackError({
|
|
221
|
+
err: err,
|
|
222
|
+
apptype: '10003',
|
|
223
|
+
...extra
|
|
224
|
+
});
|
|
225
|
+
}
|
|
199
226
|
/**
|
|
200
227
|
* 判断文件是否为图片(支持常见图片格式)
|
|
201
228
|
* @param {File} file - 上传的File对象
|
|
@@ -238,10 +265,12 @@ export class AliOssClass {
|
|
|
238
265
|
size: size, // _1024_567(可选)
|
|
239
266
|
});
|
|
240
267
|
if (signResponse.data.code !== 0) {
|
|
241
|
-
const errorMessage = `获取签名失败:${signResponse.data.message}`;
|
|
268
|
+
const errorMessage = `获取签名失败:${signResponse.data.message || signResponse.data.msg}`;
|
|
242
269
|
callbacks?.onError(errorMessage);
|
|
243
270
|
reject && reject(errorMessage);
|
|
244
271
|
console.error(errorMessage);
|
|
272
|
+
this.reportError(errorMessage, { type: 'getSts', businessType, code: signResponse.data.code });
|
|
273
|
+
return result;
|
|
245
274
|
}
|
|
246
275
|
const signData = await signResponse.data.data;
|
|
247
276
|
// console.log('后端返回的签名字段:', signData)
|
|
@@ -285,12 +314,15 @@ export class AliOssClass {
|
|
|
285
314
|
callbacks?.onError(errorMessage);
|
|
286
315
|
reject && reject(errorMessage);
|
|
287
316
|
console.error(errorMessage);
|
|
317
|
+
this.reportError(errorMessage, { type: 'ossUpload', businessType, status: uploadResponse.status });
|
|
288
318
|
}
|
|
289
319
|
}
|
|
290
320
|
catch (err) {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
321
|
+
const errMsg = err.message || err.msg || '上传异常';
|
|
322
|
+
callbacks?.onError(errMsg);
|
|
323
|
+
reject && reject(errMsg);
|
|
324
|
+
console.error('上传异常:', errMsg);
|
|
325
|
+
this.reportError(errMsg, { type: 'ossUploadException', businessType });
|
|
294
326
|
}
|
|
295
327
|
return result;
|
|
296
328
|
}
|
|
@@ -417,6 +449,7 @@ export class AliOssClass {
|
|
|
417
449
|
else {
|
|
418
450
|
callbacks.onError('上传失败');
|
|
419
451
|
reject('上传失败');
|
|
452
|
+
this.reportError('批量转换水印上传失败', { type: 'batchTransfer', businessType });
|
|
420
453
|
}
|
|
421
454
|
return;
|
|
422
455
|
}
|
|
@@ -437,6 +470,7 @@ export class AliOssClass {
|
|
|
437
470
|
else {
|
|
438
471
|
callbacks.onError('上传失败');
|
|
439
472
|
reject('上传失败');
|
|
473
|
+
this.reportError('暗水印上传失败', { type: 'darkWater', businessType });
|
|
440
474
|
}
|
|
441
475
|
return;
|
|
442
476
|
}
|
|
@@ -496,14 +530,16 @@ export class AliOssClass {
|
|
|
496
530
|
else {
|
|
497
531
|
callbacks.onError('生成预签名URL失败');
|
|
498
532
|
reject('生成预签名URL失败');
|
|
533
|
+
this.reportError('生成预签名URL失败', { type: 'preSignedUrl', businessType });
|
|
499
534
|
}
|
|
500
535
|
return;
|
|
501
536
|
}
|
|
502
537
|
}
|
|
503
538
|
catch (error) {
|
|
504
|
-
const errorMsg = error.message || '上传异常';
|
|
539
|
+
const errorMsg = error.message || error.msg || '上传异常';
|
|
505
540
|
callbacks.onError(errorMsg);
|
|
506
541
|
reject(errorMsg);
|
|
542
|
+
this.reportError(errorMsg, { type: 'ossUploadImage', businessType: option.businessType });
|
|
507
543
|
}
|
|
508
544
|
});
|
|
509
545
|
};
|
|
@@ -542,9 +578,10 @@ export class AliOssClass {
|
|
|
542
578
|
});
|
|
543
579
|
}
|
|
544
580
|
catch (error) {
|
|
545
|
-
const errorMsg = error.message || '上传异常';
|
|
581
|
+
const errorMsg = error.message || error.msg || '上传异常';
|
|
546
582
|
callbacks.onError(errorMsg);
|
|
547
583
|
reject(errorMsg);
|
|
584
|
+
this.reportError(errorMsg, { type: 'ossUploadFile', businessType: option.businessType });
|
|
548
585
|
}
|
|
549
586
|
});
|
|
550
587
|
};
|
|
@@ -605,9 +642,10 @@ export class AliOssClass {
|
|
|
605
642
|
});
|
|
606
643
|
}
|
|
607
644
|
catch (error) {
|
|
608
|
-
const errorMsg = error.message || '上传异常';
|
|
645
|
+
const errorMsg = error.message || error.msg || '上传异常';
|
|
609
646
|
callbacks.onError(errorMsg);
|
|
610
647
|
reject(errorMsg);
|
|
648
|
+
this.reportError(errorMsg, { type: 'pureOssUploadImage', businessType: option.businessType });
|
|
611
649
|
}
|
|
612
650
|
});
|
|
613
651
|
};
|