本文将深入剖析魔珐星云数字人技术在智慧文旅场景中的完整技术实现,从架构设计到核心代码,带你领略AI数字人如何赋能传统文旅行业。
一、项目背景与价值
随着文旅产业的数字化转型,传统景区面临着诸多痛点:
- 游客咨询压力大,人工客服成本高
- 景区信息展示形式单一,缺乏互动性
- 客流监控数据分散,难以实时决策
- 导览服务标准化程度低,体验参差不齐
魔珐星云数字人技术为这些问题提供了创新解决方案。本项目构建了一个完整的智慧文旅数字人体验平台,实现了:
✅ 智能导览 - 数字人实时语音交互,提供个性化游览建议
✅ 实时监控 - 景区客流可视化,拥挤度智能预警
✅ 沉浸体验 - 3D全息展示舱,科幻风格大屏设计
✅ 数据联动 - 景点信息、地图导航、语音播报一体化
智慧文旅数字人体验平台
二、技术架构全景图
2.1 整体架构设计

2.2 技术栈选型
| 层级 | 技术选型 | 选型理由 |
|---|---|---|
| 前端框架 | Vue 3 + Composition API | 响应式数据流,适合实时大屏场景 |
| 状态管理 | Pinia | 轻量级,TypeScript友好,模块化设计 |
| UI框架 | Tailwind CSS 4.0 | 原子化CSS,快速构建科幻风格UI |
| 构建工具 | Vite 8.0 | 极速HMR,开发体验优秀 |
| 后端框架 | Python Flask | 轻量级,适合中小型API服务 |
| 数据库 | MySQL 8.0 | 成熟稳定,支持复杂查询 |
| 数字人SDK | 魔珐星云 XmovAvatar | 高质量3D渲染,云端推理 |
| AI对话 | Fay数字人 | 开源方案,支持多模态交互 |
| 地图服务 | 高德地图 + MCP | 国内精准,MCP协议标准化 |
三、魔珐星云SDK集成详解
3.1 SDK引入与初始化
魔珐星云提供了轻量级Web SDK,通过CDN方式引入:
html
<!-- index.html -->
<script src="https://media.xingyun3d.com/xingyun3d/general/litesdk/xmovAvatar@latest.js"></script>
核心初始化代码 (位于 src/stores/avatar.js):
javascript
import { defineStore } from 'pinia'
export const useAvatarStore = defineStore('avatar', () => {
const sdkInstance = ref(null)
const isReady = ref(false)
const voiceStatus = ref('idle')
// 初始化魔珐星云SDK
const initSDK = async () => {
// 构建会话URL(包含数据源和自定义ID)
const url = new URL('https://nebula-agent.xingyun3d.com/user/v1/ttsa/session')
url.searchParams.append('data_source', '2') // 数据源类型
url.searchParams.append('custom_id', 'demo') // 用户标识
// 创建SDK实例
const avatar = new window.XmovAvatar({
// 挂载容器(对应DOM中的#sdk元素)
containerId: '#sdk',
// 应用凭证(从环境变量读取)
appId: import.meta.env.VITE_XMOV_APP_ID,
appSecret: import.meta.env.VITE_XMOV_APP_SECRET,
// 网关服务器地址
gatewayServer: url.toString(),
// 状态变化回调
onStateChange(state) {
console.log('魔珐星云状态:', state)
if (state === 'online') {
isReady.value = true
console.log('✅ 数字人SDK就绪')
}
},
// 语音状态回调
onVoiceStateChange(status) {
voiceStatus.value = status
console.log('语音状态:', status) // speaking / idle
}
})
sdkInstance.value = avatar
return avatar
}
// 数字人语音播报
const speak = async (text, useVoice = true, useAnimation = true) => {
if (!isReady.value || !sdkInstance.value) {
console.warn('SDK未就绪')
return
}
await sdkInstance.value.speak({
text: text, // 播报文本
useVoice: useVoice, // 是否启用语音
useAnimation: useAnimation // 是否启用表情动画
})
}
// 销毁SDK实例
const destroySDK = () => {
if (sdkInstance.value) {
sdkInstance.value.destroy()
sdkInstance.value = null
isReady.value = false
}
}
return { initSDK, speak, destroySDK, isReady, voiceStatus }
})
3.2 环境变量配置
bash
# .env
VITE_XMOV_APP_ID=your_appid_here
VITE_XMOV_APP_SECRET=your_appsecret_here
获取凭证 :填入您的魔珐星云数字人
VITE_XMOV_APP_ID和VITE_XMOV_APP_SECRET。注:Xmov SDK 凭证的获取请进入官网 https://c.c1nd.cn/9C9WW 邀请码:
JHTA3EQSZP,注册后可免费试用。
安全提示:生产环境务必通过后端代理获取临时Token,避免前端暴露密钥。
3.3 全息展示舱实现
中央面板采用"全息展示舱"概念设计,核心代码位于 src/components/scenic/CenterPanel.vue:
js
<template>
<div class="center-panel">
<!-- 全息展示舱容器 -->
<div class="holographic-cabin">
<!-- SDK挂载点 -->
<div id="sdk" class="sdk-container"></div>
<!-- 控制面板 -->
<div class="control-panel">
<!-- 麦克风开关 -->
<button @click="toggleMic" :class="{ active: micEnabled }">
<MicIcon /> 麦克风
</button>
<!-- Fay服务控制 -->
<button @click="toggleFay" :class="{ active: fayRunning }">
<BotIcon /> Fay服务
</button>
<!-- Xmov渲染控制 -->
<button @click="toggleXmov" :class="{ active: xmovRunning }">
<SparklesIcon /> 数字人
</button>
</div>
</div>
</div>
</template>
<script setup>
import { useAvatarStore } from '@/stores/avatar'
import { useWebSocket } from '@vueuse/core'
const avatarStore = useAvatarStore()
// 初始化数字人SDK
onMounted(async () => {
await avatarStore.initSDK()
})
// 启动数字人渲染
const toggleXmov = async () => {
if (!xmovRunning.value) {
await avatarStore.initSDK()
xmovRunning.value = true
} else {
avatarStore.destroySDK()
xmovRunning.value = false
}
}
</script>
<style scoped>
.holographic-cabin {
/* 科幻风格边框 */
border: 2px solid rgba(0, 255, 255, 0.3);
border-radius: 20px;
box-shadow:
0 0 20px rgba(0, 255, 255, 0.2),
inset 0 0 30px rgba(0, 255, 255, 0.1);
/* 全息投影效果 */
background: linear-gradient(
135deg,
rgba(0, 20, 40, 0.9) 0%,
rgba(0, 40, 80, 0.8) 100%
);
}
.sdk-container {
width: 100%;
height: 500px;
/* SDK会自动填充此容器 */
}
</style>

