baodan/api/insurance/app.py

184 lines
6.2 KiB
Python
Raw Normal View History

"""保单智能客服系统 — 独立后端入口
独立部署时使用的 Flask 应用不依赖 Dify 扩展
"""
import os
import sys
2026-07-12 14:17:18 +08:00
import logging
from logging.handlers import RotatingFileHandler
# 将 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)
from flask import Flask
from flask_cors import CORS
from dotenv import load_dotenv
# 加载环境变量,.env.local 优先级最高(本地开发用,不影响 Docker 部署)
_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'))
load_dotenv()
2026-07-12 14:17:18 +08:00
def setup_logging(app: Flask) -> None:
"""配置统一的日志系统。"""
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-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-12 14:17:18 +08:00
root_logger = logging.getLogger()
root_logger.setLevel(getattr(logging, log_level.upper(), logging.INFO))
# 控制台输出
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)
# 文件输出(带轮转)
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:
print(f"[logging] 文件日志配置失败: {e}")
2026-07-12 14:17:18 +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
def create_app() -> Flask:
"""创建 Flask 应用。"""
app = Flask(__name__)
# 配置
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-12 14:17:18 +08:00
setup_logging(app)
# 数据库:优先 DATABASE_URL其次 DB_* 变量构建 PostgreSQL URL最后回退到 SQLite
db_url = os.getenv('DATABASE_URL', '')
if not db_url:
# 从 DB_* 环境变量构建 PostgreSQL URL
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:
# 检查 psycopg2 是否可用
try:
import psycopg2
app.config['SQLALCHEMY_DATABASE_URI'] = db_url
except ImportError:
print("[warning] psycopg2 未安装,回退到 SQLite")
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///baodan.db'
else:
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///baodan.db'
app.config['MIGRATION_ENABLED'] = True
# Dify 配置
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', '')
# 企业微信配置
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', '')
# 访客模式
app.config['GUEST_MODE'] = os.getenv('GUEST_MODE', 'true').lower() == 'true'
# 域名(分享链接用)
app.config['DOMAIN'] = os.getenv('DOMAIN', 'localhost')
# CORS
CORS(app, resources={r"/*": {"origins": "*"}})
# 初始化数据库
from insurance.db.database import init_db
init_db(app)
# 初始化 Redis
from insurance.db.redis import init_redis
init_redis(app)
# 注册路由
from insurance.routes import register_insurance_routes
register_insurance_routes(app)
# 注册错误处理器
from insurance.utils.error_handler import register_error_handlers
register_error_handlers(app)
# 健康检查(检查实际依赖)
@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
return app
# 创建应用实例
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)