chore: add .gitignore, clean tracked junk files
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
SECRET_KEY=kU/W5y3uFbWuK1x+RTTOPgLp38yM6Z7d
|
||||
|
||||
# 数据库连接
|
||||
DATABASE_URI=mysql+pymysql://4an:GtbJp6Ai5azntnTB@10.10.10.14/4an
|
||||
SQLALCHEMY_DATABASE_URI=mysql+pymysql://4an:GtbJp6Ai5azntnTB@10.10.10.14/4an
|
||||
|
||||
# 量子密信消息Webhook
|
||||
LZMX_WEBHOOK_URL=http://imtwo.zdxlz.com/im-external/v1/webhook/send?key=ef1dc605827f425e96491ce5725b5274
|
||||
@@ -41,4 +41,5 @@ CASDOOR_CLIENT_ID=8f323670e073612794ef
|
||||
CASDOOR_CLIENT_SECRET=bc37d89f9220fe5a46c17125c731d2daf22f1299
|
||||
CASDOOR_REDIRECT_URI=https://zd.dhdx.fun/callback
|
||||
CASDOOR_ORGANIZATION_NAME=dahua
|
||||
CASDOOR_APPLICATION_NAME=4an
|
||||
CASDOOR_APPLICATION_NAME=4an
|
||||
PROJECT_NAME=4an
|
||||
Binary file not shown.
Binary file not shown.
@@ -16,6 +16,7 @@ import logging
|
||||
from logging.handlers import RotatingFileHandler
|
||||
import os
|
||||
from utils.error_handlers import ErrorHandler
|
||||
from utils.db_utils import retry_database_connection
|
||||
|
||||
def create_app():
|
||||
# 创建应用实例
|
||||
@@ -49,6 +50,12 @@ def create_app():
|
||||
|
||||
# 初始化数据库
|
||||
db.init_app(app)
|
||||
|
||||
# 确保数据库连接成功后再继续启动
|
||||
app.logger.info("正在连接数据库...")
|
||||
if not retry_database_connection(db, app):
|
||||
raise RuntimeError("无法连接到数据库,应用启动失败")
|
||||
|
||||
migrate = Migrate(app, db)
|
||||
|
||||
# 初始化异常处理器
|
||||
|
||||
+22
-2
@@ -1,7 +1,14 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# 加载.env文件 (相对于应用根目录)
|
||||
# 尽量兼容多种 .env 放置方式
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent # /home/.../4an
|
||||
# 1. 优先加载项目根目录 .env
|
||||
load_dotenv(BASE_DIR / '.env')
|
||||
# 2. 兼容放在 app 目录下的 .env
|
||||
load_dotenv(BASE_DIR / 'app' / '.env')
|
||||
# 3. 再加载一次默认搜索路径(当前工作目录及其父级)
|
||||
load_dotenv()
|
||||
|
||||
|
||||
@@ -10,7 +17,18 @@ class Config:
|
||||
环境变量优先级高于默认值,可在系统环境变量中设置这些参数
|
||||
"""
|
||||
SECRET_KEY = os.environ.get('SECRET_KEY', os.urandom(24))
|
||||
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URI')
|
||||
# 优先读取 SQLALCHEMY_DATABASE_URI,兼容旧配置;否则回退到 DATABASE_URI
|
||||
SQLALCHEMY_DATABASE_URI = os.environ.get('SQLALCHEMY_DATABASE_URI') or os.environ.get('DATABASE_URI')
|
||||
SQLALCHEMY_ENGINE_OPTIONS = {
|
||||
'pool_pre_ping': True, # 每次取连接前验证是否可用
|
||||
'pool_recycle': 1800, # 连接空闲 30 分钟后自动回收重建
|
||||
'pool_timeout': 10, # 等待连接池超时(秒)
|
||||
'connect_args': {
|
||||
'connect_timeout': 5, # 连接 MySQL 超时
|
||||
'read_timeout': 15, # 读取超时
|
||||
'write_timeout': 15, # 写入超时
|
||||
},
|
||||
}
|
||||
LZMX_WEBHOOK_URL = os.environ.get('LZMX_WEBHOOK_URL')
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||
UPLOAD_FOLDER = os.environ.get('UPLOAD_FOLDER', 'static/uploads')
|
||||
@@ -31,6 +49,8 @@ class Config:
|
||||
CASDOOR_ORGANIZATION_NAME = os.environ.get('CASDOOR_ORGANIZATION_NAME', 'built-in')
|
||||
CASDOOR_APPLICATION_NAME = os.environ.get('CASDOOR_APPLICATION_NAME', 'app-built-in')
|
||||
CASDOOR_REDIRECT_URI = os.environ.get('CASDOOR_REDIRECT_URI', 'http://localhost:18061/callback')
|
||||
# 项目名称(用于与 Casdoor 用户标签匹配权限)
|
||||
PROJECT_NAME = os.environ.get('PROJECT_NAME')
|
||||
|
||||
# 会话 Cookie 配置(跨域 SSO 时建议设置)
|
||||
SESSION_COOKIE_SAMESITE = os.environ.get('SESSION_COOKIE_SAMESITE', 'Lax')
|
||||
|
||||
-1101
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 5.2 KiB |
@@ -26,10 +26,14 @@
|
||||
<p style="margin-top: 12px; color: #666;">正在跳转至 Casdoor 登录页,如未跳转请点击按钮。</p>
|
||||
</div>
|
||||
<script>
|
||||
// 自动跳转至 Casdoor 登录
|
||||
window.onload = function() {
|
||||
window.location.href = "{{ url_for('casdoor_login') }}";
|
||||
};
|
||||
// 有错误提示时停留在本页,方便用户查看信息
|
||||
const hasError = {{ 'true' if get_flashed_messages() else 'false' }};
|
||||
if (!hasError) {
|
||||
// 自动跳转至 Casdoor 登录
|
||||
window.onload = function() {
|
||||
window.location.href = "{{ url_for('casdoor_login') }}";
|
||||
};
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -108,6 +108,34 @@ class CasdoorAuth:
|
||||
pass # 如果无法记录日志,忽略错误
|
||||
return None
|
||||
|
||||
def get_account_info(self, access_token):
|
||||
"""
|
||||
使用访问令牌获取完整账户信息(包含标签等字段)
|
||||
"""
|
||||
account_url = f"{self.endpoint}/api/get-account"
|
||||
headers = {
|
||||
'Authorization': f'Bearer {access_token}'
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.get(account_url, headers=headers)
|
||||
response.raise_for_status()
|
||||
result = response.json()
|
||||
|
||||
# Casdoor 通常返回 {"status": "ok", "data": {...}}
|
||||
if isinstance(result, dict):
|
||||
if result.get('status') == 'ok' and isinstance(result.get('data'), dict):
|
||||
return result['data']
|
||||
if 'data' in result and isinstance(result.get('data'), dict):
|
||||
return result['data']
|
||||
return result if isinstance(result, dict) else None
|
||||
except requests.exceptions.RequestException as e:
|
||||
try:
|
||||
current_app.logger.error(f"获取 Casdoor 账户信息失败: {str(e)}")
|
||||
except:
|
||||
pass
|
||||
return None
|
||||
|
||||
def verify_state(self, state):
|
||||
"""
|
||||
验证 state 参数,防止 CSRF 攻击
|
||||
|
||||
@@ -3,6 +3,7 @@ import time
|
||||
from flask import current_app
|
||||
import logging
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from sqlalchemy import text
|
||||
from scripts.models import db
|
||||
from collections import defaultdict
|
||||
|
||||
@@ -121,6 +122,44 @@ def clear_table_cache(table_name):
|
||||
if key in query_cache.access_times:
|
||||
del query_cache.access_times[key]
|
||||
|
||||
# 数据库连接重试机制
|
||||
def retry_database_connection(db_instance, app, max_retries=10, retry_delay=5):
|
||||
"""
|
||||
尝试连接数据库,在连接失败时进行重试
|
||||
:param db_instance: SQLAlchemy数据库实例
|
||||
:param app: Flask应用实例
|
||||
:param max_retries: 最大重试次数
|
||||
:param retry_delay: 重试间隔(秒)
|
||||
:return: 连接成功返回True,失败返回False
|
||||
"""
|
||||
import sqlalchemy.exc as sa_exc
|
||||
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
app.logger.info(f"尝试连接数据库... (尝试 {attempt + 1}/{max_retries})")
|
||||
|
||||
# 使用with语句确保应用上下文正确
|
||||
with app.app_context():
|
||||
# 尝试建立连接 - SQLAlchemy 2.0 兼容方式
|
||||
with db_instance.engine.connect() as connection:
|
||||
connection.execute(text("SELECT 1"))
|
||||
connection.commit() # 确保事务提交
|
||||
|
||||
app.logger.info("数据库连接成功!")
|
||||
return True
|
||||
|
||||
except (sa_exc.OperationalError, sa_exc.DatabaseError, Exception) as e:
|
||||
app.logger.warning(f"数据库连接失败: {str(e)}")
|
||||
|
||||
if attempt < max_retries - 1:
|
||||
app.logger.info(f"等待 {retry_delay} 秒后重试...")
|
||||
time.sleep(retry_delay)
|
||||
else:
|
||||
app.logger.error(f"数据库连接失败,已重试 {max_retries} 次,放弃连接")
|
||||
return False
|
||||
|
||||
return False
|
||||
|
||||
# 通用查询函数,包含异常处理和缓存
|
||||
@db_operation()
|
||||
@cache_query()
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+29
-5
@@ -13,8 +13,8 @@ def init_auth_routes(app):
|
||||
# 如果已登录,直接跳转到仪表盘
|
||||
if current_user.is_authenticated:
|
||||
return redirect(url_for('dashboard'))
|
||||
# 未登录直接跳转 Casdoor 登录页
|
||||
return redirect(url_for('casdoor_login'))
|
||||
# 未登录展示登录页(登录页会自动跳转 Casdoor,如有错误提示则不跳转)
|
||||
return render_template('login.html')
|
||||
|
||||
@app.route('/casdoor/login')
|
||||
def casdoor_login():
|
||||
@@ -65,18 +65,44 @@ def init_auth_routes(app):
|
||||
flash('登录失败:无法获取用户信息')
|
||||
return redirect(url_for('login'))
|
||||
|
||||
# 获取完整账户信息(包含标签)
|
||||
account_info = casdoor_auth.get_account_info(access_token)
|
||||
if not account_info:
|
||||
app.logger.error('获取 Casdoor 账户信息失败')
|
||||
flash('登录失败:无法获取账户信息')
|
||||
return redirect(url_for('login'))
|
||||
|
||||
# 从 Casdoor 用户信息中提取数据
|
||||
# Casdoor 返回的用户信息可能包含:name, email, phone, id 等字段
|
||||
casdoor_id = user_info.get('sub') or user_info.get('id') or user_info.get('name')
|
||||
phone = user_info.get('phone') or user_info.get('phoneNumber') or casdoor_id
|
||||
name = user_info.get('name') or user_info.get('displayName') or '未知用户'
|
||||
|
||||
# 基于标签的访问控制:用户标签必须等于项目名
|
||||
project_name = app.config.get('PROJECT_NAME')
|
||||
if not project_name:
|
||||
app.logger.error('未配置 PROJECT_NAME 环境变量,拒绝登录')
|
||||
flash('登录失败:未配置项目名')
|
||||
return redirect(url_for('login'))
|
||||
|
||||
raw_tags = account_info.get('tag') or account_info.get('tags')
|
||||
if isinstance(raw_tags, str):
|
||||
user_tags = [t.strip() for t in raw_tags.split(',') if t.strip()]
|
||||
elif isinstance(raw_tags, list):
|
||||
user_tags = [str(t).strip() for t in raw_tags if str(t).strip()]
|
||||
else:
|
||||
user_tags = []
|
||||
|
||||
if project_name not in user_tags:
|
||||
app.logger.warning(f'用户 {name}({phone}) 标签 {user_tags} 不匹配项目 {project_name}')
|
||||
flash('您没有本系统的访问权限,请联系管理员处理')
|
||||
return redirect(url_for('login'))
|
||||
|
||||
app.logger.info(f'Casdoor 用户信息: {user_info}')
|
||||
|
||||
# 查找或创建本地用户
|
||||
user = User.query.filter_by(phone=phone).first()
|
||||
if not user:
|
||||
# 如果用户不存在,创建新用户
|
||||
user = User(
|
||||
phone=phone,
|
||||
name=name,
|
||||
@@ -87,7 +113,6 @@ def init_auth_routes(app):
|
||||
db.session.commit()
|
||||
app.logger.info(f'创建新用户: {name}({phone})')
|
||||
else:
|
||||
# 更新用户信息
|
||||
user.name = name
|
||||
if user_info.get('affiliation'):
|
||||
user.branch = user_info.get('affiliation')
|
||||
@@ -96,7 +121,6 @@ def init_auth_routes(app):
|
||||
db.session.commit()
|
||||
app.logger.info(f'更新用户信息: {name}({phone})')
|
||||
|
||||
# 登录用户
|
||||
login_user(user)
|
||||
app.logger.info(f'用户 {user.name}({user.phone}) 通过 Casdoor 登录成功')
|
||||
|
||||
|
||||
Reference in New Issue
Block a user