Compare commits
No commits in common. "bebdba3c4b9e87c254a7e435fee3f0aacfb1567a" and "19ca9d06ce378f9ae976d872682edf33dd6fcef6" have entirely different histories.
bebdba3c4b
...
19ca9d06ce
Binary file not shown.
@ -5,23 +5,22 @@
|
||||
API依赖注入模块
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
from core.config import get_config_manager, ConfigManager
|
||||
from core.ai import AIAgent
|
||||
from utils.file_io import OutputManager
|
||||
|
||||
# 全局依赖
|
||||
config_manager: Optional[ConfigManager] = None
|
||||
ai_agent: Optional[AIAgent] = None
|
||||
output_manager: Optional[OutputManager] = None
|
||||
config_manager = None
|
||||
ai_agent = None
|
||||
output_manager = None
|
||||
|
||||
def initialize_dependencies():
|
||||
"""初始化全局依赖"""
|
||||
global config_manager, ai_agent, output_manager
|
||||
|
||||
# 初始化配置 - 使用服务器模式
|
||||
# 初始化配置
|
||||
config_manager = get_config_manager()
|
||||
config_manager.load_from_directory("config", server_mode=True)
|
||||
config_manager.load_from_directory("config")
|
||||
|
||||
# 初始化输出管理器
|
||||
from datetime import datetime
|
||||
@ -35,18 +34,12 @@ def initialize_dependencies():
|
||||
|
||||
def get_config() -> ConfigManager:
|
||||
"""获取配置管理器"""
|
||||
if config_manager is None:
|
||||
raise RuntimeError("配置管理器未初始化")
|
||||
return config_manager
|
||||
|
||||
def get_ai_agent() -> AIAgent:
|
||||
"""获取AI代理"""
|
||||
if ai_agent is None:
|
||||
raise RuntimeError("AI代理未初始化")
|
||||
return ai_agent
|
||||
|
||||
def get_output_manager() -> OutputManager:
|
||||
"""获取输出管理器"""
|
||||
if output_manager is None:
|
||||
raise RuntimeError("输出管理器未初始化")
|
||||
return output_manager
|
||||
Binary file not shown.
Binary file not shown.
@ -10,7 +10,7 @@ import logging
|
||||
from typing import Dict, Any, Optional, Tuple
|
||||
from pathlib import Path
|
||||
|
||||
from core.config import ConfigManager, GenerateContentConfig, GenerateTopicConfig, PosterConfig
|
||||
from core.config import ConfigManager, GenerateContentConfig
|
||||
from utils.prompts import PromptTemplate
|
||||
from api.services.prompt_service import PromptService
|
||||
|
||||
@ -30,30 +30,7 @@ class PromptBuilderService:
|
||||
"""
|
||||
self.config_manager = config_manager
|
||||
self.prompt_service = prompt_service
|
||||
|
||||
def _ensure_content_config(self) -> GenerateContentConfig:
|
||||
"""确保内容生成配置已加载"""
|
||||
# 按需加载内容生成配置
|
||||
if not self.config_manager.load_task_config('content_gen'):
|
||||
logger.warning("未找到内容生成配置,将使用默认配置")
|
||||
|
||||
return self.config_manager.get_config('content_gen', GenerateContentConfig)
|
||||
|
||||
def _ensure_topic_config(self) -> GenerateTopicConfig:
|
||||
"""确保选题生成配置已加载"""
|
||||
# 按需加载选题生成配置
|
||||
if not self.config_manager.load_task_config('topic_gen'):
|
||||
logger.warning("未找到选题生成配置,将使用默认配置")
|
||||
|
||||
return self.config_manager.get_config('topic_gen', GenerateTopicConfig)
|
||||
|
||||
def _ensure_poster_config(self) -> PosterConfig:
|
||||
"""确保海报生成配置已加载"""
|
||||
# 按需加载海报生成配置
|
||||
if not self.config_manager.load_task_config('poster_gen'):
|
||||
logger.warning("未找到海报生成配置,将使用默认配置")
|
||||
|
||||
return self.config_manager.get_config('poster_gen', PosterConfig)
|
||||
self.content_config: GenerateContentConfig = config_manager.get_config('content_gen', GenerateContentConfig)
|
||||
|
||||
def build_content_prompt(self, topic: Dict[str, Any], step: str = "content") -> Tuple[str, str]:
|
||||
"""
|
||||
@ -66,12 +43,9 @@ class PromptBuilderService:
|
||||
Returns:
|
||||
系统提示词和用户提示词的元组
|
||||
"""
|
||||
# 获取内容生成配置
|
||||
content_config = self._ensure_content_config()
|
||||
|
||||
# 加载系统提示词和用户提示词模板
|
||||
system_prompt_path = content_config.content_system_prompt
|
||||
user_prompt_path = content_config.content_user_prompt
|
||||
system_prompt_path = self.content_config.content_system_prompt
|
||||
user_prompt_path = self.content_config.content_user_prompt
|
||||
|
||||
# 创建提示词模板
|
||||
template = PromptTemplate(system_prompt_path, user_prompt_path)
|
||||
@ -109,47 +83,6 @@ class PromptBuilderService:
|
||||
|
||||
return system_prompt, user_prompt
|
||||
|
||||
def build_poster_prompt(self, topic: Dict[str, Any], content: Dict[str, Any]) -> Tuple[str, str]:
|
||||
"""
|
||||
构建海报生成提示词
|
||||
|
||||
Args:
|
||||
topic: 选题信息
|
||||
content: 生成的内容
|
||||
|
||||
Returns:
|
||||
系统提示词和用户提示词的元组
|
||||
"""
|
||||
# 获取海报生成配置
|
||||
poster_config = self._ensure_poster_config()
|
||||
|
||||
# 从配置中获取海报提示词模板路径
|
||||
system_prompt_path = poster_config.poster_system_prompt
|
||||
user_prompt_path = poster_config.poster_user_prompt
|
||||
|
||||
if not system_prompt_path or not user_prompt_path:
|
||||
raise ValueError("海报提示词模板路径不完整")
|
||||
|
||||
# 创建提示词模板
|
||||
template = PromptTemplate(system_prompt_path, user_prompt_path)
|
||||
|
||||
# 获取景区信息
|
||||
object_name = topic.get("object", "")
|
||||
object_content = self.prompt_service.get_scenic_spot_info(object_name)
|
||||
|
||||
# 构建系统提示词
|
||||
system_prompt = template.get_system_prompt()
|
||||
|
||||
# 构建用户提示词
|
||||
user_prompt = template.build_user_prompt(
|
||||
content=content.get("content", ""),
|
||||
title=content.get("title", ""),
|
||||
object_name=object_name,
|
||||
object_content=object_content
|
||||
)
|
||||
|
||||
return system_prompt, user_prompt
|
||||
|
||||
def build_topic_prompt(self, num_topics: int, month: str) -> Tuple[str, str]:
|
||||
"""
|
||||
构建选题生成提示词
|
||||
@ -161,11 +94,13 @@ class PromptBuilderService:
|
||||
Returns:
|
||||
系统提示词和用户提示词的元组
|
||||
"""
|
||||
# 获取选题生成配置
|
||||
topic_config = self._ensure_topic_config()
|
||||
# 从配置中获取选题提示词模板路径
|
||||
topic_config = self.config_manager.get_config('topic_gen', dict)
|
||||
if not topic_config:
|
||||
raise ValueError("未找到选题生成配置")
|
||||
|
||||
system_prompt_path = topic_config.topic_system_prompt
|
||||
user_prompt_path = topic_config.topic_user_prompt
|
||||
system_prompt_path = topic_config.get("topic_system_prompt", "")
|
||||
user_prompt_path = topic_config.get("topic_user_prompt", "")
|
||||
|
||||
if not system_prompt_path or not user_prompt_path:
|
||||
raise ValueError("选题提示词模板路径不完整")
|
||||
@ -220,12 +155,9 @@ class PromptBuilderService:
|
||||
Returns:
|
||||
系统提示词和用户提示词的元组
|
||||
"""
|
||||
# 获取内容生成配置
|
||||
content_config = self._ensure_content_config()
|
||||
|
||||
# 从配置中获取审核提示词模板路径
|
||||
system_prompt_path = content_config.judger_system_prompt
|
||||
user_prompt_path = content_config.judger_user_prompt
|
||||
system_prompt_path = self.content_config.judger_system_prompt
|
||||
user_prompt_path = self.content_config.judger_user_prompt
|
||||
|
||||
# 创建提示词模板
|
||||
template = PromptTemplate(system_prompt_path, user_prompt_path)
|
||||
|
||||
@ -10,9 +10,6 @@ import logging
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import traceback
|
||||
import time
|
||||
from typing import Dict, Any, Optional, List, cast
|
||||
from pathlib import Path
|
||||
import mysql.connector
|
||||
@ -36,130 +33,121 @@ class PromptService:
|
||||
config_manager: 配置管理器
|
||||
"""
|
||||
self.config_manager = config_manager
|
||||
|
||||
# 按需加载resource配置
|
||||
if 'resource' not in self.config_manager._loaded_configs:
|
||||
self.config_manager.load_task_config('resource')
|
||||
|
||||
self.resource_config = self.config_manager.get_config('resource', ResourceConfig)
|
||||
|
||||
# ResourceLoader是静态类,不需要实例化
|
||||
self.resource_dirs = self.resource_config.resource_dirs
|
||||
self.resource_config: ResourceConfig = config_manager.get_config('resource', ResourceConfig)
|
||||
|
||||
# 初始化数据库连接池
|
||||
self.db_pool = self._init_db_pool()
|
||||
self._init_db_pool()
|
||||
|
||||
# 创建必要的目录结构
|
||||
self._create_resource_directories()
|
||||
|
||||
def _create_resource_directories(self):
|
||||
"""创建必要的资源目录"""
|
||||
try:
|
||||
dirs_to_create = ['styles', 'attractions', 'products', 'audiences']
|
||||
for dir_name in dirs_to_create:
|
||||
dir_path = Path(dir_name)
|
||||
if not dir_path.exists():
|
||||
dir_path.mkdir(parents=True, exist_ok=True)
|
||||
logger.info(f"创建目录: {dir_path}")
|
||||
except Exception as e:
|
||||
logger.error(f"创建资源目录失败: {e}")
|
||||
|
||||
def _process_env_vars(self, config: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""处理配置中的环境变量"""
|
||||
processed = {}
|
||||
for key, value in config.items():
|
||||
if isinstance(value, str) and "${" in value:
|
||||
# 匹配 ${ENV_VAR:-default} 格式
|
||||
pattern = r'\${([^:-]+)(?::-([^}]+))?}'
|
||||
match = re.match(pattern, value)
|
||||
if match:
|
||||
env_var, default = match.groups()
|
||||
processed_value = os.environ.get(env_var, default or "")
|
||||
# 尝试转换为数字
|
||||
if key == "port":
|
||||
try:
|
||||
processed_value = int(processed_value)
|
||||
except (ValueError, TypeError):
|
||||
processed_value = 3306
|
||||
processed[key] = processed_value
|
||||
else:
|
||||
processed[key] = value
|
||||
else:
|
||||
processed[key] = value
|
||||
return processed
|
||||
|
||||
def _init_db_pool(self):
|
||||
"""初始化数据库连接池,尝试多种连接方式"""
|
||||
# 获取数据库配置
|
||||
raw_db_config = self.config_manager.get_raw_config('database')
|
||||
|
||||
# 处理环境变量
|
||||
db_config = self._process_env_vars(raw_db_config)
|
||||
|
||||
# 连接尝试配置
|
||||
connection_attempts = [
|
||||
{"desc": "使用配置文件中的设置", "config": db_config},
|
||||
{"desc": "使用明确的密码", "config": {**db_config, "password": "password"}},
|
||||
{"desc": "使用空密码", "config": {**db_config, "password": ""}},
|
||||
{"desc": "使用auth_plugin", "config": {**db_config, "auth_plugin": "mysql_native_password"}}
|
||||
]
|
||||
|
||||
# 尝试不同的连接方式
|
||||
for attempt in connection_attempts:
|
||||
try:
|
||||
# 打印连接信息(不包含密码)
|
||||
connection_info = {k: v for k, v in attempt["config"].items() if k != 'password'}
|
||||
logger.info(f"尝试连接数据库 ({attempt['desc']}): {connection_info}")
|
||||
"""初始化数据库连接池"""
|
||||
try:
|
||||
# 尝试直接从配置文件加载数据库配置
|
||||
config_dir = Path("config")
|
||||
db_config_path = config_dir / "database.json"
|
||||
|
||||
if not db_config_path.exists():
|
||||
logger.warning(f"数据库配置文件不存在: {db_config_path}")
|
||||
self.db_pool = None
|
||||
return
|
||||
|
||||
# 创建连接池
|
||||
pool = pooling.MySQLConnectionPool(
|
||||
pool_name=f"prompt_service_pool_{int(time.time())}",
|
||||
pool_size=5,
|
||||
**attempt["config"]
|
||||
)
|
||||
# 加载配置文件
|
||||
with open(db_config_path, 'r', encoding='utf-8') as f:
|
||||
db_config = json.load(f)
|
||||
|
||||
# 处理环境变量
|
||||
processed_config = {}
|
||||
for key, value in db_config.items():
|
||||
if isinstance(value, str) and "${" in value:
|
||||
# 匹配 ${ENV_VAR:-default} 格式
|
||||
pattern = r'\${([^:-]+)(?::-([^}]+))?}'
|
||||
match = re.match(pattern, value)
|
||||
if match:
|
||||
env_var, default = match.groups()
|
||||
processed_value = os.environ.get(env_var, default)
|
||||
# 尝试转换为数字
|
||||
if key == "port":
|
||||
try:
|
||||
processed_value = int(processed_value)
|
||||
except (ValueError, TypeError):
|
||||
processed_value = 3306
|
||||
processed_config[key] = processed_value
|
||||
else:
|
||||
processed_config[key] = value
|
||||
|
||||
# 测试连接
|
||||
with pool.get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT 1")
|
||||
cursor.fetchall()
|
||||
|
||||
logger.info(f"数据库连接池初始化成功 ({attempt['desc']})")
|
||||
return pool
|
||||
except Exception as e:
|
||||
error_details = traceback.format_exc()
|
||||
logger.error(f"数据库连接尝试 ({attempt['desc']}) 失败: {e}\n{error_details}")
|
||||
|
||||
logger.warning("所有数据库连接尝试均失败,将使用文件系统作为数据源")
|
||||
return None
|
||||
|
||||
# 创建连接池
|
||||
self.db_pool = pooling.MySQLConnectionPool(
|
||||
pool_name="prompt_pool",
|
||||
pool_size=5,
|
||||
host=processed_config.get("host", "localhost"),
|
||||
user=processed_config.get("user", "root"),
|
||||
password=processed_config.get("password", ""),
|
||||
database=processed_config.get("database", "travel_content"),
|
||||
port=processed_config.get("port", 3306),
|
||||
charset=processed_config.get("charset", "utf8mb4")
|
||||
)
|
||||
logger.info(f"数据库连接池初始化成功,连接到 {processed_config.get('host')}:{processed_config.get('port')}")
|
||||
except Exception as e:
|
||||
logger.error(f"初始化数据库连接池失败: {e}")
|
||||
self.db_pool = None
|
||||
|
||||
def get_style_content(self, style_name: str) -> str:
|
||||
"""获取风格提示词内容"""
|
||||
# 尝试从数据库获取
|
||||
"""
|
||||
获取内容风格提示词
|
||||
|
||||
Args:
|
||||
style_name: 风格名称
|
||||
|
||||
Returns:
|
||||
风格提示词内容
|
||||
"""
|
||||
# 优先从数据库获取
|
||||
if self.db_pool:
|
||||
try:
|
||||
with self.db_pool.get_connection() as conn:
|
||||
cursor = conn.cursor(dictionary=True)
|
||||
cursor.execute(
|
||||
"SELECT description FROM contentStyle WHERE styleName = %s AND isDelete = 0",
|
||||
(style_name,)
|
||||
)
|
||||
result = cursor.fetchone()
|
||||
if result:
|
||||
return result['description']
|
||||
conn = self.db_pool.get_connection()
|
||||
cursor = conn.cursor(dictionary=True)
|
||||
cursor.execute(
|
||||
"SELECT description FROM contentStyle WHERE styleName = %s",
|
||||
(style_name,)
|
||||
)
|
||||
result = cursor.fetchone()
|
||||
cursor.close()
|
||||
conn.close()
|
||||
|
||||
if result:
|
||||
logger.info(f"从数据库获取风格提示词: {style_name}")
|
||||
return result["description"]
|
||||
except Exception as e:
|
||||
logger.error(f"从数据库获取风格提示词失败: {e}")
|
||||
|
||||
# 从文件系统获取
|
||||
logger.info(f"从文件系统获取风格提示词: {style_name}")
|
||||
for dir_path in self.resource_dirs:
|
||||
style_file = ResourceLoader.find_file(os.path.join(dir_path, "styles"), style_name)
|
||||
if style_file:
|
||||
content = ResourceLoader.load_text_file(style_file)
|
||||
if content:
|
||||
return content
|
||||
|
||||
return f"未找到风格 '{style_name}' 的提示词"
|
||||
# 回退到文件系统
|
||||
try:
|
||||
style_paths = self.resource_config.style.paths
|
||||
for path_str in style_paths:
|
||||
try:
|
||||
if style_name in path_str:
|
||||
full_path = self._get_full_path(path_str)
|
||||
if full_path.exists():
|
||||
logger.info(f"从文件系统获取风格提示词: {style_name}")
|
||||
return full_path.read_text('utf-8')
|
||||
except Exception as e:
|
||||
logger.error(f"读取风格文件失败 {path_str}: {e}")
|
||||
|
||||
# 如果没有精确匹配,尝试模糊匹配
|
||||
for path_str in style_paths:
|
||||
try:
|
||||
full_path = self._get_full_path(path_str)
|
||||
if full_path.exists() and full_path.is_file():
|
||||
content = full_path.read_text('utf-8')
|
||||
if style_name.lower() in full_path.stem.lower():
|
||||
logger.info(f"通过模糊匹配找到风格提示词: {style_name} -> {full_path.name}")
|
||||
return content
|
||||
except Exception as e:
|
||||
logger.error(f"读取风格文件失败 {path_str}: {e}")
|
||||
except Exception as e:
|
||||
logger.error(f"获取风格提示词失败: {e}")
|
||||
|
||||
logger.warning(f"未找到风格提示词: {style_name},将使用默认值")
|
||||
return "通用风格"
|
||||
|
||||
def get_audience_content(self, audience_name: str) -> str:
|
||||
"""
|
||||
@ -236,7 +224,7 @@ class PromptService:
|
||||
conn = self.db_pool.get_connection()
|
||||
cursor = conn.cursor(dictionary=True)
|
||||
cursor.execute(
|
||||
"SELECT description FROM scenicSpot WHERE name = %s",
|
||||
"SELECT description FROM scenicSpot WHERE spotName = %s",
|
||||
(spot_name,)
|
||||
)
|
||||
result = cursor.fetchone()
|
||||
@ -283,7 +271,7 @@ class PromptService:
|
||||
conn = self.db_pool.get_connection()
|
||||
cursor = conn.cursor(dictionary=True)
|
||||
cursor.execute(
|
||||
"SELECT description FROM product WHERE name = %s",
|
||||
"SELECT description FROM product WHERE productName = %s",
|
||||
(product_name,)
|
||||
)
|
||||
result = cursor.fetchone()
|
||||
@ -465,7 +453,7 @@ class PromptService:
|
||||
try:
|
||||
conn = self.db_pool.get_connection()
|
||||
cursor = conn.cursor(dictionary=True)
|
||||
cursor.execute("SELECT name as name, description FROM scenicSpot")
|
||||
cursor.execute("SELECT spotName as name, description FROM scenicSpot")
|
||||
results = cursor.fetchall()
|
||||
cursor.close()
|
||||
conn.close()
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
{
|
||||
"host": "localhost",
|
||||
"user": "root",
|
||||
"password": "password",
|
||||
"database": "travel_content",
|
||||
"port": 3306,
|
||||
"charset": "utf8mb4"
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
@ -9,7 +9,7 @@ import json
|
||||
import os
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Dict, Type, TypeVar, Optional, Any, cast, List, Set
|
||||
from typing import Dict, Type, TypeVar, Optional, Any, cast
|
||||
|
||||
from core.config.models import (
|
||||
BaseConfig, AIModelConfig, SystemConfig, GenerateTopicConfig, ResourceConfig,
|
||||
@ -27,30 +27,23 @@ class ConfigManager:
|
||||
负责加载、管理和访问所有配置
|
||||
"""
|
||||
|
||||
# 服务端必要的全局配置
|
||||
SERVER_CONFIGS = {'system', 'ai_model', 'database'}
|
||||
|
||||
# 单次生成任务的配置
|
||||
TASK_CONFIGS = {'topic_gen', 'content_gen', 'poster_gen', 'resource'}
|
||||
|
||||
def __init__(self):
|
||||
self._configs: Dict[str, BaseConfig] = {}
|
||||
self._raw_configs: Dict[str, Dict[str, Any]] = {} # 存储原始配置数据
|
||||
self.config_dir: Optional[Path] = None
|
||||
self.config_objects = {
|
||||
'ai_model': AIModelConfig(),
|
||||
'system': SystemConfig(),
|
||||
'topic_gen': GenerateTopicConfig(),
|
||||
'content_gen': GenerateContentConfig(),
|
||||
'resource': ResourceConfig()
|
||||
}
|
||||
self._loaded_configs: Set[str] = set()
|
||||
|
||||
def load_from_directory(self, config_dir: str, server_mode: bool = False):
|
||||
def load_from_directory(self, config_dir: str):
|
||||
"""
|
||||
从目录加载配置
|
||||
|
||||
Args:
|
||||
config_dir: 配置文件目录
|
||||
server_mode: 是否为服务器模式,如果是则只加载必要的全局配置
|
||||
"""
|
||||
self.config_dir = Path(config_dir)
|
||||
if not self.config_dir.is_dir():
|
||||
@ -61,17 +54,15 @@ class ConfigManager:
|
||||
self._register_configs()
|
||||
|
||||
# 动态加载目录中的所有.json文件
|
||||
self._load_all_configs_from_dir(server_mode)
|
||||
self._load_all_configs_from_dir()
|
||||
|
||||
def _register_configs(self):
|
||||
"""注册所有配置"""
|
||||
self.register_config('ai_model', AIModelConfig)
|
||||
self.register_config('system', SystemConfig)
|
||||
self.register_config('resource', ResourceConfig)
|
||||
|
||||
# 这些配置在服务器模式下不会自动加载,但仍然需要注册类型
|
||||
self.register_config('poster', PosterConfig)
|
||||
self.register_config('content', ContentConfig)
|
||||
self.register_config('resource', ResourceConfig)
|
||||
self.register_config('system', SystemConfig)
|
||||
self.register_config('topic_gen', GenerateTopicConfig)
|
||||
self.register_config('content_gen', GenerateContentConfig)
|
||||
|
||||
@ -122,106 +113,49 @@ class ConfigManager:
|
||||
|
||||
return cast(T, config)
|
||||
|
||||
def get_raw_config(self, name: str) -> Dict[str, Any]:
|
||||
"""
|
||||
获取原始配置数据
|
||||
|
||||
Args:
|
||||
name: 配置名称
|
||||
|
||||
Returns:
|
||||
原始配置数据字典
|
||||
"""
|
||||
if name in self._raw_configs:
|
||||
return self._raw_configs[name]
|
||||
|
||||
# 如果没有原始配置,但有对象配置,则转换为字典
|
||||
if name in self._configs:
|
||||
return self._configs[name].to_dict()
|
||||
|
||||
# 尝试从文件加载
|
||||
if self.config_dir:
|
||||
config_path = self.config_dir / f"{name}.json"
|
||||
if config_path.exists():
|
||||
try:
|
||||
with open(config_path, 'r', encoding='utf-8') as f:
|
||||
raw_config = json.load(f)
|
||||
self._raw_configs[name] = raw_config
|
||||
return raw_config
|
||||
except Exception as e:
|
||||
logger.error(f"加载原始配置 '{name}' 失败: {e}")
|
||||
|
||||
# 返回空字典
|
||||
return {}
|
||||
|
||||
def _load_all_configs_from_dir(self, server_mode: bool = False):
|
||||
"""
|
||||
动态加载目录中的所有.json文件
|
||||
|
||||
Args:
|
||||
server_mode: 是否为服务器模式,如果是则只加载必要的全局配置
|
||||
"""
|
||||
def _load_all_configs_from_dir(self):
|
||||
"""动态加载目录中的所有.json文件"""
|
||||
try:
|
||||
# 遍历并加载目录中所有其他的 .json 文件
|
||||
# 1. 优先加载旧的主配置文件以实现向后兼容
|
||||
main_config_path = self.config_dir / 'poster_gen_config.json'
|
||||
if main_config_path.exists():
|
||||
self._load_main_config(main_config_path)
|
||||
else:
|
||||
logger.warning(f"旧的主配置文件不存在: {main_config_path}")
|
||||
|
||||
# 2. 遍历并加载目录中所有其他的 .json 文件
|
||||
for config_path in self.config_dir.glob('*.json'):
|
||||
if config_path.name == 'poster_gen_config.json':
|
||||
continue # 跳过已处理的主配置文件
|
||||
|
||||
config_name = config_path.stem # 'topic_gen.json' -> 'topic_gen'
|
||||
|
||||
# 服务器模式下,只加载必要的全局配置
|
||||
if server_mode and config_name not in self.SERVER_CONFIGS:
|
||||
logger.info(f"服务器模式下跳过非全局配置: {config_name}")
|
||||
continue
|
||||
|
||||
# 加载原始配置
|
||||
with open(config_path, 'r', encoding='utf-8') as f:
|
||||
config_data = json.load(f)
|
||||
self._raw_configs[config_name] = config_data
|
||||
|
||||
# 更新对象配置
|
||||
if config_name in self._configs:
|
||||
logger.info(f"加载配置文件 '{config_name}': {config_path}")
|
||||
with open(config_path, 'r', encoding='utf-8') as f:
|
||||
config_data = json.load(f)
|
||||
self._configs[config_name].update(config_data)
|
||||
self._loaded_configs.add(config_name)
|
||||
else:
|
||||
logger.info(f"加载原始配置 '{config_name}': {config_path}")
|
||||
logger.warning(f"在 '{config_path}' 中找到的配置 '{config_name}' 没有对应的已注册配置类型,已跳过。")
|
||||
|
||||
# 最后应用环境变量覆盖
|
||||
# 3. 最后应用环境变量覆盖
|
||||
self._apply_env_overrides()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"从目录 '{self.config_dir}' 加载配置失败: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
def load_task_config(self, config_name: str) -> bool:
|
||||
"""
|
||||
按需加载任务配置
|
||||
def _load_main_config(self, path: Path):
|
||||
"""加载主配置文件,并分发到各个配置对象"""
|
||||
logger.info(f"加载主配置文件: {path}")
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
config_data = json.load(f)
|
||||
|
||||
Args:
|
||||
config_name: 配置名称
|
||||
|
||||
Returns:
|
||||
是否成功加载
|
||||
"""
|
||||
if config_name in self._loaded_configs:
|
||||
return True
|
||||
|
||||
if self.config_dir:
|
||||
config_path = self.config_dir / f"{config_name}.json"
|
||||
if config_path.exists():
|
||||
try:
|
||||
with open(config_path, 'r', encoding='utf-8') as f:
|
||||
config_data = json.load(f)
|
||||
self._raw_configs[config_name] = config_data
|
||||
|
||||
if config_name in self._configs:
|
||||
self._configs[config_name].update(config_data)
|
||||
self._loaded_configs.add(config_name)
|
||||
logger.info(f"按需加载任务配置 '{config_name}': {config_path}")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"加载任务配置 '{config_name}' 失败: {e}")
|
||||
|
||||
logger.warning(f"未找到任务配置: {config_name}")
|
||||
return False
|
||||
for name, config_obj in self._configs.items():
|
||||
# 主配置文件可能是扁平结构或嵌套结构
|
||||
if name in config_data: # 嵌套结构
|
||||
config_obj.update(config_data[name])
|
||||
else: # 尝试从根部更新 (扁平结构)
|
||||
config_obj.update(config_data)
|
||||
|
||||
def _apply_env_overrides(self):
|
||||
"""应用环境变量覆盖"""
|
||||
@ -242,10 +176,6 @@ class ConfigManager:
|
||||
|
||||
if update_data:
|
||||
ai_model_config.update(update_data)
|
||||
# 更新原始配置
|
||||
if 'ai_model' in self._raw_configs:
|
||||
for key, value in update_data.items():
|
||||
self._raw_configs['ai_model'][key] = value
|
||||
logger.info(f"通过环境变量更新了AI模型配置: {list(update_data.keys())}")
|
||||
|
||||
def save_config(self, name: str):
|
||||
@ -261,9 +191,6 @@ class ConfigManager:
|
||||
path = self.config_dir / f"{name}.json"
|
||||
config = self.get_config(name, BaseConfig)
|
||||
config_data = config.to_dict()
|
||||
|
||||
# 更新原始配置
|
||||
self._raw_configs[name] = config_data
|
||||
|
||||
try:
|
||||
with open(path, 'w', encoding='utf-8') as f:
|
||||
|
||||
@ -161,8 +161,6 @@ class PosterConfig(BaseConfig):
|
||||
additional_images_enabled: bool = True
|
||||
template_selection: str = "random" # random, business, vibrant, original
|
||||
available_templates: List[str] = Field(default_factory=lambda: ["original", "business", "vibrant"])
|
||||
poster_system_prompt: str = "resource/prompt/generatePoster/system.txt"
|
||||
poster_user_prompt: str = "resource/prompt/generatePoster/user.txt"
|
||||
|
||||
|
||||
class ContentConfig(BaseConfig):
|
||||
|
||||
@ -1,89 +0,0 @@
|
||||
# 旅游内容创作系统数据库
|
||||
|
||||
本文档介绍了旅游内容创作系统的数据库结构和使用方法。
|
||||
|
||||
## 数据库概述
|
||||
|
||||
旅游内容创作系统数据库包含以下主要表:
|
||||
|
||||
- **用户表(user)**: 存储系统用户信息
|
||||
- **素材文件夹表(material_folder)**: 管理素材文件夹结构
|
||||
- **素材表(material)**: 存储各类素材信息
|
||||
- **景区表(scenicSpot)**: 存储景区相关信息
|
||||
- **产品表(product)**: 存储旅游产品信息
|
||||
- **内容风格表(contentStyle)**: 定义内容创作风格
|
||||
- **目标人群表(targetAudience)**: 定义目标受众群体
|
||||
- **选题信息表(topics)**: 存储选题信息
|
||||
- **关联表**: 包括选题-景区、选题-产品、选题-风格、选题-目标受众等关联表
|
||||
|
||||
## 初始化数据库
|
||||
|
||||
使用以下命令初始化数据库并插入模拟数据:
|
||||
|
||||
```bash
|
||||
# 添加执行权限
|
||||
chmod +x db/init_db.sh
|
||||
|
||||
# 执行初始化脚本
|
||||
./db/init_db.sh
|
||||
```
|
||||
|
||||
脚本将创建名为`travel_content`的数据库,创建所有必要的表,并插入模拟数据。
|
||||
|
||||
## 查询数据库
|
||||
|
||||
### 查看数据库信息
|
||||
|
||||
使用以下命令查看数据库的详细信息:
|
||||
|
||||
```bash
|
||||
# 添加执行权限
|
||||
chmod +x db/show_db_info.sh
|
||||
|
||||
# 执行查询脚本
|
||||
./db/show_db_info.sh
|
||||
```
|
||||
|
||||
此脚本将显示数据库中的所有表和主要数据。
|
||||
|
||||
## 数据库模拟数据
|
||||
|
||||
初始化脚本已插入以下模拟数据:
|
||||
|
||||
- **用户**: 3个用户(管理员、张三、李四)
|
||||
- **景区**: 4个景区(黄山风景区、张家界国家森林公园、九寨沟风景区、丽江古城)
|
||||
- **产品**: 4个产品(黄山亲子三日游、张家界避暑五日游、九寨沟摄影六日游、云南民族文化七日游)
|
||||
- **内容风格**: 6种风格(攻略风、清新文艺风、幽默诙谐风、专业严谨风、情感共鸣风、极力推荐风)
|
||||
- **目标受众**: 6种受众(年轻情侣、家庭亲子、中老年群体、商务人士、学生群体、高净值人群)
|
||||
- **选题**: 4个选题(五一黄山亲子游攻略、张家界夏季避暑游、九寨沟秋色摄影指南、云南民族文化探秘之旅)
|
||||
- **素材文件夹**: 5个文件夹
|
||||
- **素材**: 5个素材(图片、视频、文档)
|
||||
|
||||
## 数据库结构
|
||||
|
||||
数据库表之间的关系如下:
|
||||
|
||||
1. 用户(user) 1:N 素材文件夹(material_folder)
|
||||
2. 用户(user) 1:N 素材(material)
|
||||
3. 素材文件夹(material_folder) 1:N 素材(material)
|
||||
4. 用户(user) 1:N 景区(scenicSpot)
|
||||
5. 用户(user) 1:N 产品(product)
|
||||
6. 用户(user) 1:N 选题(topics)
|
||||
7. 选题(topics) N:M 景区(scenicSpot) 通过 topicScenic 关联
|
||||
8. 选题(topics) N:M 产品(product) 通过 topicProduct 关联
|
||||
9. 选题(topics) N:M 内容风格(contentStyle) 通过 topicStyle 关联
|
||||
10. 选题(topics) N:M 目标受众(targetAudience) 通过 topicAudience 关联
|
||||
|
||||
## 模拟数据特点
|
||||
|
||||
根据resource/prompt目录中的信息,模拟数据具有以下特点:
|
||||
|
||||
1. **内容风格**:包含攻略风、清新文艺风等多种风格,参考了resource/prompt/Style目录中的风格提示词
|
||||
2. **目标受众**:根据resource/prompt/Demand目录中的用户画像,细化了各类受众群体的特征
|
||||
3. **选题标题**:添加了emoji表情,使标题更有网感和吸引力
|
||||
4. **产品描述**:更加详细,突出产品特色和目标受众需求
|
||||
5. **时间节点**:参考了resource/prompt/Refer/2025各月节日宣传节点时间表.md,使用2025年的时间节点
|
||||
|
||||
## 密码信息
|
||||
|
||||
所有模拟用户的密码均为: `123456`(MD5加密后存储)
|
||||
@ -1,473 +0,0 @@
|
||||
-- 创建数据库
|
||||
DROP DATABASE IF EXISTS travel_content;
|
||||
CREATE DATABASE IF NOT EXISTS travel_content;
|
||||
USE travel_content;
|
||||
|
||||
-- 创建用户表
|
||||
CREATE TABLE IF NOT EXISTS user (
|
||||
id BIGINT AUTO_INCREMENT COMMENT 'id' PRIMARY KEY,
|
||||
userAccount VARCHAR(256) NOT NULL COMMENT '账号',
|
||||
userPassword VARCHAR(512) NOT NULL COMMENT '密码',
|
||||
userName VARCHAR(256) NULL COMMENT '用户昵称',
|
||||
userAvatar VARCHAR(1024) NULL COMMENT '用户头像',
|
||||
userEmail VARCHAR(255) NULL COMMENT '用户邮箱',
|
||||
userProfile VARCHAR(512) NULL COMMENT '用户简介',
|
||||
userRole VARCHAR(256) DEFAULT 'user' NOT NULL COMMENT '用户角色:user/admin',
|
||||
editTime DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL COMMENT '编辑时间',
|
||||
createTime DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL COMMENT '创建时间',
|
||||
updateTime DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
lastLogin DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL COMMENT '最后登录时间',
|
||||
isDelete TINYINT DEFAULT 0 NOT NULL COMMENT '是否删除',
|
||||
CONSTRAINT uk_userAccount UNIQUE (userAccount),
|
||||
CONSTRAINT userEmail UNIQUE (userEmail)
|
||||
) COMMENT '用户' COLLATE = utf8mb4_unicode_ci;
|
||||
|
||||
CREATE INDEX idx_user_name ON user (userName);
|
||||
|
||||
-- 创建素材文件夹表
|
||||
CREATE TABLE IF NOT EXISTS material_folder (
|
||||
id BIGINT AUTO_INCREMENT COMMENT 'id' PRIMARY KEY,
|
||||
userId BIGINT NOT NULL COMMENT '创建用户id',
|
||||
folderName VARCHAR(256) NOT NULL COMMENT '文件夹名称',
|
||||
parentId BIGINT DEFAULT 0 NOT NULL COMMENT '父文件夹id,0表示根目录',
|
||||
description VARCHAR(512) NULL COMMENT '文件夹描述',
|
||||
isPublic TINYINT DEFAULT 0 NOT NULL COMMENT '是否公开:0私有,1公开',
|
||||
createTime DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL COMMENT '创建时间',
|
||||
updateTime DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
isDelete TINYINT DEFAULT 0 NOT NULL COMMENT '是否删除',
|
||||
CONSTRAINT uk_folder_name_user UNIQUE (folderName, userId, parentId, isDelete)
|
||||
) COMMENT '素材文件夹表' COLLATE = utf8mb4_unicode_ci;
|
||||
|
||||
CREATE INDEX idx_material_folder_parent ON material_folder (parentId);
|
||||
CREATE INDEX idx_material_folder_user ON material_folder (userId);
|
||||
|
||||
-- 创建素材表
|
||||
CREATE TABLE IF NOT EXISTS material (
|
||||
id BIGINT AUTO_INCREMENT COMMENT 'id' PRIMARY KEY,
|
||||
userId BIGINT NOT NULL COMMENT '上传用户id',
|
||||
folderId BIGINT DEFAULT 0 NOT NULL COMMENT '所属文件夹id,0表示根目录',
|
||||
materialName VARCHAR(256) NOT NULL COMMENT '素材名称',
|
||||
materialType VARCHAR(32) NOT NULL COMMENT '素材类型:image/video/document',
|
||||
filePath VARCHAR(1024) NOT NULL COMMENT '文件存储路径',
|
||||
fileSize BIGINT NOT NULL COMMENT '文件大小(字节)',
|
||||
fileFormat VARCHAR(32) NOT NULL COMMENT '文件格式(jpg/png/mp4/pdf/doc等)',
|
||||
fileUrl VARCHAR(1024) NULL COMMENT '文件访问URL',
|
||||
description VARCHAR(512) NULL COMMENT '素材描述',
|
||||
tags VARCHAR(512) NULL COMMENT '素材标签(逗号分隔)',
|
||||
isPublic TINYINT DEFAULT 0 NOT NULL COMMENT '是否公开:0私有,1公开',
|
||||
status VARCHAR(32) DEFAULT 'active' NOT NULL COMMENT '状态:active/inactive/deleted',
|
||||
viewCount INT DEFAULT 0 NOT NULL COMMENT '查看次数',
|
||||
downloadCount INT DEFAULT 0 NOT NULL COMMENT '下载次数',
|
||||
createTime DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL COMMENT '创建时间',
|
||||
updateTime DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
isDelete TINYINT DEFAULT 0 NOT NULL COMMENT '是否删除',
|
||||
CONSTRAINT uk_material_name_user_folder UNIQUE (materialName, userId, folderId)
|
||||
) COMMENT '素材表' COLLATE = utf8mb4_unicode_ci;
|
||||
|
||||
CREATE INDEX idx_material_create_time ON material (createTime);
|
||||
CREATE INDEX idx_material_folder ON material (folderId);
|
||||
CREATE INDEX idx_material_public ON material (isPublic);
|
||||
CREATE INDEX idx_material_status ON material (status);
|
||||
CREATE INDEX idx_material_type ON material (materialType);
|
||||
CREATE INDEX idx_material_user ON material (userId);
|
||||
|
||||
-- 创建景区表
|
||||
CREATE TABLE IF NOT EXISTS scenicSpot (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键id',
|
||||
userId BIGINT NOT NULL COMMENT '用户ID',
|
||||
name VARCHAR(100) NOT NULL COMMENT '景区名称',
|
||||
address VARCHAR(255) NULL COMMENT '地址',
|
||||
trafficInfo VARCHAR(255) NULL COMMENT '交通指南',
|
||||
description TEXT NULL COMMENT '描述',
|
||||
advantage TEXT NULL COMMENT '景区优势',
|
||||
highlight TEXT NULL COMMENT '景区亮点',
|
||||
isPublic TINYINT NOT NULL DEFAULT 0 COMMENT '是否公开(0私有1公开)',
|
||||
isDelete TINYINT NOT NULL DEFAULT 0 COMMENT '是否删除',
|
||||
createTime DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
updateTime DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
|
||||
) COMMENT '景区表' COLLATE = utf8mb4_unicode_ci;
|
||||
|
||||
CREATE INDEX idx_scenicSpot_user ON scenicSpot (userId);
|
||||
CREATE INDEX idx_scenicSpot_name ON scenicSpot (name);
|
||||
CREATE INDEX idx_scenicSpot_public ON scenicSpot (isPublic);
|
||||
|
||||
-- 创建产品表
|
||||
CREATE TABLE IF NOT EXISTS product (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键id',
|
||||
userId BIGINT NOT NULL COMMENT '用户ID',
|
||||
name VARCHAR(100) NOT NULL COMMENT '产品名称',
|
||||
originPrice DECIMAL(10,2) NULL COMMENT '原价',
|
||||
realPrice DECIMAL(10,2) NULL COMMENT '实际价格',
|
||||
packageInfo VARCHAR(255) NULL COMMENT '套票详情',
|
||||
salesPeriod VARCHAR(100) NULL COMMENT '售卖期',
|
||||
stock INT NULL COMMENT '库存',
|
||||
advantage TEXT NULL COMMENT '产品优势',
|
||||
highlight TEXT NULL COMMENT '产品亮点',
|
||||
description TEXT NULL COMMENT '产品描述',
|
||||
isPublic TINYINT NOT NULL DEFAULT 0 COMMENT '是否公开(0私有1公开)',
|
||||
isDelete TINYINT NOT NULL DEFAULT 0 COMMENT '是否删除',
|
||||
createTime DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
updateTime DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
|
||||
) COMMENT '产品表' COLLATE = utf8mb4_unicode_ci;
|
||||
|
||||
CREATE INDEX idx_product_user ON product (userId);
|
||||
CREATE INDEX idx_product_name ON product (name);
|
||||
CREATE INDEX idx_product_public ON product (isPublic);
|
||||
|
||||
-- 创建内容风格表
|
||||
CREATE TABLE IF NOT EXISTS contentStyle (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键id',
|
||||
styleName VARCHAR(100) NOT NULL COMMENT '风格名称',
|
||||
description TEXT NULL COMMENT '描述',
|
||||
isDelete TINYINT NOT NULL DEFAULT 0 COMMENT '是否删除',
|
||||
createTime DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
updateTime DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
CONSTRAINT uk_styleName UNIQUE (styleName, isDelete)
|
||||
) COMMENT '内容风格表' COLLATE = utf8mb4_unicode_ci;
|
||||
|
||||
CREATE INDEX idx_contentStyle_name ON contentStyle (styleName);
|
||||
|
||||
-- 创建目标人群表
|
||||
CREATE TABLE IF NOT EXISTS targetAudience (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT 'id',
|
||||
audienceName VARCHAR(100) NOT NULL COMMENT '受众名称',
|
||||
description TEXT NULL COMMENT '描述',
|
||||
isDelete TINYINT DEFAULT 0 NOT NULL COMMENT '是否删除',
|
||||
createTime DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL COMMENT '创建时间',
|
||||
updateTime DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
CONSTRAINT uk_audienceName UNIQUE (audienceName, isDelete)
|
||||
) COMMENT '目标人群表' COLLATE = utf8mb4_unicode_ci;
|
||||
|
||||
CREATE INDEX idx_targetAudience_name ON targetAudience (audienceName);
|
||||
|
||||
-- 创建文档解析表
|
||||
CREATE TABLE IF NOT EXISTS documentParse (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键id',
|
||||
scenicName VARCHAR(100) NULL COMMENT '景区名称',
|
||||
address VARCHAR(255) NULL COMMENT '地址',
|
||||
trafficInfo VARCHAR(255) NULL COMMENT '交通指南',
|
||||
description TEXT NULL COMMENT '描述',
|
||||
advantage TEXT NULL COMMENT '景区优势',
|
||||
highlight TEXT NULL COMMENT '景区亮点',
|
||||
productName VARCHAR(100) NULL COMMENT '产品名称',
|
||||
originPrice DECIMAL(10,2) NULL COMMENT '原价',
|
||||
realPrice DECIMAL(10,2) NULL COMMENT '实际价格',
|
||||
packageInfo VARCHAR(255) NULL COMMENT '套票详情',
|
||||
salesPeriod VARCHAR(100) NULL COMMENT '售卖期',
|
||||
stock INT NULL COMMENT '库存',
|
||||
productAdvantage TEXT NULL COMMENT '产品优势',
|
||||
productHighlight TEXT NULL COMMENT '产品亮点',
|
||||
productDescription TEXT NULL COMMENT '产品描述',
|
||||
isDelete TINYINT NOT NULL DEFAULT 0 COMMENT '是否删除',
|
||||
createTime DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
updateTime DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
|
||||
) COMMENT '文档解析表' COLLATE = utf8mb4_unicode_ci;
|
||||
|
||||
CREATE INDEX idx_documentParse_scenicName ON documentParse (scenicName);
|
||||
CREATE INDEX idx_documentParse_productName ON documentParse (productName);
|
||||
|
||||
-- 创建选题信息表
|
||||
CREATE TABLE IF NOT EXISTS topics (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键id',
|
||||
userId BIGINT NOT NULL COMMENT '用户ID',
|
||||
title VARCHAR(100) NOT NULL COMMENT '选题标题',
|
||||
description VARCHAR(255) NULL COMMENT '选题描述',
|
||||
logic TEXT NULL COMMENT '选题逻辑',
|
||||
productLogic TEXT NULL COMMENT '产品选择逻辑',
|
||||
styleLogic TEXT NULL COMMENT '风格选择逻辑',
|
||||
targetAudienceLogic TEXT NULL COMMENT '目标受众选择逻辑',
|
||||
publicationDates TEXT NULL COMMENT '计划发布日期(如"2025-05-01,2025-05-03"或JSON)',
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'draft' COMMENT '状态',
|
||||
isLocked TINYINT NOT NULL DEFAULT 0 COMMENT '是否锁定',
|
||||
isDelete TINYINT NOT NULL DEFAULT 0 COMMENT '是否删除',
|
||||
createTime DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
updateTime DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
|
||||
) COMMENT '选题信息表' COLLATE = utf8mb4_unicode_ci;
|
||||
|
||||
CREATE INDEX idxTopicsUser ON topics (userId);
|
||||
CREATE INDEX idxTopicsTitle ON topics (title);
|
||||
CREATE INDEX idxTopicsStatus ON topics (status);
|
||||
|
||||
-- 创建选题-景区关联表
|
||||
CREATE TABLE IF NOT EXISTS topicScenic (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键id',
|
||||
topicId BIGINT NOT NULL COMMENT '选题ID',
|
||||
scenicId BIGINT NOT NULL COMMENT '景区ID'
|
||||
) COMMENT '选题-景区关联表' COLLATE = utf8mb4_unicode_ci;
|
||||
|
||||
CREATE INDEX idx_topic_scenic_topicId ON topicScenic (topicId);
|
||||
CREATE INDEX idx_topic_scenic_scenicId ON topicScenic (scenicId);
|
||||
|
||||
-- 创建选题-产品关联表
|
||||
CREATE TABLE IF NOT EXISTS topicProduct (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键id',
|
||||
topicId BIGINT NOT NULL COMMENT '选题ID',
|
||||
productId BIGINT NOT NULL COMMENT '产品ID'
|
||||
) COMMENT '选题-产品关联表' COLLATE = utf8mb4_unicode_ci;
|
||||
|
||||
CREATE INDEX idx_topic_product_topicId ON topicProduct (topicId);
|
||||
CREATE INDEX idx_topic_product_productId ON topicProduct (productId);
|
||||
|
||||
-- 创建选题-风格关联表
|
||||
CREATE TABLE IF NOT EXISTS topicStyle (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键id',
|
||||
topicId BIGINT NOT NULL COMMENT '选题ID',
|
||||
styleId BIGINT NOT NULL COMMENT '风格ID'
|
||||
) COMMENT '选题-风格关联表' COLLATE = utf8mb4_unicode_ci;
|
||||
|
||||
CREATE INDEX idx_topic_style_topicId ON topicStyle (topicId);
|
||||
CREATE INDEX idx_topic_style_styleId ON topicStyle (styleId);
|
||||
|
||||
-- 创建选题-目标受众关联表
|
||||
CREATE TABLE IF NOT EXISTS topicAudience (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键id',
|
||||
topicId BIGINT NOT NULL COMMENT '选题ID',
|
||||
audienceId BIGINT NOT NULL COMMENT '受众ID'
|
||||
) COMMENT '选题-目标受众关联表' COLLATE = utf8mb4_unicode_ci;
|
||||
|
||||
CREATE INDEX idx_topic_audience_topicId ON topicAudience (topicId);
|
||||
CREATE INDEX idx_topic_audience_audienceId ON topicAudience (audienceId);
|
||||
|
||||
-- 添加外键约束
|
||||
ALTER TABLE material_folder
|
||||
ADD CONSTRAINT fk_material_folder_user
|
||||
FOREIGN KEY (userId) REFERENCES user (id)
|
||||
ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE material
|
||||
ADD CONSTRAINT fk_material_user
|
||||
FOREIGN KEY (userId) REFERENCES user (id)
|
||||
ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE material
|
||||
ADD CONSTRAINT fk_material_folder
|
||||
FOREIGN KEY (folderId) REFERENCES material_folder (id)
|
||||
ON DELETE CASCADE;
|
||||
|
||||
-- 插入模拟数据
|
||||
|
||||
-- 插入用户数据
|
||||
INSERT INTO user (userAccount, userPassword, userName, userAvatar, userEmail, userProfile, userRole)
|
||||
VALUES
|
||||
('admin', 'e10adc3949ba59abbe56e057f20f883e', '管理员', 'https://example.com/avatar1.jpg', 'admin@example.com', '系统管理员', 'admin'),
|
||||
('user1', 'e10adc3949ba59abbe56e057f20f883e', '张三', 'https://example.com/avatar2.jpg', 'zhangsan@example.com', '旅游内容创作者', 'user'),
|
||||
('user2', 'e10adc3949ba59abbe56e057f20f883e', '李四', 'https://example.com/avatar3.jpg', 'lisi@example.com', '资深旅游策划', 'user');
|
||||
|
||||
-- 插入素材文件夹数据
|
||||
INSERT INTO material_folder (userId, folderName, parentId, description, isPublic)
|
||||
VALUES
|
||||
(1, '景区图片', 0, '各景区精选图片', 1),
|
||||
(1, '产品介绍', 0, '各产品介绍素材', 1),
|
||||
(2, '我的素材', 0, '个人素材库', 0),
|
||||
(2, '风景照片', 3, '各地风景照片', 0),
|
||||
(3, '宣传材料', 0, '宣传用素材', 0);
|
||||
|
||||
-- 插入素材数据
|
||||
INSERT INTO material (userId, folderId, materialName, materialType, filePath, fileSize, fileFormat, fileUrl, description, tags, isPublic)
|
||||
VALUES
|
||||
(1, 1, '黄山日出', 'image', '/uploads/huangshan_sunrise.jpg', 2048000, 'jpg', 'https://example.com/huangshan_sunrise.jpg', '黄山日出美景', '黄山,日出,风景', 1),
|
||||
(1, 1, '张家界全景', 'image', '/uploads/zhangjiajie_panorama.jpg', 3072000, 'jpg', 'https://example.com/zhangjiajie_panorama.jpg', '张家界全景图', '张家界,全景,自然', 1),
|
||||
(1, 2, '三亚双飞5日游', 'document', '/uploads/sanya_5days.pdf', 1024000, 'pdf', 'https://example.com/sanya_5days.pdf', '三亚双飞5日游产品介绍', '三亚,旅游产品,海滩', 1),
|
||||
(2, 4, '九寨沟瀑布', 'image', '/uploads/jiuzhaigou_waterfall.jpg', 2560000, 'jpg', 'https://example.com/jiuzhaigou_waterfall.jpg', '九寨沟瀑布特写', '九寨沟,瀑布,自然风光', 0),
|
||||
(3, 5, '丽江古城宣传片', 'video', '/uploads/lijiang_promo.mp4', 102400000, 'mp4', 'https://example.com/lijiang_promo.mp4', '丽江古城宣传视频', '丽江,古城,文化,宣传', 0);
|
||||
|
||||
-- 插入景区数据
|
||||
INSERT INTO scenicSpot (userId, name, address, trafficInfo, description, advantage, highlight, isPublic)
|
||||
VALUES
|
||||
(1, '黄山风景区', '安徽省黄山市黄山区汤口镇', '可乘坐高铁到黄山北站,再转乘景区大巴', '黄山是中国著名的山岳景区,以奇松、怪石、云海、温泉"四绝"闻名于世。', '国家5A级景区,世界文化与自然双遗产,云海日出景观壮观', '云海日出、迎客松、温泉、奇松怪石', 1),
|
||||
(1, '张家界国家森林公园', '湖南省张家界市武陵源区', '可乘坐飞机到张家界荷花机场,再转乘景区大巴', '张家界国家森林公园是中国第一个国家森林公园,以峰林地貌为主要特色。', '阿凡达取景地,独特的石柱地貌,夏季凉爽避暑胜地', '天子山、袁家界、金鞭溪、玻璃栈道', 1),
|
||||
(2, '九寨沟风景区', '四川省阿坝藏族羌族自治州九寨沟县', '可乘坐飞机到九黄机场,再转乘景区大巴', '九寨沟以彩池、瀑布、雪山、森林"四绝"闻名于世。', '水景资源丰富,色彩斑斓的湖泊,秋季层林尽染', '五花海、诺日朗瀑布、长海、树正群海', 0),
|
||||
(3, '丽江古城', '云南省丽江市古城区', '可乘坐飞机到丽江三义机场,再乘坐出租车前往古城', '丽江古城是一座历史文化名城,是中国为数不多的保存相当完好的少数民族古城。', '纳西族文化,古城风貌保存完好,民族风情浓郁', '四方街、木府、黑龙潭、万古楼', 1);
|
||||
|
||||
-- 插入产品数据
|
||||
INSERT INTO product (userId, name, originPrice, realPrice, packageInfo, salesPeriod, stock, advantage, highlight, description, isPublic)
|
||||
VALUES
|
||||
(1, '黄山亲子三日游', 2199.00, 1899.00, '含往返交通、住宿、门票、导游', '2025-01-01至2025-12-31', 1000, '一价全包,无隐形消费,亲子互动环节丰富', '登顶观日出,泡温泉,亲子科普活动', '黄山三日亲子游,含住宿、餐饮、交通、门票等,专业导游全程陪同,设计多个亲子互动环节,让孩子在游玩中学习自然知识。', 1),
|
||||
(1, '张家界避暑五日游', 3999.00, 3599.00, '含往返机票、住宿、门票、导游', '2025-06-01至2025-08-31', 800, '全程五星酒店,专车接送,夏季避暑胜地', '玻璃栈道,天门山索道,森林氧吧', '张家界五日避暑游,畅游武陵源核心景区,体验玻璃栈道惊险刺激,享受平均气温仅22℃的夏日清凉。', 1),
|
||||
(2, '九寨沟摄影六日游', 5299.00, 4899.00, '含往返机票、住宿、门票、导游', '2025-09-15至2025-10-31', 500, '一次游两大世界遗产地,摄影师专业指导', '九寨沟彩池,黄龙钙化池,摄影技巧培训', '九寨沟+黄龙六日摄影专线,资深摄影师全程指导,选择最佳拍摄时间和机位,捕捉秋季九寨最绚丽的色彩。', 0),
|
||||
(3, '云南民族文化七日游', 6299.00, 5899.00, '含往返机票、住宿、门票、导游', '2025-01-01至2025-12-31', 600, '深度体验云南民族文化,高端精品小团', '丽江古城,洱海环湖,民族村寨体验', '云南丽江+大理七日文化之旅,体验纳西族和白族文化,参与非遗手工艺制作,聆听东巴文化讲解,入住丽江古城内精品客栈。', 1);
|
||||
|
||||
-- 插入内容风格数据
|
||||
INSERT INTO contentStyle (styleName, description)
|
||||
VALUES
|
||||
('攻略风', '你是景区小红书爆款文案策划,你将根据要求创作爆款文案,根据以下规则一步步执行:
|
||||
一、标题创作
|
||||
步骤:先速览景区资料内容,找到与用户需求适配的景点亮点,然后通读用户给的爆款标题素材,挑选一个在用户画像+节点节日+标题吸睛三个方面最契合的进行改写,创作出全新的爆款标题。在此期间以下几点需要你遵循:
|
||||
1.必加1个emoji,标题字数18字以内
|
||||
2.有网感,结合所在地
|
||||
3.标题必须结合我给出的<标题参考格式>进行高相似度仿写
|
||||
|
||||
二、正文创作
|
||||
注意:正文以<你>这种有人味的人称代词视角创作输出,不要出现<宝子们><姐妹们>这些很假的称呼
|
||||
1. 通过了解产品和当前节点需求,如情人节要约会、儿童节要带孩子玩、临近周末会考虑周边游、传统节日偏好民俗活动等以此类推,直击用户痛点。
|
||||
2. 正文围绕所选定的风格提示词进行创作,有场景感,让人感觉是亲身体验后的推荐,有一种和用户沟通的感觉,语言平实靠谱娓娓道来
|
||||
3. 正文部分可以参考我给你的<正文范文参考>文档,结合所给景区/民宿资料进行改写创作
|
||||
4. 分段+分点论述,巧用emoji,积极换行,让整体排版更好看
|
||||
5. 如果用户给出范文案例,请参考正文范文参考案例仿写
|
||||
|
||||
三、正文案例
|
||||
‼️新疆旅行黑红榜速报‼️
|
||||
亲测6大封神景点🆚6个本地人翻白眼雷区
|
||||
避坑指南已备好✅
|
||||
这个五一,用7天解锁新疆的N种神仙打开方式
|
||||
打工人必看👉「不请假不暴走」小众路线
|
||||
每天随便躺玩就能出片!
|
||||
-
|
||||
🌤【天气预警】
|
||||
5月新疆:5°C~20°C(早晚穿羽绒,中午穿吊带)
|
||||
⚠紫外线堪比军训!防晒霜+墨镜+帽子三件套焊死!
|
||||
-
|
||||
🧳【行李清单】
|
||||
✅证件:身份证+驾照(自驾党必带)
|
||||
✅保命穿搭:冲锋衣+白裙子+工装靴(温差15℃的叠穿美学)
|
||||
✅急救包:肠胃yao+晕车yao+保湿面膜(一天五顿碳水+干燥警告)
|
||||
-
|
||||
✈【机场选择指南】
|
||||
▪首飞选乌鲁木齐!北疆环线起点,租车/拼团超方便
|
||||
▪伊宁机场:适合只玩伊犁的姐妹,落地直冲赛里木湖
|
||||
-
|
||||
🚗【7天6晚游玩路线】
|
||||
Day1⃣:出发地-乌鲁木齐
|
||||
Day2⃣:乌鲁木齐-天山天池-奎屯
|
||||
Day3⃣:奎屯-赛里木湖-伊宁
|
||||
Day4⃣:伊宁-库尔德宁-那拉提
|
||||
Day5⃣:那拉提-空中草原-精河/博乐
|
||||
Day6⃣:博乐-独山子大峡谷-乌鲁木齐
|
||||
Day7⃣:乌鲁木齐-温馨的家
|
||||
-
|
||||
🍖【新疆美食清单】
|
||||
🔥必吃Top3:
|
||||
▪沙湾大盘鸡(加份皮带面!)
|
||||
▪和田街烤包子(酥到掉渣爆汁)
|
||||
▪精河过油肉拌面(本地人私藏小店)
|
||||
💣避雷:景区门口的“天价”羊肉串
|
||||
-
|
||||
⚠【血泪经验Tips】
|
||||
🔥 温差刺客:薄羽绒+草帽+墨镜,5月草原早晚温差15℃,中午晒到穿吊带!
|
||||
🔥 信号消失术:赛湖、库尔德宁山区常无信号,建议常备现金。
|
||||
🔥 防晒保命:紫外线比三亚狠!面罩焊脸上,防晒霜2小时补一次。
|
||||
🔥 出片玄学:赛湖穿白裙子,那拉提配牛仔帽,独山子穿工装裤,谁拍谁封神!
|
||||
-
|
||||
📸【出片玄学指南】
|
||||
✔赛湖穿白裙+奔跑,拍出“海蒂和爷爷”电影感
|
||||
✔那拉提网红桥早起抢机位,下午人从众
|
||||
✔独山子穿卡其工装,秒变西部牛仔
|
||||
❗PS:带1TB胃和256G手机内存,美景美食根本停不下来!
|
||||
-
|
||||
|
||||
四、TAG标签创作
|
||||
每一篇笔记需要携带9个TAG标签,以下举例的加号(+)这个符号不要体现,仅代表内容连接的意思
|
||||
1.前三个TAG标签的内容围绕<#产品所在地区+旅游><#产品所在省份+旅游><#产品所在地区+周边游><#产品周边一线城市+周边游>
|
||||
2.接着三个TAG标签的内容围绕<#景区/酒店名称><#景区/酒店名称+攻略><#景区/酒店名称+推荐>
|
||||
3.最后三个TAG标签的内容围绕<#周末去哪儿玩><#小众旅游地><##产品所在地区+周末好去处>
|
||||
注意:以上TAG直接按要求直出内容,不需要在直出的时候进行分类
|
||||
'),
|
||||
('极力推荐风', '你是景区小红书爆款文案策划,你将根据要求创作爆款文案,根据以下规则一步步执行:
|
||||
一、标题创作
|
||||
步骤:先速览景区资料内容,找到与用户需求适配的景点亮点,然后通读用户给的爆款标题素材,挑选一个在用户画像+节点节日+标题吸睛三个方面最契合的进行改写,创作出全新的爆款标题。在此期间以下几点需要你遵循:
|
||||
1.必加emoji
|
||||
2.标题爆破式句,有网感
|
||||
3.标题字数不超过10个字
|
||||
|
||||
二、正文创作法
|
||||
1. 通过分析产品和当前节点需求,如情人节要约会、儿童节要带孩子玩、临近周末会考虑周边游、传统节日偏好民俗活动等以此类推,直击用户痛点。
|
||||
2. 正文不要出现过多比喻句,表达风格更朴实一点,不要过度联想
|
||||
3. 情绪饱满,推荐时要始终面向人群需求结合
|
||||
4. 内容需要条理清晰,分段论述,总分总结构结构,开头吸引+中间详细攻略+细节补充+总结推荐
|
||||
5. 如果用户给出范文案例,请参考范文案例的写作风格和内容
|
||||
6. 针对用户的痛点和需求对正文内容和产品内容的推荐进行优化,可参考我给出的用户文旅需求的材料
|
||||
'),
|
||||
|
||||
-- 插入目标人群数据
|
||||
INSERT INTO targetAudience (audienceName, description)
|
||||
VALUES
|
||||
('亲子向', '亲子向用户画像:
|
||||
1. 基本属性
|
||||
- 年龄:25-45岁(家长群体),孩子年龄集中在3-12岁.
|
||||
- 性别:女性主导型,约70%为妈妈群体.
|
||||
|
||||
2. 平台行为
|
||||
- 互动习惯:偏好收藏实用攻略.
|
||||
- 发布内容:分享带娃旅行的温馨瞬间
|
||||
|
||||
3.中国旅游出游做攻略高峰点:
|
||||
1. 春节假期:1-2月
|
||||
2. 暑假:6-8月
|
||||
3. 国庆黄金周:9-10月初
|
||||
4. 五一假期:4-5月初
|
||||
5. 元旦:12月中下旬
|
||||
6.清明:4月初
|
||||
7.端午:5-6月
|
||||
8.中秋:8月底-9月
|
||||
9.寒假:12-2月
|
||||
'),
|
||||
('周边游', '周边游用户画像:
|
||||
1. 基本属性
|
||||
- 年龄:全龄覆盖(主要围绕三天内的短期假期出游需求和周末出游需求).
|
||||
|
||||
2. 平台行为
|
||||
- 互动习惯:高频搜索「周末去哪」「短途游攻略」,收藏本地美食地图,周末前3天搜索量激增,关注"(城市名称)1小时可达"标签.
|
||||
- 发布内容:分享城市周边秘境、农家乐体验、短途自驾游记,高频使用"说走就走""逃离城市"等关键词.
|
||||
|
||||
3. 消费特征
|
||||
- 决策偏好:注重便利性与性价比,自驾游便利、公共交通便利、高铁几小时以内等出行便利是优势.
|
||||
- 消费倾向:倾向购买景区联票(含停车+餐饮+体验项目打包).
|
||||
- 消费品类:民宿套餐、采摘体验、古镇一日游,近郊农场/房车营地/文创市集/夜游项目.
|
||||
|
||||
4. 兴趣偏好
|
||||
- 偏好:轻旅行(露营、骑行)、季节限定活动(赏樱、滑雪)
|
||||
|
||||
5.中国旅游出游做攻略高峰点:
|
||||
1. 春节假期:1-2月
|
||||
2. 暑假:6-8月
|
||||
3. 国庆黄金周:9-10月初
|
||||
4. 五一假期:4-5月初
|
||||
5. 元旦:12月中下旬
|
||||
6.清明:4月初
|
||||
7.端午:5-6月
|
||||
8.中秋:8月底-9月
|
||||
9.寒假:12-2月
|
||||
'),
|
||||
|
||||
-- 插入选题信息数据
|
||||
INSERT INTO topics (userId, title, description, logic, productLogic, styleLogic, targetAudienceLogic, publicationDates, status)
|
||||
VALUES
|
||||
(1, '五一黄山亲子游攻略🏞️', '五一假期黄山亲子游全攻略', '针对五一假期黄山旅游人流量大的情况,提供亲子游实用攻略', '推荐黄山亲子三日游产品,错峰出行,亲子互动环节丰富', '采用攻略风风格,直接明了,提供详细实用信息', '主要面向家庭亲子群体,提供亲子互动和安全提示', '2025-04-15,2025-04-20,2025-04-25', 'published'),
|
||||
(1, '张家界夏季避暑游🌲', '夏季张家界避暑旅游指南', '针对夏季炎热天气,推荐张家界作为避暑胜地,平均气温仅22℃', '推荐张家界避暑五日游产品,强调凉爽体验和夏季特色活动', '采用清新文艺风格,突出自然之美和凉爽体验', '主要面向家庭亲子和学生群体,暑期出游首选', '2025-06-01,2025-06-15,2025-07-01', 'draft'),
|
||||
(2, '九寨沟秋色摄影指南📸', '九寨沟秋季摄影攻略', '针对九寨沟秋季绝美景色,提供专业摄影指导和最佳拍摄点', '推荐九寨沟摄影六日游产品,强调最佳拍摄时机和专业指导', '采用专业严谨风格,提供专业摄影知识和技巧', '主要面向摄影爱好者和年轻情侣,追求高质量照片', '2025-09-01,2025-09-15', 'draft'),
|
||||
(3, '云南民族文化探秘之旅🏮', '探索云南多元民族文化', '深入探索云南多民族文化特色,体验不同民族风情和非遗文化', '推荐云南民族文化七日游产品,强调文化体验和民族特色', '采用情感共鸣风格,讲述动人故事和文化传承', '主要面向文化爱好者、高净值人群和学生群体', '2025-07-01,2025-07-15', 'published');
|
||||
|
||||
-- 插入选题-景区关联数据
|
||||
INSERT INTO topicScenic (topicId, scenicId)
|
||||
VALUES
|
||||
(1, 1),
|
||||
(2, 2),
|
||||
(3, 3),
|
||||
(4, 4);
|
||||
|
||||
-- 插入选题-产品关联数据
|
||||
INSERT INTO topicProduct (topicId, productId)
|
||||
VALUES
|
||||
(1, 1),
|
||||
(2, 2),
|
||||
(3, 3),
|
||||
(4, 4);
|
||||
|
||||
-- 插入选题-风格关联数据
|
||||
INSERT INTO topicStyle (topicId, styleId)
|
||||
VALUES
|
||||
(1, 1),
|
||||
(2, 2),
|
||||
(3, 4),
|
||||
(4, 5);
|
||||
|
||||
-- 插入选题-目标受众关联数据
|
||||
INSERT INTO topicAudience (topicId, audienceId)
|
||||
VALUES
|
||||
(1, 2),
|
||||
(2, 2),
|
||||
(2, 5),
|
||||
(3, 1),
|
||||
(3, 6),
|
||||
(4, 5),
|
||||
(4, 6);
|
||||
103
db/init_db.sh
103
db/init_db.sh
@ -1,103 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "开始初始化旅游内容创作系统数据库..."
|
||||
|
||||
# 检查MySQL是否安装
|
||||
if ! command -v mysql &> /dev/null; then
|
||||
echo "错误: MySQL未安装,请先安装MySQL"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 询问MySQL root密码
|
||||
read -sp "请输入MySQL root密码(如果没有设置请直接回车): " MYSQL_ROOT_PASSWORD
|
||||
echo ""
|
||||
|
||||
# 创建环境变量配置文件
|
||||
cat > .env.db <<EOF
|
||||
# 数据库环境变量配置
|
||||
DB_HOST=localhost
|
||||
DB_USER=travel_user
|
||||
DB_PASSWORD=travel123
|
||||
DB_NAME=travel_content
|
||||
DB_PORT=3306
|
||||
EOF
|
||||
|
||||
echo "已创建环境变量配置文件 .env.db"
|
||||
echo "可以通过以下命令加载环境变量:"
|
||||
echo "source .env.db"
|
||||
|
||||
# 自动加载环境变量
|
||||
source .env.db
|
||||
echo "已自动加载环境变量"
|
||||
|
||||
# 根据是否有密码使用不同的命令执行SQL脚本
|
||||
echo "执行SQL脚本创建数据库和表..."
|
||||
if [ -z "$MYSQL_ROOT_PASSWORD" ]; then
|
||||
# 无密码
|
||||
mysql -u root <<EOF
|
||||
-- 创建数据库
|
||||
CREATE DATABASE IF NOT EXISTS travel_content DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- 创建用户并授权
|
||||
CREATE USER IF NOT EXISTS 'travel_user'@'localhost' IDENTIFIED BY 'travel123';
|
||||
GRANT ALL PRIVILEGES ON travel_content.* TO 'travel_user'@'localhost';
|
||||
GRANT ALL PRIVILEGES ON travel_content.* TO 'root'@'localhost';
|
||||
|
||||
-- 刷新权限
|
||||
FLUSH PRIVILEGES;
|
||||
|
||||
-- 使用数据库
|
||||
USE travel_content;
|
||||
|
||||
-- 执行完整的SQL脚本
|
||||
source db/init_database.sql;
|
||||
EOF
|
||||
else
|
||||
# 有密码
|
||||
mysql -u root -p"$MYSQL_ROOT_PASSWORD" <<EOF
|
||||
-- 创建数据库
|
||||
CREATE DATABASE IF NOT EXISTS travel_content DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- 创建用户并授权
|
||||
CREATE USER IF NOT EXISTS 'travel_user'@'localhost' IDENTIFIED BY 'travel123';
|
||||
GRANT ALL PRIVILEGES ON travel_content.* TO 'travel_user'@'localhost';
|
||||
GRANT ALL PRIVILEGES ON travel_content.* TO 'root'@'localhost';
|
||||
|
||||
-- 刷新权限
|
||||
FLUSH PRIVILEGES;
|
||||
|
||||
-- 使用数据库
|
||||
USE travel_content;
|
||||
|
||||
-- 执行完整的SQL脚本
|
||||
source db/init_database.sql;
|
||||
EOF
|
||||
fi
|
||||
|
||||
# 检查执行结果
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "数据库初始化成功!"
|
||||
echo "数据库名称: travel_content"
|
||||
echo "用户: travel_user (密码: travel123)"
|
||||
|
||||
# 显示表信息
|
||||
echo "已创建表:"
|
||||
mysql -u travel_user -ptravel123 -e "USE travel_content; SHOW TABLES;"
|
||||
|
||||
# 更新配置文件
|
||||
cat > config/database.json <<EOF
|
||||
{
|
||||
"host": "\${DB_HOST:-localhost}",
|
||||
"user": "\${DB_USER:-travel_user}",
|
||||
"password": "\${DB_PASSWORD:-travel123}",
|
||||
"database": "\${DB_NAME:-travel_content}",
|
||||
"port": "\${DB_PORT:-3306}",
|
||||
"charset": "utf8mb4"
|
||||
}
|
||||
EOF
|
||||
echo "已更新数据库配置文件"
|
||||
else
|
||||
echo "数据库初始化失败,请检查错误信息"
|
||||
fi
|
||||
|
||||
echo "初始化完成"
|
||||
@ -1,44 +0,0 @@
|
||||
USE travel_content;
|
||||
|
||||
-- 查询选题的完整信息,包括关联的景区、产品、风格和目标受众
|
||||
SELECT
|
||||
CONCAT('【', t.id, '】') AS '选题ID',
|
||||
t.title AS '选题标题',
|
||||
t.description AS '选题描述',
|
||||
CASE
|
||||
WHEN t.status = 'published' THEN '已发布'
|
||||
WHEN t.status = 'draft' THEN '草稿'
|
||||
ELSE t.status
|
||||
END AS '状态',
|
||||
u.userName AS '创建者',
|
||||
s.name AS '关联景区',
|
||||
p.name AS '关联产品',
|
||||
CONCAT(p.originPrice, ' → ', p.realPrice) AS '价格(原价→实际)',
|
||||
cs.styleName AS '内容风格',
|
||||
GROUP_CONCAT(DISTINCT ta.audienceName SEPARATOR '、') AS '目标受众'
|
||||
FROM
|
||||
topics t
|
||||
JOIN
|
||||
user u ON t.userId = u.id
|
||||
JOIN
|
||||
topicScenic ts ON t.id = ts.topicId
|
||||
JOIN
|
||||
scenicSpot s ON ts.scenicId = s.id
|
||||
JOIN
|
||||
topicProduct tp ON t.id = tp.topicId
|
||||
JOIN
|
||||
product p ON tp.productId = p.id
|
||||
JOIN
|
||||
topicStyle tst ON t.id = tst.topicId
|
||||
JOIN
|
||||
contentStyle cs ON tst.styleId = cs.id
|
||||
LEFT JOIN
|
||||
topicAudience taud ON t.id = taud.topicId
|
||||
LEFT JOIN
|
||||
targetAudience ta ON taud.audienceId = ta.id
|
||||
WHERE
|
||||
t.isDelete = 0
|
||||
GROUP BY
|
||||
t.id, t.title, t.description, t.status, u.userName, s.name, p.name, p.originPrice, p.realPrice, cs.styleName
|
||||
ORDER BY
|
||||
t.status = 'published' DESC, t.id;
|
||||
@ -1,87 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "===== 旅游内容创作系统数据库信息 ====="
|
||||
echo ""
|
||||
|
||||
echo "【1】数据库表列表:"
|
||||
mysql -u root -e "USE travel_content; SHOW TABLES;"
|
||||
echo ""
|
||||
|
||||
echo "【2】用户信息:"
|
||||
mysql -u root -e "USE travel_content; SELECT id, userAccount, userName, userRole, userEmail FROM user;"
|
||||
echo ""
|
||||
|
||||
echo "【3】景区信息:"
|
||||
mysql -u root -e "USE travel_content; SELECT id, name, address, advantage, highlight, isPublic FROM scenicSpot;"
|
||||
echo ""
|
||||
|
||||
echo "【4】产品信息:"
|
||||
mysql -u root -e "USE travel_content; SELECT id, name, originPrice, realPrice, packageInfo, advantage FROM product;"
|
||||
echo ""
|
||||
|
||||
echo "【5】内容风格:"
|
||||
mysql -u root -e "USE travel_content; SELECT id, styleName, description FROM contentStyle;"
|
||||
echo ""
|
||||
|
||||
echo "【6】目标受众:"
|
||||
mysql -u root -e "USE travel_content; SELECT id, audienceName, description FROM targetAudience;"
|
||||
echo ""
|
||||
|
||||
echo "【7】选题信息:"
|
||||
mysql -u root -e "USE travel_content; SELECT id, title, description, status FROM topics;"
|
||||
echo ""
|
||||
|
||||
echo "【8】选题详细信息(包含关联数据):"
|
||||
mysql -u root -e "USE travel_content;
|
||||
SELECT
|
||||
CONCAT('【', t.id, '】') AS '选题ID',
|
||||
t.title AS '选题标题',
|
||||
t.description AS '选题描述',
|
||||
CASE
|
||||
WHEN t.status = 'published' THEN '已发布'
|
||||
WHEN t.status = 'draft' THEN '草稿'
|
||||
ELSE t.status
|
||||
END AS '状态',
|
||||
u.userName AS '创建者',
|
||||
s.name AS '关联景区',
|
||||
p.name AS '关联产品',
|
||||
CONCAT(p.originPrice, ' → ', p.realPrice) AS '价格(原价→实际)',
|
||||
cs.styleName AS '内容风格',
|
||||
GROUP_CONCAT(DISTINCT ta.audienceName SEPARATOR '、') AS '目标受众'
|
||||
FROM
|
||||
topics t
|
||||
JOIN
|
||||
user u ON t.userId = u.id
|
||||
JOIN
|
||||
topicScenic ts ON t.id = ts.topicId
|
||||
JOIN
|
||||
scenicSpot s ON ts.scenicId = s.id
|
||||
JOIN
|
||||
topicProduct tp ON t.id = tp.topicId
|
||||
JOIN
|
||||
product p ON tp.productId = p.id
|
||||
JOIN
|
||||
topicStyle tst ON t.id = tst.topicId
|
||||
JOIN
|
||||
contentStyle cs ON tst.styleId = cs.id
|
||||
LEFT JOIN
|
||||
topicAudience taud ON t.id = taud.topicId
|
||||
LEFT JOIN
|
||||
targetAudience ta ON taud.audienceId = ta.id
|
||||
WHERE
|
||||
t.isDelete = 0
|
||||
GROUP BY
|
||||
t.id, t.title, t.description, t.status, u.userName, s.name, p.name, p.originPrice, p.realPrice, cs.styleName
|
||||
ORDER BY
|
||||
t.status = 'published' DESC, t.id;"
|
||||
echo ""
|
||||
|
||||
echo "【9】素材文件夹:"
|
||||
mysql -u root -e "USE travel_content; SELECT id, userId, folderName, parentId, description, isPublic FROM material_folder;"
|
||||
echo ""
|
||||
|
||||
echo "【10】素材信息:"
|
||||
mysql -u root -e "USE travel_content; SELECT id, userId, folderId, materialName, materialType, fileFormat, tags, isPublic FROM material;"
|
||||
echo ""
|
||||
|
||||
echo "===== 数据库信息展示完毕 ====="
|
||||
Loading…
x
Reference in New Issue
Block a user