diff --git a/.env b/.env
new file mode 100644
index 0000000..f121ae1
--- /dev/null
+++ b/.env
@@ -0,0 +1,22 @@
+ENV=dev
+
+# MySQL(业务库)
+DB_HOST=8.163.14.142
+DB_PORT=30006
+DB_USER=root
+DB_PASSWORD=^CgDatabase2020
+DB_NAME=offerpie
+MYSQL_POOL_SIZE=10
+MYSQL_MAX_OVERFLOW=10
+
+# 连接池获取连接等待超时(秒)
+DB_POOL_TIMEOUT=30
+
+# AI 供应商
+# 火山引擎(OpenAI 兼容风格)
+VOLCENGINE_API_KEY=fd065993-bee2-4f31-8bf2-56d5d3012c02
+VOLCENGINE_BASE_URL=https://ark.cn-beijing.volces.com/api/v3
+
+# Claude(Anthropic 风格)
+ANTHROPIC_API_KEY=sk-43ccdb29caa7e9ebe0db8ac0958c63f6d3a2d62e59064d3d26d94332055a9bc9
+ANTHROPIC_BASE_URL=https://code.warpdevloper.cloud
diff --git a/.env.prod b/.env.prod
new file mode 100644
index 0000000..7c05f6c
--- /dev/null
+++ b/.env.prod
@@ -0,0 +1,22 @@
+ENV=prod
+
+# MySQL(业务库)
+DB_HOST=8.163.14.142
+DB_PORT=30006
+DB_USER=root
+DB_PASSWORD=^CgDatabase2020
+DB_NAME=offerpie
+MYSQL_POOL_SIZE=10
+MYSQL_MAX_OVERFLOW=10
+
+# 连接池获取连接等待超时(秒)
+DB_POOL_TIMEOUT=30
+
+# AI 供应商
+# 火山引擎(OpenAI 兼容风格)
+VOLCENGINE_API_KEY=fd065993-bee2-4f31-8bf2-56d5d3012c02
+VOLCENGINE_BASE_URL=https://ark.cn-beijing.volces.com/api/v3
+
+# Claude(Anthropic 风格)
+ANTHROPIC_API_KEY=sk-43ccdb29caa7e9ebe0db8ac0958c63f6d3a2d62e59064d3d26d94332055a9bc9
+ANTHROPIC_BASE_URL=https://code.warpdevloper.cloud
diff --git a/.env.test b/.env.test
new file mode 100644
index 0000000..ffd6e44
--- /dev/null
+++ b/.env.test
@@ -0,0 +1,22 @@
+ENV=test
+
+# MySQL(业务库)
+DB_HOST=192.168.31.105
+DB_PORT=3306
+DB_USER=root
+DB_PASSWORD=123456
+DB_NAME=offerpie
+MYSQL_POOL_SIZE=10
+MYSQL_MAX_OVERFLOW=10
+
+# 连接池获取连接等待超时(秒)
+DB_POOL_TIMEOUT=30
+
+# AI 供应商
+# 火山引擎(OpenAI 兼容风格)
+VOLCENGINE_API_KEY=fd065993-bee2-4f31-8bf2-56d5d3012c02
+VOLCENGINE_BASE_URL=https://ark.cn-beijing.volces.com/api/v3
+
+# Claude(Anthropic 风格)
+ANTHROPIC_API_KEY=sk-43ccdb29caa7e9ebe0db8ac0958c63f6d3a2d62e59064d3d26d94332055a9bc9
+ANTHROPIC_BASE_URL=https://code.warpdevloper.cloud
diff --git a/.gitignore b/.gitignore
index c3b5d5f..1dbc335 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,4 @@
.venv/
-.env
-.env.test
-.env.prod
.idea/
.vscode/
.history/
diff --git a/app/config/settings.py b/app/config/settings.py
index 6b7271a..3fd374e 100644
--- a/app/config/settings.py
+++ b/app/config/settings.py
@@ -6,6 +6,18 @@ class Settings(BaseSettings):
env: str = "dev"
+ # ──────────── MySQL(业务库)────────────
+ db_host: str = "192.168.31.105"
+ db_port: int = 3306
+ db_user: str = "root"
+ db_password: str = "123456"
+ db_name: str = "offerpie"
+ mysql_pool_size: int = 10
+ mysql_max_overflow: int = 10
+
+ # 连接池获取连接的等待超时(秒)
+ db_pool_timeout: int = 30
+
# 火山引擎(OpenAI 兼容风格)
volcengine_api_key: str = ""
volcengine_base_url: str = "https://ark.cn-beijing.volces.com/api/v3"
@@ -14,6 +26,18 @@ class Settings(BaseSettings):
anthropic_api_key: str = ""
anthropic_base_url: str = ""
+ # ──────────── 日志 ────────────
+ logging_level: str = "INFO"
+ log_file_name: str = "spider.log"
+
+ @property
+ def mysql_url(self) -> str:
+ from urllib.parse import quote
+ return (
+ f"mysql+pymysql://{self.db_user}:{quote(self.db_password, safe='')}"
+ f"@{self.db_host}:{self.db_port}/{self.db_name}"
+ )
+
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
diff --git a/app/core/__init__.py b/app/core/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/app/core/database.py b/app/core/database.py
new file mode 100644
index 0000000..13e3c74
--- /dev/null
+++ b/app/core/database.py
@@ -0,0 +1,50 @@
+"""MySQL 业务库数据源(同步)"""
+
+from typing import Optional
+
+from sqlalchemy import Engine, create_engine
+from sqlalchemy.orm import DeclarativeBase, Session, sessionmaker
+
+from app.config import settings
+from app.core.logger import log
+
+# ──────────── 内部变量 ────────────
+_mysql_engine: Optional[Engine] = None
+_mysql_session_factory: Optional[sessionmaker[Session]] = None
+
+
+class MysqlBase(DeclarativeBase):
+ """MySQL ORM 声明基类"""
+ pass
+
+
+def init_db() -> None:
+ """初始化 MySQL 数据源"""
+ global _mysql_engine, _mysql_session_factory
+
+ _mysql_engine = create_engine(
+ settings.mysql_url,
+ pool_size=settings.mysql_pool_size,
+ max_overflow=settings.mysql_max_overflow,
+ pool_timeout=settings.db_pool_timeout,
+ pool_recycle=3600,
+ pool_pre_ping=True,
+ echo=False,
+ )
+ _mysql_session_factory = sessionmaker(_mysql_engine, expire_on_commit=False)
+
+ log.info("MySQL 数据源初始化完成: {}", settings.db_host)
+
+
+def close_db() -> None:
+ """关闭 MySQL 数据源"""
+ if _mysql_engine:
+ _mysql_engine.dispose()
+ log.info("MySQL 数据源已关闭")
+
+
+def MysqlSession() -> Session:
+ """获取 MySQL 会话(用作 with MysqlSession() as session)"""
+ if _mysql_session_factory is None:
+ raise RuntimeError("数据库未初始化,请先调用 init_db()")
+ return _mysql_session_factory()
diff --git a/app/core/logger.py b/app/core/logger.py
new file mode 100644
index 0000000..17d913e
--- /dev/null
+++ b/app/core/logger.py
@@ -0,0 +1,34 @@
+"""日志配置"""
+
+import sys
+from pathlib import Path
+
+from loguru import logger
+
+from app.config import settings
+
+# 日志目录
+_log_dir = Path("logs")
+_log_dir.mkdir(exist_ok=True)
+
+# 移除默认 handler
+logger.remove()
+
+# 控制台输出
+logger.add(
+ sys.stdout,
+ level=settings.logging_level,
+ format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {name}:{line} - {message}",
+)
+
+# 文件输出(按天轮转,保留30天)
+logger.add(
+ _log_dir / settings.log_file_name,
+ level=settings.logging_level,
+ rotation="00:00",
+ retention="30 days",
+ encoding="utf-8",
+ format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {name}:{line} - {message}",
+)
+
+log = logger
diff --git a/requirements.txt b/requirements.txt
index c6284f1..ca5727b 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,6 +4,15 @@ onnxruntime>=1.17
opencv-python>=4.5
playwright>=1.49
pydantic-settings>=2.0
+
+# 数据库
+sqlalchemy>=2.0
+pymysql>=1.1
+
+# AI
langchain-openai>=0.3
langchain-anthropic>=0.3
langchain-core>=0.3
+
+# 工具
+loguru>=0.7