feat: 优化
This commit is contained in:
@@ -1,6 +1,17 @@
|
|||||||
import createClientAxios from '@gold/api/axios/client'
|
import createClientAxios from '@gold/api/axios/client'
|
||||||
import { refreshToken } from '@/api/auth'
|
import { refreshToken } from '@/api/auth'
|
||||||
|
import { clearUserInfoCache } from '@/api/userinfo'
|
||||||
import router from '@/router'
|
import router from '@/router'
|
||||||
|
import tokenManager from '@gold/utils/token-manager'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理401/403错误 - 清理凭证并跳转登录页
|
||||||
|
*/
|
||||||
|
const handleAuthError = (error) => {
|
||||||
|
tokenManager.clearTokens()
|
||||||
|
clearUserInfoCache()
|
||||||
|
router.push('/login')
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建HTTP客户端实例
|
* 创建HTTP客户端实例
|
||||||
@@ -16,8 +27,8 @@ export function createHttpClient(options = {}) {
|
|||||||
baseURL: '/',
|
baseURL: '/',
|
||||||
timeout: 180000,
|
timeout: 180000,
|
||||||
refreshTokenFn: refreshToken,
|
refreshTokenFn: refreshToken,
|
||||||
on401: on401 || ((error) => router.push('/login')),
|
on401: on401 || handleAuthError,
|
||||||
on403: on403 || ((error) => router.push('/login')),
|
on403: on403 || handleAuthError,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -90,17 +90,13 @@ export function getLipSyncTask(taskId) {
|
|||||||
* 识别已上传的视频
|
* 识别已上传的视频
|
||||||
*/
|
*/
|
||||||
export async function identifyUploadedVideo(videoFile) {
|
export async function identifyUploadedVideo(videoFile) {
|
||||||
try {
|
const urlRes = await MaterialService.getVideoPlayUrl(videoFile.fileId)
|
||||||
const urlRes = await MaterialService.getVideoPlayUrl(videoFile.fileId)
|
if (urlRes.code !== 0 || !urlRes.data) {
|
||||||
if (urlRes.code !== 0 || !urlRes.data) {
|
throw new Error(urlRes.msg || '获取播放链接失败')
|
||||||
throw new Error(urlRes.msg || '获取播放链接失败')
|
|
||||||
}
|
|
||||||
|
|
||||||
const identifyData = await performFaceIdentification(urlRes.data)
|
|
||||||
return buildIdentifyResponse(videoFile.id, urlRes.data, identifyData, false)
|
|
||||||
} catch (error) {
|
|
||||||
throw error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const identifyData = await performFaceIdentification(urlRes.data)
|
||||||
|
return buildIdentifyResponse(videoFile.id, urlRes.data, identifyData, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -109,23 +105,19 @@ export async function identifyUploadedVideo(videoFile) {
|
|||||||
export async function uploadAndIdentifyVideo(file) {
|
export async function uploadAndIdentifyVideo(file) {
|
||||||
const coverBase64 = await extractVideoCoverOptional(file)
|
const coverBase64 = await extractVideoCoverOptional(file)
|
||||||
|
|
||||||
try {
|
const uploadRes = await MaterialService.uploadFile(file, 'digital_human', coverBase64, null, null)
|
||||||
const uploadRes = await MaterialService.uploadFile(file, 'video', coverBase64, null, null)
|
if (uploadRes.code !== 0) {
|
||||||
if (uploadRes.code !== 0) {
|
throw new Error(uploadRes.msg || '上传失败')
|
||||||
throw new Error(uploadRes.msg || '上传失败')
|
|
||||||
}
|
|
||||||
|
|
||||||
const fileId = uploadRes.data
|
|
||||||
|
|
||||||
const urlRes = await MaterialService.getVideoPlayUrl(fileId)
|
|
||||||
if (urlRes.code !== 0) {
|
|
||||||
throw new Error(urlRes.msg || '获取播放链接失败')
|
|
||||||
}
|
|
||||||
|
|
||||||
const identifyData = await performFaceIdentification(urlRes.data)
|
|
||||||
return buildIdentifyResponse(fileId, urlRes.data, identifyData, true)
|
|
||||||
} catch (error) {
|
|
||||||
throw error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fileId = uploadRes.data
|
||||||
|
|
||||||
|
const urlRes = await MaterialService.getVideoPlayUrl(fileId)
|
||||||
|
if (urlRes.code !== 0) {
|
||||||
|
throw new Error(urlRes.msg || '获取播放链接失败')
|
||||||
|
}
|
||||||
|
|
||||||
|
const identifyData = await performFaceIdentification(urlRes.data)
|
||||||
|
return buildIdentifyResponse(fileId, urlRes.data, identifyData, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ const fetchVideoList = async () => {
|
|||||||
const params = {
|
const params = {
|
||||||
page: currentPage.value,
|
page: currentPage.value,
|
||||||
pageSize: pageSize.value,
|
pageSize: pageSize.value,
|
||||||
fileCategory: 'video',
|
fileCategory: 'digital_human',
|
||||||
fileName: searchKeyword.value.trim() || undefined
|
fileName: searchKeyword.value.trim() || undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,32 +239,18 @@ const formatDuration = (seconds) => {
|
|||||||
const formatFileSize = (bytes) => {
|
const formatFileSize = (bytes) => {
|
||||||
if (!bytes) return '0 B'
|
if (!bytes) return '0 B'
|
||||||
const units = ['B', 'KB', 'MB', 'GB']
|
const units = ['B', 'KB', 'MB', 'GB']
|
||||||
let size = bytes
|
const index = Math.floor(Math.log2(bytes) / 10)
|
||||||
let unitIndex = 0
|
const size = bytes / Math.pow(1024, index)
|
||||||
while (size >= 1024 && unitIndex < units.length - 1) {
|
return `${size.toFixed(1)} ${units[index]}`
|
||||||
size /= 1024
|
|
||||||
unitIndex++
|
|
||||||
}
|
|
||||||
return `${size.toFixed(1)} ${units[unitIndex]}`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getVideoPreviewUrl = (video) => {
|
const getVideoPreviewUrl = (video) => {
|
||||||
if (video.coverBase64) {
|
if (video.coverBase64) {
|
||||||
if (!video.coverBase64.startsWith('data:')) {
|
return video.coverBase64.startsWith('data:')
|
||||||
return `data:image/jpeg;base64,${video.coverBase64}`
|
? video.coverBase64
|
||||||
}
|
: `data:image/jpeg;base64,${video.coverBase64}`
|
||||||
return video.coverBase64
|
|
||||||
}
|
}
|
||||||
|
return video.previewUrl || video.coverUrl || defaultCover
|
||||||
if (video.previewUrl) {
|
|
||||||
return video.previewUrl
|
|
||||||
}
|
|
||||||
|
|
||||||
if (video.coverUrl) {
|
|
||||||
return video.coverUrl
|
|
||||||
}
|
|
||||||
|
|
||||||
return defaultCover
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
|
|||||||
@@ -7,11 +7,43 @@ import { ref, reactive } from 'vue'
|
|||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
import { MaterialService } from '@/api/material'
|
import { MaterialService } from '@/api/material'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取视频时长(秒)
|
||||||
|
* @param {File} file - 视频文件对象
|
||||||
|
* @returns {Promise<number|null>} 时长(秒)
|
||||||
|
*/
|
||||||
|
function getVideoDuration(file) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
if (!file.type.startsWith('video/')) {
|
||||||
|
resolve(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const video = document.createElement('video')
|
||||||
|
video.preload = 'metadata'
|
||||||
|
video.muted = true
|
||||||
|
|
||||||
|
video.onloadedmetadata = function() {
|
||||||
|
const duration = Math.round(video.duration)
|
||||||
|
URL.revokeObjectURL(video.src)
|
||||||
|
resolve(duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
video.onerror = function() {
|
||||||
|
URL.revokeObjectURL(video.src)
|
||||||
|
resolve(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
video.src = URL.createObjectURL(file)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} UploadOptions
|
* @typedef {Object} UploadOptions
|
||||||
* @property {string} fileCategory - 文件分类(video/voice/audio/image)
|
* @property {string} fileCategory - 文件分类(video/voice/audio/image/digital_human)
|
||||||
* @property {number|null} groupId - 分组编号(可选,仅素材库模块使用)
|
* @property {number|null} groupId - 分组编号(可选,仅素材库模块使用)
|
||||||
* @property {string|null} coverBase64 - 封面base64(可选)
|
* @property {string|null} coverBase64 - 封面base64(可选)
|
||||||
|
* @property {number|null} duration - 视频时长(可选,视频文件自动获取)
|
||||||
* @property {Function} onProgress - 进度回调(可选)
|
* @property {Function} onProgress - 进度回调(可选)
|
||||||
* @property {Function} onStart - 开始回调(可选)
|
* @property {Function} onStart - 开始回调(可选)
|
||||||
* @property {Function} onSuccess - 成功回调(可选)
|
* @property {Function} onSuccess - 成功回调(可选)
|
||||||
@@ -94,6 +126,7 @@ export function useUpload() {
|
|||||||
fileCategory,
|
fileCategory,
|
||||||
groupId = null,
|
groupId = null,
|
||||||
coverBase64 = null,
|
coverBase64 = null,
|
||||||
|
duration: inputDuration,
|
||||||
onProgress,
|
onProgress,
|
||||||
onStart,
|
onStart,
|
||||||
onSuccess,
|
onSuccess,
|
||||||
@@ -108,7 +141,13 @@ export function useUpload() {
|
|||||||
state.progress = 0
|
state.progress = 0
|
||||||
|
|
||||||
// 通知开始
|
// 通知开始
|
||||||
onStart && onStart()
|
onStart?.()
|
||||||
|
|
||||||
|
// 获取视频时长(如果是视频文件且未提供时长)
|
||||||
|
let duration = inputDuration
|
||||||
|
if (duration === undefined && file.type.startsWith('video/')) {
|
||||||
|
duration = await getVideoDuration(file)
|
||||||
|
}
|
||||||
|
|
||||||
// 第一步:获取预签名URL
|
// 第一步:获取预签名URL
|
||||||
const presignedData = await MaterialService.getPresignedUrl({
|
const presignedData = await MaterialService.getPresignedUrl({
|
||||||
@@ -121,7 +160,7 @@ export function useUpload() {
|
|||||||
// 第二步:直传文件到OSS
|
// 第二步:直传文件到OSS
|
||||||
await uploadToOSS(file, presignedData.data, (progress) => {
|
await uploadToOSS(file, presignedData.data, (progress) => {
|
||||||
state.progress = progress
|
state.progress = progress
|
||||||
onProgress && onProgress(progress)
|
onProgress?.(progress)
|
||||||
})
|
})
|
||||||
|
|
||||||
// 第三步:确认上传并保存记录
|
// 第三步:确认上传并保存记录
|
||||||
@@ -133,7 +172,7 @@ export function useUpload() {
|
|||||||
fileType: file.type,
|
fileType: file.type,
|
||||||
groupId,
|
groupId,
|
||||||
coverBase64,
|
coverBase64,
|
||||||
duration: file.type.startsWith('video/') ? null : undefined
|
duration
|
||||||
})
|
})
|
||||||
|
|
||||||
state.uploading = false
|
state.uploading = false
|
||||||
@@ -142,14 +181,14 @@ export function useUpload() {
|
|||||||
|
|
||||||
const fileId = completeData.data?.infraFileId || completeData.data?.userFileId
|
const fileId = completeData.data?.infraFileId || completeData.data?.userFileId
|
||||||
const fileUrl = presignedData.data.presignedUrl
|
const fileUrl = presignedData.data.presignedUrl
|
||||||
onSuccess && onSuccess(fileId, fileUrl)
|
onSuccess?.(fileId, fileUrl)
|
||||||
|
|
||||||
return fileId
|
return fileId
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
state.uploading = false
|
state.uploading = false
|
||||||
state.status = 'error'
|
state.status = 'error'
|
||||||
state.error = error.message || '上传失败'
|
state.error = error.message || '上传失败'
|
||||||
onError && onError(error)
|
onError?.(error)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -205,7 +205,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { CloudUploadOutlined, CrownFilled } from '@ant-design/icons-vue'
|
import { CloudUploadOutlined, CrownFilled } from '@ant-design/icons-vue'
|
||||||
import { useVoiceCopyStore } from '@/stores/voiceCopy'
|
import { useVoiceCopyStore } from '@/stores/voiceCopy'
|
||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
@@ -242,7 +242,6 @@ const {
|
|||||||
canGenerate,
|
canGenerate,
|
||||||
maxTextLength,
|
maxTextLength,
|
||||||
textareaPlaceholder,
|
textareaPlaceholder,
|
||||||
audioDurationSec,
|
|
||||||
|
|
||||||
// Pipeline 状态(单一状态源)
|
// Pipeline 状态(单一状态源)
|
||||||
pipelineState,
|
pipelineState,
|
||||||
@@ -284,9 +283,6 @@ const handleFileSelectWrapper = (e: Event) => {
|
|||||||
controller.handleFileSelect(e)
|
controller.handleFileSelect(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== 生命周期 ====================
|
|
||||||
|
|
||||||
import { onMounted } from 'vue'
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await voiceStore.refresh()
|
await voiceStore.refresh()
|
||||||
})
|
})
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -19,7 +19,8 @@ public enum TikFileCategoryEnum implements ArrayValuable<Integer> {
|
|||||||
GENERATE(2, "generate", "生成集"),
|
GENERATE(2, "generate", "生成集"),
|
||||||
AUDIO(3, "audio", "配音集"),
|
AUDIO(3, "audio", "配音集"),
|
||||||
MIX(4, "mix", "混剪集"),
|
MIX(4, "mix", "混剪集"),
|
||||||
VOICE(5, "voice", "声音集");
|
VOICE(5, "voice", "声音集"),
|
||||||
|
DIGITAL_HUMAN(6, "digital_human", "数字人素材");
|
||||||
|
|
||||||
public static final Integer[] ARRAYS = Arrays.stream(values()).map(TikFileCategoryEnum::getCode).toArray(Integer[]::new);
|
public static final Integer[] ARRAYS = Arrays.stream(values()).map(TikFileCategoryEnum::getCode).toArray(Integer[]::new);
|
||||||
|
|
||||||
|
|||||||
@@ -77,24 +77,14 @@ public class TikOssInitServiceImpl implements TikOssInitService {
|
|||||||
/**
|
/**
|
||||||
* OSS路径信息
|
* OSS路径信息
|
||||||
*/
|
*/
|
||||||
private static class OssPathInfo {
|
private record OssPathInfo(
|
||||||
final String ossRootPath;
|
String ossRootPath,
|
||||||
final String videoPath;
|
String videoPath,
|
||||||
final String generatePath;
|
String generatePath,
|
||||||
final String audioPath;
|
String audioPath,
|
||||||
final String mixPath;
|
String mixPath,
|
||||||
final String voicePath;
|
String voicePath
|
||||||
|
) {}
|
||||||
OssPathInfo(String ossRootPath, String videoPath, String generatePath,
|
|
||||||
String audioPath, String mixPath, String voicePath) {
|
|
||||||
this.ossRootPath = ossRootPath;
|
|
||||||
this.videoPath = videoPath;
|
|
||||||
this.generatePath = generatePath;
|
|
||||||
this.audioPath = audioPath;
|
|
||||||
this.mixPath = mixPath;
|
|
||||||
this.voicePath = voicePath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建OSS路径信息
|
* 构建OSS路径信息
|
||||||
@@ -176,6 +166,7 @@ public class TikOssInitServiceImpl implements TikOssInitService {
|
|||||||
case "audio" -> ossInit.getAudioPath();
|
case "audio" -> ossInit.getAudioPath();
|
||||||
case "mix" -> ossInit.getMixPath();
|
case "mix" -> ossInit.getMixPath();
|
||||||
case "voice" -> ossInit.getVoicePath();
|
case "voice" -> ossInit.getVoicePath();
|
||||||
|
case "digital_human" -> ossInit.getOssRootPath() + "/digital_human";
|
||||||
default -> throw exception(OSS_INIT_FAILED, "不支持的文件分类:" + fileCategory);
|
default -> throw exception(OSS_INIT_FAILED, "不支持的文件分类:" + fileCategory);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user