This commit is contained in:
@@ -2,14 +2,17 @@ import {
|
||||
dataSourceConfig
|
||||
} from '@/config/dataSource'
|
||||
import {
|
||||
normalizeSameOriginPublicUrl
|
||||
} from '@/utils/publicUrl'
|
||||
import type {
|
||||
AudioPlayTargetType
|
||||
type AudioPlayTargetType
|
||||
} from '@/domain/museum'
|
||||
import {
|
||||
toGuideAudioPlayInfo,
|
||||
toGuideAudioTextInfo,
|
||||
toGuideStopInfo,
|
||||
type BackendAudioPlayInfo,
|
||||
type BackendAudioTextInfo,
|
||||
type BackendGuideStopInfo,
|
||||
type GuideAudioPlayInfo,
|
||||
type GuideAudioTextInfo,
|
||||
type GuideStopInfo
|
||||
} from '@/data/adapters/guideStopInfoAdapter'
|
||||
|
||||
@@ -19,6 +22,7 @@ export interface AudioPlayInfoRequest {
|
||||
targetType: AudioPlayTargetType
|
||||
targetId: string
|
||||
lang?: AudioLanguage
|
||||
refresh?: boolean
|
||||
}
|
||||
|
||||
export interface GuideStopInfoRequest {
|
||||
@@ -33,29 +37,12 @@ interface GuideStopInfoResponse {
|
||||
data?: BackendGuideStopInfo
|
||||
}
|
||||
|
||||
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
|
||||
hasText?: boolean
|
||||
fallback?: boolean
|
||||
fallbackReason?: string | null
|
||||
reason?: string | null
|
||||
}
|
||||
export interface AudioPlayInfo extends GuideAudioPlayInfo {}
|
||||
|
||||
interface AudioPlayInfoResponse {
|
||||
code: number
|
||||
msg?: string
|
||||
data?: AudioPlayInfo
|
||||
data?: BackendAudioPlayInfo
|
||||
}
|
||||
|
||||
export interface AudioTextInfoRequest {
|
||||
@@ -64,23 +51,12 @@ export interface AudioTextInfoRequest {
|
||||
lang?: AudioLanguage
|
||||
}
|
||||
|
||||
export interface AudioTextInfo {
|
||||
available: boolean
|
||||
targetType: AudioPlayTargetType
|
||||
targetId: string | number
|
||||
lang: AudioLanguage
|
||||
narrationTier?: 'STANDARD' | 'EXTENDED'
|
||||
title?: string | null
|
||||
text?: string | null
|
||||
textLength?: number | null
|
||||
textHash?: string | null
|
||||
reason?: string | null
|
||||
}
|
||||
export interface AudioTextInfo extends GuideAudioTextInfo {}
|
||||
|
||||
interface AudioTextInfoResponse {
|
||||
code: number
|
||||
msg?: string
|
||||
data?: AudioTextInfo
|
||||
data?: BackendAudioTextInfo
|
||||
}
|
||||
|
||||
export interface AudioPlayInfoRepository {
|
||||
@@ -143,19 +119,23 @@ const normalizeBaseUrl = (baseUrl: string) => {
|
||||
}
|
||||
|
||||
const resolveAudioApiBaseUrl = () => {
|
||||
const baseUrl = normalizeBaseUrl(dataSourceConfig.audioApiBaseUrl)
|
||||
const baseUrl = normalizeBaseUrl(dataSourceConfig.apiBaseUrl)
|
||||
return baseUrl.endsWith('/app-api') ? baseUrl : `${baseUrl}/app-api`
|
||||
}
|
||||
|
||||
const audioKey = ({ targetType, targetId, lang }: Required<AudioPlayInfoRequest>) => (
|
||||
type RequiredAudioPlayInfoRequest = Required<Omit<AudioPlayInfoRequest, 'refresh'>>
|
||||
type RequiredGuideStopInfoRequest = Required<GuideStopInfoRequest>
|
||||
type RequiredAudioTextInfoRequest = Required<AudioTextInfoRequest>
|
||||
|
||||
const audioKey = ({ targetType, targetId, lang }: RequiredAudioPlayInfoRequest) => (
|
||||
`${targetType}:${targetId}:${lang}`
|
||||
)
|
||||
|
||||
const stopInfoKey = ({ targetType, targetId, lang }: Required<GuideStopInfoRequest>) => (
|
||||
const stopInfoKey = ({ targetType, targetId, lang }: RequiredGuideStopInfoRequest) => (
|
||||
`${targetType}:${targetId}:${lang}`
|
||||
)
|
||||
|
||||
const audioTextKey = ({ targetType, targetId, lang }: Required<AudioTextInfoRequest>) => (
|
||||
const audioTextKey = ({ targetType, targetId, lang }: RequiredAudioTextInfoRequest) => (
|
||||
`${targetType}:${targetId}:${lang}`
|
||||
)
|
||||
|
||||
@@ -190,7 +170,7 @@ export class DefaultAudioPlayInfoRepository implements AudioPlayInfoRepository {
|
||||
throw new Error('讲解展示信息接口暂不可用')
|
||||
}
|
||||
|
||||
const normalizedRequest: Required<GuideStopInfoRequest> = {
|
||||
const normalizedRequest: RequiredGuideStopInfoRequest = {
|
||||
targetType: request.targetType,
|
||||
targetId: request.targetId,
|
||||
lang: request.lang || (dataSourceConfig.audioLanguage as AudioLanguage)
|
||||
@@ -221,7 +201,7 @@ export class DefaultAudioPlayInfoRepository implements AudioPlayInfoRepository {
|
||||
}
|
||||
|
||||
async getPlayInfo(request: AudioPlayInfoRequest): Promise<AudioPlayInfo> {
|
||||
const normalizedRequest: Required<AudioPlayInfoRequest> = {
|
||||
const normalizedRequest: RequiredAudioPlayInfoRequest = {
|
||||
targetType: request.targetType,
|
||||
targetId: request.targetId,
|
||||
lang: request.lang || (dataSourceConfig.audioLanguage as AudioLanguage)
|
||||
@@ -233,13 +213,15 @@ export class DefaultAudioPlayInfoRepository implements AudioPlayInfoRepository {
|
||||
targetType: normalizedRequest.targetType,
|
||||
targetId: normalizedRequest.targetId,
|
||||
lang: normalizedRequest.lang,
|
||||
hasText: false,
|
||||
fallback: false,
|
||||
reason: 'SERVICE_ERROR'
|
||||
}
|
||||
}
|
||||
|
||||
const cached = this.cache.get(key)
|
||||
|
||||
if (cached && !isExpired(cached.expiresAt)) {
|
||||
if (!request.refresh && cached && !isExpired(cached.expiresAt)) {
|
||||
return cached
|
||||
}
|
||||
|
||||
@@ -256,17 +238,16 @@ export class DefaultAudioPlayInfoRepository implements AudioPlayInfoRepository {
|
||||
throw new Error(response.msg || '语音播放解析失败')
|
||||
}
|
||||
|
||||
response.data.playUrl = normalizeSameOriginPublicUrl(response.data.playUrl)
|
||||
response.data.subtitleUrl = normalizeSameOriginPublicUrl(response.data.subtitleUrl)
|
||||
const playInfo = toGuideAudioPlayInfo(response.data, normalizedRequest)
|
||||
|
||||
if (response.data.playable || response.data.reason !== 'TARGET_NOT_FOUND') {
|
||||
this.cache.set(key, response.data)
|
||||
if (playInfo.playable || playInfo.reason !== 'TARGET_NOT_FOUND') {
|
||||
this.cache.set(key, playInfo)
|
||||
}
|
||||
return response.data
|
||||
return playInfo
|
||||
}
|
||||
|
||||
async getTextInfo(request: AudioTextInfoRequest): Promise<AudioTextInfo> {
|
||||
const normalizedRequest: Required<AudioTextInfoRequest> = {
|
||||
const normalizedRequest: RequiredAudioTextInfoRequest = {
|
||||
targetType: request.targetType,
|
||||
targetId: request.targetId,
|
||||
lang: request.lang || (dataSourceConfig.audioLanguage as AudioLanguage)
|
||||
@@ -301,11 +282,13 @@ export class DefaultAudioPlayInfoRepository implements AudioPlayInfoRepository {
|
||||
throw new Error(response.msg || '讲解词正文解析失败')
|
||||
}
|
||||
|
||||
if (response.data.available) {
|
||||
this.textCache.set(key, response.data)
|
||||
const textInfo = toGuideAudioTextInfo(response.data, normalizedRequest)
|
||||
|
||||
if (textInfo.available) {
|
||||
this.textCache.set(key, textInfo)
|
||||
}
|
||||
|
||||
return response.data
|
||||
return textInfo
|
||||
}
|
||||
|
||||
clearCache(lang?: AudioLanguage) {
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
type MuseumContentRepository
|
||||
} from '@/repositories/MuseumContentRepository'
|
||||
import {
|
||||
staticMuseumContentProvider,
|
||||
explainContentProvider,
|
||||
type ExplainContentProvider
|
||||
} from '@/data/providers/staticMuseumContentProvider'
|
||||
|
||||
@@ -38,7 +38,7 @@ export class DefaultExplainRepository implements ExplainRepository {
|
||||
constructor(
|
||||
private readonly content: MuseumContentRepository = museumContentRepository,
|
||||
private readonly media: MediaRepository = mediaRepository,
|
||||
private readonly explainContent: ExplainContentProvider = staticMuseumContentProvider
|
||||
private readonly explainContent: ExplainContentProvider = explainContentProvider
|
||||
) {}
|
||||
|
||||
listExplainExhibits() {
|
||||
@@ -54,11 +54,12 @@ export class DefaultExplainRepository implements ExplainRepository {
|
||||
}
|
||||
|
||||
listHalls() {
|
||||
return this.content.listHalls()
|
||||
return this.explainContent.listHalls()
|
||||
}
|
||||
|
||||
getHallById(id: string) {
|
||||
return this.content.getHallById(id)
|
||||
async getHallById(id: string) {
|
||||
const halls = await this.explainContent.listHalls()
|
||||
return halls.find((hall) => hall.id === id || hall.poiId === id) || null
|
||||
}
|
||||
|
||||
async listGuideStopsByHall(hallId: string) {
|
||||
|
||||
@@ -1,39 +1,8 @@
|
||||
import {
|
||||
dataSourceConfig
|
||||
} from '@/config/dataSource'
|
||||
import type {
|
||||
AudioPlayTargetType
|
||||
} from '@/domain/museum'
|
||||
import type {
|
||||
AudioLanguage,
|
||||
AudioPlayInfoRequest
|
||||
} from '@/repositories/AudioPlayInfoRepository'
|
||||
|
||||
interface CommonResult<T> {
|
||||
code: number
|
||||
msg?: string
|
||||
data?: T
|
||||
}
|
||||
|
||||
interface PublishedGuideContent {
|
||||
id?: string | number | null
|
||||
title?: string | null
|
||||
targetType?: string | null
|
||||
targetId?: string | number | null
|
||||
}
|
||||
|
||||
interface PublishedExhibitAudioSummary {
|
||||
id?: string | number | null
|
||||
name?: string | null
|
||||
hasAudio?: boolean
|
||||
supportedLanguages?: string[]
|
||||
audioStatus?: string | null
|
||||
playTargetType?: string | null
|
||||
playTargetId?: string | number | null
|
||||
audioDuration?: number | null
|
||||
guideContents?: PublishedGuideContent[]
|
||||
}
|
||||
|
||||
export interface PublishedExhibitAudioResolution {
|
||||
targets: AudioPlayInfoRequest[]
|
||||
}
|
||||
@@ -42,120 +11,10 @@ export interface PublishedExhibitAudioRepository {
|
||||
resolveByExhibitName(exhibitName: string, lang?: AudioLanguage): Promise<PublishedExhibitAudioResolution | null>
|
||||
}
|
||||
|
||||
const stringifyId = (value: string | number | null | undefined) => (
|
||||
value === null || typeof value === 'undefined' ? '' : String(value)
|
||||
)
|
||||
|
||||
const normalizeBaseUrl = (baseUrl: string) => baseUrl.replace(/\/+$/, '')
|
||||
|
||||
const resolveAppApiBaseUrl = () => {
|
||||
const baseUrl = normalizeBaseUrl(dataSourceConfig.audioApiBaseUrl || dataSourceConfig.apiBaseUrl)
|
||||
return baseUrl.endsWith('/app-api') ? baseUrl : `${baseUrl}/app-api`
|
||||
}
|
||||
|
||||
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',
|
||||
timeout: 5000,
|
||||
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 normalizeAudioTargetType = (targetType: string | null | undefined): AudioPlayTargetType | null => {
|
||||
const normalizedType = targetType?.toUpperCase()
|
||||
return normalizedType === 'STOP' || normalizedType === 'ITEM'
|
||||
? normalizedType
|
||||
: null
|
||||
}
|
||||
|
||||
const addTarget = (
|
||||
targets: AudioPlayInfoRequest[],
|
||||
targetType: string | null | undefined,
|
||||
targetId: string | number | null | undefined,
|
||||
lang: AudioLanguage
|
||||
) => {
|
||||
const normalizedType = normalizeAudioTargetType(targetType)
|
||||
const normalizedId = stringifyId(targetId)
|
||||
if (!normalizedType || !normalizedId) return
|
||||
|
||||
const exists = targets.some((target) => (
|
||||
target.targetType === normalizedType && target.targetId === normalizedId && target.lang === lang
|
||||
))
|
||||
if (exists) return
|
||||
|
||||
targets.push({
|
||||
targetType: normalizedType,
|
||||
targetId: normalizedId,
|
||||
lang
|
||||
})
|
||||
}
|
||||
|
||||
const buildResolution = (
|
||||
exhibit: PublishedExhibitAudioSummary,
|
||||
lang: AudioLanguage
|
||||
): PublishedExhibitAudioResolution => {
|
||||
const targets: AudioPlayInfoRequest[] = []
|
||||
|
||||
addTarget(targets, exhibit.playTargetType, exhibit.playTargetId, lang)
|
||||
exhibit.guideContents?.forEach((guide) => {
|
||||
addTarget(targets, guide.targetType, guide.targetId, lang)
|
||||
})
|
||||
addTarget(targets, 'ITEM', exhibit.id, lang)
|
||||
|
||||
return { targets }
|
||||
}
|
||||
|
||||
export class DefaultPublishedExhibitAudioRepository implements PublishedExhibitAudioRepository {
|
||||
async resolveByExhibitName(exhibitName: string, lang: AudioLanguage = dataSourceConfig.audioLanguage as AudioLanguage) {
|
||||
const keyword = exhibitName.trim()
|
||||
if (!keyword) return null
|
||||
|
||||
const searchUrl = `${resolveAppApiBaseUrl()}/gis/exhibit/search?keyword=${encodeURIComponent(keyword)}`
|
||||
const searchResponse = await requestJson<CommonResult<PublishedExhibitAudioSummary[]>>(searchUrl)
|
||||
if (searchResponse.code !== 0 || !searchResponse.data?.length) return null
|
||||
|
||||
const exactMatches = searchResponse.data.filter((item) => item.name === keyword)
|
||||
const matched = (
|
||||
exactMatches.find((item) => item.hasAudio && item.supportedLanguages?.includes(lang))
|
||||
|| exactMatches.find((item) => item.hasAudio)
|
||||
|| exactMatches[0]
|
||||
|| searchResponse.data[0]
|
||||
)
|
||||
const exhibitId = stringifyId(matched.id)
|
||||
let detail = matched
|
||||
|
||||
if (exhibitId) {
|
||||
const detailUrl = `${resolveAppApiBaseUrl()}/gis/exhibit/get?id=${encodeURIComponent(exhibitId)}`
|
||||
const detailResponse = await requestJson<CommonResult<PublishedExhibitAudioSummary>>(detailUrl)
|
||||
if (detailResponse.code === 0 && detailResponse.data) {
|
||||
detail = detailResponse.data
|
||||
}
|
||||
}
|
||||
|
||||
return buildResolution(detail, lang)
|
||||
export class DisabledPublishedExhibitAudioRepository implements PublishedExhibitAudioRepository {
|
||||
async resolveByExhibitName() {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export const publishedExhibitAudioRepository = new DefaultPublishedExhibitAudioRepository()
|
||||
export const publishedExhibitAudioRepository = new DisabledPublishedExhibitAudioRepository()
|
||||
|
||||
Reference in New Issue
Block a user