import json import requests from django.conf import settings from django.db.models import Q from openai import OpenAI from .models import City, Attraction, TravelPlan, DayPlan, DayPlanAttraction from django.core.cache import cache import logging logger = logging.getLogger(__name__) import json from django.conf import settings from openai import OpenAI from .models import City, Attraction from django.core.cache import cache from datetime import datetime, timedelta import random # class MoonshotAIService: # @staticmethod # def generate_travel_plan(preference, is_regeneration=False, previous_plan=None): # """ # 增强版红色旅游路线规划AI服务 # """ # # 处理输入参数 # if isinstance(preference, dict): # city_ids = preference.get('city_ids', []) # cities = City.objects.filter(id__in=city_ids) # days = preference.get('days', 3) # interests = preference.get('interests', []) # transport = preference.get('transport', 'public') # custom_requirements = preference.get('custom_requirements', '') # else: # cities = preference.cities.all() # days = preference.days # interests = preference.interests # transport = preference.get_transport_display() # custom_requirements = preference.custom_requirements or '无' # # # 获取相关景点(优先红色旅游景点) # attractions = Attraction.objects.filter( # city__in=cities, # tags__contains="红色旅游" # ).select_related('city') # # # 如果没有足够红色景点,补充其他景点 # if len(attractions) < days * 3: # additional_attractions = Attraction.objects.filter( # city__in=cities # ).exclude(tags__contains="红色旅游")[:10] # attractions = list(attractions) + list(additional_attractions) # # # 构建提示词 # prompt = MoonshotAIService._build_enhanced_prompt( # cities, days, interests, transport, # custom_requirements, attractions, # is_regeneration, previous_plan # ) # # # 调用Moonshot AI API # client = OpenAI( # api_key=settings.MOONSHOT_API_KEY, # base_url="https://api.moonshot.cn/v1" # ) # # try: # response = client.chat.completions.create( # model="moonshot-v1-8k", # messages=[ # { # "role": "system", # "content": "你是一个红色旅游规划专家,熟悉山东省所有红色景点。请按照指定格式输出,包含详细路线、时间安排和景点介绍。" # }, # {"role": "user", "content": prompt} # ], # response_format={"type": "json_object"}, # temperature=0.7 if is_regeneration else 0.5 # ) # # # 解析并处理返回数据 # plan_data = json.loads(response.choices[0].message.content.strip()) # return MoonshotAIService._process_ai_response(plan_data, attractions) # # except Exception as e: # print(f"AI服务异常: {str(e)}") # return { # "error": "行程生成失败", # "details": str(e) # } # # @staticmethod # def _build_enhanced_prompt(cities, days, interests, transport, requirements, attractions, is_regeneration, # previous_plan): # """构建增强版红色旅游提示词""" # city_names = [city.name for city in cities] # attraction_list = [ # f"{att.id}:{att.name}[标签:{','.join(att.tags)}][城市:{att.city.name}][描述:{att.short_desc}]" # for att in attractions # ] # # base_prompt = f""" # 你是一个专业的红色旅游规划AI助手,请根据以下信息为游客规划山东省红色旅游行程: # # 基本要求: # - 城市:{city_names} # - 天数:{days}天 # - 兴趣偏好:{interests or '无特别偏好'} # - 交通方式:{transport} # - 特殊要求:{requirements or '无'} # # 可选景点信息(ID:名称[标签][城市][简短描述]): # {attraction_list} # # 请设计一个富有教育意义的红色旅游路线,要求: # 1. 每天安排3-4个景点,包含至少2个红色景点 # 2. 合理安排景点间的交通时间和午餐时间 # 3. 每个景点提供详细的参观建议和背景介绍 # 4. 路线设计要连贯,避免来回奔波 # 5. 包含早中晚餐的推荐地点(尽量选择红色教育基地附近的餐馆) # """ # # if is_regeneration: # base_prompt += f""" # 这是之前的行程计划,请重新设计不同的路线: # {json.dumps(previous_plan, ensure_ascii=False, indent=2)} # """ # # base_prompt += f""" # 请返回JSON格式的行程计划,结构如下: # {{ # "title": "行程标题(突出红色主题)", # "description": "行程整体描述(200字左右)", # "suitable_for": "适合人群(如'亲子家庭'、'学生团体'等)", # "days": [ # {{ # "day": 1, # "theme": "当日主题(如'革命精神传承之旅')", # "description": "当日详细描述", # "transport": "交通安排建议", # "morning": {{ # "start_time": "08:00", # "attraction": 景点ID, # "visit_time": "建议参观时间(如'1.5小时')", # "description": "景点详细介绍(300字左右)", # "recommendation": "参观建议(如'建议先参观纪念馆主展厅')" # }}, # "lunch": {{ # "time": "12:00", # "recommendation": "午餐推荐地点和特色菜", # "description": "餐馆简介(100字左右)" # }}, # "afternoon": [ # {{ # "start_time": "13:30", # "attraction": 景点ID, # "visit_time": "建议参观时间", # "description": "景点详细介绍", # "recommendation": "参观建议" # }}, # ... # ], # "dinner": {{ # "time": "18:00", # "recommendation": "晚餐推荐", # "description": "餐馆简介" # }}, # "evening_activity": {{ # "description": "晚间活动建议(如'观看红色主题演出')", # "recommendation": "活动详情" # }} # }}, # ... # ], # "travel_tips": [ # "穿着建议:...", # "注意事项:...", # "红色教育重点:..." # ] # }} # """ # return base_prompt # # @staticmethod # def _process_ai_response(plan_data, attractions): # """处理AI返回数据,补充完整景点信息""" # attraction_map = {att.id: att for att in attractions} # # for day in plan_data.get('days', []): # # 处理上午景点 # if 'morning' in day and day['morning']['attraction'] in attraction_map: # att = attraction_map[day['morning']['attraction']] # day['morning'].update({ # 'attraction_data': { # 'name': att.name, # 'image': att.image.url if att.image else '', # 'address': att.address, # 'open_hours': att.open_hours, # 'ticket_price': str(att.ticket_price) # } # }) # # # 处理下午景点 # if 'afternoon' in day: # for item in day['afternoon']: # if item['attraction'] in attraction_map: # att = attraction_map[item['attraction']] # item.update({ # 'attraction_data': { # 'name': att.name, # 'image': att.image.url if att.image else '', # 'address': att.address, # 'open_hours': att.open_hours, # 'ticket_price': str(att.ticket_price) # } # }) # # return plan_data # services.py class MoonshotAIService: RED_TOURISM_TAGS = ['红色旅游', '革命', '烈士', '纪念馆', '党史'] @staticmethod def generate_travel_plan(preference): """ 红色旅游专用生成方法 """ try: # 1. 获取城市和景点 cities = City.objects.filter(id__in=preference['city_ids']) if not cities.exists(): return {'error': '未找到指定城市'} # 2. 优先获取红色景点 attractions = Attraction.objects.filter( city__in=cities, tags__overlap=MoonshotAIService.RED_TOURISM_TAGS ) # 如果红色景点不足,补充其他景点 if len(attractions) < preference['days'] * 2: extra_attractions = Attraction.objects.filter( city__in=cities ).exclude(tags__overlap=MoonshotAIService.RED_TOURISM_TAGS)[:10] attractions = list(attractions) + list(extra_attractions) # 3. 构建AI提示 prompt = MoonshotAIService._build_red_tourism_prompt( cities, preference['days'], preference['interests'], preference['transport'], preference.get('custom_requirements', ''), attractions ) # 4. 调用AI接口 client = OpenAI( api_key=settings.MOONSHOT_API_KEY, base_url="https://api.moonshot.cn/v1" ) response = client.chat.completions.create( model="moonshot-v1-8k", messages=[ { "role": "system", "content": "你是红色旅游专家,专门规划革命教育路线。必须包含党史学习内容。" }, {"role": "user", "content": prompt} ], response_format={"type": "json_object"}, temperature=0.5 ) # 5. 解析响应 plan_data = json.loads(response.choices[0].message.content) return MoonshotAIService._process_red_tourism_response(plan_data, attractions) except Exception as e: return {'error': str(e)} @staticmethod def _build_red_tourism_prompt(cities, days, interests, transport, requirements, attractions): """构建红色旅游专用提示词""" city_names = [city.name for city in cities] attraction_list = "\n".join([ f"{att.id}:{att.name}[标签:{','.join(att.tags)}][城市:{att.city.name}]" for att in attractions ]) return f""" 请规划一个{days}天的红色旅游路线,要求: - 城市:{city_names} - 必须包含至少{max(2, days)}个红色教育基地 - 交通方式:{transport} - 特殊要求:{requirements or '无'} 可选景点: {attraction_list} 返回JSON格式,包含: - title: 行程标题(必须含"红色"或"革命") - description: 行程描述(突出教育意义) - days: 每日安排(必须包含educational_points教育要点) - 每个景点标注is_red_tourism是否为红色景点 """ @staticmethod def _process_red_tourism_response(plan_data, attractions): """处理AI返回的红色旅游数据""" attraction_map = {att.id: att for att in attractions} for day in plan_data.get('days', []): for attraction in day.get('attractions', []): att_id = attraction.get('id') if att_id in attraction_map: att = attraction_map[att_id] attraction.update({ 'image': att.image.url if att.image else '', 'address': att.address, 'open_hours': att.open_hours, 'is_red_tourism': any(tag in att.tags for tag in MoonshotAIService.RED_TOURISM_TAGS) }) return plan_data @staticmethod def regenerate_travel_plan(params): """ 增强版重新生成方法,确保返回完整景点数据 """ try: # 1. 参数验证和转换 city_ids = params.get('city_ids', []) if isinstance(city_ids, int): # 处理单个城市ID的情况 city_ids = [city_ids] cities = City.objects.filter(id__in=city_ids) if not cities.exists(): return {'error': '未找到指定城市'} days = int(params.get('days', 3)) # 2. 获取所有相关景点(原行程景点 + 新红色景点) previous_attractions = [] for day in params.get('previous_plan', {}).get('days', []): for attr in day.get('attractions', []): if attr.get('id'): previous_attractions.append(attr['id']) # 查询数据库获取完整景点对象 attractions = Attraction.objects.filter( Q(id__in=previous_attractions) | Q(city__in=cities, tags__overlap=MoonshotAIService.RED_TOURISM_TAGS) ).distinct() # 3. 构建更明确的提示词 prompt = f""" 请基于原行程重新规划{days}天红色旅游路线,要求: **必须包含以下元素**: 1. 至少{max(2, days)}个红色教育基地 2. 保留原行程中评分高的景点 3. 每日主题明确(如"革命精神传承") **城市范围**:{[city.name for city in cities]} **可选景点**: {[f"{a.id}:{a.name}[红色:{any(tag in a.tags for tag in MoonshotAIService.RED_TOURISM_TAGS)}]" for a in attractions]} **返回格式示例**: {{ "title": "新行程标题", "days": [ {{ "day": 1, "attractions": [ {{ "id": 景点ID, "name": "景点名称", "is_red_tourism": true/false, "educational_value": "高/中/低" }} ] }} ] }} """ # 4. 调用AI接口(示例代码,实际需要替换为您的AI调用) client = OpenAI(api_key=settings.MOONSHOT_API_KEY, base_url="https://api.moonshot.cn/v1") response = client.chat.completions.create( model="moonshot-v1-8k", messages=[ {"role": "system", "content": "你是红色旅游专家,严格按照要求生成行程"}, {"role": "user", "content": prompt} ], response_format={"type": "json_object"}, temperature=0.6 ) # 5. 处理响应数据 plan_data = json.loads(response.choices[0].message.content) # 补充景点完整信息 attraction_map = {a.id: a for a in attractions} for day in plan_data.get('days', []): for attr in day.get('attractions', []): if attr['id'] in attraction_map: a = attraction_map[attr['id']] attr.update({ 'image': a.image.url if a.image else '', 'address': a.address, 'open_hours': a.open_hours, 'is_red_tourism': any(tag in a.tags for tag in MoonshotAIService.RED_TOURISM_TAGS) }) return plan_data except Exception as e: logger.error(f"重新生成失败: {str(e)}", exc_info=True) return {'error': str(e)} # class MoonshotAIService: # @staticmethod # def generate_travel_plan(preference, is_regeneration=False, previous_plan=None): # """ # 调用Moonshot AI API生成旅行计划 # :param preference: 偏好设置(字典或Django模型对象) # :param is_regeneration: 是否为重新生成请求 # :param previous_plan: 之前的行程计划(仅重新生成时使用) # :return: 生成的行程计划字典 # """ # # 处理输入参数(支持字典或Django模型对象) # if isinstance(preference, dict): # city_ids = preference.get('city_ids', []) # cities = City.objects.filter(id__in=city_ids) # days = preference.get('days', 3) # interests = preference.get('interests', []) # transport = preference.get('transport', 'public') # custom_requirements = preference.get('custom_requirements', '') # else: # cities = preference.cities.all() # days = preference.days # interests = preference.interests # transport = preference.get_transport_display() # custom_requirements = preference.custom_requirements or '无' # # # 获取相关景点 # attractions = Attraction.objects.filter(city__in=cities) # # # 构建不同的提示词基于是否是重新生成 # if is_regeneration: # prompt = MoonshotAIService._build_regeneration_prompt( # cities, days, interests, transport, # custom_requirements, attractions, previous_plan # ) # else: # prompt = MoonshotAIService._build_initial_prompt( # cities, days, interests, transport, # custom_requirements, attractions # ) # # # 调用Moonshot AI API # client = OpenAI( # api_key=settings.MOONSHOT_API_KEY, # base_url="https://api.moonshot.cn/v1" # ) # # try: # response = client.chat.completions.create( # model="moonshot-v1-8k", # messages=[ # { # "role": "system", # "content": "你是一个旅行规划专家,严格按照用户指定的JSON格式输出,不添加额外解释。" # }, # {"role": "user", "content": prompt} # ], # response_format={"type": "json_object"}, # temperature=0.7 if is_regeneration else 0.5 # 重新生成时增加一点随机性 # ) # # # 解析并返回JSON # return json.loads(response.choices[0].message.content.strip()) # # except json.JSONDecodeError: # print("API返回的不是有效JSON,尝试修复...") # raw_content = response.choices[0].message.content # start_idx = raw_content.find("{") # end_idx = raw_content.rfind("}") + 1 # return json.loads(raw_content[start_idx:end_idx]) # # except Exception as e: # print(f"Moonshot AI API调用异常: {str(e)}") # return { # "error": "行程生成失败", # "details": str(e) # } # # @staticmethod # def _build_initial_prompt(cities, days, interests, transport, custom_requirements, attractions): # """构建初始行程提示词""" # return f""" # 你是一个专业的旅行规划AI助手,请根据以下信息规划行程: # - 城市:{[city.name for city in cities]} # - 天数:{days}天 # - 兴趣:{', '.join(interests) if interests else '无特别偏好'} # - 交通方式:{transport} # - 特殊要求:{custom_requirements if custom_requirements else '无'} # # 可选景点(格式:名称[标签]): # {[f"{att.name}[{', '.join(att.tags)}]" for att in attractions]} # # 请返回JSON格式的行程计划,结构如下: # {{ # "title": "行程标题", # "description": "行程描述", # "suitable_for": "适合人群描述", # "days": [ # {{ # "day": 1, # "theme": "当日主题", # "description": "当日描述", # "transport": "交通方式描述", # "attractions": [ # {{ # "id": 景点ID, # "name": "景点名称", # "visit_time": "建议参观时间", # "notes": "备注信息" # }} # ] # }} # ] # }} # """ # # @staticmethod # def _build_regeneration_prompt(cities, days, interests, transport, custom_requirements, attractions, previous_plan): # """构建重新生成行程提示词""" # previous_plan_str = json.dumps(previous_plan, ensure_ascii=False, indent=2) if previous_plan else "无" # # return f""" # 你是一个专业的旅行规划AI助手,请基于相同的偏好但不同的安排重新规划行程: # # 原行程计划: # {previous_plan_str} # # 偏好设置: # - 城市:{[city.name for city in cities]} # - 天数:{days}天 # - 兴趣:{', '.join(interests) if interests else '无特别偏好'} # - 交通方式:{transport} # - 特殊要求:{custom_requirements if custom_requirements else '无'} # # 可选景点(格式:名称[标签]): # {[f"{att.name}[{', '.join(att.tags)}]" for att in attractions]} # # 请返回一个不同的JSON格式行程计划,要求: # 1. 使用不同的景点组合 # 2. 调整每天的行程顺序 # 3. 创建新的每日主题 # 4. 保持相同的天数和城市 # # 结构如下: # {{ # "title": "新行程标题(与之前不同)", # "description": "新行程描述", # "suitable_for": "适合人群描述", # "days": [ # {{ # "day": 1, # "theme": "新当日主题", # "description": "当日描述", # "transport": "交通方式描述", # "attractions": [ # {{ # "id": 景点ID, # "name": "景点名称", # "visit_time": "建议参观时间", # "notes": "备注信息" # }} # ] # }} # ] # }} # """ # # import json # from django.db.models import Q # from django.conf import settings # from openai import OpenAI # from .models import City, Attraction # # # class MoonshotAIService: # """ # 红色文化旅游规划服务类 # 严格限定只选择烈士陵园、革命纪念馆、红色博物馆等红色文化景点 # """ # # # 定义合法的红色景点类型标签 # RED_ATTRACTION_TAGS = ['martyrs', 'memorial', 'museum', 'revolution', '红色旅游'] # # @staticmethod # def generate_travel_plan(preference, is_regeneration=False, previous_plan=None): # """ # 生成红色文化主题旅行计划 # :param preference: 偏好设置(dict或Django模型) # :param is_regeneration: 是否重新生成 # :param previous_plan: 原行程(重新生成时用) # :return: 行程计划(dict)或错误信息 # """ # # 1. 参数解析 # params = MoonshotAIService._parse_preferences(preference) # if 'error' in params: # return params # # cities, days, interests, transport, custom_reqs = params # # # 2. 获取红色文化景点 # attractions = MoonshotAIService._get_red_attractions(cities) # if isinstance(attractions, dict) and 'error' in attractions: # return attractions # # # 3. 构建AI提示词 # prompt = MoonshotAIService._build_prompt( # cities, days, interests, transport, # custom_reqs, attractions, is_regeneration, previous_plan # ) # # # 4. 调用AI接口 # plan = MoonshotAIService._call_moonshot_api(prompt, is_regeneration) # if 'error' in plan: # return plan # # # 5. 验证结果 # if not MoonshotAIService._validate_red_plan(plan): # return { # "error": "行程验证失败", # "details": "生成的行程不符合红色文化主题要求" # } # # return plan # # @staticmethod # def _parse_preferences(preference): # """解析偏好参数""" # try: # if isinstance(preference, dict): # city_ids = preference.get('city_ids', []) # cities = City.objects.filter(id__in=city_ids) # days = preference.get('days', 3) # interests = preference.get('interests', []) # transport = preference.get('transport', 'public') # custom_reqs = preference.get('custom_requirements', '') # else: # cities = preference.cities.all() # days = preference.days # interests = preference.interests # transport = preference.get_transport_display() # custom_reqs = preference.custom_requirements or '无' # # if not cities.exists(): # return {"error": "未选择有效城市", "details": "请至少选择一个城市"} # # return cities, days, interests, transport, custom_reqs # # except Exception as e: # return {"error": "参数解析失败", "details": str(e)} # # @staticmethod # def _get_red_attractions(cities): # """获取红色文化景点""" # try: # # 精确查询 # attractions = Attraction.objects.filter( # city__in=cities, # tags__overlap=MoonshotAIService.RED_ATTRACTION_TAGS # ).distinct() # # # 放宽条件查询 # if not attractions.exists(): # attractions = Attraction.objects.filter( # city__in=cities, # name__iregex=r'革命|烈士|党史|红色|纪念馆' # ) # # if not attractions.exists(): # red_cities = ["北京", "延安", "井冈山", "遵义", "韶山"] # return { # "error": "未找到红色景点", # "details": "当前城市未找到红色文化景点", # "suggestion": f"建议选择{','.join(red_cities)}等红色旅游城市" # } # # return attractions # # except Exception as e: # return {"error": "景点查询失败", "details": str(e)} # # @staticmethod # def _build_prompt(cities, days, interests, transport, custom_reqs, attractions, is_regeneration, previous_plan): # """构建AI提示词""" # if is_regeneration: # return MoonshotAIService._build_red_regeneration_prompt( # cities, days, interests, transport, # custom_reqs, attractions, previous_plan # ) # return MoonshotAIService._build_red_initial_prompt( # cities, days, interests, transport, # custom_reqs, attractions # ) # # @staticmethod # def _build_red_initial_prompt(cities, days, interests, transport, custom_reqs, attractions): # """红色文化初始行程提示词""" # city_names = [city.name for city in cities] # attraction_list = [ # f"{att.name}[{'烈士纪念' if 'martyrs' in att.tags else '革命遗址' if 'revolution' in att.tags else '红色博物馆'}]" # for att in attractions # ] # # return f""" # 你是一个红色文化旅行规划专家,请严格按照以下要求规划行程: # # **硬性要求**: # 1. 所有景点必须为以下类型: # - 烈士陵园/纪念碑(含martyrs标签) # - 革命纪念馆/遗址(含memorial/revolution标签) # - 红色博物馆(含museum标签) # 2. 每日主题必须包含党史/革命史教育内容 # 3. 景点顺序应符合历史时间线 # # **行程信息**: # - 城市:{city_names} # - 天数:{days}天 # - 兴趣偏好:{interests if interests else '红色文化教育'} # - 交通方式:{transport} # - 特殊要求:{custom_reqs if custom_reqs else '无'} # # **可选红色景点**: # {attraction_list} # # **输出格式**: # {{ # "title": "红色主题标题(必须含'红色'或'革命')", # "description": "突出爱国主义教育的描述", # "suitable_for": "适合人群(如:党员干部、学生等)", # "days": [ # {{ # "day": 1, # "theme": "教育主题(如:'井冈山革命精神')", # "description": "当日教育重点", # "transport": "{transport}", # "attractions": [ # {{ # "id": 景点ID, # "name": "景点名称", # "visit_time": "建议时长(如:2小时)", # "notes": "具体教育意义(如:该景点展示了XX历史)" # }} # ] # }} # ] # }} # """ # # @staticmethod # def _build_red_regeneration_prompt(cities, days, interests, transport, custom_reqs, attractions, previous_plan): # """红色文化重新生成提示词""" # prev_plan = json.dumps(previous_plan, ensure_ascii=False, indent=2) if previous_plan else "无" # # return f""" # 请基于相同偏好重新规划不同的红色文化行程: # # **原行程**: # {prev_plan} # # **新行程要求**: # 1. 使用不同类型的红色景点(如原行程以纪念馆为主,新行程改为以博物馆为主) # 2. 按新的历史视角安排(如按时间倒序) # 3. 创建新的教育主题(如从"革命历程"改为"英雄人物") # # **偏好设置**: # - 城市:{[city.name for city in cities]} # - 天数:{days}天 # - 交通:{transport} # - 特殊要求:{custom_reqs or '无'} # # **请返回新行程**: # {{ # "title": "新红色主题(区别于原行程)", # "days": [ # {{ # "attractions": [ # {{ # "educational_focus": "新的教育侧重点" # }} # ] # }} # ] # }} # """ # # @staticmethod # def _call_moonshot_api(prompt, is_regeneration): # """调用Moonshot AI接口""" # try: # client = OpenAI( # api_key=settings.MOONSHOT_API_KEY, # base_url="https://api.moonshot.cn/v1" # ) # # response = client.chat.completions.create( # model="moonshot-v1-8k", # messages=[ # { # "role": "system", # "content": "你是一个红色文化专家,严格按用户要求生成行程,不添加无关内容" # }, # {"role": "user", "content": prompt} # ], # response_format={"type": "json_object"}, # temperature=0.7 if is_regeneration else 0.5 # ) # # content = response.choices[0].message.content # return json.loads(content.strip()) # # except json.JSONDecodeError: # try: # content = response.choices[0].message.content # start = content.find('{') # end = content.rfind('}') + 1 # return json.loads(content[start:end]) # except: # return {"error": "响应解析失败", "details": "无法解析AI返回的JSON"} # # except Exception as e: # return {"error": "API调用失败", "details": str(e)} # # @staticmethod # def _validate_red_plan(plan): # """验证行程是否符合红色主题""" # required_phrases = ['红色', '革命', '烈士', '党史', '爱国主义'] # plan_str = json.dumps(plan, ensure_ascii=False) # # # 检查关键词 # if not any(phrase in plan_str for phrase in required_phrases): # return False # # # 检查景点备注 # for day in plan.get('days', []): # for attr in day.get('attractions', []): # if not any(phrase in attr.get('notes', '') for phrase in required_phrases): # return False # # return True class TravelPlanService: @staticmethod def create_travel_plan(plan_data): """ 根据输入数据创建旅行计划(无需登录版) :param plan_data: 包含 city_ids, days, interests, transport 等 :return: 生成的旅行计划对象 """ try: # 1. 验证城市是否存在 cities = City.objects.filter(id__in=plan_data['city_ids']) if not cities.exists(): raise ValueError("选择的城市不存在") # 2. 调用AI生成计划数据 moonshot_data = { 'city_ids': plan_data['city_ids'], 'days': plan_data['days'], 'interests': plan_data['interests'], 'transport': plan_data['transport'], 'custom_requirements': plan_data.get('custom_requirements', '') } plan_data = MoonshotAIService.generate_travel_plan(moonshot_data) if not plan_data or 'error' in plan_data: return None # 3. 创建旅行计划(无用户关联) travel_plan = TravelPlan.objects.create( title=plan_data['title'], description=plan_data['description'], days=len(plan_data['days']), suitable_for=plan_data.get('suitable_for', ''), status='completed' ) # 4. 创建每日计划 for day_info in plan_data['days']: day_plan = DayPlan.objects.create( travel_plan=travel_plan, day=day_info['day'], theme=day_info['theme'], description=day_info.get('description', ''), transport=day_info.get('transport', plan_data.get('transport', '')) ) # 5. 添加每日景点 for attraction_info in day_info['attractions']: try: attraction = Attraction.objects.get(id=attraction_info['id']) DayPlanAttraction.objects.create( day_plan=day_plan, attraction=attraction, order=attraction_info.get('order', 1), visit_time=attraction_info.get('visit_time', ''), notes=attraction_info.get('notes', '') ) except Attraction.DoesNotExist: continue return travel_plan except Exception as e: print(f"创建旅行计划失败: {str(e)}") return None @staticmethod def create_travel_plan_from_preference(preference): """ 根据用户偏好创建旅行计划(需要登录) """ # 调用AI生成计划 plan_data = MoonshotAIService.generate_travel_plan({ 'city_ids': list(preference.cities.values_list('id', flat=True)), 'days': preference.days, 'interests': preference.interests, 'transport': preference.transport, 'custom_requirements': preference.custom_requirements }) if not plan_data or 'error' in plan_data: return None # 创建旅行计划 travel_plan = TravelPlan.objects.create( user=preference.user, preference=preference, title=plan_data['title'], description=plan_data['description'], days=preference.days, suitable_for=plan_data.get('suitable_for', ''), status='completed' ) # 创建每日计划 for day_info in plan_data['days']: day_plan = DayPlan.objects.create( travel_plan=travel_plan, day=day_info['day'], theme=day_info['theme'], description=day_info.get('description', ''), transport=day_info.get('transport', '') ) # 添加每日景点 for attraction_info in day_info['attractions']: try: attraction = Attraction.objects.get(id=attraction_info['id']) DayPlanAttraction.objects.create( day_plan=day_plan, attraction=attraction, order=attraction_info.get('order', 1), visit_time=attraction_info.get('visit_time', ''), notes=attraction_info.get('notes', '') ) except Attraction.DoesNotExist: continue return travel_plan class CacheService: @staticmethod def get_cities(): """ 获取城市列表(带缓存) """ cache_key = 'all_cities' cities = cache.get(cache_key) if not cities: cities = list(City.objects.filter(is_hot=True).values('id', 'name', 'code', 'image')) cache.set(cache_key, cities, timeout=3600) # 缓存1小时 return cities @staticmethod def get_attractions_by_city(city_id): """ 获取城市景点列表(带缓存) """ cache_key = f'attractions_city_{city_id}' attractions = cache.get(cache_key) if not attractions: attractions = list(Attraction.objects.filter(city_id=city_id).values( 'id', 'name', 'short_desc', 'image', 'tags' )) cache.set(cache_key, attractions, timeout=3600) # 缓存1小时 return attractions