feat: 优化

This commit is contained in:
2026-02-24 23:58:17 +08:00
parent caa69d7353
commit cfaf8cab49
8 changed files with 754 additions and 609 deletions

View File

@@ -1,6 +1,17 @@
import createClientAxios from '@gold/api/axios/client'
import { refreshToken } from '@/api/auth'
import { clearUserInfoCache } from '@/api/userinfo'
import router from '@/router'
import tokenManager from '@gold/utils/token-manager'
/**
* 处理401/403错误 - 清理凭证并跳转登录页
*/
const handleAuthError = (error) => {
tokenManager.clearTokens()
clearUserInfoCache()
router.push('/login')
}
/**
* 创建HTTP客户端实例
@@ -16,8 +27,8 @@ export function createHttpClient(options = {}) {
baseURL: '/',
timeout: 180000,
refreshTokenFn: refreshToken,
on401: on401 || ((error) => router.push('/login')),
on403: on403 || ((error) => router.push('/login')),
on401: on401 || handleAuthError,
on403: on403 || handleAuthError,
})
}

View File

@@ -90,17 +90,13 @@ export function getLipSyncTask(taskId) {
* 识别已上传的视频
*/
export async function identifyUploadedVideo(videoFile) {
try {
const urlRes = await MaterialService.getVideoPlayUrl(videoFile.fileId)
if (urlRes.code !== 0 || !urlRes.data) {
throw new Error(urlRes.msg || '获取播放链接失败')
}
const identifyData = await performFaceIdentification(urlRes.data)
return buildIdentifyResponse(videoFile.id, urlRes.data, identifyData, false)
} catch (error) {
throw error
const urlRes = await MaterialService.getVideoPlayUrl(videoFile.fileId)
if (urlRes.code !== 0 || !urlRes.data) {
throw new Error(urlRes.msg || '获取播放链接失败')
}
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) {
const coverBase64 = await extractVideoCoverOptional(file)
try {
const uploadRes = await MaterialService.uploadFile(file, 'video', coverBase64, null, null)
if (uploadRes.code !== 0) {
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 uploadRes = await MaterialService.uploadFile(file, 'digital_human', coverBase64, null, null)
if (uploadRes.code !== 0) {
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)
}

View File

@@ -176,7 +176,7 @@ const fetchVideoList = async () => {
const params = {
page: currentPage.value,
pageSize: pageSize.value,
fileCategory: 'video',
fileCategory: 'digital_human',
fileName: searchKeyword.value.trim() || undefined
}
@@ -239,32 +239,18 @@ const formatDuration = (seconds) => {
const formatFileSize = (bytes) => {
if (!bytes) return '0 B'
const units = ['B', 'KB', 'MB', 'GB']
let size = bytes
let unitIndex = 0
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024
unitIndex++
}
return `${size.toFixed(1)} ${units[unitIndex]}`
const index = Math.floor(Math.log2(bytes) / 10)
const size = bytes / Math.pow(1024, index)
return `${size.toFixed(1)} ${units[index]}`
}
const getVideoPreviewUrl = (video) => {
if (video.coverBase64) {
if (!video.coverBase64.startsWith('data:')) {
return `data:image/jpeg;base64,${video.coverBase64}`
}
return video.coverBase64
return video.coverBase64.startsWith('data:')
? video.coverBase64
: `data:image/jpeg;base64,${video.coverBase64}`
}
if (video.previewUrl) {
return video.previewUrl
}
if (video.coverUrl) {
return video.coverUrl
}
return defaultCover
return video.previewUrl || video.coverUrl || defaultCover
}
const handleCancel = () => {

View File

@@ -7,11 +7,43 @@ import { ref, reactive } from 'vue'
import { message } from 'ant-design-vue'
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
* @property {string} fileCategory - 文件分类video/voice/audio/image
* @property {string} fileCategory - 文件分类video/voice/audio/image/digital_human
* @property {number|null} groupId - 分组编号(可选,仅素材库模块使用)
* @property {string|null} coverBase64 - 封面base64可选
* @property {number|null} duration - 视频时长(可选,视频文件自动获取)
* @property {Function} onProgress - 进度回调(可选)
* @property {Function} onStart - 开始回调(可选)
* @property {Function} onSuccess - 成功回调(可选)
@@ -94,6 +126,7 @@ export function useUpload() {
fileCategory,
groupId = null,
coverBase64 = null,
duration: inputDuration,
onProgress,
onStart,
onSuccess,
@@ -108,7 +141,13 @@ export function useUpload() {
state.progress = 0
// 通知开始
onStart && onStart()
onStart?.()
// 获取视频时长(如果是视频文件且未提供时长)
let duration = inputDuration
if (duration === undefined && file.type.startsWith('video/')) {
duration = await getVideoDuration(file)
}
// 第一步获取预签名URL
const presignedData = await MaterialService.getPresignedUrl({
@@ -121,7 +160,7 @@ export function useUpload() {
// 第二步直传文件到OSS
await uploadToOSS(file, presignedData.data, (progress) => {
state.progress = progress
onProgress && onProgress(progress)
onProgress?.(progress)
})
// 第三步:确认上传并保存记录
@@ -133,7 +172,7 @@ export function useUpload() {
fileType: file.type,
groupId,
coverBase64,
duration: file.type.startsWith('video/') ? null : undefined
duration
})
state.uploading = false
@@ -142,14 +181,14 @@ export function useUpload() {
const fileId = completeData.data?.infraFileId || completeData.data?.userFileId
const fileUrl = presignedData.data.presignedUrl
onSuccess && onSuccess(fileId, fileUrl)
onSuccess?.(fileId, fileUrl)
return fileId
} catch (error) {
state.uploading = false
state.status = 'error'
state.error = error.message || '上传失败'
onError && onError(error)
onError?.(error)
throw error
}
}

View File

@@ -205,7 +205,7 @@
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { ref, onMounted } from 'vue'
import { CloudUploadOutlined, CrownFilled } from '@ant-design/icons-vue'
import { useVoiceCopyStore } from '@/stores/voiceCopy'
import { useUserStore } from '@/stores/user'
@@ -242,7 +242,6 @@ const {
canGenerate,
maxTextLength,
textareaPlaceholder,
audioDurationSec,
// Pipeline 状态(单一状态源)
pipelineState,
@@ -284,9 +283,6 @@ const handleFileSelectWrapper = (e: Event) => {
controller.handleFileSelect(e)
}
// ==================== 生命周期 ====================
import { onMounted } from 'vue'
onMounted(async () => {
await voiceStore.refresh()
})

File diff suppressed because it is too large Load Diff

View File

@@ -19,7 +19,8 @@ public enum TikFileCategoryEnum implements ArrayValuable<Integer> {
GENERATE(2, "generate", "生成集"),
AUDIO(3, "audio", "配音集"),
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);

View File

@@ -77,24 +77,14 @@ public class TikOssInitServiceImpl implements TikOssInitService {
/**
* OSS路径信息
*/
private static class OssPathInfo {
final String ossRootPath;
final String videoPath;
final String generatePath;
final String audioPath;
final String mixPath;
final 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;
}
}
private record OssPathInfo(
String ossRootPath,
String videoPath,
String generatePath,
String audioPath,
String mixPath,
String voicePath
) {}
/**
* 构建OSS路径信息
@@ -176,6 +166,7 @@ public class TikOssInitServiceImpl implements TikOssInitService {
case "audio" -> ossInit.getAudioPath();
case "mix" -> ossInit.getMixPath();
case "voice" -> ossInit.getVoicePath();
case "digital_human" -> ossInit.getOssRootPath() + "/digital_human";
default -> throw exception(OSS_INIT_FAILED, "不支持的文件分类:" + fileCategory);
};
}