chore: freeze guide and explain update
This commit is contained in:
157
src/repositories/AudioPlayInfoRepository.ts
Normal file
157
src/repositories/AudioPlayInfoRepository.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
import {
|
||||
dataSourceConfig
|
||||
} from '@/config/dataSource'
|
||||
import {
|
||||
normalizeSameOriginPublicUrl
|
||||
} from '@/utils/publicUrl'
|
||||
import type {
|
||||
AudioPlayTargetType
|
||||
} from '@/domain/museum'
|
||||
|
||||
export type AudioLanguage = 'zh-CN' | 'en-US'
|
||||
|
||||
export interface AudioPlayInfoRequest {
|
||||
targetType: AudioPlayTargetType
|
||||
targetId: string
|
||||
lang?: AudioLanguage
|
||||
}
|
||||
|
||||
export interface AudioPlayInfo {
|
||||
playable: boolean
|
||||
targetType: AudioPlayTargetType
|
||||
targetId: string | number
|
||||
lang: AudioLanguage
|
||||
narrationTier?: 'STANDARD' | 'EXTENDED'
|
||||
audioId?: string | number | null
|
||||
title?: string | null
|
||||
duration?: number | null
|
||||
format?: string | null
|
||||
playUrl?: string | null
|
||||
expiresAt?: string | null
|
||||
subtitleUrl?: string | null
|
||||
fallback?: boolean
|
||||
fallbackReason?: string | null
|
||||
reason?: string | null
|
||||
}
|
||||
|
||||
interface AudioPlayInfoResponse {
|
||||
code: number
|
||||
msg?: string
|
||||
data?: AudioPlayInfo
|
||||
}
|
||||
|
||||
export interface AudioPlayInfoRepository {
|
||||
getPlayInfo(request: AudioPlayInfoRequest): Promise<AudioPlayInfo>
|
||||
clearCache(lang?: AudioLanguage): void
|
||||
}
|
||||
|
||||
const reasonMessageMap: Record<string, string> = {
|
||||
NO_PUBLISHED_AUDIO: '当前语言暂无语音讲解',
|
||||
NO_GUIDE_STOP: '该展品暂未配置语音讲解',
|
||||
NO_GUIDE_CONTENT: '该目标暂无讲解内容',
|
||||
UNSUPPORTED_LANGUAGE: '不支持该语言',
|
||||
UNSUPPORTED_TARGET_TYPE: '该目标类型暂不支持播放',
|
||||
TARGET_NOT_FOUND: '该讲解音频暂不可用,当前提供图文讲解',
|
||||
SERVICE_ERROR: '语音服务暂不可用,请稍后重试'
|
||||
}
|
||||
|
||||
const parseJsonPayload = <T>(payload: unknown): T => {
|
||||
if (typeof payload === 'string') {
|
||||
return JSON.parse(payload) as T
|
||||
}
|
||||
|
||||
return payload as T
|
||||
}
|
||||
|
||||
const requestJson = <T>(url: string): Promise<T> => new Promise((resolve, reject) => {
|
||||
uni.request({
|
||||
url,
|
||||
method: 'GET',
|
||||
success: (response) => {
|
||||
const statusCode = Number(response.statusCode || 0)
|
||||
if (statusCode < 200 || statusCode >= 300) {
|
||||
reject(new Error(`语音播放解析接口请求失败: ${statusCode}`))
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
resolve(parseJsonPayload<T>(response.data))
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
},
|
||||
fail: reject
|
||||
})
|
||||
})
|
||||
|
||||
const normalizeBaseUrl = (baseUrl: string) => baseUrl.replace(/\/+$/, '')
|
||||
|
||||
const resolveAudioApiBaseUrl = () => {
|
||||
const baseUrl = normalizeBaseUrl(dataSourceConfig.audioApiBaseUrl)
|
||||
return baseUrl.endsWith('/app-api') ? baseUrl : `${baseUrl}/app-api`
|
||||
}
|
||||
|
||||
const audioKey = ({ targetType, targetId, lang }: Required<AudioPlayInfoRequest>) => (
|
||||
`${targetType}:${targetId}:${lang}`
|
||||
)
|
||||
|
||||
const isExpired = (expiresAt?: string | null) => (
|
||||
Boolean(expiresAt) && new Date(expiresAt as string).getTime() <= Date.now()
|
||||
)
|
||||
|
||||
export const audioReasonToText = (reason?: string | null) => (
|
||||
reason ? reasonMessageMap[reason] || '该讲解暂无可播放音频' : '该讲解暂无可播放音频'
|
||||
)
|
||||
|
||||
export class DefaultAudioPlayInfoRepository implements AudioPlayInfoRepository {
|
||||
private readonly cache = new Map<string, AudioPlayInfo>()
|
||||
|
||||
async getPlayInfo(request: AudioPlayInfoRequest): Promise<AudioPlayInfo> {
|
||||
const normalizedRequest: Required<AudioPlayInfoRequest> = {
|
||||
targetType: request.targetType,
|
||||
targetId: request.targetId,
|
||||
lang: request.lang || (dataSourceConfig.audioLanguage as AudioLanguage)
|
||||
}
|
||||
const key = audioKey(normalizedRequest)
|
||||
const cached = this.cache.get(key)
|
||||
|
||||
if (cached && !isExpired(cached.expiresAt)) {
|
||||
return cached
|
||||
}
|
||||
|
||||
const params = new URLSearchParams({
|
||||
targetType: normalizedRequest.targetType,
|
||||
targetId: normalizedRequest.targetId,
|
||||
lang: normalizedRequest.lang
|
||||
})
|
||||
const url = `${resolveAudioApiBaseUrl()}/gis/guide/audio/play-info?${params.toString()}`
|
||||
const response = await requestJson<AudioPlayInfoResponse>(url)
|
||||
|
||||
if (response.code !== 0 || !response.data) {
|
||||
throw new Error(response.msg || '语音播放解析失败')
|
||||
}
|
||||
|
||||
response.data.playUrl = normalizeSameOriginPublicUrl(response.data.playUrl)
|
||||
response.data.subtitleUrl = normalizeSameOriginPublicUrl(response.data.subtitleUrl)
|
||||
|
||||
if (response.data.playable || response.data.reason !== 'TARGET_NOT_FOUND') {
|
||||
this.cache.set(key, response.data)
|
||||
}
|
||||
return response.data
|
||||
}
|
||||
|
||||
clearCache(lang?: AudioLanguage) {
|
||||
if (!lang) {
|
||||
this.cache.clear()
|
||||
return
|
||||
}
|
||||
|
||||
Array.from(this.cache.keys()).forEach((key) => {
|
||||
if (key.endsWith(`:${lang}`)) {
|
||||
this.cache.delete(key)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const audioPlayInfoRepository = new DefaultAudioPlayInfoRepository()
|
||||
Reference in New Issue
Block a user