Procházet zdrojové kódy

添加 aliyun oss 前端直传 demo AliyunOssUpload.vue

reghao před 4 dny
rodič
revize
d759087d2c
2 změnil soubory, kde provedl 170 přidání a 11 odebrání
  1. 10 11
      src/api/file.js
  2. 160 0
      src/views/admin/AliyunOssUpload.vue

+ 10 - 11
src/api/file.js

@@ -1,11 +1,10 @@
-import { post, postForm } from '@/utils/request'
+import { get, post, postForm } from '@/utils/request'
 
 const fileApi = {
   ossServerApi: '/api/file/oss/serverinfo',
   ossUploadApi: '/api/file/oss',
   fileUploadApi: '/api/file/upload',
-  ossStsApi: '/api/file/aliyun/sts_token',
-  ossSignedUrlApi: '/api/file/aliyun/signed_url'
+  aliyunOssApi: '/api/file/aliyun'
 }
 
 export function getAvatarChannelInfo() {
@@ -32,18 +31,18 @@ export function checkSample(payload) {
   return post(fileApi.ossUploadApi + '/check_sample', payload)
 }
 
-export function getFileChannelInfo() {
-  return post(fileApi.ossServerApi + '/file')
+export function uploadFile(payload) {
+  return postForm(fileApi.fileUploadApi, payload)
 }
 
-export function getStsToken() {
-  return post(fileApi.ossStsApi)
+export function getAliyunOssSignature() {
+  return get(fileApi.aliyunOssApi + '/signature')
 }
 
-export function getSignedUrl(data) {
-  return post(fileApi.ossSignedUrlApi, data)
+export function addUploadedObject(payload) {
+  return post(fileApi.aliyunOssApi + '/uploaded', payload)
 }
 
-export function uploadFile(payload) {
-  return postForm(fileApi.fileUploadApi, payload)
+export function getOssSignedUrl(payload) {
+  return post(fileApi.aliyunOssApi + '/signed_url', payload)
 }

+ 160 - 0
src/views/admin/AliyunOssUpload.vue

@@ -0,0 +1,160 @@
+<template>
+  <div class="oss-upload-container">
+    <el-upload
+      action=""
+      :http-request="handleOssUpload"
+      :before-upload="beforeUpload"
+      :on-success="handleSuccess"
+      :on-error="handleError"
+      :show-file-list="true"
+      list-type="text"
+    >
+      <el-button size="small" type="primary">点击上传到 OSS</el-button>
+      <div slot="tip" class="el-upload__tip">只能上传不超过 100MB 的文件</div>
+    </el-upload>
+
+    <div v-if="signedUrl" class="video-preview-wrapper">
+      <p class="preview-title">视频上传成功,预览如下:</p>
+      <video
+        :src="signedUrl"
+        controls
+        autoplay
+        width="100%"
+        class="video-player"
+      >
+        您的浏览器不支持 video 标签。
+      </video>
+    </div>
+  </div>
+</template>
+
+<script>
+import axios from 'axios'
+import { addUploadedObject, getAliyunOssSignature } from '@/api/file'
+
+export default {
+  name: 'OssUpload',
+  data() {
+    return {
+      objectName: null,
+      signedUrl: null
+    }
+  },
+  methods: {
+    /**
+     * 上传前的检查(可选)
+     */
+    beforeUpload(file) {
+      const isLt100M = file.size / 1024 / 1024 < 100
+      if (!isLt100M) {
+        this.$message.error('上传文件大小不能超过 100MB!')
+      }
+      return isLt100M
+    },
+
+    /**
+     * 自定义 OSS 上传核心逻辑
+     */
+    async handleOssUpload(options) {
+      const { file, onProgress, onSuccess, onError } = options
+      try {
+        // 在开始上传新视频时,可以先清空上一个视频的链接,避免画面残留
+        this.signedUrl = null
+
+        // 1. 向你的后端服务器请求 OSS 签名数据
+        const res = await getAliyunOssSignature()
+        const ossConfig = res.data
+        console.log(ossConfig)
+
+        // 2. 构造唯一的文件名
+        const suffix = file.name.substring(file.name.lastIndexOf('.'))
+        const uniqueName = `${new Date().getTime()}_${Math.random().toString(36).substr(2, 5)}${suffix}`
+        const fileKey = 'a/b/' + uniqueName
+        this.objectName = fileKey
+
+        // 3. 组装符合 OSS 要求的 FormData
+        const formData = new FormData()
+        formData.append('key', fileKey)
+        formData.append('OSSAccessKeyId', ossConfig.OSSAccessKeyId)
+        formData.append('policy', ossConfig.policy)
+        formData.append('signature', ossConfig.signature)
+        formData.append('success_action_status', '200')
+        formData.append('file', file)
+
+        // 4. 使用 Axios 发起 POST 请求直传到 OSS
+        await axios.post(ossConfig.host, formData, {
+          headers: { 'Content-Type': 'multipart/form-data' },
+          onUploadProgress: (progressEvent) => {
+            if (progressEvent.total > 0) {
+              const percent = Math.round((progressEvent.loaded * 100) / progressEvent.total)
+              onProgress({ percent })
+            }
+          }
+        })
+
+        // 5. 拼接出文件的最终访问绝对路径
+        const fileUrl = `${ossConfig.host}/${fileKey}`
+
+        // 6. 调用 Element UI 成功的钩子
+        onSuccess({ url: fileUrl }, file)
+      } catch (err) {
+        console.error('OSS 上传失败:', err)
+        onError(err)
+      }
+    },
+
+    /**
+     * 上传成功回调
+     */
+    handleSuccess(response, file, fileList) {
+      this.$message.success('文件上传成功!')
+      const objectUrl = response.url
+      if (this.objectName !== null) {
+        const jsonData = {}
+        jsonData.sha256sum = ''
+        jsonData.objectName = this.objectName
+        jsonData.objectUrl = objectUrl
+        jsonData.filename = file.filename
+        jsonData.size = file.size
+        addUploadedObject(jsonData).then(resp => {
+          if (resp.code === 0) {
+            console.log('objectId -> ' + resp.data.objectId)
+            // this.signedUrl = resp.data
+            // console.log('signed url -> ' + this.signedUrl)
+          }
+        })
+      }
+    },
+    /**
+     * 上传失败回调
+     */
+    handleError(err) {
+      this.$message.error('文件上传失败,请重试: ' + err.message)
+    }
+  }
+}
+</script>
+
+<style scoped>
+/* 🎨 简单的视频美化样式 */
+.video-preview-wrapper {
+  margin-top: 20px;
+  padding: 15px;
+  background-color: #f8f9fa;
+  border-radius: 6px;
+  border: 1px solid #e4e7ed;
+  max-width: 600px; /* 限制最大宽度,防止大视频撑满全屏 */
+}
+
+.preview-title {
+  font-size: 14px;
+  color: #606266;
+  margin-bottom: 10px;
+}
+
+.video-player {
+  border-radius: 4px;
+  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+  outline: none;
+}
+</style>