本文主要解决前端使用uniapp开发app时需要文件上传所遇到的问题,刷到就是赚到。

1.选取文件

其实正常来说这根本就不能算是一个问题,因为uniapp的api支持。现在不支持文件选取了,具体可以看文档uni.chooseFile。但是话又说回来还是现在uniapp还是支持正常的图片上传视频上传的。
直接上代码,这是我苦苦找了很久才找到的。注意仅仅是app

plus.io.chooseFile({
				title: 'x',
				multiple: false,
			}, (res) => {
				console.error(res);
			})

2.直传阿里云

这个才是困扰我很长时间的问题,刚开始尝试下载了ali-oss让后发现没法用。也并不是没法用主要是阿里云的芬片上传和直传我只找到了使用file文件上传的方法,并没有说可以用临时路径上传,后续又开始找各种方法把临时路径转为file文件尝试上传但是一直不行一直不行!!!真是一步一个坑,解决一个问题就会有下一个问题。

1.前端生成Policy和Signature方法

// 生成Policy(前端动态生成,替换接口返回的Policy)
function generatePolicy(options) {
  const {
    bucket,
    expire = 3600,
    allowPrefix = '*',
    allowMimeTypes = ['']
  } = options;

  const expiration = new Date();
  expiration.setTime(expiration.getTime() + expire * 1000);
  const expirationISO = expiration.toISOString();

  // 关键修改:仅保留必要条件,删除所有与$method相关的限制
  const policyConditions = [
    ['eq', '$bucket', bucket], // 限制bucket
    ['starts-with', '$key', allowPrefix], // 限制上传路径
    ...allowMimeTypes.map(type => ['starts-with', '$Content-Type', type]) // 限制文件类型
    // 移除所有["eq", "$method", "..."]条件
  ];

  const policyObject = { expiration: expirationISO, conditions: policyConditions };
  return CryptoJS.enc.Base64.stringify(
    CryptoJS.enc.Utf8.parse(JSON.stringify(policyObject))
  );
}
// 生成Signature(前端动态生成,替换接口返回的Signature)
function generateSignature(policy, accessKeySecret) {
    const hmac = CryptoJS.HmacSHA1(policy, accessKeySecret);
    return CryptoJS.enc.Base64.stringify(hmac);
}

注意:需要下载crypto-js

2.完整代码临时token从后端接口拿

上代码创建uploadOss.js

import store from '@/store';
import { getOsstoken } from '@/api/index.js';
import CryptoJS from 'crypto-js';

// 获取文件大小
function getfileSize(val) {
    if (val === 0) return "0 B";
    const k = 1024;
    const sizes = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
    const i = Math.floor(Math.log(val) / Math.log(k));
    return (val / Math.pow(k, i)).toFixed(2) + " " + sizes[i];
}

// 时间格式转换
function parseTime(time, pattern) {
    if (arguments.length === 0 || !time) return null;
    const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}';
    let date;
    if (typeof time === 'object') {
        date = time;
    } else {
        if (typeof time === 'string' && /^[0-9]+$/.test(time)) {
            time = parseInt(time);
        } else if (typeof time === 'string') {
            time = time.replace(/-/gm, '/').replace('T', ' ').replace(/\.[\d]{3}/gm, '');
        }
        if (typeof time === 'number' && time.toString().length === 10) {
            time *= 1000;
        }
        date = new Date(time);
    }
    const formatObj = {
        y: date.getFullYear(),
        m: date.getMonth() + 1,
        d: date.getDate(),
        h: date.getHours(),
        i: date.getMinutes(),
        s: date.getSeconds(),
        a: date.getDay()
    };
    return format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
        let value = formatObj[key];
        if (key === 'a') return ['日', '一', '二', '三', '四', '五', '六'][value];
        return result.length > 0 && value < 10 ? `0${value}` : value || 0;
    });
}

// 生成Policy(前端动态生成,替换接口返回的Policy)
function generatePolicy(options) {
  const {
    bucket,
    expire = 3600,
    allowPrefix = '*',
    allowMimeTypes = ['']
  } = options;

  const expiration = new Date();
  expiration.setTime(expiration.getTime() + expire * 1000);
  const expirationISO = expiration.toISOString();

  // 关键修改:仅保留必要条件,删除所有与$method相关的限制
  const policyConditions = [
    ['eq', '$bucket', bucket], // 限制bucket
    ['starts-with', '$key', allowPrefix], // 限制上传路径
    ...allowMimeTypes.map(type => ['starts-with', '$Content-Type', type]) // 限制文件类型
    // 移除所有["eq", "$method", "..."]条件
  ];

  const policyObject = { expiration: expirationISO, conditions: policyConditions };
  return CryptoJS.enc.Base64.stringify(
    CryptoJS.enc.Utf8.parse(JSON.stringify(policyObject))
  );
}
// 生成Signature(前端动态生成,替换接口返回的Signature)
function generateSignature(policy, accessKeySecret) {
    const hmac = CryptoJS.HmacSHA1(policy, accessKeySecret);
    return CryptoJS.enc.Base64.stringify(hmac);
}

// 缓存管理对象
const tokenCache = {
    token: null,
    expireTime: 0,
    tokenPromise: null,
};

