@haluo/util 2.0.23 → 2.0.25

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.
@@ -1,734 +0,0 @@
1
- /**
2
- * @file 阿里云OSS上传统一工具类
3
- * @description 整合所有项目的aliOss业务逻辑,通过传参形式支持不同业务场景
4
- * @Author: wanghui
5
- * @createBy: @2025.11.17
6
- *
7
- * 文档:https://help.aliyun.com/document_detail/64041.html
8
- *
9
- * 使用示例:
10
- * ```typescript
11
- * import { createAliOssUploader } from '@haluo/util'
12
- *
13
- * // 创建上传实例,传入业务相关的API函数
14
- * const ossUploader = createAliOssUploader({
15
- * getAliyunSts: (params) => api.getAliyunSts(params),
16
- * darkWaterUploadImage: (params) => api.darkWaterUploadImage(params),
17
- * multiTransferImage: (params) => api.multiTransferImage(params),
18
- * aliyunPersist: (params) => api.aliyunPersist(params),
19
- * generatePrePresignedUrl: (params) => api.generatePrePresignedUrl(params),
20
- * uploadConf: (params) => api.uploadConf(params),
21
- * dateFormat: util.date.format, // 或 util.filter.format
22
- * messageWarning: (msg) => window.$message.warning(msg)
23
- * })
24
- *
25
- * // 图片上传
26
- * ossUploader.ossUploadImage({
27
- * file: file,
28
- * imageType: 'official', // nowater、official、panoram、forum、avatar、square、carport
29
- * quality: 0.7, // 压缩质量
30
- * businessType: 1500, // 业务类型:0(无水印)、1(头像)、4(GIF)、6(视频)、8(UnProtect)、1000(文章)、1199(私有)、1299(加密)、1500(默认)
31
- * batchTransfer: false, // 是否批量转换水印
32
- * notCompress: false, // 是否不压缩
33
- * idCard: false, // 是否身份证上传
34
- * onProgress: (e) => console.log(e.percent),
35
- * onSuccess: (val) => console.log(val),
36
- * onError: (err) => console.error(err)
37
- * })
38
- *
39
- * // 文件上传
40
- * ossUploader.ossUploadFile({
41
- * file: file,
42
- * isVideo: false, // 是否视频
43
- * isUnProtect: false, // 是否UnProtect
44
- * isDocument: false, // 是否文档
45
- * businessType: 1500,
46
- * onProgress: (e) => console.log(e.percent),
47
- * onSuccess: (val) => console.log(val),
48
- * onError: (err) => console.error(err)
49
- * })
50
- * ```
51
- */
52
- import OSS from 'ali-oss';
53
- import lrz from 'lrz';
54
- // ==================== 常量定义 ====================
55
- /** 支持的图片格式 */
56
- const SUPPORTED_IMAGE_TYPES = ['image/png', 'image/gif', 'image/jpg', 'image/jpeg'];
57
- /** 小图片阈值(KB) */
58
- const SMALL_IMAGE_THRESHOLD = 100;
59
- /** 长图宽高比阈值 */
60
- const LONG_IMAGE_RATIO = 2;
61
- /** 默认文件大小限制(MB) */
62
- const DEFAULT_FILE_SIZE_LIMIT = 10;
63
- /** 默认压缩质量 */
64
- const DEFAULT_QUALITY = 0.7;
65
- /** 默认图片类型 */
66
- const DEFAULT_IMAGE_TYPE = 'official';
67
- /** OSS默认区域 */
68
- const DEFAULT_OSS_REGION = 'oss-cn-beijing';
69
- /** 预签名URL过期时间(毫秒) */
70
- const PRESIGNED_URL_EXPIRE_TIME = 60000;
71
- /** 随机字符串字符集 */
72
- const RANDOM_CHARS = '0123456789abcdefghijklmnopqrstuvwxyz';
73
- /** OSS元数据 */
74
- const OSS_META_PEOPLE = 'x-oss-meta-motor';
75
- // ==================== 枚举定义 ====================
76
- /**
77
- * 业务类型枚举
78
- * 不同的businessType对应不同的OSS bucket和水印策略
79
- */
80
- export var BusinessType;
81
- (function (BusinessType) {
82
- BusinessType[BusinessType["NO_WATER"] = 0] = "NO_WATER";
83
- BusinessType[BusinessType["AVATAR"] = 1] = "AVATAR";
84
- BusinessType[BusinessType["GIF"] = 4] = "GIF";
85
- BusinessType[BusinessType["VIDEO"] = 6] = "VIDEO";
86
- BusinessType[BusinessType["UNPROTECT"] = 8] = "UNPROTECT";
87
- BusinessType[BusinessType["ARTICLE"] = 1000] = "ARTICLE";
88
- BusinessType[BusinessType["PRIVATE"] = 1199] = "PRIVATE";
89
- BusinessType[BusinessType["ENCRYPTED"] = 1299] = "ENCRYPTED";
90
- BusinessType[BusinessType["DEFAULT"] = 1500] = "DEFAULT"; // 默认
91
- })(BusinessType || (BusinessType = {}));
92
- /**
93
- * bucket 图片类型后缀枚举
94
- */
95
- export const SuffixEnum = {
96
- nowater: '!nowater',
97
- official: '!official',
98
- panoram: '!panoram',
99
- forum: '!forum',
100
- avatar: '!avatar',
101
- square: '!square',
102
- carport: '!carport' // 车库、经销商水印
103
- };
104
- /** 不打水印的图片类型 */
105
- const NO_WATER_IMAGE_TYPES = ['nowater', 'forum'];
106
- // ==================== 工具函数 ====================
107
- /**
108
- * 生成随机字符串
109
- * @param length 字符串长度
110
- * @returns 随机字符串
111
- */
112
- function randomString(length) {
113
- let result = '';
114
- for (let i = 0; i < length; i++) {
115
- const randomIndex = Math.floor(Math.random() * RANDOM_CHARS.length);
116
- result += RANDOM_CHARS[randomIndex];
117
- }
118
- return result;
119
- }
120
- /**
121
- * 过滤对象中的undefined值
122
- * @param obj 原始对象
123
- * @returns 过滤后的对象
124
- */
125
- function filterUndefined(obj) {
126
- const result = {};
127
- Object.keys(obj).forEach(key => {
128
- if (obj[key] !== undefined) {
129
- result[key] = obj[key];
130
- }
131
- });
132
- return result;
133
- }
134
- /**
135
- * 检查是否为不打水印的图片类型
136
- * @param imageType 图片类型
137
- * @returns 是否不打水印
138
- */
139
- function isNoWaterImageType(imageType) {
140
- return NO_WATER_IMAGE_TYPES.includes(imageType);
141
- }
142
- /**
143
- * 检查文件类型是否支持
144
- * @param fileType 文件类型
145
- * @returns 是否支持
146
- */
147
- function isSupportedImageType(fileType) {
148
- return SUPPORTED_IMAGE_TYPES.includes(fileType);
149
- }
150
- /**
151
- * 计算文件大小(KB)
152
- * @param size 文件大小(字节)
153
- * @returns 文件大小(KB)
154
- */
155
- function getFileSizeInKB(size) {
156
- return Math.floor(size / 1024);
157
- }
158
- /**
159
- * 计算文件大小(MB)
160
- * @param size 文件大小(字节)
161
- * @returns 文件大小(MB)
162
- */
163
- function getFileSizeInMB(size) {
164
- return size / (1024 * 1024);
165
- }
166
- // ==================== 主类 ====================
167
- /**
168
- * 阿里云OSS上传类
169
- * 整合所有项目的aliOss业务逻辑,通过依赖注入实现业务API解耦
170
- */
171
- export class AliOssClass {
172
- apiConfig;
173
- clientCache;
174
- constructor(apiConfig) {
175
- this.apiConfig = apiConfig;
176
- this.clientCache = new Map();
177
- }
178
- /**
179
- * 创建OSS客户端
180
- * @param businessType 业务类型
181
- * @returns OSS客户端实例
182
- */
183
- async createOssClient(businessType) {
184
- const cacheKey = `${businessType}`;
185
- // 注释:某些项目使用缓存,某些不使用。这里提供可选的缓存机制
186
- // if (this.clientCache.has(cacheKey)) {
187
- // return this.clientCache.get(cacheKey)
188
- // }
189
- const res = await this.apiConfig.getAliyunSts({ businessType });
190
- if (res.data.code !== 0) {
191
- throw new Error('获取OSS配置失败');
192
- }
193
- const clientParams = res.data.data || {};
194
- clientParams.suffix = clientParams.style || '';
195
- // 过滤掉undefined值的属性,并确保必需字段存在
196
- const filteredParams = filterUndefined(clientParams);
197
- const client = new OSS({
198
- region: DEFAULT_OSS_REGION,
199
- ...filteredParams
200
- });
201
- // 保存客户端选项供后续使用
202
- client.options = {
203
- ...client.options,
204
- realmName: clientParams.realmName,
205
- suffix: clientParams.suffix
206
- };
207
- this.clientCache.set(cacheKey, client);
208
- return client;
209
- }
210
- /**
211
- * 预加载图片获取尺寸
212
- * @param file 文件对象
213
- * @returns 图片对象
214
- */
215
- imgUpload(file) {
216
- return new Promise((resolve, reject) => {
217
- const URL = window.URL || window.webkitURL;
218
- const image = new Image();
219
- const objectUrl = URL.createObjectURL(file);
220
- image.onload = () => {
221
- URL.revokeObjectURL(objectUrl); // 释放内存
222
- resolve(image);
223
- };
224
- image.onerror = () => {
225
- URL.revokeObjectURL(objectUrl); // 释放内存
226
- reject(new Error('图片加载失败'));
227
- };
228
- image.src = objectUrl;
229
- });
230
- }
231
- /**
232
- * 生成文件名
233
- * @param imageType 图片类型
234
- * @param extension 文件扩展名
235
- * @returns 文件名信息
236
- */
237
- generateFileName(imageType, extension) {
238
- const now = new Date();
239
- const year = this.apiConfig.dateFormat(now, 'YYYY');
240
- const date = this.apiConfig.dateFormat(now, 'YYYYMMDD');
241
- const dateTime = this.apiConfig.dateFormat(now, 'YYYYMMDDHHmmss');
242
- const randomStr = randomString(4);
243
- const fileName = `${imageType}/${date}/${dateTime}_${randomStr}${extension}`;
244
- return { year, fileName };
245
- }
246
- /**
247
- * 检查图片是否为长图
248
- * @param width 图片宽度
249
- * @param height 图片高度
250
- * @returns 是否为长图
251
- */
252
- isLongImage(width, height) {
253
- const maxDimension = Math.max(width, height);
254
- const minDimension = Math.min(width, height);
255
- return maxDimension / minDimension > LONG_IMAGE_RATIO;
256
- }
257
- /**
258
- * 执行OSS上传
259
- * @param client OSS客户端
260
- * @param fileName 文件名
261
- * @param file 文件对象
262
- * @param year 年份
263
- * @param mimeType MIME类型
264
- * @param onProgress 进度回调
265
- * @returns 上传结果
266
- */
267
- async performOssUpload(client, fileName, file, year, mimeType, onProgress) {
268
- return await client.multipartUpload(fileName, file, {
269
- progress: (p) => {
270
- onProgress?.({ percent: Math.floor(p * 100) });
271
- },
272
- meta: { year, people: OSS_META_PEOPLE },
273
- mime: mimeType
274
- });
275
- }
276
- /**
277
- * 确定业务类型
278
- * @param option 上传选项
279
- * @param isGif 是否为GIF
280
- * @returns 业务类型
281
- */
282
- determineBusinessType(option, isGif) {
283
- // 如果已指定businessType,直接使用
284
- if (option.businessType !== undefined) {
285
- return option.businessType;
286
- }
287
- const imageType = option.imageType || DEFAULT_IMAGE_TYPE;
288
- // 注释:GIF图片使用businessType=4
289
- if (isGif) {
290
- return BusinessType.GIF;
291
- }
292
- // 注释:根据imageType确定默认businessType
293
- if (imageType === 'avatar') {
294
- return BusinessType.AVATAR;
295
- }
296
- if (isNoWaterImageType(imageType)) {
297
- return BusinessType.NO_WATER;
298
- }
299
- return BusinessType.DEFAULT;
300
- }
301
- /**
302
- * 加载图片并返回结果
303
- * 用于标准的图片上传场景
304
- * @param params 加载参数
305
- */
306
- loadImage(params) {
307
- const { url, val, suffix, file, option, resolve, reject, isGif } = params;
308
- let img = new Image();
309
- img.src = url;
310
- img.onload = function () {
311
- if (!img)
312
- return;
313
- val.imgOrgUrl = `${url}?_${img.width}_${img.height}`;
314
- // 注释:全景图和GIF图片缩略图后缀不带300,其他需要
315
- val.imgUrl =
316
- suffix === '!panoram' || isGif
317
- ? `${url}?_${img.width}_${img.height}`
318
- : `${url}300?_${img.width}_${img.height}`;
319
- val.name = file.name;
320
- val.fileName = file.name;
321
- option.onSuccess?.(val);
322
- resolve(val);
323
- img = null;
324
- };
325
- img.onerror = function () {
326
- option.onError?.('图片加载失败');
327
- reject('图片加载失败');
328
- img = null;
329
- };
330
- }
331
- /**
332
- * 加载图片并返回结果(新版)
333
- * 用于avatar、idCard等特殊场景,返回格式不同
334
- * @param params 加载参数
335
- */
336
- loadImageNew(params) {
337
- const { url, val, file, option, resolve, reject } = params;
338
- let img = new Image();
339
- img.src = url;
340
- img.onload = function () {
341
- val.imgOrgUrl = url;
342
- val.imgUrl = val.name;
343
- val.name = file.name;
344
- val.fileName = file.name;
345
- option.onSuccess?.(val);
346
- resolve(val);
347
- img = null;
348
- };
349
- img.onerror = function () {
350
- option.onError?.('图片加载失败');
351
- reject('图片加载失败');
352
- img = null;
353
- };
354
- }
355
- /**
356
- * 图片上传主方法
357
- * 整合了所有项目的图片上传逻辑
358
- * @param option 上传选项
359
- * @returns Promise
360
- */
361
- ossUploadImage = async (option) => {
362
- if (!(option.file instanceof File)) {
363
- return Promise.reject('file is not instanceof File');
364
- }
365
- const file = option.file;
366
- // 初始化回调函数
367
- const callbacks = {
368
- onError: option.onError || (() => { }),
369
- onSuccess: option.onSuccess || (() => { }),
370
- onProgress: option.onProgress || (() => { })
371
- };
372
- option.onError = callbacks.onError;
373
- option.onSuccess = callbacks.onSuccess;
374
- option.onProgress = callbacks.onProgress;
375
- // 文件类型验证
376
- if (!isSupportedImageType(file.type)) {
377
- callbacks.onError('');
378
- this.apiConfig.messageWarning?.('上传失败,请上传后缀为png、gif、jpg的文件');
379
- return Promise.reject('文件类型不支持');
380
- }
381
- return new Promise(async (resolve, reject) => {
382
- try {
383
- const imageType = option.imageType || DEFAULT_IMAGE_TYPE;
384
- const suffix = SuffixEnum[imageType] || SuffixEnum.official;
385
- // 生成文件名
386
- const extensionName = `.${file.name.split('.').pop()}`;
387
- const { year, fileName } = this.generateFileName(imageType, extensionName);
388
- // 注释:小图片(≤100KB)不压缩,quality设为1 (MAIN-2481)
389
- const isSmallImage = getFileSizeInKB(file.size) <= SMALL_IMAGE_THRESHOLD;
390
- let quality = isSmallImage ? 1 : (option.quality || DEFAULT_QUALITY);
391
- const isGif = file.type === 'image/gif';
392
- const idCard = option.idCard || false;
393
- // 确定业务类型
394
- const businessType = this.determineBusinessType(option, isGif);
395
- // 注释:特殊业务类型1299(加密)直接返回bucket路径,不加载图片
396
- if (businessType === BusinessType.ENCRYPTED) {
397
- const client = await this.createOssClient(businessType);
398
- const result = await this.performOssUpload(client, fileName, file, year, file.type, callbacks.onProgress);
399
- if (result.res.statusCode === 200) {
400
- const val = {
401
- ...result,
402
- imgUrl: `${result.bucket}://${result.name}`,
403
- imgOrgUrl: `${result.bucket}://${result.name}`,
404
- fileName: file.name
405
- };
406
- callbacks.onSuccess(val);
407
- resolve(val);
408
- }
409
- else {
410
- callbacks.onError('上传失败');
411
- reject('上传失败');
412
- }
413
- return;
414
- }
415
- // 注释:batchTransfer选项 - 批量转换水印(用于某些特殊业务场景)
416
- if (option.batchTransfer && this.apiConfig.multiTransferImage) {
417
- const res = await this.apiConfig.multiTransferImage({ file });
418
- if (res.data.code === 0) {
419
- this.loadImage({
420
- url: res.data.data,
421
- val: res.data,
422
- suffix,
423
- option,
424
- file,
425
- resolve,
426
- reject
427
- });
428
- }
429
- else {
430
- callbacks.onError('上传失败');
431
- reject('上传失败');
432
- }
433
- return;
434
- }
435
- // 注释:carport类型需要打暗水印
436
- if (option.imageType === 'carport' && this.apiConfig.darkWaterUploadImage) {
437
- const res = await this.apiConfig.darkWaterUploadImage({ file });
438
- if (res.data.code === 0) {
439
- this.loadImage({
440
- url: res.data.data,
441
- val: res.data,
442
- suffix,
443
- option,
444
- file,
445
- resolve,
446
- reject
447
- });
448
- }
449
- else {
450
- callbacks.onError('上传失败');
451
- reject('上传失败');
452
- }
453
- return;
454
- }
455
- // 预加载图片获取尺寸
456
- const image = await this.imgUpload(file);
457
- // 图片压缩
458
- const rst = await lrz(file, { quality });
459
- // 注释:长图压缩有问题,长宽比>2:1的图片不压缩
460
- const isLong = this.isLongImage(image.width, image.height);
461
- // 确定最终上传的文件
462
- let postFile;
463
- if (option.notCompress || isGif || isLong) {
464
- // 注释:GIF、notCompress选项或长图不压缩
465
- postFile = file;
466
- }
467
- else {
468
- postFile = rst.file;
469
- }
470
- // 文件大小验证
471
- const imgSize = postFile.size;
472
- const maxSizeMB = DEFAULT_FILE_SIZE_LIMIT;
473
- if (getFileSizeInMB(imgSize) > maxSizeMB) {
474
- const errorMsg = `图片不能超过${maxSizeMB}M`;
475
- callbacks.onError(errorMsg);
476
- reject(errorMsg);
477
- return;
478
- }
479
- // 设置文件名(确保压缩后的文件有名称)
480
- if (!postFile.name) {
481
- Object.defineProperty(postFile, 'name', {
482
- value: file.name,
483
- writable: false
484
- });
485
- }
486
- // 注释:根据不同场景选择不同的OSS客户端
487
- // GIF -> UnProtect(8), idCard -> Private(1199), nowater/forum -> NoWater(0), 其他 -> 默认
488
- const client = await this.createOssClient(businessType);
489
- // 执行上传
490
- const result = await this.performOssUpload(client, fileName, postFile, year, postFile.type, callbacks.onProgress);
491
- if (result.res.statusCode !== 200) {
492
- callbacks.onError('上传失败');
493
- reject('上传失败');
494
- return;
495
- }
496
- // 注释:idCard场景 - 生成预签名URL
497
- if (idCard && this.apiConfig.generatePrePresignedUrl) {
498
- const res = await this.apiConfig.generatePrePresignedUrl({
499
- objectId: result.name,
500
- expireMils: PRESIGNED_URL_EXPIRE_TIME
501
- });
502
- if (res.data.code === 0) {
503
- this.loadImageNew({
504
- url: res.data.data,
505
- val: result,
506
- file: postFile,
507
- option,
508
- resolve,
509
- reject
510
- });
511
- }
512
- else {
513
- callbacks.onError('生成预签名URL失败');
514
- reject('生成预签名URL失败');
515
- }
516
- return;
517
- }
518
- // 注释:avatar场景 - 调用aliyunPersist处理
519
- if (imageType === 'avatar' && this.apiConfig.aliyunPersist) {
520
- const res = await this.apiConfig.aliyunPersist({
521
- object: `${result.bucket}://${result.name}`,
522
- businessType: BusinessType.AVATAR
523
- });
524
- if (res.data.code === 0) {
525
- this.loadImageNew({
526
- url: res.data.data,
527
- val: result,
528
- file: postFile,
529
- option,
530
- resolve,
531
- reject
532
- });
533
- }
534
- else {
535
- callbacks.onError('头像处理失败');
536
- reject('头像处理失败');
537
- }
538
- return;
539
- }
540
- // 注释:标准场景 - 使用client.options中的realmName和suffix
541
- const clientParams = client.options;
542
- const url = `${clientParams.realmName}${result.name}${clientParams.suffix || ''}`;
543
- this.loadImage({
544
- url,
545
- val: result,
546
- suffix,
547
- file: postFile,
548
- option,
549
- resolve,
550
- reject,
551
- isGif
552
- });
553
- }
554
- catch (error) {
555
- const errorMsg = error.message || '上传异常';
556
- callbacks.onError(errorMsg);
557
- reject(errorMsg);
558
- }
559
- });
560
- };
561
- /**
562
- * 文件上传方法
563
- * 支持视频、文档、APK等文件类型
564
- * @param option 上传选项
565
- * @returns Promise
566
- */
567
- ossUploadFile = async (option) => {
568
- if (!(option.file instanceof File)) {
569
- return Promise.reject('file is not instanceof File');
570
- }
571
- const file = option.file;
572
- // 初始化回调函数
573
- const callbacks = {
574
- onError: option.onError || (() => { }),
575
- onSuccess: option.onSuccess || (() => { }),
576
- onProgress: option.onProgress || (() => { })
577
- };
578
- option.onError = callbacks.onError;
579
- option.onSuccess = callbacks.onSuccess;
580
- option.onProgress = callbacks.onProgress;
581
- return new Promise(async (resolve, reject) => {
582
- try {
583
- // 生成文件名
584
- const extensionName = `.${file.name.split('.').pop()}`;
585
- const fileType = extensionName === '.apk' ? 'apk' : 'file';
586
- const { year, fileName } = this.generateFileName(fileType, extensionName);
587
- const isVideo = !!option.isVideo;
588
- const isDocument = !!option.isDocument;
589
- const isUnProtect = !!option.isUnProtect;
590
- // 注释:根据不同场景选择businessType
591
- // isUnProtect -> 8, isVideo -> 6, isDocument -> 6, 其他 -> 1500
592
- let businessType = option.businessType;
593
- if (!businessType) {
594
- if (isUnProtect) {
595
- businessType = BusinessType.UNPROTECT;
596
- }
597
- else if (isVideo || isDocument) {
598
- businessType = BusinessType.VIDEO; // 视频和文档都使用6
599
- }
600
- else {
601
- businessType = BusinessType.DEFAULT;
602
- }
603
- }
604
- // 注释:文档上传可能需要先调用uploadConf获取配置
605
- if (isDocument && this.apiConfig.uploadConf) {
606
- await this.apiConfig.uploadConf(businessType);
607
- }
608
- const client = await this.createOssClient(businessType);
609
- // 执行上传
610
- const result = await this.performOssUpload(client, fileName, file, year, file.type, callbacks.onProgress);
611
- if (result.res.statusCode === 200) {
612
- const clientParams = client.options;
613
- const url = `${clientParams.realmName}${result.name}${clientParams.suffix || ''}`;
614
- const uploadResult = {
615
- ...result,
616
- url,
617
- fileName: file.name
618
- };
619
- callbacks.onSuccess(uploadResult);
620
- resolve(uploadResult);
621
- }
622
- else {
623
- callbacks.onError('上传失败');
624
- reject('上传失败');
625
- }
626
- }
627
- catch (error) {
628
- const errorMsg = error.message || '上传异常';
629
- callbacks.onError(errorMsg);
630
- reject(errorMsg);
631
- }
632
- });
633
- };
634
- /**
635
- * 商品详情图片上传
636
- * 注释:此方法用于商品详情页的图片上传,不压缩,使用NoWater客户端
637
- * @param option 上传选项
638
- * @returns Promise
639
- */
640
- shopDetailUpdate = async (option) => {
641
- if (!(option.file instanceof File)) {
642
- return Promise.reject('file is not instanceof File');
643
- }
644
- const file = option.file;
645
- // 初始化回调函数
646
- const callbacks = {
647
- onError: option.onError || (() => { }),
648
- onSuccess: option.onSuccess || (() => { }),
649
- onProgress: option.onProgress || (() => { })
650
- };
651
- option.onError = callbacks.onError;
652
- option.onSuccess = callbacks.onSuccess;
653
- option.onProgress = callbacks.onProgress;
654
- // 文件类型验证
655
- if (!isSupportedImageType(file.type)) {
656
- callbacks.onError('');
657
- this.apiConfig.messageWarning?.('上传失败,请上传后缀为png、gif、jpg的文件');
658
- return Promise.reject('文件类型不支持');
659
- }
660
- return new Promise(async (resolve, reject) => {
661
- try {
662
- // 文件大小验证
663
- if (getFileSizeInMB(file.size) > DEFAULT_FILE_SIZE_LIMIT) {
664
- const errorMsg = `图片不能超过${DEFAULT_FILE_SIZE_LIMIT}M`;
665
- callbacks.onError(errorMsg);
666
- reject(errorMsg);
667
- return;
668
- }
669
- // 预加载图片(虽然不需要尺寸,但保持原有逻辑)
670
- await this.imgUpload(file);
671
- const imageType = option.imageType || DEFAULT_IMAGE_TYPE;
672
- const suffix = SuffixEnum[imageType] || SuffixEnum.official;
673
- // 生成文件名
674
- const extensionName = `.${file.name.split('.').pop()}`;
675
- const { year, fileName } = this.generateFileName(imageType, extensionName);
676
- // 使用NoWater客户端
677
- const client = await this.createOssClient(BusinessType.NO_WATER);
678
- // 执行上传
679
- const result = await this.performOssUpload(client, fileName, file, year, file.type, callbacks.onProgress);
680
- if (result.res.statusCode === 200) {
681
- const clientParams = client.options;
682
- const url = `${clientParams.realmName}${result.name}${clientParams.suffix || ''}`;
683
- this.loadImage({
684
- url,
685
- val: result,
686
- suffix,
687
- file,
688
- option,
689
- resolve,
690
- reject
691
- });
692
- }
693
- else {
694
- callbacks.onError('上传失败');
695
- reject('上传失败');
696
- }
697
- }
698
- catch (error) {
699
- const errorMsg = error.message || '上传异常';
700
- callbacks.onError(errorMsg);
701
- reject(errorMsg);
702
- }
703
- });
704
- };
705
- /**
706
- * 生成随机字符串
707
- * @param num 字符串长度
708
- */
709
- randomString = randomString;
710
- /**
711
- * 后缀枚举
712
- */
713
- suffixEnum = SuffixEnum;
714
- /**
715
- * 业务类型枚举
716
- */
717
- businessType = BusinessType;
718
- }
719
- /**
720
- * 创建阿里云OSS上传器(工厂函数)
721
- * @param apiConfig API配置对象,包含业务相关的API函数
722
- * @returns OSS上传器实例
723
- */
724
- export function createAliOssUploader(apiConfig) {
725
- return new AliOssClass(apiConfig);
726
- }
727
- /**
728
- * 默认导出
729
- */
730
- export default {
731
- createAliOssUploader,
732
- BusinessType,
733
- SuffixEnum
734
- };