feat: 临时改造讲解页方案 B 链路
接入展厅列表、展厅讲解点分组生成临时业务单元,并迁移讲解详情播放正文到新接口链路。
This commit is contained in:
@@ -7,6 +7,11 @@ import {
|
||||
import type {
|
||||
AudioPlayTargetType
|
||||
} from '@/domain/museum'
|
||||
import {
|
||||
toGuideStopInfo,
|
||||
type BackendGuideStopInfo,
|
||||
type GuideStopInfo
|
||||
} from '@/data/adapters/guideStopInfoAdapter'
|
||||
|
||||
export type AudioLanguage = 'zh-CN' | 'en-US'
|
||||
|
||||
@@ -16,6 +21,18 @@ export interface AudioPlayInfoRequest {
|
||||
lang?: AudioLanguage
|
||||
}
|
||||
|
||||
export interface GuideStopInfoRequest {
|
||||
targetType: AudioPlayTargetType
|
||||
targetId: string
|
||||
lang?: AudioLanguage
|
||||
}
|
||||
|
||||
interface GuideStopInfoResponse {
|
||||
code: number
|
||||
msg?: string
|
||||
data?: BackendGuideStopInfo
|
||||
}
|
||||
|
||||
export interface AudioPlayInfo {
|
||||
playable: boolean
|
||||
targetType: AudioPlayTargetType
|
||||
@@ -67,6 +84,7 @@ interface AudioTextInfoResponse {
|
||||
}
|
||||
|
||||
export interface AudioPlayInfoRepository {
|
||||
getStopInfo(request: GuideStopInfoRequest): Promise<GuideStopInfo>
|
||||
getPlayInfo(request: AudioPlayInfoRequest): Promise<AudioPlayInfo>
|
||||
getTextInfo(request: AudioTextInfoRequest): Promise<AudioTextInfo>
|
||||
clearCache(lang?: AudioLanguage): void
|
||||
@@ -76,6 +94,7 @@ const reasonMessageMap: Record<string, string> = {
|
||||
NO_PUBLISHED_AUDIO: '当前语言暂无语音讲解',
|
||||
NO_GUIDE_STOP: '该展品暂未配置语音讲解',
|
||||
NO_GUIDE_CONTENT: '该目标暂无讲解内容',
|
||||
NO_TEXT: '当前语言暂无讲解词',
|
||||
UNSUPPORTED_LANGUAGE: '不支持该语言',
|
||||
UNSUPPORTED_TARGET_TYPE: '该目标类型暂不支持播放',
|
||||
TARGET_NOT_FOUND: '该讲解音频暂不可用,当前提供图文讲解',
|
||||
@@ -112,6 +131,8 @@ const requestJson = <T>(url: string): Promise<T> => new Promise((resolve, reject
|
||||
})
|
||||
})
|
||||
|
||||
let guideAudioApiUnavailable = false
|
||||
|
||||
const normalizeBaseUrl = (baseUrl: string) => baseUrl.replace(/\/+$/, '')
|
||||
|
||||
const resolveAudioApiBaseUrl = () => {
|
||||
@@ -123,6 +144,10 @@ const audioKey = ({ targetType, targetId, lang }: Required<AudioPlayInfoRequest>
|
||||
`${targetType}:${targetId}:${lang}`
|
||||
)
|
||||
|
||||
const stopInfoKey = ({ targetType, targetId, lang }: Required<GuideStopInfoRequest>) => (
|
||||
`${targetType}:${targetId}:${lang}`
|
||||
)
|
||||
|
||||
const audioTextKey = ({ targetType, targetId, lang }: Required<AudioTextInfoRequest>) => (
|
||||
`${targetType}:${targetId}:${lang}`
|
||||
)
|
||||
@@ -131,14 +156,60 @@ const isExpired = (expiresAt?: string | null) => (
|
||||
Boolean(expiresAt) && new Date(expiresAt as string).getTime() <= Date.now()
|
||||
)
|
||||
|
||||
const isMissingRouteResponse = (response: { code: number; msg?: string }) => (
|
||||
response.code === 404 && /请求地址不存在/.test(response.msg || '')
|
||||
)
|
||||
|
||||
const markGuideAudioApiUnavailable = (response: { code: number; msg?: string }) => {
|
||||
if (isMissingRouteResponse(response)) {
|
||||
guideAudioApiUnavailable = true
|
||||
}
|
||||
}
|
||||
|
||||
export const audioReasonToText = (reason?: string | null) => (
|
||||
reason ? reasonMessageMap[reason] || '该讲解暂无可播放音频' : '该讲解暂无可播放音频'
|
||||
)
|
||||
|
||||
export class DefaultAudioPlayInfoRepository implements AudioPlayInfoRepository {
|
||||
private readonly stopInfoCache = new Map<string, GuideStopInfo>()
|
||||
private readonly cache = new Map<string, AudioPlayInfo>()
|
||||
private readonly textCache = new Map<string, AudioTextInfo>()
|
||||
|
||||
async getStopInfo(request: GuideStopInfoRequest): Promise<GuideStopInfo> {
|
||||
if (guideAudioApiUnavailable) {
|
||||
throw new Error('讲解展示信息接口暂不可用')
|
||||
}
|
||||
|
||||
const normalizedRequest: Required<GuideStopInfoRequest> = {
|
||||
targetType: request.targetType,
|
||||
targetId: request.targetId,
|
||||
lang: request.lang || (dataSourceConfig.audioLanguage as AudioLanguage)
|
||||
}
|
||||
const key = stopInfoKey(normalizedRequest)
|
||||
const cached = this.stopInfoCache.get(key)
|
||||
|
||||
if (cached) {
|
||||
return cached
|
||||
}
|
||||
|
||||
const params = new URLSearchParams({
|
||||
targetType: normalizedRequest.targetType,
|
||||
targetId: normalizedRequest.targetId,
|
||||
lang: normalizedRequest.lang
|
||||
})
|
||||
const url = `${resolveAudioApiBaseUrl()}/gis/guide/stop/info?${params.toString()}`
|
||||
const response = await requestJson<GuideStopInfoResponse>(url)
|
||||
|
||||
if (response.code !== 0 || !response.data) {
|
||||
markGuideAudioApiUnavailable(response)
|
||||
throw new Error(response.msg || '讲解点展示信息加载失败')
|
||||
}
|
||||
|
||||
const stopInfo = toGuideStopInfo(response.data, normalizedRequest)
|
||||
this.stopInfoCache.set(key, stopInfo)
|
||||
return stopInfo
|
||||
}
|
||||
|
||||
async getPlayInfo(request: AudioPlayInfoRequest): Promise<AudioPlayInfo> {
|
||||
const normalizedRequest: Required<AudioPlayInfoRequest> = {
|
||||
targetType: request.targetType,
|
||||
@@ -146,6 +217,16 @@ export class DefaultAudioPlayInfoRepository implements AudioPlayInfoRepository {
|
||||
lang: request.lang || (dataSourceConfig.audioLanguage as AudioLanguage)
|
||||
}
|
||||
const key = audioKey(normalizedRequest)
|
||||
if (guideAudioApiUnavailable) {
|
||||
return {
|
||||
playable: false,
|
||||
targetType: normalizedRequest.targetType,
|
||||
targetId: normalizedRequest.targetId,
|
||||
lang: normalizedRequest.lang,
|
||||
reason: 'SERVICE_ERROR'
|
||||
}
|
||||
}
|
||||
|
||||
const cached = this.cache.get(key)
|
||||
|
||||
if (cached && !isExpired(cached.expiresAt)) {
|
||||
@@ -161,6 +242,7 @@ export class DefaultAudioPlayInfoRepository implements AudioPlayInfoRepository {
|
||||
const response = await requestJson<AudioPlayInfoResponse>(url)
|
||||
|
||||
if (response.code !== 0 || !response.data) {
|
||||
markGuideAudioApiUnavailable(response)
|
||||
throw new Error(response.msg || '语音播放解析失败')
|
||||
}
|
||||
|
||||
@@ -180,6 +262,16 @@ export class DefaultAudioPlayInfoRepository implements AudioPlayInfoRepository {
|
||||
lang: request.lang || (dataSourceConfig.audioLanguage as AudioLanguage)
|
||||
}
|
||||
const key = audioTextKey(normalizedRequest)
|
||||
if (guideAudioApiUnavailable) {
|
||||
return {
|
||||
available: false,
|
||||
targetType: normalizedRequest.targetType,
|
||||
targetId: normalizedRequest.targetId,
|
||||
lang: normalizedRequest.lang,
|
||||
reason: 'SERVICE_ERROR'
|
||||
}
|
||||
}
|
||||
|
||||
const cached = this.textCache.get(key)
|
||||
|
||||
if (cached) {
|
||||
@@ -195,6 +287,7 @@ export class DefaultAudioPlayInfoRepository implements AudioPlayInfoRepository {
|
||||
const response = await requestJson<AudioTextInfoResponse>(url)
|
||||
|
||||
if (response.code !== 0 || !response.data) {
|
||||
markGuideAudioApiUnavailable(response)
|
||||
throw new Error(response.msg || '讲解词正文解析失败')
|
||||
}
|
||||
|
||||
@@ -207,11 +300,18 @@ export class DefaultAudioPlayInfoRepository implements AudioPlayInfoRepository {
|
||||
|
||||
clearCache(lang?: AudioLanguage) {
|
||||
if (!lang) {
|
||||
this.stopInfoCache.clear()
|
||||
this.cache.clear()
|
||||
this.textCache.clear()
|
||||
guideAudioApiUnavailable = false
|
||||
return
|
||||
}
|
||||
|
||||
Array.from(this.stopInfoCache.keys()).forEach((key) => {
|
||||
if (key.endsWith(`:${lang}`)) {
|
||||
this.stopInfoCache.delete(key)
|
||||
}
|
||||
})
|
||||
Array.from(this.cache.keys()).forEach((key) => {
|
||||
if (key.endsWith(`:${lang}`)) {
|
||||
this.cache.delete(key)
|
||||
|
||||
Reference in New Issue
Block a user