四、Fay AI大脑集成与实时通信
4.1 Fay服务架构
Fay是一个开源的AI数字人项目,提供:
- 多模态交互:语音识别、自然语言理解、语音合成
- LLM引擎:支持ChatGPT、文心一言等大模型
- 工具调用:通过MCP协议调用外部工具(如地图导航)

4.2 WebSocket实时通信
前端通过WebSocket与Fay服务建立长连接,实时接收AI回复:
javascript
// src/components/scenic/CenterPanel.vue
import { useWebSocket } from '@vueuse/core'
// 建立WebSocket连接(Fay默认端口10002)
const { status: wsStatus, data: wsData, send: wsSend } = useWebSocket(
'ws://127.0.0.1:10002',
{
// 自动重连配置
autoReconnect: {
retries: 5, // 最大重试次数
delay: 3000 // 重试间隔
},
// 连接成功回调
onConnected() {
console.log('✅ Fay WebSocket 已成功连接')
// 发送初始化消息
wsSend(JSON.stringify({ Output: false }))
},
// 连接失败回调
onError(error) {
console.error('❌ WebSocket连接失败:', error)
}
}
)
// 监听Fay返回的消息
const messageBuffer = ref('')
watch(wsData, (newData) => {
try {
const msg = JSON.parse(newData)
// 处理文本消息
if (msg?.Data?.Key === 'text') {
messageBuffer.value += msg.Data.Value
// 消息结束标志
if (msg.Data.IsEnd === 1) {
// 调用魔珐星云播报
avatarStore.speak(messageBuffer.value, true, true)
messageBuffer.value = '' // 清空缓冲
}
}
} catch (e) {
console.error('消息解析失败:', e)
}
})
4.3 Fay API封装
javascript
// src/api/fay.js
import request from '@/utils/request'
const FAY_BASE_URL = 'http://127.0.0.1:5000'
// 开启Fay直播模式
export function startFayLive() {
return request.post(`${FAY_BASE_URL}/api/live/start`)
}
// 停止Fay直播
export function stopFayLive() {
return request.post(`${FAY_BASE_URL}/api/live/stop`)
}
// 发送文本消息给Fay
export function sendFayMessage(msg, username = 'User') {
return request.post(`${FAY_BASE_URL}/api/message`, {
msg: msg,
username: username
})
}
// 获取Fay运行状态
export function getFayStatus() {
return request.get(`${FAY_BASE_URL}/api/status`)
}
// 麦克风开关
export function toggleMicrophone(enabled) {
return request.post(`${FAY_BASE_URL}/api/microphone`, {
enabled: enabled
})
}
五、智慧文旅数据服务实现
5.1 数据库设计

