fix(admin): 转码媒资 id 按字符串传递并处理空态
This commit is contained in:
1 parent
a4893c31a2
commit
fecc3b4817
6 files changed
+82
-13
No files matched your search
@@ -0,0 +1,18 @@
|
|||||||
|
# 转码媒资 ID 前端精度修复
|
||||||
|
|
||||||
|
## 根因
|
||||||
|
|
||||||
|
聚合接口把 Snowflake 媒资 ID 以字符串返回,但面板使用
|
||||||
|
`Number(detail.mediaAssetId)`,超过 `2^53` 后发生精度丢失,手动转码请求了错误 ID。
|
||||||
|
|
||||||
|
## 修复
|
||||||
|
|
||||||
|
- 聚合响应中的 episodeId/mediaAssetId/taskId 类型改为字符串。
|
||||||
|
- 父面板不再 Number 归一化,媒体 ID 校验改为字符串 `/\d+/`。
|
||||||
|
- 手动转码、帧图分页 URL 原样传递字符串。
|
||||||
|
- mediaAssetId 为空时禁用重新转码并展示明确空态。
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
|
||||||
|
- 超 2^53 ID 字符串保持逐位一致。
|
||||||
|
- `pnpm lint:eslint`、`pnpm build:dev`。
|
||||||
@@ -100,3 +100,54 @@ Build successful. Please see dist-dev directory
|
|||||||
|
|
||||||
解读:组件 props/emits、类型检查、ESLint 和开发构建全部通过;
|
解读:组件 props/emits、类型检查、ESLint 和开发构建全部通过;
|
||||||
循环 chunk 为项目既有警告。轮询启停、接口路径、分页参数和 locales 文案未改变。
|
循环 chunk 为项目既有警告。轮询启停、接口路径、分页参数和 locales 文案未改变。
|
||||||
|
|
||||||
|
## 2026-10-22 转码媒资 ID 前端精度修复
|
||||||
|
|
||||||
|
- 分支:`feature/transcode-media-id-fix-ui`
|
||||||
|
- 基线:`develop` / `origin/develop` = `a4893c3`
|
||||||
|
|
||||||
|
已复现 `Number('2113150398670966786')` 变为 `2113150398670966800`。
|
||||||
|
本次将媒资、任务 id 全链路按字符串处理,禁止 Number 转换;没有媒资时
|
||||||
|
禁用重新转码并显示空态。
|
||||||
|
|
||||||
|
```text
|
||||||
|
$ node -e "const id='2113150398670966786'; console.log(String(Number(id)))"
|
||||||
|
2113150398670966800
|
||||||
|
```
|
||||||
|
|
||||||
|
解读:Snowflake id 超过 JS 安全整数,字符串化后的 Number 已失真。
|
||||||
|
|
||||||
|
### 实现
|
||||||
|
|
||||||
|
- `EpisodeTranscodeItem` 的 episodeId/mediaAssetId/taskId,以及 rendition/thumbnail
|
||||||
|
相关 id 类型改为字符串。
|
||||||
|
- 父面板使用 `detail.mediaAssetId` 原始字符串和正则校验,不再 Number 转换。
|
||||||
|
- 手动转码与帧图分页接口原样传递字符串 id。
|
||||||
|
- `mediaAssetId` 为空时按钮保持禁用,空态优先展示后端 errorMsg 原因。
|
||||||
|
|
||||||
|
静态检查确认:
|
||||||
|
|
||||||
|
```text
|
||||||
|
$ rg -n "Number\([^\n]*mediaAssetId|mediaAssetId[^\n]*Number\(" \
|
||||||
|
src/views/contentManagement/upload src/api/admin
|
||||||
|
(无匹配)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 门禁
|
||||||
|
|
||||||
|
```text
|
||||||
|
$ pnpm lint:eslint
|
||||||
|
> eslint --fix --ext .js,.ts,.vue ./src
|
||||||
|
(0 error / 0 warning)
|
||||||
|
|
||||||
|
$ pnpm ts:check
|
||||||
|
> vue-tsc --noEmit --skipLibCheck
|
||||||
|
(通过)
|
||||||
|
|
||||||
|
$ pnpm build:dev
|
||||||
|
WARN Circular chunk: element-plus -> vue-chunks -> element-plus.
|
||||||
|
Build successful. Please see dist-dev directory
|
||||||
|
```
|
||||||
|
|
||||||
|
解读:字符串 id 链路、类型检查、ESLint 与开发构建均通过;
|
||||||
|
循环 chunk 为项目既有警告。
|
||||||
@@ -12,7 +12,7 @@ export const getEpisodeTranscodeApi = (episodeId: number | string) => {
|
|||||||
|
|
||||||
/** 按媒体分页读取帧图;页面切换时复用该接口。 */
|
/** 按媒体分页读取帧图;页面切换时复用该接口。 */
|
||||||
export const getMediaThumbnailsApi = (
|
export const getMediaThumbnailsApi = (
|
||||||
mediaAssetId: number | string,
|
mediaAssetId: string,
|
||||||
pageIndex: number,
|
pageIndex: number,
|
||||||
pageSize: number
|
pageSize: number
|
||||||
) => {
|
) => {
|
||||||
@@ -23,7 +23,7 @@ export const getMediaThumbnailsApi = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 手动触发或重转当前分集绑定的媒体。 */
|
/** 手动触发或重转当前分集绑定的媒体。 */
|
||||||
export const triggerMediaTranscodeApi = (mediaAssetId: number | string) => {
|
export const triggerMediaTranscodeApi = (mediaAssetId: string) => {
|
||||||
return request.post<TranscodeTriggerResult>({
|
return request.post<TranscodeTriggerResult>({
|
||||||
url: `/admin/media/${mediaAssetId}/transcode`
|
url: `/admin/media/${mediaAssetId}/transcode`
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -249,7 +249,7 @@ export interface MediaTranscodeProfile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface MediaRenditionItem {
|
export interface MediaRenditionItem {
|
||||||
id: number
|
id: string
|
||||||
quality: string
|
quality: string
|
||||||
qualityName: string
|
qualityName: string
|
||||||
width: number
|
width: number
|
||||||
@@ -292,8 +292,8 @@ export interface MediaThumbnailSpriteInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface MediaThumbnailResult {
|
export interface MediaThumbnailResult {
|
||||||
mediaAssetId?: number | null
|
mediaAssetId?: string | null
|
||||||
episodeId?: number | null
|
episodeId?: string | null
|
||||||
/** READY / FAILED / EMPTY / INVALID / NO_EPISODE */
|
/** READY / FAILED / EMPTY / INVALID / NO_EPISODE */
|
||||||
status: string
|
status: string
|
||||||
errorMsg?: string
|
errorMsg?: string
|
||||||
@@ -304,9 +304,9 @@ export interface MediaThumbnailResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface EpisodeTranscodeItem {
|
export interface EpisodeTranscodeItem {
|
||||||
episodeId: number
|
episodeId: string
|
||||||
mediaAssetId?: number | null
|
mediaAssetId?: string | null
|
||||||
taskId?: number | null
|
taskId?: string | null
|
||||||
status: string
|
status: string
|
||||||
progress?: number
|
progress?: number
|
||||||
retryCount?: number
|
retryCount?: number
|
||||||
@@ -316,7 +316,7 @@ export interface EpisodeTranscodeItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface TranscodeTriggerResult {
|
export interface TranscodeTriggerResult {
|
||||||
taskId: number
|
taskId: string
|
||||||
status: string
|
status: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import {
|
|||||||
} from './episodeTranscodeUi'
|
} from './episodeTranscodeUi'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
episodeId: number
|
episodeId: string | number
|
||||||
active: boolean
|
active: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
@@ -45,8 +45,8 @@ let pollTimer: ReturnType<typeof setTimeout> | null = null
|
|||||||
let requestSeq = 0
|
let requestSeq = 0
|
||||||
let thumbnailSeq = 0
|
let thumbnailSeq = 0
|
||||||
|
|
||||||
const mediaAssetId = computed(() => Number(detail.value?.mediaAssetId || 0))
|
const mediaAssetId = computed(() => detail.value?.mediaAssetId || '')
|
||||||
const hasMedia = computed(() => mediaAssetId.value > 0)
|
const hasMedia = computed(() => /^[1-9]\d*$/.test(mediaAssetId.value))
|
||||||
const normalizedStatus = computed(() => String(detail.value?.status || '').toUpperCase())
|
const normalizedStatus = computed(() => String(detail.value?.status || '').toUpperCase())
|
||||||
const renditions = computed(() => detail.value?.renditions || [])
|
const renditions = computed(() => detail.value?.renditions || [])
|
||||||
const isProcessing = computed(
|
const isProcessing = computed(
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ const { t } = useI18n()
|
|||||||
</div>
|
</div>
|
||||||
<el-empty
|
<el-empty
|
||||||
v-else-if="detail && !view.hasMedia"
|
v-else-if="detail && !view.hasMedia"
|
||||||
:description="t('episodeTranscode.noMedia')"
|
:description="detail.errorMsg || t('episodeTranscode.noMedia')"
|
||||||
:image-size="72"
|
:image-size="72"
|
||||||
/>
|
/>
|
||||||
<template v-else-if="detail">
|
<template v-else-if="detail">
|
||||||
|
|||||||
Reference in new issue
Block a user