国庆出行黑科技:10分钟搭建AI旅行规划助手(附Dify+高德地图完整教程)
摘要: 本文介绍如何利用Dify平台和高德地图API快速搭建智能旅行助手,实现国庆行程一键规划。核心功能包括实时天气查询、智能路径规划和个性化行程生成。通过3步完成环境准备:部署Dify服务、获取高德API Key、安装Dify插件。详细讲解核心代码实现,包括天气查询工具和Dify工作流设计。进阶内容涵盖多操作系统部署、数据库性能调优、自定义插件开发(如天气工具)、高德地图API高级应用(周边景点
·
🌟 标题:10分钟搭建智能旅行助手!Dify+高德地图API实现国庆行程一键规划

📌 核心功能
- 实时天气查询:接入高德天气API,避开雨天景点
- 智能路径规划:支持驾车/步行/公交多种方案
- 个性化行程生成:根据预算、天数自动推荐景点和美食
📋 目录
- 环境准备(3步上手)
- 核心代码实现
- Dify高级配置与性能优化
- Dify插件开发实战
- 高德地图API高级应用
- 复杂工作流设计
- 性能优化与监控
- 实战案例:多城市旅行规划
- 附录:资源与工具
- 前端界面定制开发
- 多模态功能实现
- 企业级部署方案
- 高级工作流案例:旅行预算计算器
- 扩展工具集成:火车票API
- 用户体验优化
一、环境准备(3步上手)
1. 部署Dify服务
# 克隆代码库
git clone https://github.com/langgenius/dify.git # 获取Dify开源代码
cd dify/docker
cp .env.example .env # 复制环境变量模板
docker compose up -d # 启动Docker容器集群
访问 http://localhost:3000,注册管理员账号并配置模型(推荐DeepSeek-V3)。
2. 获取高德API Key
⚠️ 警告:API Key需妥善保管,避免公开泄露
- 登录高德开放平台,创建应用并添加「Web服务」Key
- 配置MCP服务器URL:
{
"mcpServers": {
"amap-maps": {
"url": "https://mcp.amap.com/sse?key=你的API_KEY", // 替换为实际Key
"timeout": 60 // 连接超时时间(秒)
}
}
}
3. 安装Dify插件
- 在「插件市场」搜索并安装 MCP SSE 插件
- 粘贴上述JSON配置,完成高德地图服务授权
二、核心代码实现
1. 天气查询工具(Python)
import requests # 导入HTTP请求库
def get_weather(city):
"""
查询城市天气信息
:param city: 城市名称(如"北京")
:return: 包含日期、天气状况和温度的字典
"""
url = f"https://restapi.amap.com/v3/weather/weatherInfo?key=你的API_KEY&city={city}&extensions=all"
response = requests.get(url) # 发送GET请求
data = response.json() # 解析JSON响应
return {
"date": data["forecasts"][0]["date"], # 日期
"weather": data["forecasts"][0]["casts"][0]["dayweather"], # 日间天气
"temp": f"{data['forecasts'][0]['casts'][0]['daytemp']}°C" # 日间温度
}
2. Dify工作流设计
- 创建Agent应用:选择「ReAct策略」,添加MCP工具
- 设置提示词:
你是国庆旅行规划师,需调用高德地图工具完成:
1. 获取用户输入:目的地、天数、预算
2. 调用maps_weather查询天气
3. 调用maps_direction_driving规划路线
4. 生成markdown格式行程表
- 配置输入表单:
- 目的地(文本框)
- 旅行天数(数字)
- 预算范围(下拉选择:经济型/舒适型)
三、Dify高级配置与性能优化
1. 多操作系统部署详解
Windows系统(WSL2配置)
# 安装WSL2
wsl --install Ubuntu-22.04 # 安装Ubuntu子系统
# 设置默认WSL版本
wsl --set-default-version 2
# 启动Ubuntu并设置用户
ubuntu config --default-user your_username # 替换为实际用户名
解决WSL网络问题:修改/etc/resolv.conf,替换DNS为nameserver 8.8.8.8,避免Docker容器网络不通。
macOS配置(M芯片适配)
# 安装Docker Desktop
brew install --cask docker # 通过Homebrew安装Docker
# 启动后配置资源:Preferences→Resources→分配4GB内存+2CPU
# 克隆代码并启动
git clone https://github.com/langgenius/dify.git
cd dify/docker
docker compose up -d # 启动服务
2. 数据库与Redis性能调优
PostgreSQL配置(修改.env文件):
# 连接池大小(默认5,高并发建议10-20)
SQLALCHEMY_POOL_SIZE=15
# 连接超时(秒)
SQLALCHEMY_POOL_RECYCLE=300 # 避免长连接失效
Redis缓存策略:
# 启动Redis容器时配置
docker run -d --name redis -p 6379:6379 redis:alpine \
--maxmemory 2gb \ # 最大内存限制
--maxmemory-policy allkeys-lru # 内存满时淘汰最近最少使用的键
三、Dify插件开发实战
1. 自定义天气工具开发
步骤1:创建插件项目
# 安装插件脚手架
pip install dify-plugin-cli # Dify插件开发工具
# 初始化项目
dify plugin init --type tool --name weather-tool # 创建天气工具项目
cd weather-tool
步骤2:配置manifest.yaml
identity:
name: weather-tool
label:
zh_Hans: 天气查询
description:
zh_Hans: 调用高德天气API获取实时天气
icon: icon.svg
credentials:
api_key:
type: secret-input
required: true
label:
zh_Hans: 高德API Key # 插件认证所需参数
步骤3:实现工具逻辑(main.py)
import requests
from dify_plugin import ToolProvider
class WeatherTool(ToolProvider):
def invoke(self, user_input, credentials, parameters):
"""
工具调用入口方法
:param user_input: 用户输入文本
:param credentials: 包含api_key的认证信息
:param parameters: 工具调用参数(城市名)
"""
api_key = credentials['api_key'] # 从配置中获取API Key
city = parameters['city'] # 获取目标城市
url = f"https://restapi.amap.com/v3/weather/weatherInfo?key={api_key}&city={city}"
response = requests.get(url) # 调用高德天气API
data = response.json()
if data['status'] == '1': # API调用成功(status=1)
return {
"date": data['forecasts'][0]['date'],
"weather": data['forecasts'][0]['casts'][0]['dayweather'],
"temp": f"{data['forecasts'][0]['casts'][0]['daytemp']}°C"
}
else:
raise Exception(f"天气查询失败: {data['info']}") # 抛出错误信息
步骤4:签名与发布
# 生成签名密钥
dify signature generate -f weather_tool # 创建插件签名密钥
# 打包插件
dify plugin pack --name weather-tool --version 1.0.0 # 生成插件安装包
# 上传至Dify插件市场
四、高德地图API高级应用
1. 周边景点搜索实现
def search_nearby_attractions(location, radius=2000, keywords="景点"):
"""
搜索指定坐标周边景点
:param location: 经纬度(格式"经度,纬度")
:param radius: 搜索半径(米),默认2000米
:param keywords: 搜索关键词,默认"景点"
:return: 包含名称、地址、距离和评分的景点列表
"""
url = f"https://restapi.amap.com/v3/place/around?key={api_key}&location={location}&radius={radius}&keywords={keywords}"
response = requests.get(url)
data = response.json()
if data['status'] == '1': # API调用成功
return [
{
"name": poi['name'],
"address": poi['address'],
"distance": poi['distance'] + "米",
"rating": poi.get('biz_ext', {}).get('rating', '暂无评分') # 处理评分可能不存在的情况
} for poi in data['pois'][:5] # 取前5个结果
]
return []
2. 多路径规划对比
def compare_routes(origin, destination):
"""对比驾车和公交路线"""
# 驾车路线API
driving_url = f"https://restapi.amap.com/v3/direction/driving?origin={origin}&destination={destination}&key={api_key}"
# 公交路线API
transit_url = f"https://restapi.amap.com/v3/direction/transit/integrated?origin={origin}&destination={destination}&key={api_key}"
driving_data = requests.get(driving_url).json()
transit_data = requests.get(transit_url).json()
return {
"driving": {
"distance": driving_data['route']['paths'][0]['distance'] + "米", # 驾车距离
"duration": driving_data['route']['paths'][0]['duration'] + "秒" # 驾车时间
},
"transit": {
"distance": transit_data['route']['paths'][0]['distance'] + "米", # 公交距离
"duration": transit_data['route']['paths'][0]['duration'] + "秒", # 公交时间
"bus_lines": len(transit_data['route']['paths'][0]['segments']) # 公交换乘次数
}
}
五、复杂工作流设计
1. 条件分支与迭代节点
场景:根据用户输入的旅行类型(亲子游/自驾游)生成不同行程
-
条件分支配置:
- 若
travel_type == "亲子游":调用儿童友好型景点API,优先推荐乐园、博物馆 - 若
travel_type == "自驾游":调用沿途加油站、停车场POI
- 若
-
迭代节点应用:
使用循环处理多日行程生成,每个迭代生成一天的详细安排,包含景点、交通、餐饮。
2. 并行处理优化
配置:同时调用天气、景点、酒店API,减少总耗时
graph TD
A[用户输入] --> B{并行调用}
B --> C[天气API] # 获取天气数据
B --> D[景点API] # 获取景点数据
B --> E[酒店API] # 获取酒店数据
C & D & E --> F[变量聚合器] # 合并多接口结果
F --> G[生成行程]
3. 错误处理机制
import time # 导入时间模块用于延迟重试
# API调用重试装饰器
def retry(max_retries=3, delay=1):
"""
函数重试装饰器
:param max_retries: 最大重试次数
:param delay: 初始延迟时间(秒),指数退避
"""
def decorator(func):
def wrapper(*args, **kwargs):
for i in range(max_retries):
try:
return func(*args, **kwargs) # 执行原函数
except Exception as e:
if i == max_retries - 1: # 最后一次重试失败则抛出异常
raise e
time.sleep(delay * (i + 1)) # 指数退避等待
return wrapper
return decorator
@retry() # 应用重试装饰器
def call_amap_api(url):
return requests.get(url, timeout=10) # 设置10秒超时
六、性能优化与监控
1. Redis缓存实现
import redis
import json # 导入JSON模块用于数据序列化
r = redis.Redis(host='localhost', port=6379, db=0) # 连接Redis服务器
def get_cached_weather(city):
"""带缓存的天气查询"""
cache_key = f"weather:{city}" # 缓存键格式:weather:城市名
cached = r.get(cache_key) # 尝试获取缓存
if cached:
return json.loads(cached) # 缓存命中,返回解析后的数据
# 缓存未命中,调用API
weather = get_weather(city)
r.setex(cache_key, 7200, json.dumps(weather)) # 设置2小时过期缓存
return weather
2. 监控指标配置(Prometheus)
# prometheus.yml
scrape_configs:
- job_name: 'dify' # 任务名称
static_configs:
- targets: ['dify-api:5000'] # Dify API服务地址
metrics_path: '/metrics' # 指标暴露路径
3. 负载均衡(Nginx配置)
http {
upstream dify_servers {
server dify-instance-1:3000; # 实例1
server dify-instance-2:3000; # 实例2
}
server {
listen 80;
location / {
proxy_pass http://dify_servers; # 反向代理到实例集群
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
}
七、实战案例:多城市旅行规划
行程生成代码
def generate_multi_city_itinerary(cities, days_per_city):
"""
生成多城市行程计划
:param cities: 城市列表(如["北京", "西安", "成都"])
:param days_per_city: 每个城市停留天数(如[3,2,4])
:return: 结构化行程数据
"""
itinerary = []
for city, days in zip(cities, days_per_city):
# 获取城市坐标
location = geocode(city) # 需实现地理编码函数
# 获取天气(带缓存)
weather = get_cached_weather(city)
# 获取景点
attractions = search_nearby_attractions(location)
# 生成每日行程
daily_plan = []
for day in range(1, days + 1):
daily_plan.append({
"day": day,
"weather": weather,
"attractions": attractions[(day-1)*3 : day*3], # 每天3个景点
"dining": search_nearby_attractions(location, radius=1000, keywords="美食")[:2] # 每天2家餐厅
})
itinerary.append({
"city": city,
"days": daily_plan
})
return itinerary
# 使用示例
itinerary = generate_multi_city_itinerary(
cities=["北京", "西安", "成都"],
days_per_city=[3, 2, 4]
)
结果输出(Markdown格式)
## 🗺️ 多城市旅行计划(北京-西安-成都)
### 📅 北京(3天)
#### Day 1
- **天气**:晴,22°C
- **景点**:
1. 天安门广场(距离1.2公里)
2. 故宫博物院(评分4.8)
3. 景山公园(距离故宫500米)
- **餐饮**:四季民福烤鸭(距离故宫800米)
八、附录:资源与工具
1. 开发工具清单
- Docker Compose:一键部署Dify服务
- Postman:测试API接口(含Dify和高德API集合)
- Grafana:监控面板模板(Dify性能指标)
- VSCode插件:Dify插件开发脚手架
2. 代码仓库
3. 参考文档
九、前端界面定制开发
1. React组件深度集成
行程卡片组件(TripCard.jsx)
import React, { useState } from 'react';
import { Card, Button, Spin, Tag } from 'antd'; // 导入Ant Design组件
import { EditOutlined, MapOutlined } from '@ant-design/icons'; // 导入图标组件
import './TripCard.css'; // 导入自定义样式
const TripCard = ({ itinerary, onEdit }) => {
const [loading, setLoading] = useState(false); // 地图加载状态
const handleViewMap = () => {
setLoading(true);
// 调用地图组件显示行程路线
setTimeout(() => setLoading(false), 800); // 模拟加载延迟
};
return (
<Card
className="trip-card"
title={`${itinerary.city} (${itinerary.days.length}天)`} // 卡片标题
extra={[
<Button icon={<EditOutlined />} onClick={onEdit}>编辑</Button>,
<Button icon={<MapOutlined />} loading={loading} onClick={handleViewMap}>
查看地图
</Button>
]}
>
{itinerary.days.map(day => ( // 遍历每日行程
<div key={day.day} className="day-section">
<Tag color="blue">Day {day.day}</Tag> // 日期标签
<div className="weather-info">
🌤️ {day.weather.weather} {day.weather.temp} // 天气信息
</div>
<div className="attractions-list">
{day.attractions.map((attraction, idx) => ( // 遍历景点
<div key={idx} className="attraction-item">
{idx+1}. {attraction.name}
<span className="distance">({attraction.distance})</span> // 距离信息
</div>
))}
</div>
</div>
))}
</Card>
);
};
export default TripCard;
地图可视化组件(使用高德地图JS API)
import React, { useEffect, useRef } from 'react';
const TripMap = ({ itinerary }) => {
const mapRef = useRef(null); // DOM引用
useEffect(() => {
// 动态加载高德地图JS API
const script = document.createElement('script');
script.src = `https://webapi.amap.com/maps?v=2.0&key=${process.env.REACT_APP_AMAP_KEY}`; // 从环境变量获取Key
script.onload = initMap; // 脚本加载完成后初始化地图
document.body.appendChild(script);
return () => document.body.removeChild(script); // 组件卸载时清理
}, []);
const initMap = () => {
const map = new AMap.Map(mapRef.current, {
zoom: 10,
center: [116.39748, 39.90882] // 默认北京坐标
});
// 添加行程途经点标记
itinerary.forEach(cityPlan => {
AMap.plugin('AMap.Geocoder', () => { // 加载地理编码插件
const geocoder = new AMap.Geocoder();
geocoder.getLocation(cityPlan.city, (status, result) => {
if (status === 'complete') {
const center = result.geocodes[0].location;
new AMap.Marker({ // 创建地图标记
position: center,
title: cityPlan.city,
map: map
});
}
});
});
});
};
return <div ref={mapRef} style={{ width: '100%', height: '400px' }} />; // 地图容器
};
export default TripMap;
2. 状态管理实现(Redux)
// store/tripSlice.js
import { createSlice } from '@reduxjs/toolkit'; // 导入Redux Toolkit
const initialState = {
currentItinerary: null, // 当前行程
savedItineraries: [], // 保存的行程列表
isEditing: false // 编辑状态
};
export const tripSlice = createSlice({
name: 'trip',
initialState,
reducers: {
setCurrentItinerary: (state, action) => {
state.currentItinerary = action.payload; // 设置当前行程
},
saveItinerary: (state) => {
if (state.currentItinerary) {
const existingIndex = state.savedItineraries.findIndex(
item => item.id === state.currentItinerary.id
);
if (existingIndex >= 0) {
state.savedItineraries[existingIndex] = state.currentItinerary; // 更新现有行程
} else {
state.savedItineraries.push({ // 添加新行程
...state.currentItinerary,
id: Date.now().toString() // 使用时间戳作为唯一ID
});
}
}
},
deleteItinerary: (state, action) => {
// 删除指定ID的行程
state.savedItineraries = state.savedItineraries.filter(
item => item.id !== action.payload
);
}
}
});
export const { setCurrentItinerary, saveItinerary, deleteItinerary } = tripSlice.actions;
export default tripSlice.reducer;
十、多模态功能实现
1. PDF行程解析
import PyPDF2
from dify_plugin import ToolProvider
class PDFTripParser(ToolProvider):
def invoke(self, user_input, credentials, parameters):
"""解析PDF格式行程文件"""
file_path = parameters['file_path'] # 获取文件路径
text = ""
with open(file_path, 'rb') as file:
reader = PyPDF2.PdfReader(file) # 创建PDF阅读器
for page in reader.pages:
text += page.extract_text() # 提取页面文本
# 使用正则表达式提取关键信息
import re
cities = re.findall(r'城市:([^,,]+)', text) # 提取城市名
days = re.findall(r'共(\d+)天', text) # 提取总天数
attractions = re.findall(r'景点:([^,,]+)', text) # 提取景点名
return {
"parsed_result": {
"cities": cities,
"total_days": days[0] if days else "未知",
"key_attractions": attractions[:5] # 取前5个景点
},
"raw_text": text[:500] + "..." # 返回文本预览(前500字符)
}
2. 语音交互集成(Whisper API)
import openai
from dify_plugin import ToolProvider
class VoiceAssistantTool(ToolProvider):
def invoke(self, user_input, credentials, parameters):
"""语音转文字/文字转语音工具"""
openai.api_key = credentials['openai_key'] # 设置OpenAI API密钥
if parameters['action'] == 'transcribe':
# 语音转文字
audio_file = open(parameters['audio_path'], 'rb')
transcript = openai.Audio.transcribe(
"whisper-1", # 使用Whisper模型
audio_file,
language="zh" # 指定中文
)
return {"text": transcript['text']}
elif parameters['action'] == 'tts':
# 文字转语音
response = openai.Audio.create(
model="tts-1", # 使用TTS模型
voice="alloy", # 语音风格
input=parameters['text']
)
return {"audio_url": response['data'][0]['url']} # 返回音频URL
3. 旅行照片分类
from transformers import CLIPProcessor, CLIPModel
import torch # 导入PyTorch深度学习框架
class PhotoClassifier:
def __init__(self):
"""初始化CLIP图像分类模型"""
self.model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32") # 加载预训练模型
self.processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32") # 加载处理器
self.labels = ["风景", "人物", "美食", "建筑", "动物", "植物"] # 分类标签
def classify(self, image_path):
"""
分类旅行照片
:param image_path: 图片路径
:return: 分类结果及置信度
"""
from PIL import Image
image = Image.open(image_path).convert("RGB") # 打开并转换为RGB格式
inputs = self.processor(
text=[f"a photo of {label}" for label in self.labels], # 生成文本提示
images=image,
return_tensors="pt", # 返回PyTorch张量
padding=True
)
with torch.no_grad(): # 禁用梯度计算
outputs = self.model(**inputs)
logits_per_image = outputs.logits_per_image # 图像-文本相似度分数
probs = logits_per_image.softmax(dim=1) # 转换为概率
top_probs, top_indices = probs.topk(2) # 获取前2个结果
return [
{"label": self.labels[i], "score": float(p)}
for i, p in zip(top_indices[0], top_probs[0])
]
# 使用示例
classifier = PhotoClassifier()
print(classifier.classify("travel_photo.jpg"))
# 输出: [{"label": "风景", "score": 0.87}, {"label": "建筑", "score": 0.12}]
十一、企业级部署方案
1. Kubernetes部署清单
dify-deployment.yaml
apiVersion: apps/v1
kind: StatefulSet # 使用StatefulSet确保稳定的网络标识
metadata:
name: dify
spec:
serviceName: dify
replicas: 3 # 部署3个副本实现高可用
selector:
matchLabels:
app: dify
template:
metadata:
labels:
app: dify
spec:
containers:
- name: dify-api
image: langgenius/dify-api:latest # 使用官方镜像
ports:
- containerPort: 5000 # API服务端口
envFrom:
- configMapRef:
name: dify-config # 引用配置文件
- secretRef:
name: dify-secrets # 引用密钥
resources:
requests:
memory: "1Gi" # 请求资源
cpu: "500m"
limits:
memory: "2Gi" # 限制资源
cpu: "1000m"
livenessProbe: # 存活探针
httpGet:
path: /health
port: 5000
initialDelaySeconds: 30 # 初始化延迟
periodSeconds: 10 # 检查周期
volumeMounts:
- name: dify-data
mountPath: /app/data # 数据持久化
volumeClaimTemplates:
- metadata:
name: dify-data
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "standard"
resources:
requests:
storage: 10Gi # 请求10GB存储
hpa.yaml(自动扩缩容)
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: dify-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: StatefulSet
name: dify
minReplicas: 2 # 最小副本数
maxReplicas: 10 # 最大副本数
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70 # CPU使用率阈值70%
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80 # 内存使用率阈值80%
2. 备份策略(CronJob)
apiVersion: batch/v1
kind: CronJob
metadata:
name: dify-backup
spec:
schedule: "0 3 * * *" # 每天凌晨3点执行
jobTemplate:
spec:
template:
spec:
containers:
- name: backup
image: postgres:14 # 使用PostgreSQL镜像(含pg_dump工具)
command: ["/bin/sh", "-c"]
args:
- pg_dump -h postgres -U $POSTGRES_USER $POSTGRES_DB > /backup/dify_$(date +%Y%m%d).sql;
gzip /backup/dify_$(date +%Y%m%d).sql; # 压缩备份文件
env:
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: postgres-secrets
key: username # 从密钥获取数据库用户名
- name: POSTGRES_DB
value: dify # 数据库名称
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: postgres-secrets
key: password # 从密钥获取密码
volumeMounts:
- name: backup-volume
mountPath: /backup # 挂载备份存储卷
volumes:
- name: backup-volume
persistentVolumeClaim:
claimName: backup-pvc # 使用PVC存储备份
restartPolicy: OnFailure # 失败时重启
3. 安全加固(Nginx配置)
server {
listen 443 ssl; # 启用HTTPS
server_name assistant.yourcompany.com; # 替换为实际域名
# SSL配置
ssl_certificate /etc/nginx/certs/fullchain.pem; # 证书链
ssl_certificate_key /etc/nginx/certs/privkey.pem; # 私钥
ssl_protocols TLSv1.2 TLSv1.3; # 支持的TLS版本
ssl_ciphers HIGH:!aNULL:!MD5; # 加密套件
# API限流
limit_req_zone $binary_remote_addr zone=dify_api:10m rate=10r/s; # 限制每秒10请求
location /api/ {
limit_req zone=dify_api burst=20 nodelay; # 突发20请求无延迟
proxy_pass http://dify-api:5000; # 代理到API服务
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# CORS设置
add_header Access-Control-Allow-Origin "https://yourfrontend.com"; # 允许前端域名
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS"; # 允许方法
add_header Access-Control-Allow-Headers "Content-Type, Authorization"; # 允许头信息
}
# 静态资源缓存
location /static/ {
alias /var/www/dify/static/; # 静态文件路径
expires 1d; # 缓存1天
add_header Cache-Control "public, max-age=86400"; # 缓存控制头
}
}
十二、高级工作流案例:旅行预算计算器
1. 预算计算工具实现
def calculate_trip_budget(itinerary, user_preferences):
"""
计算旅行预算
:param itinerary: 行程计划
:param user_preferences: 用户偏好 {transport_level, hotel_level, dining_level}
level: 1-经济, 2-舒适, 3-豪华
"""
# 基础价格系数(不同级别)
transport_factors = {1: 1.0, 2: 1.8, 3: 3.0}
hotel_factors = {1: 1.0, 2: 2.5, 3: 5.0}
dining_factors = {1: 1.0, 2: 2.0, 3: 4.0}
budget = {
"transport": 0, # 交通费用
"accommodation": 0, # 住宿费用
"dining": 0, # 餐饮费用
"attractions": 0, # 景点门票
"shopping": 0, # 购物预算
"total": 0 # 总预算
}
# 城市间交通费用(高铁二等座均价:0.4元/公里)
city_distances = {
("北京", "西安"): 1200,
("西安", "成都"): 700,
# 其他城市间距离...
}
# 计算城市间交通费用
for i in range(len(itinerary)-1):
from_city = itinerary[i]['city']
to_city = itinerary[i+1]['city']
distance = city_distances.get((from_city, to_city), 500) # 默认500公里
base_cost = distance * 0.4 # 基础费用
budget['transport'] += base_cost * transport_factors[user_preferences['transport_level']]
# 计算住宿费用(日均基础价格:经济150元/晚)
for city_plan in itinerary:
nights = len(city_plan['days']) # 住宿天数 = 城市停留天数
base_hotel_cost = 150 * nights
budget['accommodation'] += base_hotel_cost * hotel_factors[user_preferences['hotel_level']]
# 计算餐饮费用(日均基础价格:经济80元/天)
total_days = sum(len(plan['days']) for plan in itinerary) # 总天数
base_dining_cost = 80 * total_days
budget['dining'] += base_dining_cost * dining_factors[user_preferences['dining_level']]
# 景点门票(平均200元/天)
budget['attractions'] = total_days * 200
# 购物预算(餐饮费用的50%)
budget['shopping'] = budget['dining'] * 0.5
# 总计(加10%应急费用)
budget['total'] = sum(budget.values()) * 1.1
# 格式化金额(保留两位小数)
for key in budget:
budget[key] = round(budget[key], 2)
return budget
# 使用示例
budget = calculate_trip_budget(
itinerary=itinerary, # 前面生成的行程计划
user_preferences={
"transport_level": 2, # 舒适交通
"hotel_level": 2, # 舒适酒店
"dining_level": 2 # 舒适餐饮
}
)
2. 预算可视化(Markdown表格)
## 💰 旅行预算明细
| 项目 | 金额(元) | 占比 |
|--------------|------------|------|
| 交通 | {transport} | {transport/total*100}% |
| 住宿 | {accommodation} | {accommodation/total*100}% |
| 餐饮 | {dining} | {dining/total*100}% |
| 景点门票 | {attractions} | {attractions/total*100}% |
| 购物 | {shopping} | {shopping/total*100}% |
| **总计** | **{total}** | **100%** |
> 📌 注:已包含10%应急费用
> 🔄 可通过调整偏好设置重新计算预算
十三、扩展工具集成:火车票API
12306 API封装(使用第三方API服务)
import requests
class TrainTicketAPI:
def __init__(self, api_key):
self.base_url = "https://api.juheapi.com/train/ticket_price" # 聚合数据API
self.api_key = api_key # 第三方API密钥
def get_ticket_price(self, from_station, to_station, date):
"""
获取火车票价格
:param from_station: 出发站
:param to_station: 到达站
:param date: 日期(YYYY-MM-DD)
:return: 不同席别价格字典
"""
params = {
"key": self.api_key,
"from": from_station,
"to": to_station,
"date": date
}
response = requests.get(self.base_url, params=params)
data = response.json()
if data['error_code'] == 0: # API调用成功
# 提取主要席别价格
tickets = {}
for item in data['result']['list']:
tickets[item['seat']] = {
"price": item['price'],
"remaining": item['remain'] # 余票情况
}
return tickets
else:
raise Exception(f"获取火车票价格失败: {data['reason']}")
# 使用示例
train_api = TrainTicketAPI(api_key="your_juhe_api_key") # 替换为实际Key
prices = train_api.get_ticket_price("北京", "西安", "2023-10-01")
print(prices)
# 输出: {
# "二等座": {"price": "515.0", "remaining": "有"},
# "一等座": {"price": "820.0", "remaining": "少量"},
# "商务座": {"price": "1600.0", "remaining": "充足"}
# }
十四、用户体验优化
1. 加载状态组件(Skeleton屏)
import React from 'react';
import { Skeleton, Card } from 'antd';
const TripCardSkeleton = () => (
<Card className="trip-card-skeleton">
<Skeleton.Title level={4} /> {/* 标题占位 */}
<div className="day-skeleton">
<Skeleton.Button style={{ width: 60 }} /> {/* 日期标签占位 */}
<Skeleton.Input style={{ width: 120, margin: '0 10px' }} /> {/* 天气信息占位 */}
</div>
<div className="attractions-skeleton">
{[1, 2, 3].map(i => ( {/* 生成3个景点占位 */}
<div key={i} className="attraction-item-skeleton">
<Skeleton.Input style={{ width: '80%' }} /> {/* 景点名称占位 */}
</div>
))}
</div>
</Card>
);
export default TripCardSkeleton;
2. 错误处理UI
import React from 'react';
import { Alert, Button, Space } from 'antd';
import { ReloadOutlined, QuestionCircleOutlined } from '@ant-design/icons';
const ErrorBoundary = ({ error, onRetry, children }) => {
if (error) {
return (
<div className="error-boundary">
<Alert
message="操作失败"
description={
<div>
<p>{error.message || "发生未知错误"}</p> {/* 显示错误信息 */}
<Space style={{ marginTop: 16 }}>
<Button
icon={<ReloadOutlined />}
onClick={onRetry} {/* 重试按钮 */}
>
重试
</Button>
<Button
icon={<QuestionCircleOutlined />}
type="primary"
onClick={() => window.open("/help/trip-planner", "_blank")} {/* 打开帮助文档 */}
>
查看帮助
</Button>
</Space>
</div>
}
type="error"
showIcon
/>
</div>
);
}
return children; {/* 无错误时渲染子组件 */}
};
export default ErrorBoundary;
3. 行程热力图(ECharts实现)
# 使用pyecharts生成行程热力图
from pyecharts import options as opts
from pyecharts.charts import HeatMap
from pyecharts.commons.utils import JsCode
def generate_trip_heatmap(itinerary):
"""生成行程热力图(按景点访问频率)"""
# 模拟数据:景点访问热度(1-10)
heat_data = []
for city_plan in itinerary:
for day in city_plan['days']:
for idx, attraction in enumerate(day['attractions']):
# 模拟经纬度(实际应从高德API获取)
lng = 116.3 + idx * 0.01
lat = 39.9 + idx * 0.01
heat_data.append([lng, lat, 10 - idx]) # 按顺序热度递减
heatmap = (
HeatMap()
.add_coordinate_json(
# 高德地图JSON数据(需提前下载)
json_file="amap_cities.json",
name_field="name",
lng_field="center_lng",
lat_field="center_lat",
)
.add(
series_name="景点热度",
data=heat_data,
label_opts=opts.LabelOpts(is_show=False),
emphasis_itemstyle_opts=opts.ItemStyleOpts(
color=JsCode("""new echarts.graphic.RadialGradient(0.4, 0.3, 1, [{
offset: 0,
color: 'rgba(255, 255, 255, 0.8)'
}, {
offset: 1,
color: 'rgba(255, 150, 0, 0)'
}])""")
),
)
.set_global_opts(
title_opts=opts.TitleOpts(title="行程热力图"),
visualmap_opts=opts.VisualMapOpts(
min_=0,
max_=10,
range_text=["热度高", "热度低"],
orient="horizontal",
pos_right="center",
pos_bottom="10%",
),
tooltip_opts=opts.TooltipOpts(formatter="{b}: {c}"),
)
)
return heatmap.render("trip_heatmap.html") # 生成HTML文件
```]]
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)