景区信息表:
sql
CREATE TABLE scenic_info (
id INT AUTO_INCREMENT PRIMARY KEY,
scenic_name VARCHAR(100) COMMENT '景区名称',
scenic_en_name VARCHAR(100) COMMENT '英文名称',
cover_image VARCHAR(255) COMMENT '封面图URL',
weather_temp VARCHAR(20) COMMENT '天气温度',
weather_desc VARCHAR(50) COMMENT '天气描述',
introduction TEXT COMMENT '景区简介',
ticket_price DECIMAL(10,2) COMMENT '票价',
opening_hours VARCHAR(50) COMMENT '营业时间',
address VARCHAR(255) COMMENT '地理位置',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
景点列表表:
sql
CREATE TABLE scenic_spots (
id INT AUTO_INCREMENT PRIMARY KEY,
scenic_id INT COMMENT '景区ID',
spot_name VARCHAR(100) COMMENT '景点名称',
en_name VARCHAR(100) COMMENT '英文名称',
description VARCHAR(255) COMMENT '描述',
image_url VARCHAR(255) COMMENT '景点图片',
max_capacity INT COMMENT '最大承载人数',
sort_order INT DEFAULT 0 COMMENT '排序权重',
FOREIGN KEY (scenic_id) REFERENCES scenic_info(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
客流监控表:
sql
CREATE TABLE scenic_flow (
id INT AUTO_INCREMENT PRIMARY KEY,
spot_id INT COMMENT '景点ID',
current_visitors INT COMMENT '当前在园人数',
recorded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (spot_id) REFERENCES scenic_spots(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
5.2 拥挤度智能计算
后端服务动态计算景点拥挤状态:
python
# backend/app/services/spot_service.py
class SpotService:
def get_scenic_spots_with_flow(self):
"""获取景点列表及实时客流信息"""
sql = """
SELECT
s.id, s.spot_name, s.en_name, s.description,
s.image_url, s.max_capacity,
IFNULL(f.current_visitors, 0) as current_visitors
FROM scenic_spots s
LEFT JOIN scenic_flow f ON s.id = f.spot_id
ORDER BY s.sort_order ASC
"""
spots = self.db.query(sql)
# 动态计算拥挤度
for spot in spots:
ratio = spot['current_visitors'] / spot['max_capacity']
if ratio >= 0.8:
spot['status'] = '拥挤'
spot['status_color'] = '#ff4d4f'
elif ratio >= 0.5:
spot['status'] = '适中'
spot['status_color'] = '#faad14'
else:
spot['status'] = '畅通'
spot['status_color'] = '#52c41a'
# 计算拥挤度百分比
spot['crowd_ratio'] = round(ratio * 100, 1)
return spots
5.3 前端状态管理
使用Pinia实现全局状态管理,一处修改,多面板联动:
javascript
// src/stores/scenic.js
import { defineStore } from 'pinia'
import { getScenicInfo, getSpotList } from '@/api/scenic'
export const useScenicStore = defineStore('scenic', () => {
const scenicInfo = ref({})
const spotList = ref([])
const loadingInfo = ref(false)
const loadingSpots = ref(false)
// 获取景区信息
const fetchScenicInfo = async () => {
loadingInfo.value = true
try {
const res = await getScenicInfo()
scenicInfo.value = res.data
} finally {
loadingInfo.value = false
}
}
// 获取景点列表
const fetchSpotList = async () => {
loadingSpots.value = true
try {
const res = await getSpotList()
spotList.value = res.data
} finally {
loadingSpots.value = false
}
}
// 计算属性:总在园人数
const totalVisitors = computed(() => {
return spotList.value.reduce((sum, spot) => {
return sum + (spot.current_visitors || 0)
}, 0)
})
// 计算属性:整体舒适度
const overallStatus = computed(() => {
if (spotList.value.length === 0) return '未知'
const avgRatio = spotList.value.reduce((sum, spot) => {
return sum + (spot.current_visitors / spot.max_capacity)
}, 0) / spotList.value.length
if (avgRatio >= 0.8) return '拥挤'
if (avgRatio >= 0.5) return '适中'
return '畅通'
})
return {
scenicInfo, spotList, loadingInfo, loadingSpots,
fetchScenicInfo, fetchSpotList,
totalVisitors, overallStatus
}
})
六、MCP地图导航服务
6.1 MCP协议介绍
MCP(Model Context Protocol)是Anthropic提出的工具调用协议,让LLM能够标准化调用外部工具。本项目实现了高德地图导航MCP服务。
6.2 MCP Server实现
python
# mcp_server/main.py
from mcp.server import Server
import httpx
mcp = Server("scenic-map-navigation")
AMAP_KEY = "your_amap_key"
@mcp.tool()
def get_map_route(start_point: str, end_point: str) -> str:
"""
获取两点之间的步行导航路线。
Args:
start_point: 起点名称(支持"当前位置")
end_point: 终点名称
Returns:
导航路线的自然语言描述
"""
# 智能处理"当前位置"
if "当前位置" in start_point:
start_point = get_current_scenic_address()
# 地理编码:地址 → 坐标
origin_coord = geocode(start_point)
dest_coord = geocode(end_point)
# 调用高德步行路径规划API
url = f"https://restapi.amap.com/v3/direction/walking"
params = {
"origin": origin_coord,
"destination": dest_coord,
"key": AMAP_KEY
}
response = httpx.get(url, params=params)
data = response.json()
# 解析路线并生成自然语言描述
if data['status'] == '1':
route = data['route']['paths'][0]
distance = int(route['distance']) # 米
duration = int(route['duration']) # 秒
steps_desc = []
for step in route['steps']:
steps_desc.append(step['instruction'])
return f"""
导航路线:
- 总距离:{distance}米(约{distance/1000:.1f}公里)
- 预计用时:{duration//60}分钟
- 详细路线:{' → '.join(steps_desc)}
""".strip()
return "导航规划失败,请检查地点名称"
def geocode(address: str) -> str:
"""地址转坐标"""
url = "https://restapi.amap.com/v3/geocode/geo"
params = {"address": address, "key": AMAP_KEY}
response = httpx.get(url, params=params)
data = response.json()
return data['geocodes'][0]['location']
# 启动MCP服务
if __name__ == "__main__":
mcp.run()
6.3 Fay调用MCP工具
当用户询问"怎么去XXX景点"时,Fay会自动调用MCP工具:
用户:请问怎么去玻璃栈道?
Fay内部处理:
1. LLM识别意图:需要导航工具
2. 提取参数:start_point="当前位置", end_point="玻璃栈道"
3. 调用MCP工具:get_map_route("当前位置", "玻璃栈道")
4. 获取结果:导航路线描述
5. 生成回复:根据导航结果组织语言
Fay回复:您好!从当前位置前往玻璃栈道的路线如下:
- 总距离:850米
- 预计用时:12分钟
- 详细路线:从游客中心出发 → 沿主路向北步行300米 → 右转进入观景步道 → 直行550米到达玻璃栈道入口
七、科幻风格UI设计实现
7.1 整体设计理念

本项目采用"赛博朋克+全息投影"设计风格,核心特点:
- 深色主题:深蓝、青色为主色调
- 光晕效果:多层box-shadow营造发光感
- 几何裁剪:clip-path实现不规则边框
- 粒子特效:Canvas绘制动态粒子背景
7.2 粒子背景实现
js
<!-- src/pages/ScenicScreen.vue -->
<template>
<div class="scenic-screen">
<!-- 粒子背景Canvas -->
<canvas ref="particleCanvas" class="particle-bg"></canvas>
<!-- 三栏布局 -->
<div class="layout-container">
<LeftPanel class="left-panel" />
<CenterPanel class="center-panel" />
<RightPanel class="right-panel" />
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const particleCanvas = ref(null)
onMounted(() => {
const canvas = particleCanvas.value
const ctx = canvas.getContext('2d')
// 设置Canvas尺寸
canvas.width = window.innerWidth
canvas.height = window.innerHeight
// 粒子数组
const particles = []
const particleCount = 100
// 初始化粒子
for (let i = 0; i < particleCount; i++) {
particles.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
vx: (Math.random() - 0.5) * 0.5,
vy: (Math.random() - 0.5) * 0.5,
radius: Math.random() * 2 + 1,
opacity: Math.random() * 0.5 + 0.2
})
}
// 动画循环
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height)
particles.forEach(p => {
// 更新位置
p.x += p.vx
p.y += p.vy
// 边界检测
if (p.x < 0 || p.x > canvas.width) p.vx *= -1
if (p.y < 0 || p.y > canvas.height) p.vy *= -1
// 绘制粒子
ctx.beginPath()
ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2)
ctx.fillStyle = `rgba(0, 255, 255, ${p.opacity})`
ctx.fill()
})
// 绘制连线
particles.forEach((p1, i) => {
particles.slice(i + 1).forEach(p2 => {
const dist = Math.hypot(p1.x - p2.x, p1.y - p2.y)
if (dist < 150) {
ctx.beginPath()
ctx.moveTo(p1.x, p1.y)
ctx.lineTo(p2.x, p2.y)
ctx.strokeStyle = `rgba(0, 255, 255, ${0.1 * (1 - dist / 150)})`
ctx.stroke()
}
})
})
requestAnimationFrame(animate)
}
animate()
})
</script>
<style scoped>
.particle-bg {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
pointer-events: none;
}
.layout-container {
position: relative;
z-index: 1;
display: grid;
grid-template-columns: 300px 1fr 300px;
gap: 20px;
padding: 20px;
height: 100vh;
}
</style>
7.3 科幻风格卡片组件
js
<!-- 科幻风格卡片 -->
<template>
<div class="scifi-card">
<div class="card-header">
<div class="title-decoration"></div>
<h3>{{ title }}</h3>
</div>
<div class="card-content">
<slot></slot>
</div>
</div>
</template>
<style scoped>
.scifi-card {
/* 深色背景 */
background: linear-gradient(
135deg,
rgba(0, 20, 40, 0.95) 0%,
rgba(0, 40, 80, 0.9) 100%
);
/* 发光边框 */
border: 1px solid rgba(0, 255, 255, 0.3);
border-radius: 12px;
/* 多层光晕 */
box-shadow:
0 0 20px rgba(0, 255, 255, 0.15),
0 0 40px rgba(0, 255, 255, 0.1),
inset 0 0 30px rgba(0, 255, 255, 0.05);
/* 几何裁剪 */
clip-path: polygon(
0 0,
calc(100% - 20px) 0,
100% 20px,
100% 100%,
20px 100%,
0 calc(100% - 20px)
);
}
.card-header {
position: relative;
padding: 15px 20px;
border-bottom: 1px solid rgba(0, 255, 255, 0.2);
}
.title-decoration {
position: absolute;
top: 0;
left: 0;
width: 60px;
height: 3px;
background: linear-gradient(
90deg,
transparent,
rgba(0, 255, 255, 0.8),
transparent
);
}
.card-header h3 {
color: #00ffff;
font-size: 16px;
font-weight: 600;
text-shadow: 0 0 10px rgba(0, 255, 255, 0.5);
letter-spacing: 2px;
}
</style>
八、完整业务流程演示
8.1 用户交互流程

