2026-05-29 10:28:07 +08:00
|
|
|
|
"""学生课程管理系统 - Flask主应用入口"""
|
|
|
|
|
|
import os
|
|
|
|
|
|
from flask import Flask
|
|
|
|
|
|
from config import Config, write_engine
|
|
|
|
|
|
from models import db
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_app():
|
|
|
|
|
|
"""应用工厂函数"""
|
|
|
|
|
|
app = Flask(__name__)
|
|
|
|
|
|
app.config.from_object(Config)
|
|
|
|
|
|
app.config['TEMPLATES_AUTO_RELOAD'] = True
|
|
|
|
|
|
|
2026-05-29 10:40:41 +08:00
|
|
|
|
# 初始化数据库
|
2026-05-29 10:28:07 +08:00
|
|
|
|
db.init_app(app)
|
2026-05-29 10:40:41 +08:00
|
|
|
|
# 仅当存在有效的 write_engine(非 None)时才复用
|
|
|
|
|
|
if write_engine is not None:
|
|
|
|
|
|
if app in db._app_engines:
|
|
|
|
|
|
old_engine = db._app_engines[app].get(None)
|
|
|
|
|
|
if old_engine is not None and old_engine is not write_engine:
|
|
|
|
|
|
old_engine.dispose()
|
|
|
|
|
|
db._app_engines[app][None] = write_engine
|
2026-05-29 10:28:07 +08:00
|
|
|
|
|
|
|
|
|
|
# 注入模板上下文变量
|
|
|
|
|
|
@app.context_processor
|
|
|
|
|
|
def inject_globals():
|
|
|
|
|
|
from flask import session
|
|
|
|
|
|
from datetime import date
|
|
|
|
|
|
from models import User, ConsumptionRecord, Student
|
|
|
|
|
|
from sqlalchemy import func
|
|
|
|
|
|
|
|
|
|
|
|
user_id = session.get('user_id')
|
|
|
|
|
|
user = db.session.get(User, user_id) if user_id else None
|
|
|
|
|
|
panel_progress = 0
|
|
|
|
|
|
if user:
|
|
|
|
|
|
month_start = date.today().replace(day=1)
|
|
|
|
|
|
month_h = db.session.query(
|
|
|
|
|
|
func.coalesce(func.sum(ConsumptionRecord.hours_consumed), 0)
|
|
|
|
|
|
).filter(ConsumptionRecord.consume_date >= month_start).scalar() or 0
|
|
|
|
|
|
students = Student.query.filter_by(status=1).count() or 1
|
|
|
|
|
|
target = max(students * 4, 20)
|
|
|
|
|
|
panel_progress = min(100, int(float(month_h) / target * 100))
|
|
|
|
|
|
return {'current_user': user, 'panel_progress': panel_progress}
|
|
|
|
|
|
|
|
|
|
|
|
# 注册路由(直接注册到app,不使用蓝图)
|
|
|
|
|
|
with app.app_context():
|
|
|
|
|
|
from routes import register_all_routes
|
|
|
|
|
|
register_all_routes(app)
|
|
|
|
|
|
|
|
|
|
|
|
# 建表 + 自动迁移(Doris / MySQL / SQLite)
|
|
|
|
|
|
from db_bootstrap import bootstrap_database
|
|
|
|
|
|
bootstrap_database()
|
|
|
|
|
|
|
|
|
|
|
|
_init_default_data()
|
|
|
|
|
|
_sync_account_original_prices()
|
|
|
|
|
|
|
|
|
|
|
|
return app
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _sync_account_original_prices():
|
|
|
|
|
|
"""未录原价时:课程表单价 → 最新月度快照单价"""
|
|
|
|
|
|
from models import db, StudentAccount, Course, MonthlySnapshot
|
|
|
|
|
|
from sqlalchemy import func
|
|
|
|
|
|
try:
|
|
|
|
|
|
accounts = StudentAccount.query.filter(
|
|
|
|
|
|
db.or_(
|
|
|
|
|
|
StudentAccount.original_price_per_lesson.is_(None),
|
|
|
|
|
|
StudentAccount.original_price_per_lesson == 0,
|
|
|
|
|
|
)
|
|
|
|
|
|
).all()
|
|
|
|
|
|
n = 0
|
|
|
|
|
|
for acc in accounts:
|
|
|
|
|
|
if acc.original_price_per_lesson and float(acc.original_price_per_lesson or 0) > 0:
|
|
|
|
|
|
continue
|
|
|
|
|
|
crs = db.session.get(Course, acc.course_id)
|
|
|
|
|
|
if crs and float(crs.price_per_hour or 0) > 0:
|
|
|
|
|
|
acc.original_price_per_lesson = crs.price_per_hour
|
|
|
|
|
|
n += 1
|
|
|
|
|
|
continue
|
|
|
|
|
|
row = (
|
|
|
|
|
|
db.session.query(MonthlySnapshot.unit_price)
|
|
|
|
|
|
.filter(
|
|
|
|
|
|
MonthlySnapshot.account_id == acc.id,
|
|
|
|
|
|
MonthlySnapshot.unit_price > 0,
|
|
|
|
|
|
)
|
|
|
|
|
|
.order_by(
|
|
|
|
|
|
MonthlySnapshot.year.desc(),
|
|
|
|
|
|
MonthlySnapshot.month.desc(),
|
|
|
|
|
|
)
|
|
|
|
|
|
.first()
|
|
|
|
|
|
)
|
|
|
|
|
|
if row and float(row[0] or 0) > 0:
|
|
|
|
|
|
acc.original_price_per_lesson = row[0]
|
|
|
|
|
|
n += 1
|
|
|
|
|
|
if n:
|
|
|
|
|
|
db.session.commit()
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
db.session.rollback()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _init_default_data():
|
|
|
|
|
|
"""初始化默认数据: 确保admin用户和管理员角色存在"""
|
|
|
|
|
|
from models import User, Role
|
|
|
|
|
|
from utils import PERMISSIONS
|
|
|
|
|
|
|
|
|
|
|
|
# §八 默认角色层级
|
|
|
|
|
|
default_roles = [
|
|
|
|
|
|
('超级管理员', 'all', '全部权限'),
|
|
|
|
|
|
('校区管理员', ','.join(PERMISSIONS.keys()), '本校区全部业务'),
|
|
|
|
|
|
(
|
|
|
|
|
|
'财务',
|
|
|
|
|
|
'recharge_view,recharge_add,recharge_export,recharge_activity_view,'
|
|
|
|
|
|
'recharge_activity_add,recharge_activity_edit,refund_view,refund_add,'
|
|
|
|
|
|
'refund_export,statistics_view,statistics_export,log_view',
|
|
|
|
|
|
'充值退费与报表',
|
|
|
|
|
|
),
|
|
|
|
|
|
(
|
|
|
|
|
|
'授课老师',
|
|
|
|
|
|
'student_view,course_view,class_view,class_add,attendance_view,'
|
|
|
|
|
|
'attendance_add,consumption_view,consumption_add,schedule_view',
|
|
|
|
|
|
'消课考勤与班级',
|
|
|
|
|
|
),
|
|
|
|
|
|
]
|
|
|
|
|
|
for name, perms, remark in default_roles:
|
|
|
|
|
|
if not Role.query.filter_by(name=name).first():
|
|
|
|
|
|
db.session.add(Role(name=name, permissions=perms, remark=remark))
|
|
|
|
|
|
db.session.commit()
|
|
|
|
|
|
|
|
|
|
|
|
# 校区管理员:补齐后续新增的权限项(如课时核对表)
|
|
|
|
|
|
campus = Role.query.filter_by(name='校区管理员').first()
|
|
|
|
|
|
if campus and campus.permissions != 'all':
|
|
|
|
|
|
all_keys = sorted(PERMISSIONS.keys())
|
|
|
|
|
|
campus.permissions = ','.join(all_keys)
|
|
|
|
|
|
db.session.commit()
|
|
|
|
|
|
|
|
|
|
|
|
admin_role = Role.query.filter_by(name='超级管理员').first()
|
|
|
|
|
|
if not admin_role:
|
|
|
|
|
|
admin_role = Role(name='超级管理员', permissions='all', remark='系统默认管理员角色')
|
|
|
|
|
|
db.session.add(admin_role)
|
|
|
|
|
|
db.session.commit()
|
|
|
|
|
|
admin_role = Role.query.filter_by(name='超级管理员').first()
|
|
|
|
|
|
|
|
|
|
|
|
# 确保admin用户存在且密码正确
|
|
|
|
|
|
admin = User.query.filter_by(username='admin').first()
|
|
|
|
|
|
default_password = 'admin123'
|
|
|
|
|
|
if not admin:
|
|
|
|
|
|
admin = User(
|
|
|
|
|
|
username='admin', real_name='系统管理员',
|
|
|
|
|
|
role_id=admin_role.id, status=1, remark='默认管理员'
|
|
|
|
|
|
)
|
|
|
|
|
|
admin.set_password(default_password)
|
|
|
|
|
|
db.session.add(admin)
|
|
|
|
|
|
db.session.commit()
|
|
|
|
|
|
elif not admin.check_password(default_password):
|
|
|
|
|
|
admin.set_password(default_password)
|
|
|
|
|
|
db.session.commit()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app = create_app()
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
|
port = int(os.environ.get('DEPLOY_RUN_PORT', 5000))
|
|
|
|
|
|
app.run(host='0.0.0.0', port=port, debug=True, use_reloader=True)
|