@ -3,15 +3,39 @@ import { useAuthSession } from '../../../composables/useAuthSession'
definePageMeta ( { title : '媒体存储校验' } )
type RefDetail = { ownerType : string ; ownerId : number }
type AuditRow = {
id : number
userId : number
storageKey : string
refCount : number
refs : RefDetail [ ]
}
type AuditReport = {
scannedAt : string
dbRowCount : number
diskFileCount : number
missingOnDisk : Array < { id : number ; userId : number ; storageKey : string ; refCount : number } >
invalidStorageKey : Array < { id : number ; userId : number ; storageKey : string ; refCount : number } >
missingOnDisk : AuditRow [ ]
invalidStorageKey : AuditRow [ ]
onDiskNotInDb : string [ ]
}
function refSourceLabel ( ownerType : string , ownerId : number ) : string {
if ( ownerType === 'post' ) {
return ` 文章 # ${ ownerId } `
}
if ( ownerType === 'profile' ) {
return ` 个人资料 # ${ ownerId } `
}
return ` ${ ownerType } # ${ ownerId } `
}
function formatRefSources ( refs : RefDetail [ ] ) : string {
return refs . map ( ( r ) => refSourceLabel ( r . ownerType , r . ownerId ) ) . join ( ' · ' )
}
const { user , refresh } = useAuthSession ( )
const { fetchData } = useClientApi ( )
const toast = useToast ( )
@ -45,7 +69,7 @@ async function runCleanup() {
'/api/admin/media/storage-audit-cleanup' ,
{ method : 'POST' , body : { } } ,
)
toast . add ( { title : ` 已移除 ${ removed } 条失效记录(无引用 且磁盘无文件) ` , color : 'success' } )
toast . add ( { title : ` 已移除 ${ removed } 条失效记录(无 media_refs 且磁盘无文件) ` , color : 'success' } )
cleanupOpen . value = false
await runAudit ( )
} finally {
@ -69,7 +93,7 @@ const riskyMissingCount = computed(() => {
const cleanupDescription = computed ( ( ) => {
const n = safeToCleanupCount . value
return ` 将删除 ${ n } 条 media_assets 记录:磁盘上无对应文件,且当前无任何文章引用 。此操作不可撤销。 `
return ` 将删除 ${ n } 条 media_assets 记录:磁盘上无对应文件,且当前无任何 media_refs 引用(文章/个人资料等) 。此操作不可撤销。 `
} )
onMounted ( async ( ) => {
@ -87,7 +111,7 @@ onMounted(async () => {
< p class = "text-sm text-muted mt-1 max-w-3xl" >
比对 < code class = "text-xs" > public / assets < / code > 与表 < code class = "text-xs" > media_assets < / code > :
库中有记录但文件缺失 、 非法 storageKey 、 以及磁盘上未登记的文件 。
「 一键清理 」 仅删除 < strong > 无文章 引用 < / strong > 且 < strong > 磁盘上确实没有文件 < / strong > 的库记录 , 不会删磁盘文件 。
「 一键清理 」 仅删除 < strong > 无 media_refs 引用 < / strong > 且 < strong > 磁盘上确实没有文件 < / strong > 的库记录 , 不会删磁盘文件 。
< / p >
< / div >
< UButton to = "/me" variant = "ghost" size = "sm" >
@ -119,10 +143,10 @@ onMounted(async () => {
< UCard v-if ="riskyMissingCount > 0" >
< template # header >
< span class = "text-warning" > 需人工处理 : 文件缺失但仍被文章 引用 ( { { riskyMissingCount } } ) < / span >
< span class = "text-warning" > 需人工处理 : 文件缺失但仍被引用 ( { { riskyMissingCount } } ) < / span >
< / template >
< p class = "text-sm text-muted mb-3" >
请先修改相关文章正文或封面中的图片地址 , 再处理库记录 ; 勿使用 「 清理失效库记录 」 删除这些行 ( 当前实现不会删除有引用的行 ) 。
「 引用来源 」 列出 media_refs 的 owner_type / owner_id ( 如文章 id 、 用户资料 user id ) 。 请先修改对应正文 、 封面或个人资料中的图片 , 再处理库记录 ; 勿使用 「 清理失效库记录 」 删除这些行 。
< / p >
< div class = "overflow-x-auto rounded border border-default" >
< table class = "w-full text-sm" >
@ -140,6 +164,9 @@ onMounted(async () => {
< th class = "p-2" >
引用数
< / th >
< th class = "p-2 min-w-[12rem]" >
引用来源 ( owner_type )
< / th >
< / tr >
< / thead >
< tbody >
@ -156,6 +183,9 @@ onMounted(async () => {
< td class = "p-2 tabular-nums" >
{ { row . refCount } }
< / td >
< td class = "p-2 text-xs align-top" >
{ { formatRefSources ( row . refs ) } }
< / td >
< / tr >
< / tbody >
< / table >
@ -183,6 +213,9 @@ onMounted(async () => {
< th class = "p-2" >
引用数
< / th >
< th class = "p-2 min-w-[12rem]" >
引用来源 ( owner_type )
< / th >
< / tr >
< / thead >
< tbody >
@ -199,6 +232,10 @@ onMounted(async () => {
< td class = "p-2 tabular-nums" >
{ { row . refCount } }
< / td >
< td class = "p-2 text-xs align-top" >
< span v-if ="row.refCount === 0" class="text-muted" > — < / span >
< span v-else > {{ formatRefSources ( row.refs ) }} < / span >
< / td >
< / tr >
< / tbody >
< / table >
@ -225,6 +262,9 @@ onMounted(async () => {
< th class = "p-2" >
引用数
< / th >
< th class = "p-2 min-w-[12rem]" >
引用来源 ( owner_type )
< / th >
< / tr >
< / thead >
< tbody >
@ -241,6 +281,10 @@ onMounted(async () => {
< td class = "p-2 tabular-nums" >
{ { row . refCount } }
< / td >
< td class = "p-2 text-xs align-top" >
< span v-if ="row.refCount === 0" class="text-muted" > — < / span >
< span v-else > {{ formatRefSources ( row.refs ) }} < / span >
< / td >
< / tr >
< / tbody >
< / table >