8.2 地图导航流程

九、性能优化与最佳实践
9.1 前端性能优化
1. 按需加载魔珐星云SDK
javascript
// 仅在用户点击"启动数字人"时加载SDK
const loadSDK = async () => {
if (!window.XmovAvatar) {
await loadScript('https://media.xingyun3d.com/.../xmovAvatar@latest.js')
}
await initSDK()
}
2. WebSocket心跳保活
javascript
// 每30秒发送心跳包
setInterval(() => {
if (wsStatus.value === 'OPEN') {
wsSend(JSON.stringify({ type: 'heartbeat' }))
}
}, 30000)
3. 图片懒加载
js
<img
v-for="spot in spotList"
:key="spot.id"
:src="spot.image_url"
loading="lazy"
decoding="async"
/>
9.2 后端性能优化
1. 数据库索引优化
sql
-- 为高频查询字段添加索引
CREATE INDEX idx_spot_scenic ON scenic_spots(scenic_id);
CREATE INDEX idx_flow_spot ON scenic_flow(spot_id);
CREATE INDEX idx_flow_time ON scenic_flow(recorded_at);
2. API响应缓存
python
from functools import lru_cache
@lru_cache(maxsize=128)
def get_scenic_info_cached(scenic_id: int):
"""缓存景区信息(适合低频变更数据)"""
return get_scenic_info(scenic_id)
3. 异步处理图片上传
python
import asyncio
async def upload_image_async(file):
"""异步处理图片上传"""
filename = f"{uuid.uuid4()}.{file.filename.split('.')[-1]}"
filepath = os.path.join(UPLOAD_DIR, filename)
# 异步写入文件
async with aiofiles.open(filepath, 'wb') as f:
content = await file.read()
await f.write(content)
return filename
9.3 安全性考虑
1. 文件上传安全
python
from werkzeug.utils import secure_filename
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
def upload_file():
file = request.files['file']
if not allowed_file(file.filename):
return {'error': '不支持的文件类型'}, 400
# 安全文件名处理
filename = secure_filename(file.filename)
# 使用UUID重命名,防止路径穿越
filename = f"{uuid.uuid4()}_{filename}"
2. API密钥保护
python
# 后端代理魔珐星云API
@app.route('/api/avatar/token')
def get_avatar_token():
# 生成临时Token,避免前端暴露密钥
token = generate_temp_token(
app_id=APP_ID,
app_secret=APP_SECRET,
expires_in=3600
)
return {'token': token}
十、项目部署与运维
10.1 一键启动脚本
Windows (scripts/start.bat):
batch
@echo off
echo ========================================
echo 智慧文旅数字人平台 - 启动脚本
echo ========================================
:: 启动后端服务
echo [1/3] 启动后端服务...
cd backend
start python run.py
cd ..
:: 等待后端启动
timeout /t 3 /nobreak
:: 启动前端服务
echo [2/3] 启动前端服务...
start npm run dev
:: 启动Fay服务(需提前配置)
echo [3/3] 请确保Fay服务已启动(端口5000/10002)
echo ========================================
echo 启动完成!
echo 前端地址: http://localhost:5173
echo 后端地址: http://localhost:8888
echo ========================================
pause
Linux/Mac (scripts/start.sh):
bash
#!/bin/bash
echo "========================================"
echo " 智慧文旅数字人平台 - 启动脚本"
echo "========================================"
# 启动后端服务
echo "[1/3] 启动后端服务..."
cd backend
python3 run.py &
BACKEND_PID=$!
cd ..
# 等待后端启动
sleep 3
# 启动前端服务
echo "[2/3] 启动前端服务..."
npm run dev &
FRONTEND_PID=$!
echo "[3/3] 请确保Fay服务已启动(端口5000/10002)"
echo "========================================"
echo " 启动完成!"
echo " 前端地址: http://localhost:5173"
echo " 后端地址: http://localhost:8888"
echo "========================================"
# 等待子进程
wait $BACKEND_PID $FRONTEND_PID
10.2 生产环境部署

