此时应保留现有业务 SPI 思路,但把执行载体迁移到独立进程、容器或远程方案。结语 PF4J 解决“插件怎么被发现和管理”,Spring 子容器解决“插件里的业务对象怎么被组织”,定制 ClassLoader 解决“主程序和插件如何共享同一个契约类型”。
import type { AvatarConfig } from '../types'import { generateContainerId, getPromiseState } from '../utils'import { SDK_CONFIG, APP_CONFIG } from '../constants'interface AvatarCallbacks { onSubtitleOn: (text: string) => void onSubtitleOff: () => void onStateChange: (state: string) => void onVoiceStateChange?: (status: string) => void}class AvatarService { private containerId: string constructor() { this.containerId = generateContainerId() } getContainerId(): string { return this.containerId } async connect(config: AvatarConfig, callbacks: AvatarCallbacks): Promise { const { appId, appSecret } = config const { onSubtitleOn, onSubtitleOff, onStateChange, onVoiceStateChange } = callbacks // 检查容器是否存在 const containerEl = document.getElementById(this.containerId) if (!containerEl) { console.error(`[AvatarService] 容器 #${this.containerId} 不存在!`) throw new Error(`容器 #${this.containerId} 不存在,请确保 AvatarRender 组件已渲染`) } // 构建网关URL const url = new URL(SDK_CONFIG.GATEWAY_URL) url.searchParams.append('data_source', SDK_CONFIG.DATA_SOURCE) url.searchParams.append('custom_id', SDK_CONFIG.CUSTOM_ID) // Promise管理连接状态 let resolve: (value: boolean) => void let reject: (reason?: any) => void const connectPromise = new Promise((res, rej) => { resolve = res reject = rej }) // SDK构造选项 const constructorOptions = { containerId: `#${this.containerId}`, appId, appSecret, enableDebugger: false, gatewayServer: url.toString(), onProxyWidgetEvent: (event: any) => { console.log('SDK事件:', event) }, onStateChange, onMessage: async (error: any) => { const state = await getPromiseState(connectPromise) const plainError = new Error(error.message) if (state === 'pending') { reject(plainError) } }, onVoiceStateChange: (status: string) => { // 当状态为 'end' 时,表示数字人停止说话 if (status.includes('end')) { onVoiceStateChange?.(status) } }, } // 创建SDK实例 const avatar = new window.XmovAvatar(constructorOptions) // 等待初始化 await new Promise(resolve => { setTimeout(resolve, APP_CONFIG.AVATAR_INIT_TIMEOUT) }) // 初始化SDK await avatar.init({ onDownloadProgress: (progress: number) => { console.log(`初始化进度: ${progress}%`) if (progress >= 100) { resolve(true) } }, onClose: () => { onStateChange('') console.log('SDK连接关闭') } }) // 等待连接完成(设置超时避免永久挂起) const connectTimeout = new Promise((_, rej) => { setTimeout(() => rej(new Error('SDK连接超时')), 15000) }) try { await Promise.race([connectPromise, connectTimeout]) console.log('[AvatarService] 连接成功') } catch (error) { console.warn('SDK连接等待结束:', error) // 超时不抛错,可能已经初始化完成但没触发100%回调 } // 连接成功后,注入CSS隐藏SDK字幕 this.injectSubtitleKiller() return avatar } private injectSubtitleKiller(): void { // 注入全局CSS,隐藏SDK自带的字幕元素 console.log('[AvatarService] 字幕隐藏已禁用,排查数字人渲染问题') return } disconnect(avatar: any): void { if (!avatar) return // 移除注入的CSS const killerStyle = document.getElementById('xmov-subtitle-killer') if (killerStyle) killerStyle.remove() try { avatar.stop() avatar.destroy() } catch (error) { console.error('断开连接时出错:', error) } }}export const avatarService = new AvatarService()关键配置项说明:enableDebugger: false:生产环境关闭调试模式,避免控制台输出过多 SDK 内部日志gatewayServer:魔珐星云网关服务器地址,支持自定义数据源,是参数流架构的核心入口onDownloadProgress:初始化进度回调,100% 时表示资源加载完成,连接就绪onVoiceStateChange:语音状态回调,当数字人说话结束时触发,用于流式播报的段落控制getPromiseState:检查连接 Promise 的状态,避免在已 resolve 的情况下重复 reject4.4 llm.ts - 大语言模型服务基于 OpenAI SDK 封装,支持火山引擎豆包大模型。提供普通对话和流式对话两种模式,流式模式支持实时逐字输出。
作为一款中式探险题材游戏,《超自然行动组》2025年11月曾邀请沿河土家族自治县傩面具雕刻第七代传人杨云霞,共同打造傩戏联动时装,让非遗技艺以数字化形式走进年轻群体。
公司新闻 三星电子、SK集团与美国大型科技公司签署价值9500亿美元的交易协议 周末,韩国宣布推出总规模达9500亿美元的新一轮人工智能合作计划,参与方包括三星电子、SK集团以及多家美国科技企业。