2026-07-24 12:32:26 +08:00
|
|
|
|
"""保单智能客服系统 — 独立后端入口
|
2026-07-02 23:13:23 +08:00
|
|
|
|
|
2026-07-24 12:32:26 +08:00
|
|
|
|
独立部署时使用的 Flask 应用,不依赖 Dify 扩展。
|
|
|
|
|
|
"""
|
2026-07-02 23:13:23 +08:00
|
|
|
|
import os
|
2026-07-24 12:39:57 +08:00
|
|
|
|
import sys
|
2026-07-12 14:17:18 +08:00
|
|
|
|
import logging
|
|
|
|
|
|
from logging.handlers import RotatingFileHandler
|
2026-07-24 12:39:57 +08:00
|
|
|
|
|
|
|
|
|
|
# 将 api/ 目录加入搜索路径,使 `from insurance.xxx` 可从任意位置运行
|
|
|
|
|
|
_this_dir = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
|
|
_api_dir = os.path.dirname(_this_dir)
|
|
|
|
|
|
if _api_dir not in sys.path:
|
|
|
|
|
|
sys.path.insert(0, _api_dir)
|
|
|
|
|
|
|
2026-07-02 23:13:23 +08:00
|
|
|
|
from flask import Flask
|
|
|
|
|
|
from flask_cors import CORS
|
|
|
|
|
|
from dotenv import load_dotenv
|
|
|
|
|
|
|
2026-07-24 12:32:26 +08:00
|
|
|
|
# 加载环境变量,.env.local 优先级最高(本地开发用,不影响 Docker 部署)
|
2026-07-24 12:39:57 +08:00
|
|
|
|
_project_root = os.path.dirname(_api_dir) # baodanagent/
|
|
|
|
|
|
load_dotenv(os.path.join(_project_root, '.env.local'), override=True)
|
|
|
|
|
|
load_dotenv(os.path.join(_project_root, '.env'))
|
2026-07-02 23:13:23 +08:00
|
|
|
|
load_dotenv()
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-12 14:17:18 +08:00
|
|
|
|
def setup_logging(app: Flask) -> None:
|
2026-07-24 12:32:26 +08:00
|
|
|
|
"""配置统一的日志系统。"""
|
2026-07-12 14:17:18 +08:00
|
|
|
|
log_level = app.config.get('LOG_LEVEL', 'INFO')
|
|
|
|
|
|
log_file = app.config.get('LOG_FILE', 'logs/insurance.log')
|
|
|
|
|
|
|
2026-07-24 12:32:26 +08:00
|
|
|
|
# 创建日志目录
|
2026-07-12 14:17:18 +08:00
|
|
|
|
log_dir = os.path.dirname(log_file)
|
|
|
|
|
|
if log_dir and not os.path.exists(log_dir):
|
|
|
|
|
|
os.makedirs(log_dir, exist_ok=True)
|
|
|
|
|
|
|
2026-07-24 12:32:26 +08:00
|
|
|
|
# 配置根日志记录器
|
2026-07-12 14:17:18 +08:00
|
|
|
|
root_logger = logging.getLogger()
|
|
|
|
|
|
root_logger.setLevel(getattr(logging, log_level.upper(), logging.INFO))
|
|
|
|
|
|
|
2026-07-24 12:32:26 +08:00
|
|
|
|
# 控制台输出
|
|
|
|
|
|
console_handler = logging.StreamHandler()
|
2026-07-12 14:17:18 +08:00
|
|
|
|
console_handler.setLevel(logging.INFO)
|
|
|
|
|
|
console_format = logging.Formatter(
|
|
|
|
|
|
'%(asctime)s [%(levelname)s] %(name)s: %(message)s',
|
|
|
|
|
|
datefmt='%Y-%m-%d %H:%M:%S'
|
|
|
|
|
|
)
|
|
|
|
|
|
console_handler.setFormatter(console_format)
|
|
|
|
|
|
root_logger.addHandler(console_handler)
|
|
|
|
|
|
|
2026-07-24 12:32:26 +08:00
|
|
|
|
# 文件输出(带轮转)
|
|
|
|
|
|
try:
|
2026-07-12 14:17:18 +08:00
|
|
|
|
file_handler = RotatingFileHandler(
|
|
|
|
|
|
log_file,
|
|
|
|
|
|
maxBytes=10 * 1024 * 1024, # 10MB
|
|
|
|
|
|
backupCount=5,
|
|
|
|
|
|
encoding='utf-8'
|
|
|
|
|
|
)
|
|
|
|
|
|
file_handler.setLevel(logging.DEBUG)
|
|
|
|
|
|
file_format = logging.Formatter(
|
|
|
|
|
|
'%(asctime)s [%(levelname)s] %(name)s [%(filename)s:%(lineno)d]: %(message)s',
|
|
|
|
|
|
datefmt='%Y-%m-%d %H:%M:%S'
|
|
|
|
|
|
)
|
|
|
|
|
|
file_handler.setFormatter(file_format)
|
|
|
|
|
|
root_logger.addHandler(file_handler)
|
|
|
|
|
|
except Exception as e:
|
2026-07-24 12:32:26 +08:00
|
|
|
|
print(f"[logging] 文件日志配置失败: {e}")
|
2026-07-12 14:17:18 +08:00
|
|
|
|
|
2026-07-24 12:32:26 +08:00
|
|
|
|
# 将 print 替换为 logging
|
2026-07-12 14:17:18 +08:00
|
|
|
|
import builtins
|
|
|
|
|
|
original_print = builtins.print
|
|
|
|
|
|
def logging_print(*args, **kwargs):
|
|
|
|
|
|
msg = ' '.join(str(arg) for arg in args)
|
|
|
|
|
|
logging.info(msg)
|
|
|
|
|
|
builtins.print = logging_print
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-02 23:13:23 +08:00
|
|
|
|
def create_app() -> Flask:
|
2026-07-24 12:32:26 +08:00
|
|
|
|
"""创建 Flask 应用。"""
|
2026-07-02 23:13:23 +08:00
|
|
|
|
app = Flask(__name__)
|
|
|
|
|
|
|
2026-07-24 12:32:26 +08:00
|
|
|
|
# 配置
|
2026-07-02 23:13:23 +08:00
|
|
|
|
app.config['SECRET_KEY'] = os.getenv('JWT_SECRET', 'baodan-jwt-secret-2026')
|
|
|
|
|
|
app.config['JWT_SECRET'] = os.getenv('JWT_SECRET', 'baodan-jwt-secret-2026')
|
|
|
|
|
|
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
|
|
|
|
|
app.config['REDIS_URL'] = os.getenv('REDIS_URL')
|
|
|
|
|
|
|
2026-07-24 12:32:26 +08:00
|
|
|
|
# 配置日志
|
2026-07-12 14:17:18 +08:00
|
|
|
|
setup_logging(app)
|
|
|
|
|
|
|
2026-07-24 12:32:26 +08:00
|
|
|
|
# 数据库:优先 DATABASE_URL,其次 DB_* 变量构建 PostgreSQL URL,最后回退到 SQLite
|
2026-07-02 23:13:23 +08:00
|
|
|
|
db_url = os.getenv('DATABASE_URL', '')
|
|
|
|
|
|
if not db_url:
|
2026-07-24 12:32:26 +08:00
|
|
|
|
# 从 DB_* 环境变量构建 PostgreSQL URL
|
2026-07-02 23:13:23 +08:00
|
|
|
|
db_host = os.getenv('DB_HOST', '')
|
|
|
|
|
|
db_port = os.getenv('DB_PORT', '5432')
|
|
|
|
|
|
db_user = os.getenv('DB_USERNAME', '')
|
|
|
|
|
|
db_pass = os.getenv('DB_PASSWORD', '')
|
|
|
|
|
|
db_name = os.getenv('DB_DATABASE', '')
|
|
|
|
|
|
if db_host and db_user and db_name:
|
|
|
|
|
|
db_url = f"postgresql://{db_user}:{db_pass}@{db_host}:{db_port}/{db_name}"
|
|
|
|
|
|
if db_url and 'postgresql' in db_url:
|
2026-07-24 12:32:26 +08:00
|
|
|
|
# 检查 psycopg2 是否可用
|
2026-07-02 23:13:23 +08:00
|
|
|
|
try:
|
|
|
|
|
|
import psycopg2
|
|
|
|
|
|
app.config['SQLALCHEMY_DATABASE_URI'] = db_url
|
|
|
|
|
|
except ImportError:
|
2026-07-24 12:32:26 +08:00
|
|
|
|
print("[warning] psycopg2 未安装,回退到 SQLite")
|
2026-07-02 23:13:23 +08:00
|
|
|
|
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///baodan.db'
|
|
|
|
|
|
else:
|
|
|
|
|
|
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///baodan.db'
|
|
|
|
|
|
app.config['MIGRATION_ENABLED'] = True
|
|
|
|
|
|
|
2026-07-24 12:32:26 +08:00
|
|
|
|
# Dify 配置
|
2026-07-02 23:13:23 +08:00
|
|
|
|
app.config['BAODAN_API_URL'] = os.getenv('DIFY_BASE_URL', 'http://localhost:5001')
|
|
|
|
|
|
app.config['BAODAN_CHAT_API_KEY'] = os.getenv('DIFY_CHAT_APP_API_KEY', '')
|
|
|
|
|
|
app.config['BAODAN_WORKFLOW_API_KEY'] = os.getenv('DIFY_WORKFLOW_APP_API_KEY', '')
|
|
|
|
|
|
|
2026-07-24 12:32:26 +08:00
|
|
|
|
# 企业微信配置
|
2026-07-02 23:13:23 +08:00
|
|
|
|
app.config['WECOM_CORP_ID'] = os.getenv('WECOM_CORP_ID', '')
|
|
|
|
|
|
app.config['WECOM_SECRET'] = os.getenv('WECOM_SECRET', '')
|
|
|
|
|
|
app.config['WECOM_TOKEN'] = os.getenv('WECOM_TOKEN', '')
|
|
|
|
|
|
app.config['WECOM_AES_KEY'] = os.getenv('WECOM_AES_KEY', '')
|
|
|
|
|
|
app.config['WECOM_WEBHOOK_URL'] = os.getenv('WECOM_WEBHOOK_URL', '')
|
|
|
|
|
|
|
2026-07-24 12:32:26 +08:00
|
|
|
|
# 访客模式
|
2026-07-02 23:13:23 +08:00
|
|
|
|
app.config['GUEST_MODE'] = os.getenv('GUEST_MODE', 'true').lower() == 'true'
|
|
|
|
|
|
|
2026-07-24 12:32:26 +08:00
|
|
|
|
# 域名(分享链接用)
|
|
|
|
|
|
app.config['DOMAIN'] = os.getenv('DOMAIN', 'localhost')
|
2026-07-02 23:13:23 +08:00
|
|
|
|
|
|
|
|
|
|
# CORS
|
|
|
|
|
|
CORS(app, resources={r"/*": {"origins": "*"}})
|
|
|
|
|
|
|
2026-07-24 12:32:26 +08:00
|
|
|
|
# 初始化数据库
|
2026-07-02 23:13:23 +08:00
|
|
|
|
from insurance.db.database import init_db
|
|
|
|
|
|
init_db(app)
|
|
|
|
|
|
|
2026-07-24 12:32:26 +08:00
|
|
|
|
# 初始化 Redis
|
2026-07-02 23:13:23 +08:00
|
|
|
|
from insurance.db.redis import init_redis
|
|
|
|
|
|
init_redis(app)
|
|
|
|
|
|
|
2026-07-24 12:32:26 +08:00
|
|
|
|
# 注册路由
|
2026-07-02 23:13:23 +08:00
|
|
|
|
from insurance.routes import register_insurance_routes
|
|
|
|
|
|
register_insurance_routes(app)
|
|
|
|
|
|
|
2026-07-24 12:32:26 +08:00
|
|
|
|
# 注册错误处理器
|
|
|
|
|
|
from insurance.utils.error_handler import register_error_handlers
|
2026-07-02 23:13:23 +08:00
|
|
|
|
register_error_handlers(app)
|
|
|
|
|
|
|
2026-07-24 12:32:26 +08:00
|
|
|
|
# 健康检查(检查实际依赖)
|
2026-07-02 23:13:23 +08:00
|
|
|
|
@app.route('/health')
|
|
|
|
|
|
def health():
|
2026-07-12 14:17:18 +08:00
|
|
|
|
checks = {'status': 'ok', 'service': 'baodanagent-api'}
|
|
|
|
|
|
try:
|
|
|
|
|
|
from insurance.db.compat import db
|
|
|
|
|
|
db.session.execute(db.text('SELECT 1'))
|
|
|
|
|
|
checks['database'] = 'ok'
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
checks['database'] = f'error: {str(e)[:100]}'
|
|
|
|
|
|
checks['status'] = 'degraded'
|
|
|
|
|
|
try:
|
|
|
|
|
|
from insurance.db.compat import redis_client
|
|
|
|
|
|
if redis_client:
|
|
|
|
|
|
redis_client.ping()
|
|
|
|
|
|
checks['redis'] = 'ok'
|
|
|
|
|
|
else:
|
|
|
|
|
|
checks['redis'] = 'not configured'
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
checks['redis'] = f'error: {str(e)[:100]}'
|
|
|
|
|
|
checks['status'] = 'degraded'
|
|
|
|
|
|
return checks
|
2026-07-02 23:13:23 +08:00
|
|
|
|
|
|
|
|
|
|
return app
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-24 12:32:26 +08:00
|
|
|
|
# 创建应用实例
|
2026-07-02 23:13:23 +08:00
|
|
|
|
app = create_app()
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
|
port = int(os.getenv('PORT', 5001))
|
|
|
|
|
|
debug = os.getenv('FLASK_DEBUG', 'false').lower() == 'true'
|
|
|
|
|
|
app.run(host='0.0.0.0', port=port, debug=debug)
|