Docker部署示例:
dockerfile
# Dockerfile - 后端
FROM python:3.9-slim
WORKDIR /app
COPY backend/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY backend/ .
EXPOSE 8888
CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:8888", "run:app"]
yaml
# docker-compose.yml
version: '3.8'
services:
backend:
build: .
ports:
- "8888:8888"
environment:
- DB_HOST=mysql
- DB_USER=root
- DB_PASSWORD=password
depends_on:
- mysql
frontend:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./dist:/usr/share/nginx/html
- ./nginx.conf:/etc/nginx/nginx.conf
mysql:
image: mysql:8.0
environment:
- MYSQL_ROOT_PASSWORD=password
- MYSQL_DATABASE=scenic_digital
volumes:
- mysql_data:/var/lib/mysql
- ./database/scenic_init.sql:/docker-entrypoint-initdb.d/init.sql
volumes:
mysql_data:
十一、总结与展望
11.1 技术亮点总结
本项目成功实现了魔珐星云数字人在智慧文旅场景的深度应用,核心亮点包括:
- 全链路AI交互:从语音识别到数字人播报,形成完整闭环
- 实时数据联动:客流监控、地图导航、语音播报一体化
- 科幻沉浸体验:粒子特效、全息展示舱、光晕渲染
- 标准化工具调用:MCP协议实现AI与外部工具解耦
- 完整管理后台:景区信息、景点列表、图片上传全功能CRUD
项目开源地址 :智慧文旅数字人体验平台
