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