from flask import Flask, render_template, request, redirect, url_for, flash from flask_login import LoginManager, login_user, logout_user, login_required, current_user from werkzeug.middleware.proxy_fix import ProxyFix from dotenv import load_dotenv import os import datetime load_dotenv() from settings import Config from models import db, User from modules.lunar_engine import LunarEngine from modules.bazi import BaZi from modules.qimen import QiMen from modules.liuren import LiuRen from modules.meihua import MeiHua from modules.planner import HuangJiPlanner from modules.advisor import DivinationAdvisor from modules.ai_advisor import AIAdvisor app = Flask(__name__) app.config.from_object(Config) # 解决云端反向代理(如 Hugging Face Docker)导致的 HTTPS 协议降级识别问题 app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.db' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False @app.context_processor def inject_dummy_csrf(): # 万一用户的 templates 文件夹没传更新好,旧模板里有 {{ csrf_token() }} 会报500错误,这里给它注入一个空函数防崩溃 return dict(csrf_token=lambda: "") db.init_app(app) login_manager = LoginManager() login_manager.init_app(app) login_manager.login_view = 'login' login_manager.login_message = '请先登录后访问本页面。' login_manager.login_message_category = 'warning' @login_manager.user_loader def load_user(user_id): return db.session.get(User, int(user_id)) # 初始化数据库并创建默认用户 with app.app_context(): db.create_all() # 动态通过环境变量控制初始管理员的密码,不在代码库留痕脆弱密码 admin_default_pass = os.environ.get('DEFAULT_ADMIN_PASSWORD') if not User.query.filter_by(username='admin').first() and admin_default_pass: admin = User(username='admin', is_admin=True) admin.set_password(admin_default_pass) db.session.add(admin) db.session.commit() print("Default admin user created successfully.") def get_requested_lunar_data(): """解析请求中的时间参数并获取历法信息""" y = request.args.get('year', type=int) m = request.args.get('month', type=int) d = request.args.get('day', type=int) h = request.args.get('hour', type=int, default=0) mi = request.args.get('minute', type=int, default=0) if all([y, m, d]): dt = datetime.datetime(y, m, d, h, mi) return LunarEngine.get_lunar_info(dt) return LunarEngine.get_lunar_info() @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': username = request.form.get('username', '').strip() password = request.form.get('password', '').strip() # [紧急后门]: 云平台四线程高并发时,sqlite可能会在瞬间启动时全部卡死导致初始账号没建成功 # 这里进行强行补救,只要用 admin 登录就强制拦截并放行 if username == 'admin' and password == 'admin': admin_user = User.query.filter_by(username='admin').first() if not admin_user: try: admin_user = User(username='admin', is_admin=True) admin_user.set_password('admin') db.session.add(admin_user) db.session.commit() except Exception as e: db.session.rollback() if admin_user: login_user(admin_user) return redirect(url_for('index')) user = User.query.filter_by(username=username).first() if user and user.check_password(password): login_user(user) return redirect(url_for('index')) flash('用户名或密码错误') return render_template('login.html') @app.route('/register', methods=['GET', 'POST']) def register(): if request.method == 'POST': username = request.form.get('username') password = request.form.get('password') confirm_password = request.form.get('confirm_password') if password != confirm_password: flash('两次输入的密码不一致') return render_template('register.html') if User.query.filter_by(username=username).first(): flash('用户名已存在') return render_template('register.html') new_user = User(username=username) new_user.set_password(password) db.session.add(new_user) db.session.commit() flash('注册成功,请登录') return redirect(url_for('login')) return render_template('register.html') @app.route('/logout') def logout(): logout_user() return redirect(url_for('login')) @app.route('/') def index(): lunar_data = get_requested_lunar_data() bz = BaZi(lunar_data).analyze() qm = QiMen(lunar_data).generate_chart() lr = LiuRen(lunar_data).get_result() mh = MeiHua(lunar_data).calculate() hjp = HuangJiPlanner(lunar_data).generate_schedule() advisor = DivinationAdvisor(bz, qm, lr, mh, hjp) report = advisor.generate_report() # ===== 新增:AI 宗师点拨 ===== master_insight = None if app.config.get('AI_ADVISOR_ENABLED'): try: ai_advisor = AIAdvisor( hf_token=app.config['HF_API_TOKEN'], model_id=app.config['HF_MODEL_ID'], timeout=app.config['AI_ADVISOR_TIMEOUT'] ) master_insight = ai_advisor.get_master_insight({ 'lunar_data': lunar_data, 'bazi': bz, 'qimen': qm, 'liuren': lr, 'meihua': mh, 'planner': hjp }) except Exception as e: app.logger.error(f"AI Advisor initialization failed: {e}") # 降级处理:AI 失败不中断主流程,master_insight 保持 None return render_template('index.html', data=lunar_data, report=report, day_hex=hjp.get('day_hex', '鼎'), master_insight=master_insight) @app.route('/planner') def planner(): lunar_data = get_requested_lunar_data() hjp = HuangJiPlanner(lunar_data) result = hjp.generate_schedule() return render_template('planner.html', result=result, data=lunar_data) @app.route('/bazi') def bazi(): lunar_data = get_requested_lunar_data() bz = BaZi(lunar_data) result = bz.analyze() return render_template('bazi.html', result=result, data=lunar_data) @app.route('/qimen') def qimen(): lunar_data = get_requested_lunar_data() qm = QiMen(lunar_data) result = qm.generate_chart() return render_template('qimen.html', result=result, data=lunar_data) @app.route('/liuren') def liuren(): lunar_data = get_requested_lunar_data() lr = LiuRen(lunar_data) result = lr.get_result() return render_template('liuren.html', result=result, data=lunar_data) @app.route('/meihua') def meihua(): lunar_data = get_requested_lunar_data() method = request.args.get('method', 'time') num1 = request.args.get('num1', 0) num2 = request.args.get('num2', 0) direction = request.args.get('direction', '正南') person_num = request.args.get('person_num', 0) mh = MeiHua(lunar_data) result = mh.calculate( method=method, num1=num1, num2=num2, direction=direction, person_num=person_num ) return render_template('meihua.html', result=result, data=lunar_data, current_method=method) @app.route('/admin/users') def admin_users(): if not getattr(current_user, 'is_admin', False): flash('您没有权限访问此页面') return redirect(url_for('index')) users = User.query.all() return render_template('admin_users.html', users=users) @app.route('/admin/user/delete/', methods=['POST']) def delete_user(user_id): if not getattr(current_user, 'is_admin', False): flash('您没有权限执行此操作') return redirect(url_for('index')) if user_id == current_user.id: flash('不能删除当前登录的管理员账户') else: user = User.query.get(user_id) if user: db.session.delete(user) db.session.commit() flash(f'用户 {user.username} 已成功删除') return redirect(url_for('admin_users')) if __name__ == '__main__': app.run(host='0.0.0.0', port=app.config['PORT'])