第一步当然要引入ali-oss了
我这里是react写的用的hooks
import { useState } from 'react';
import OSS from 'ali-oss';
import guid from './guid';
let credentials = {
accessKeyId: '',
accessKeySecret: '',
stsToken: '',
expiration: '',
}; // STS凭证
const bucket = ''; // bucket名称
const region = ''; // oss服务区域名称
const partSize = 1 * 1024 * 1024; // 每个分片大小1MB
const parallel = 3; // 同时上传的分片数
const ossClientMap = {}; // oss客户端实例
const checkpointMap = {}; // 所有分片上传文件的检查点
const optionMap = {};
const fileNameMap = {};
// 上边的一大坨东西需要自己去oss拿,建议问下后端
export default function useOss(newPath = '') {
const [percent, setPercent] = useState(0);
const [loading, setLoading] = useState(false);
const [uuid] = useState(guid.newGUID());
// 获取STS Token
function getCredential() {
return getSecurityToken().then((res) => {
credentials = {
accessKeyId: res.accessKeyId,
accessKeySecret: res.accessKeySecret,
stsToken: res.token,
expiration: res.expiration,
};
return res;
});
}
// 创建OSS Client
function initOSSClient() {
ossClientMap[uuid] = new OSS({
...credentials,
bucket,
region,
secure: true,
});
}
// 文件直传
// const { run } = useRequest(insertRecord, {
// manual: true,
// });
// 普通上传
async function commonUpload(file, type) {
let folderPath = '';
if (type) {
const folder = file?.webkitRelativePath.split('/');
folderPath = `${folder.splice(0, folder.length - 1).join('/')}/`;
}
if (!ossClientMap[uuid]) {
await initOSSClient();
}
return ossClientMap[uuid]
.put(newPath + folderPath + fileNameMap[uuid], file)
.then((resFile) => {
const { url } = resFile;
// if (newPath) {
// url = `http://${bucket}.${region}.aliyuncs.com/${newPath}${folderPath}${fileNameMap[uuid]}`
// } else {
// url = `http://${bucket}.${region}.aliyuncs.com/${fileNameMap[uuid]}`
// }
run(file.name, file.size, resFile.name, newPath + folderPath).then((res) => {
optionMap[uuid].onSuccess({ fileId: res?.fileId, url }, optionMap[uuid].file);
setLoading(false);
});
return url;
})
.catch((err) => {
console.error(err);
throw err;
});
}
// 断点续传
async function resumeMultipartUpload() {
Object.values(checkpointMap[uuid]).forEach((checkpoint) => {
const { uploadId, file, name } = checkpoint;
ossClientMap[uuid]
.multipartUpload(uploadId, file, {
parallel,
partSize,
progress: onMultipartUploadProgress,
checkpoint,
})
.then(() => {
delete checkpointMap[uuid][checkpoint.uploadId];
const url = `http://${bucket}.${region}.aliyuncs.com/${name}`;
optionMap[uuid].onSuccess({ fileId: null, url }, optionMap[uuid].file);
return url;
})
.catch((err) => {
console.error(err);
throw err;
});
});
}
// 分片上传进度改变回调
async function onMultipartUploadProgress(progress, checkpoint) {
// 计算上传进度
const percentNum = Number.parseInt(`${progress * 100}`, 10);
setPercent(percentNum);
if (optionMap[uuid]) {
optionMap[uuid].onProgress({ percent: percentNum }, checkpoint.file);
}
checkpointMap[uuid] = {
[checkpoint.uploadId]: checkpoint,
};
// 判断STS Token是否将要过期,过期则重新获取(我这里没有传的oss内容是写死的没有使用这个方法)
/* const { expiration } = credentials;
const timegap = 2;
if (expiration && moment(expiration).subtract(timegap, 'minute').isBefore(moment())) {
console.log(
`STS token will expire in ${timegap} minutes,uploading will pause and resume after getting new STS token`,
);
if (ossClientMap[uuid]) {
ossClientMap[uuid].cancel();
}
await getCredential();
await resumeMultipartUpload();
}
*/
}
// 分片上传
async function multipartUpload(file) {
if (!ossClientMap[uuid]) {
await initOSSClient();
}
return ossClientMap[uuid]
.multipartUpload(newPath + optionMap[uuid].folderPath, file, {
parallel,
partSize,
progress: onMultipartUploadProgress,
})
.then((resFile) => {
const { requestUrls } = resFile.res;
// 上传完成后返回url(我这里没看到返回值里有url,一看有一个requestUrls数组里边刚好就是我要的东西)
optionMap[uuid].onSuccess(requestUrls[0].split('?')[0], requestUrls[0].split('?')[1]);
return requestUrls[0];
})
.catch((err) => {
console.error(`Multipart upload ${file.name} failed === `, err);
throw err;
});
}
const upload = async (initOptions) => {
try {
setLoading(true);
checkpointMap[uuid] = {};
// 获取STS Token
// await getCredential();
optionMap[uuid] = initOptions;
/* 判断文件大小确定是否使用分片上传
fileNameMap[uuid] = generateFileName(optionMap[uuid].file);
if (optionMap[uuid].file.size < partSize) {
return commonUpload(optionMap[uuid].file, type);
}
*/
return multipartUpload(optionMap[uuid].file);
} catch (error) {
if (error?.status !== 0) {
initOptions.onError?.(error);
throw error;
}
return null;
}
};
// 暂停上传
function stop() {
if (ossClientMap[uuid]) ossClientMap[uuid].cancel();
}
// 续传
function resume() {
if (ossClientMap[uuid]) resumeMultipartUpload();
}
return {
stop,
resume,
upload,
percent,
loading,
};
}
// 这里是上边中引用的guid文件
function GUID() {
this.date = new Date();
/* 判断是否初始化过,如果初始化过以下代码,则以下代码将不再执行,实际中只执行一次 */
if (typeof this.newGUID != 'function') {
/* 生成GUID码 */
GUID.prototype.newGUID = function () {
this.date = new Date();
var guidStr = '',
sexadecimalDate = this.hexadecimal(this.getGUIDDate(), 16),
sexadecimalTime = this.hexadecimal(this.getGUIDTime(), 16);
for (var i = 0; i < 9; i++) {
guidStr += Math.floor(Math.random() * 16).toString(16);
}
guidStr += sexadecimalDate;
guidStr += sexadecimalTime;
while (guidStr.length < 32) {
guidStr += Math.floor(Math.random() * 16).toString(16);
}
return this.formatGUID(guidStr);
};
/*
* 功能:获取当前日期的GUID格式,即8位数的日期:19700101
* 返回值:返回GUID日期格式的字条串
*/
GUID.prototype.getGUIDDate = function () {
return (
this.date.getFullYear() +
this.addZero(this.date.getMonth() + 1) +
this.addZero(this.date.getDay())
);
};
/*
* 功能:获取当前时间的GUID格式,即8位数的时间,包括毫秒,毫秒为2位数:12300933
* 返回值:返回GUID日期格式的字条串
*/
GUID.prototype.getGUIDTime = function () {
return (
this.addZero(this.date.getHours()) +
this.addZero(this.date.getMinutes()) +
this.addZero(this.date.getSeconds()) +
this.addZero(parseInt(this.date.getMilliseconds() / 10))
);
};
/*
* 功能: 为一位数的正整数前面添加0,如果是可以转成非NaN数字的字符串也可以实现
* 参数: 参数表示准备再前面添加0的数字或可以转换成数字的字符串
* 返回值: 如果符合条件,返回添加0后的字条串类型,否则返回自身的字符串
*/
GUID.prototype.addZero = function (num) {
if (Number(num).toString() != 'NaN' && num >= 0 && num < 10) {
return '0' + Math.floor(num);
} else {
return num.toString();
}
};
/*
* 功能:将y进制的数值,转换为x进制的数值
* 参数:第1个参数表示欲转换的数值;第2个参数表示欲转换的进制;第3个参数可选,表示当前的进制数,如不写则为10
* 返回值:返回转换后的字符串
*/
GUID.prototype.hexadecimal = function (num, x, y) {
if (y != undefined) {
return parseInt(num.toString(), y).toString(x);
} else {
return parseInt(num.toString()).toString(x);
}
};
/*
* 功能:格式化32位的字符串为GUID模式的字符串
* 参数:第1个参数表示32位的字符串
* 返回值:标准GUID格式的字符串
*/
GUID.prototype.formatGUID = function (guidStr) {
var str1 = guidStr.slice(0, 8) + '-',
str2 = guidStr.slice(8, 12) + '-',
str3 = guidStr.slice(12, 16) + '-',
str4 = guidStr.slice(16, 20) + '-',
str5 = guidStr.slice(20);
return str1 + str2 + str3 + str4 + str5;
};
}
}
export default new GUID();
```javascript
// 封装上传组件 这里基于antd的上传所以记得引用
const getFilenameForUrl = (str) => decodeURIComponent(str).split('/').pop();
export default (props) => {
const oss = useOss('');
const { accept = '.mp4', value, onChange = () => {}, ...params } = props;
const [fileList, setFileList] = useState([]);
const [loading, setLoading] = useState(false);
const [percent, setPercent] = useState(0);
useEffect(() => {
if (!value) return;
let list = (Array.isArray(value) ? value : [value]).map((v, i) => ({
uid: i,
name: getFilenameForUrl(v),
status: 'done',
response: { data: { url: v } },
url: v,
}));
setFileList(list);
}, [value]);
const beforeUpload = () => {};
// 监听上传进度
const onProgress = (inf0) => {
setPercent(inf0.percent);
};
// 分片上传成功
const onSuccess = (url) => {
setFileList(
[url].map((v, i) => {
return {
uid: i,
name: getFilenameForUrl(v),
status: 'done',
response: { data: { url: v } },
url: v,
};
}),
);
setLoading(false);
onChange(url);
};
const uploadRequest = async (opts) => {
setLoading(true);
if (opts) {
oss.upload({
file: opts.file,
folderPath: `/${moment().format('YYYYMMDDHHmmss')}/${opts.file.name}`,
onProgress,
onSuccess,
});
}
};
const onChangeHandler = () => {};
return (
<div className={styles.ossUpload}>
<Upload
{...params}
accept={accept}
beforeUpload={beforeUpload}
customRequest={uploadRequest}
onChange={onChangeHandler}
fileList={fileList}
showUploadList={false}
className={styles.uploadVideo}
>
{fileList.length > 0 ? (
<div className={styles.video}>
<video controls src={fileList[0].url} width="160" height="90" />
<div className={styles.closeHover}>
<CloseCircleFilled
onClick={(e) => {
e.stopPropagation();
setFileList([]);
onChange(null);
}}
/>
</div>
</div>
) : !loading ? (
<>
<PlusOutlined />
上传视频
</>
) : null}
</Upload>
{loading && (
<div className={styles.progress}>
<Progress type="circle" style={{ width: '100%', height: '100%' }} percent={percent} />
</div>
)}
</div>
);
};