baodan/api/insurance/app.py
wsb1224 8b4988b0d5 fix: add api/ to sys.path so app.py can run from project root
Also resolve .env.local and .env paths relative to project root
instead of current working directory.
2026-07-24 12:39:57 +08:00

184 lines
6.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""保单智能客服系统 — 独立后端入口
独立部署时使用的 Flask 应用,不依赖 Dify 扩展。
"""
import os
import sys
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()
def setup_logging(app: Flask) -> None:
"""配置统一的日志系统。"""
log_level = app.config.get('LOG_LEVEL', 'INFO')
log_file = app.config.get('LOG_FILE', 'logs/insurance.log')
# 创建日志目录
log_dir = os.path.dirname(log_file)
if log_dir and not os.path.exists(log_dir):
os.makedirs(log_dir, exist_ok=True)
# 配置根日志记录器
root_logger = logging.getLogger()
root_logger.setLevel(getattr(logging, log_level.upper(), logging.INFO))
# 控制台输出
console_handler = logging.StreamHandler()
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:
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}")
# 将 print 替换为 logging
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')
# 配置日志
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():
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)