Selaa lähdekoodia

火山云迁移

xgb 3 viikkoa sitten
vanhempi
commit
3c6524f5ae
5 muutettua tiedostoa jossa 297 lisäystä ja 37 poistoa
  1. 4 0
      .env.development
  2. 2 0
      package.json
  3. 34 0
      src/api/course/userVideo.js
  4. 92 0
      src/utils/hsy.js
  5. 165 37
      src/views/course/videoResource/index.vue

+ 4 - 0
.env.development

@@ -26,6 +26,10 @@ VUE_APP_COS_REGION = ap-chongqing
 VUE_APP_VIDEO_LINE_1 = https://xfktcpv.ylrzcloud.com
 # 线路二地址
 VUE_APP_VIDEO_LINE_2 = https://xfkobs.ylrztop.com
+#火山云视频地址域名
+VUE_APP_VIDEO_URL = https://myhkvolcengine.ylrztop.com
+#火山云视频点播空间名
+VUE_APP_HSY_SPACE = myhk-2114522511
 
 # 开发环境配置
 ENV = 'production'

+ 2 - 0
package.json

@@ -79,6 +79,7 @@
     "file-saver": "2.0.4",
     "fuse.js": "6.4.3",
     "highlight.js": "9.18.5",
+    "hls.js": "^1.6.14",
     "image-conversion": "^2.1.1",
     "js-beautify": "1.13.0",
     "js-cookie": "2.2.1",
@@ -88,6 +89,7 @@
     "quill": "1.3.7",
     "screenfull": "5.0.2",
     "sortablejs": "1.10.2",
+    "tt-uploader": "^1.5.5",
     "vod-js-sdk-v6": "^1.7.0",
     "vue": "2.6.12",
     "vue-baidu-map": "^0.21.22",

+ 34 - 0
src/api/course/userVideo.js

@@ -104,3 +104,37 @@ export function getThumbnail(file) {
     }
   });
 }
+
+
+//火山云视频上传
+export function uploadUserVideo(file,uploadId) {
+  const formData = new FormData();
+  formData.append('file', file);
+  formData.append('uploadId', uploadId)
+  return request({
+    url: '/course/userVideo/uploadUserVideo',
+    method: 'post',
+    data: formData,
+    headers: {
+      'Content-Type': 'multipart/form-data'
+    }
+  });
+}
+
+// 查询火山云视频上传进度
+export function getUploadProgress(uploadId) {
+  return request({
+    url: '/course/userVideo/uploadProgress',
+    method: 'get',
+    params: { uploadId }
+  });
+}
+
+export function HsyAssumeRoleService() {
+  return request({
+    url: '/course/userVideo/HsyAssumeRoleService',
+    method: 'get',
+  });
+}
+
+

+ 92 - 0
src/utils/hsy.js

