Files
xk-doctor-wx/common/js/oss-upload.js

136 lines
4.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 阿里云 OSS PostObject 直传(对齐 xk-admin oss-upload.ts
*
* 运维:微信小程序 uploadFile 合法域名需包含 OSS host
* 例如 https://xiaokang88.oss-cn-hangzhou.aliyuncs.com
*/
import { getBusinessOssSignatureApi, unwrapOssSignature, registerBusinessOssFile } from '@/api/upload.js';
import { checkImage } from '@/common/js/content-security.js';
/**
* @param {string} filePath 本地临时文件路径
* @returns {string} xk_upload_YYYYMMDD/uuid_timestamp.ext
*/
export function generateObjectName(filePath) {
const today = new Date();
const year = today.getFullYear();
const month = String(today.getMonth() + 1).padStart(2, '0');
const day = String(today.getDate()).padStart(2, '0');
const dateStr = `${year}${month}${day}`;
const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
const r = (Math.random() * 16) | 0;
const v = c === 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
const timestamp = Math.floor(Date.now() / 1000);
const pathStr = String(filePath || '');
const lastDotIndex = pathStr.lastIndexOf('.');
const extension = lastDotIndex > 0 ? pathStr.substring(lastDotIndex + 1).toLowerCase() : 'jpg';
return `xk_upload_${dateStr}/${uuid}_${timestamp}.${extension}`;
}
function basenameFromPath(filePath) {
const pathStr = String(filePath || '');
const parts = pathStr.split(/[/\\]/);
return parts[parts.length - 1] || '';
}
/**
* 根据文件路径/文件名推断 Content-Type
* 小程序本地临时路径通常没有 MIME必须按扩展名推断否则 OSS 会落成 octet-stream
* 与后端 Policy 的 starts-with $Content-Type 配套:表单必须带该字段
*/
function resolveUploadContentType(filePath, fileName) {
const name = String(fileName || filePath || '').toLowerCase();
if (name.endsWith('.pdf')) return 'application/pdf';
if (name.endsWith('.png')) return 'image/png';
if (name.endsWith('.jpg') || name.endsWith('.jpeg')) return 'image/jpeg';
if (name.endsWith('.gif')) return 'image/gif';
if (name.endsWith('.webp')) return 'image/webp';
if (name.endsWith('.bmp')) return 'image/bmp';
// 医生端直传以图片为主,缺省按 jpeg避免浏览器当附件下载
return 'image/jpeg';
}
/**
* @returns {Promise<{ accessKeyId, policy, signature, host, bucket, key, expire }>}
*/
export async function getOssSignatureFromBackend() {
const res = await getBusinessOssSignatureApi();
const signature = unwrapOssSignature(res);
if (!signature) {
throw new Error('获取 OSS 签名失败');
}
return signature;
}
/**
* @param {{ filePath: string, onProgress?: (percent: number) => void }} options
* @returns {Promise<{ url: string, objectName: string }>}
*/
export async function uploadToOss(options) {
const { filePath, onProgress, fileSize = 0, fileName = '' } = options || {};
if (!filePath) {
throw new Error('文件路径为空');
}
await checkImage(filePath, 1);
const signature = await getOssSignatureFromBackend();
const objectName = generateObjectName(filePath);
// Content-Type / Content-Disposition 必须与后端 PostPolicy 条件一致,否则 OSS 报 AccessDenied
// starts-with 空前缀要求字段存在inline 便于浏览器预览而非附件下载)
const formData = {
key: objectName,
policy: signature.policy,
OSSAccessKeyId: signature.accessKeyId,
signature: signature.signature,
success_action_status: '200',
'x-oss-object-acl': 'public-read',
'Content-Type': resolveUploadContentType(filePath, fileName),
'Content-Disposition': 'inline',
};
return new Promise((resolve, reject) => {
const uploadTask = uni.uploadFile({
url: signature.host,
filePath,
name: 'file',
formData,
success: (uploadRes) => {
const code = uploadRes.statusCode;
if (code >= 200 && code < 300 || code === 204) {
const host = String(signature.host).replace(/\/$/, '');
const url = `${host}/${objectName}`;
const displayName = fileName || basenameFromPath(filePath);
registerBusinessOssFile(url, fileSize, displayName).catch((err) => {
console.warn('OSS 文件登记失败:', err);
});
resolve({
url,
objectName,
});
return;
}
reject(new Error(`OSS 上传失败HTTP ${code}`));
},
fail: (err) => {
reject(err instanceof Error ? err : new Error((err && err.errMsg) || 'OSS 上传失败'));
},
});
if (onProgress && uploadTask && typeof uploadTask.onProgressUpdate === 'function') {
uploadTask.onProgressUpdate((res) => {
if (res.totalBytesExpectedToSend > 0) {
const percent = Math.round((res.totalBytesSent / res.totalBytesExpectedToSend) * 100);
onProgress(percent);
}
});
}
});
}