// 获取OSS临时凭证(带缓存)
async function getTokenWithCache() {
    const now = Date.now();
    if (tokenCache.token && now < tokenCache.expireTime) {
        return tokenCache.token;
    }
    if (tokenCache.tokenPromise) {
        return tokenCache.tokenPromise;
    }

    tokenCache.tokenPromise = getOsstoken()
        .then(res => {
            if (res.code === 200) {
                tokenCache.token = res.data;
                tokenCache.expireTime = now + 50 * 60 * 1000; // 50分钟缓存
                return res.data;
            }
            throw new Error('获取OSS凭证失败');
        })
        .finally(() => {
            tokenCache.tokenPromise = null;
        });

    return tokenCache.tokenPromise;
}

// 生成随机文件名
function generateFileName(originalName) {
    const ext = originalName.substring(originalName.lastIndexOf('.')) || '';
    const randomStr = Math.random().toString(36).substring(2, 10);
    return `${Date.now()}_${randomStr}${ext}`;
}

// 统一文件上传方法(核心修改:前端生成Policy和Signature)
async function uploadFileToOSS(filePath, originalFileName, isMultipart = false, onProgress = () => {}) {
    try {
        const tokenData = await getTokenWithCache();
        console.log('OSS临时凭证:', tokenData);

        // 1. 生成OSS存储路径
        const datePath = parseTime(new Date(), '{y}/{m}/{d}/');
        const fileName = generateFileName(originalFileName || 'file');
        const ossPath = `lawyer/${datePath}${fileName}`; // 上传到lawyer目录

        // 2. 前端动态生成Policy(关键:替换接口返回的Policy)
        const policy = generatePolicy({
            bucket: tokenData.bucket,
            expire: 3600, // 与缓存时间保持一致
            allowPrefix: 'lawyer/' // 限制仅能上传到lawyer前缀路径
        });

        // 3. 前端动态生成Signature(关键:替换接口返回的Signature)
        const signature = generateSignature(policy, tokenData.accessKeySecret);

        // 4. 构建上传URL和表单数据
        const uploadUrl = `https://${tokenData.bucket}.${tokenData.region}.aliyuncs.com`;
        const formData = {
            'key': ossPath, // 上传路径
            'OSSAccessKeyId': tokenData.accessKeyId, // 临时AK
            'policy': policy, // 前端生成的Policy
            'Signature': signature, // 前端生成的Signature
            'x-oss-security-token': tokenData.stsToken, // STS Token(必须)
            'success_action_status': '200' // 成功状态码
        };
        // 5. 使用uni.uploadFile上传
        return new Promise((resolve, reject) => {
            const uploadTask = uni.uploadFile({
                url: uploadUrl,
                filePath: filePath, // 本地文件路径
                name: 'file', // 表单字段名固定为file
                formData: formData,
				method: 'POST',
                success: (res) => {
                    console.log('上传响应:', res);
                    if (res.statusCode === 200) {
                        resolve({
                            url: `https://${tokenData.bucket}.${tokenData.region}.aliyuncs.com/${ossPath}`,
                            ossPath: ossPath,
                            status: 'success'
                        });
                    } else {
                        reject(new Error(`上传失败,状态码: ${res.statusCode},响应: ${res.data}`));
                    }
                },
                fail: (err) => {
                    console.error('上传失败:', err);
                    reject(err);
                }
            });

            // 监听进度
            uploadTask.onProgressUpdate((progress) => {
				console.log(progress,'-----');
                onProgress(progress.progress); // 转为0-1的进度值
            });
        });
    } catch (error) {
        console.error('OSS上传错误:', error);
        throw error;
    }
}

// 获取文件Base64(适配Uniapp)
function getBase64(filePath) {
    return new Promise((resolve, reject) => {
        uni.getFileSystemManager().readFile({
            filePath: filePath,
            encoding: 'base64',
            success: (res) => {
                resolve(`data:image/jpeg;base64,${res.data}`);
            },
            fail: (err) => {
                reject(err);
            }
        });
    });
}

// 对外暴露API
export default {
    uploadFile(filePath, originalFileName, onProgress) {
        return uploadFileToOSS(filePath, originalFileName, false, onProgress);
    },
    uploadLargeFile(filePath, originalFileName, onProgress) {
        return uploadFileToOSS(filePath, originalFileName, true, onProgress);
    },
    getfileSize,
    getBase64,
    clearTokenCache() {
        tokenCache.token = null;
        tokenCache.expireTime = 0;
        tokenCache.tokenPromise = null;
    }
};

3.页面中使用

正常使用上传图片

async uploadImage() {
				try {
					// 直接选择图片
					const res = await uni.chooseImage({
						count: 1,
						sourceType: ['album', 'camera']
					});
					let originalFileName = res.tempFilePaths[0].substring(res.tempFilePaths[0].lastIndexOf("/") + 1)
					oss.uploadFile(res.tempFilePaths[0], originalFileName, (progress) => {
						console.log('上传进度:', progress);
					}).then(result => {
						console.log('上传成功:', result.url);
						editUser({
							avatar: result.url
						}).then(res => {
							if (res.code == 200) {
								uni.showToast({
									title: '头像设置成功',
									icon: 'none'
								})
								this.info()
							}
						})
					}).catch(error => {
						console.error('上传失败:', error);
					});
				} catch (e) {
					console.error('图片上传失败:', e);
				}
			},

上传文件

			uploadFiless() {
				plus.io.chooseFile({
					title: 'x',
					multiple: false,
				}, (res) => {
					let originalFileName = res.files[0].substring(res.files[0].lastIndexOf("/") + 1)
					oss.uploadFile(res.files[0], originalFileName, (progress) => {
						console.log('上传进度:', progress);
						this.pgList = progress
					}).then(result => {
						console.log('上传成功:', result.url);
						this.videoUrl = result.url
					}).catch(error => {
						console.error('上传失败:', error);
					});
				})
			},
Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