@@ -0,0 +1,92 @@
+import TTUploader from 'tt-uploader'
+import { HsyAssumeRoleService } from '@/api/course/userVideo'
+const  spaceName = process.env.VUE_APP_HSY_SPACE
+export const uploadToHSY = async (file, onProgress, type, callBackUp) => {
+  try {
+    const res = await HsyAssumeRoleService()
+    //console.log('火山云 STS 凭证:', res)
+    const credentials = res.data.result.credentials
+    //console.log('火山云 credentials 凭证:', credentials)
+
+    if (!credentials) {
+      throw new Error('未获取到火山云 STS 凭证')
+    }
+
+    const uploader = new TTUploader({
+      appId: '917052',
+      userId: '68185444',
+      videoConfig: {
+        spaceName: spaceName,
+      }
+    })
+
+    const date = new Date();
+    const yyyy = date.getFullYear();
+    const MM = String(date.getMonth() + 1).padStart(2, '0');
+    const dd = String(date.getDate()).padStart(2, '0');
+    const datePath = `${yyyy}${MM}${dd}`;
+
+
+    const fileName = `${Date.now()}.mp4`;
+
+    const remoteFileName = `course/${datePath}/${fileName}`;
+
+    const fileKey = uploader.addFile({
+      file,
+      type: 'video',
+      fileName:remoteFileName,
+      stsToken: {
+        CurrentTime: credentials.currentTime,
+        ExpiredTime: credentials.expiredTime,
+        SessionToken: credentials.sessionToken,
+        AccessKeyID: credentials.accessKeyId,
+        SecretAccessKey: credentials.secretAccessKey,
+      },
+    })
+
+    return new Promise((resolve, reject) => {
+
+      // 上传进度
+      uploader.on('progress', (info) => {
+        onProgress && onProgress({
+          percent: info.percent,
+        })
+      })
+
+      // 上传完成
+      uploader.on('complete', (info) => {
+        resolve({
+          ...info.uploadResult,
+          fileKey,
+        })
+      })
+
+      // 上传失败
+      uploader.on('error', (info) => {
+        reject(info)
+      })
+
+      // 取消上传支持
+      if (callBackUp) {
+        callBackUp({
+          uploader,
+          fileKey,
+          cancel: () => {
+            try {
+              uploader.removeFile(fileKey)
+              reject(new Error('Upload cancelled by user'))
+            } catch (e) {
+              reject(e)
+            }
+          }
+        })
+      }
+
+      uploader.start(fileKey)
+    })
+
+  } catch (error) {
+    console.error('火山云上传失败:', error)
+    throw error
+  }
+}

+ 165 - 37
src/views/course/videoResource/index.vue

@@ -550,7 +550,7 @@
           </el-form-item>
 
           <el-form-item label="子分类" prop="typeSubId" :rules="[{ required: true, message: '请选择子分类', trigger: 'blur' }]">
-            <el-select v-model="batchUploadForm.typeSubId" clearable placeholder="请选择子分类" style="width: 100%">
+            <el-select v-model="batchUploadForm.typeSubId" clearable placeholder="请选择子分类" style="width: 100%" @change="changeSubType">
               <el-option
                 v-for="item in subTypeList"
                 :key="item.dictValue"
@@ -560,7 +560,7 @@
             </el-select>
           </el-form-item>
 
-          <el-form-item label="关联题目" prop="projectIds" style="display: none">
+          <el-form-item label="关联题目" prop="projectIds" v-show="currentProject === 'myhk'">
             <el-select
               ref="customSelect"
               class="custom-select-class"
@@ -774,7 +774,9 @@ import {getByIds, listCourseQuestionBank} from '@/api/course/courseQuestionBank'
 import {getThumbnail} from "@/api/course/userVideo";
 import {uploadObject} from "@/utils/cos.js";
 import {uploadToOBS} from "@/utils/obs.js";
+import {uploadToHSY} from "@/utils/hsy.js";
 import MinimizableDialog from "@/components/MinimizableDialog"
+import log from "@/views/monitor/job/log.vue";
 
 export default {
   name: 'VideoResource',
@@ -827,6 +829,8 @@ export default {
         typeSubId: null,
         projectIds: [],
         sort: null,
+        hsyVid:null,//火山云上传视频返回vid
+        hsyVodUrl:null,//火山云url
         // 新增上传状态字段
         uploadStatus: 'pending', // pending, uploading, success, failed
         uploadProgress: {
@@ -945,6 +949,7 @@ export default {
       isProcessingBatch: false, // 是否正在处理批次
       currentBatchIndex: 0, // 当前批次索引
       uploadCancellationTokens: new Map(), // Store cancellation functions by video ID
+      currentProject: process.env.VUE_APP_PROJECT
     }
   },
   watch: {
@@ -1107,6 +1112,7 @@ export default {
           this.add = true
 
           const params = Object.assign({}, this.form);
+          console.log("提交素材表单参数",this.form)
           params.projectIds = this.form.projectIds.join(',');
           if (this.form.id != null) {
             updateVideoResource(params).then(response => {
@@ -1162,6 +1168,9 @@ export default {
       }
       if (type === 3) {
         this.batchUploadForm.typeSubId = null
+        if (this.currentProject === 'myhk') {
+          this.batchUploadForm.projectIds = []
+        }
       }
       this.subTypeList = []
       if (!val) {
@@ -1171,6 +1180,25 @@ export default {
         this.subTypeList = response.data
       })
     },
+    changeSubType(val) {
+      if (this.currentProject !== 'myhk') {
+        return
+      }
+      this.projectShowList = []
+      this.batchUploadForm.projectIds = []
+      listCourseQuestionBank({questionSubType: val}).then(async response => {
+        const projectIds = response.rows.map(item => item.id)
+
+        // 如果存在关联项目,获取项目详情用于回显
+        if (projectIds && projectIds.length > 0) {
+          // 加载项目列表信息用于回显
+          await getByIds({ids: projectIds.join(',')}).then(reponse => {
+            this.projectShowList = reponse.data
+            this.batchUploadForm.projectIds = projectIds
+          });
+        }
+      })
+    },
     /** 预览视频 */
     handleVideoPreview(url) {
       this.videoPreviewVisible = true;
@@ -1301,37 +1329,95 @@ export default {
       }
     },
     //上传华为云Obs
-    async uploadVideoToHwObs(file, form, onProgress) {
+    // async uploadVideoToHwObs(file, form, onProgress) {
+    //   try {
+    //     // 更新线路2状态为上传中
+    //     this.updateUploadProgress('line2Status', 'uploading');
+    //
+    //     const data = await uploadToOBS(file, (progress) => {
+    //       const progressPercent = Math.floor(progress);
+    //       this.updateUploadProgress('line2', progressPercent);
+    //       const progressEvent = { percent: progressPercent, loaded: progress, total: progress, lengthComputable: true };
+    //       onProgress(progressEvent);
+    //     }, 1, (uploadInfo) => {
+    //       if (form.tempId) {
+    //         const tokens = this.uploadCancellationTokens.get(form.tempId) || {};
+    //         tokens.obs = uploadInfo.cancel;
+    //         this.uploadCancellationTokens.set(form.tempId, tokens);
+    //       }
+    //     });
+    //
+    //     form.line2 = `${process.env.VUE_APP_VIDEO_LINE_2}/${data.urlPath}`;
+    //
+    //     // 更新线路2状态为成功
+    //     this.updateUploadProgress('line2Status', 'success');
+    //     this.updateUploadProgress('line2', 100);
+    //
+    //     this.$message.success("线路二上传成功");
+    //     return { success: true, url: form.line2 };
+    //   } catch (error) {
+    //     // 更新线路2状态为失败
+    //     this.updateUploadProgress('line2Status', 'failed');
+    //     this.$message.error("线路二上传失败");
+    //     return { success: false, error: error.message };
+    //   }
+    // },
+    //上传火山云
+    async uploadVideoToHsy(file, form, onProgress) {
       try {
-        // 更新线路2状态为上传中
         this.updateUploadProgress('line2Status', 'uploading');
 
-        const data = await uploadToOBS(file, (progress) => {
-          const progressPercent = Math.floor(progress);
-          this.updateUploadProgress('line2', progressPercent);
-          const progressEvent = { percent: progressPercent, loaded: progress, total: progress, lengthComputable: true };
-          onProgress(progressEvent);
-        }, 1, (uploadInfo) => {
-          if (form.tempId) {
-            const tokens = this.uploadCancellationTokens.get(form.tempId) || {};
-            tokens.obs = uploadInfo.cancel;
-            this.uploadCancellationTokens.set(form.tempId, tokens);
-          }
-        });
+        const data = await uploadToHSY(
+          file,
+          (progress) => {
+            // 火山云的进度是小数0-1
+            if (typeof progress.percent === 'number') {
+              const percent = Math.floor(progress.percent * 100);
+
+              // 更新线路2进度
+              this.updateUploadProgress('line2', percent);
+
+              // 对外统一 progress 事件(模拟 xhr)
+              onProgress?.({
+                percent,
+                loaded: percent,
+                total: 100,
+                lengthComputable: true
+              });
+            }
 
-        form.line2 = `${process.env.VUE_APP_VIDEO_LINE_2}/${data.urlPath}`;
+            // 状态同步(成功 / 失败)
+            if (progress.status === 'success') {
+              this.updateUploadProgress('line2Status', 'success');
+              this.updateUploadProgress('line2', 100);
+            }
 
-        // 更新线路2状态为成功
-        this.updateUploadProgress('line2Status', 'success');
-        this.updateUploadProgress('line2', 100);
+            if (progress.status === 'failed') {
+              this.updateUploadProgress('line2Status', 'failed');
+            }
+          },
+          1,
+          (uploadInfo) => {
+            if (form.tempId) {
+              const tokens = this.uploadCancellationTokens.get(form.tempId) || {};
+              tokens.hsy = uploadInfo.cancel;
+              this.uploadCancellationTokens.set(form.tempId, tokens);
+            }
+          }
+        );
+        console.log("上传火山云返回参数",data)
 
-        this.$message.success("线路二上传成功");
+        form.line2 = `${process.env.VUE_APP_VIDEO_URL}/${data.SourceInfo.FileName}`;
+        this.form.hsyVid = data.Vid
+        this.form.hsyVodUrl = process.env.VUE_APP_VIDEO_URL+"/"+data.SourceInfo.FileName
+        console.log("this.form",this.form)
+        this.$message.success('线路二上传成功');
         return { success: true, url: form.line2 };
+
       } catch (error) {
-        // 更新线路2状态为失败
         this.updateUploadProgress('line2Status', 'failed');
-        this.$message.error("线路二上传失败");
-        return { success: false, error: error.message };
+        this.$message.error('线路二上传失败');
+        return { success: false, error: error?.message || 'upload failed' };
       }
     },
     // 更新上传进度的辅助方法
@@ -1368,7 +1454,8 @@ export default {
         await this.getFirstThumbnail(file, this.form);
         const [line1Result, line2Result] = await Promise.allSettled([
           this.uploadVideoToTxPcdn(file, this.form, options.onProgress),
-          this.uploadVideoToHwObs(file, this.form, options.onProgress)
+          //this.uploadVideoToHwObs(file, this.form, options.onProgress)
+          this.uploadVideoToHsy(file, this.form, options.onProgress)
         ]);
 
         const line1Success = line1Result.status === 'fulfilled' && line1Result.value.success;
@@ -1454,6 +1541,8 @@ export default {
     cancelBatch() {
       if (!this.videoList || this.videoList.length === 0) {
         this.batchAddVisible = false
+        this.batchUpdateVisible = false
+
         return
       }
       this.$confirm('关闭窗口视频需要重新上传,确定关闭吗?', '提示', {
@@ -1462,11 +1551,13 @@ export default {
         type: 'warning'
       }).then(() => {
         this.batchAddVisible = false
+        this.batchUpdateVisible = false
         this.changeCateType(this.queryParams.typeId)
       }).catch(() => { });
     },
     /** 批量修改 */
     submitBatchUpdate() {
+      console.log("批量上传表单提交参数",this.form)
       this.$refs["form"].validate(valid => {
         if (valid) {
           if (this.batchUpdateForm.ids.length === 0) {
@@ -1500,6 +1591,7 @@ export default {
       }
 
       // 检查是否所有选中的视频都已上传完成
+      console.log("videoList",this.videoList)
       const incompleteVideos = this.videoList.filter(item => (item.progress || 0) < 100);
       if (incompleteVideos.length > 0) {
         this.$message.warning('有未完成上传的视频,请先完成上传');
@@ -1658,14 +1750,23 @@ export default {
     /** 显示上传面板 */
     showUploadPanel() {
       this.showUpload = true;
-      this.batchUploadForm = {
-        typeId: null,
-        typeSubId: null,
-        projectIds: [],
-        files: []
-      };
+
+      if (this.currentProject === 'myhk') {
+        this.batchUploadForm = {
+          ...this.batchUploadForm,
+          files: []
+        };
+      } else {
+        this.batchUploadForm = {
+          typeId: null,
+          typeSubId: null,
+          projectIds: [],
+          files: []
+        };
+        this.subTypeList = []
+      }
+
       this.batchFileList = [];
-      this.subTypeList = []
       if (this.$refs.batchVideoUpload) {
         this.$refs.batchVideoUpload.clearFiles();
       }
@@ -1773,6 +1874,7 @@ export default {
       this.isProcessingBatch = false;
       this.isUploading = false;
       this.$message.success('所有视频上传队列处理完成!');
+      console.log("批量上传form",this.form)
     },
 
     async uploadSingleVideo(tempVideo) {
@@ -1783,7 +1885,9 @@ export default {
         // 并行上传到两个服务器
         const [line1Result, line2Result] = await Promise.allSettled([
           this.uploadVideoToTxPcdnBatch(tempVideo.file, tempVideo),
-          this.uploadVideoToHwObsBatch(tempVideo.file, tempVideo)
+          // this.uploadVideoToHwObsBatch(tempVideo.file, tempVideo)
+          this.uploadVideoToHSYBatch(tempVideo.file, tempVideo),
+
         ]);
 
         // 检查上传结果
@@ -2107,9 +2211,31 @@ export default {
       }
     },
     // 批量上传 - 华为云
-    async uploadVideoToHwObsBatch(file, tempVideo) {
+    // async uploadVideoToHwObsBatch(file, tempVideo) {
+    //   try {
+    //     const data = await uploadToOBS(file, (progress) => {
+    //       const progressPercent = Math.floor(progress);
+    //       const index = this.videoList.findIndex(item => item.tempId === tempVideo.tempId);
+    //       if (index !== -1) {
+    //         this.videoList[index].uploadDetails.line2 = progressPercent;
+    //         this.videoList[index].uploadDetails.line2Status = 'uploading';
+    //         this.updateBatchProgress(index);
+    //       }
+    //     }, 1, (uploadInfo) => {
+    //       const tokens = this.uploadCancellationTokens.get(tempVideo.tempId) || {};
+    //       tokens.obs = uploadInfo.cancel;
+    //       this.uploadCancellationTokens.set(tempVideo.tempId, tokens);
+    //     });
+    //
+    //     tempVideo.line2 = `${process.env.VUE_APP_VIDEO_LINE_2}/${data.urlPath}`;
+    //     return { success: true, url: tempVideo.line2 };
+    //   } catch (error) {
+    //     return { success: false, error: error.message };
+    //   }
+    // },
+    async uploadVideoToHSYBatch(file, tempVideo) {
       try {
-        const data = await uploadToOBS(file, (progress) => {
+        const data = await uploadToHSY(file, (progress) => {
           const progressPercent = Math.floor(progress);
           const index = this.videoList.findIndex(item => item.tempId === tempVideo.tempId);
           if (index !== -1) {
@@ -2122,9 +2248,11 @@ export default {
           tokens.obs = uploadInfo.cancel;
           this.uploadCancellationTokens.set(tempVideo.tempId, tokens);
         });
+        console.log("批量上传返回参数",data)
+        tempVideo.line2 = `${process.env.VUE_APP_VIDEO_URL}/${data.SourceInfo.FileName}`;
+        tempVideo.hsyVid = data.Vid;
 
-        tempVideo.line2 = `${process.env.VUE_APP_VIDEO_LINE_2}/${data.urlPath}`;
-        return { success: true, url: tempVideo.line2 };
+        return { success: true, url: tempVideo.line2};
       } catch (error) {
         return { success: false, error: error.message };
       }