777ca50328
update. 调整融合主菜单与板块 update. 分离新闻模块
164 lines
6.3 KiB
Python
164 lines
6.3 KiB
Python
"""
|
||
URL configuration for server project.
|
||
|
||
The `urlpatterns` list routes URLs to views. For more information please see:
|
||
https://docs.djangoproject.com/en/6.0/topics/http/urls/
|
||
Examples:
|
||
Function views
|
||
1. Add an import: from my_app import views
|
||
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||
Class-based views
|
||
1. Add an import: from other_app.views import Home
|
||
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||
Including another URLconf
|
||
1. Import the include() function: from django.urls import include, path
|
||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||
"""
|
||
from django.conf.urls.static import static
|
||
from django.contrib import admin
|
||
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||
from django.urls import path, include, re_path
|
||
|
||
import logging
|
||
import os
|
||
import pkgutil
|
||
import traceback
|
||
import importlib
|
||
|
||
import jwt
|
||
from django.contrib.auth.models import User
|
||
from django.http import JsonResponse
|
||
from jwt import exceptions
|
||
from ninja import NinjaAPI
|
||
from ninja.errors import ValidationError
|
||
from ninja.security import HttpBearer
|
||
|
||
from base.views import custom_upload_file
|
||
from common.common_response import CommonResponse
|
||
from .settings import SECRET_KEY
|
||
|
||
logger = logging.getLogger('ptools')
|
||
|
||
|
||
class GlobalAuth(HttpBearer):
|
||
def authenticate(self, request, token):
|
||
try:
|
||
logger.debug('用户认证中')
|
||
logger.debug(f'解析 Auth Token:{token}')
|
||
res = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
|
||
user = User.objects.filter(id=res.get('id'), username=res.get('username')).first()
|
||
logger.debug(user)
|
||
if not user:
|
||
raise exceptions.PyJWKError('用户不存在')
|
||
request.user = user
|
||
return token
|
||
except exceptions.PyJWKError:
|
||
msg = 'Token 认证失败,请重新登陆!'
|
||
logger.error(msg)
|
||
raise
|
||
except exceptions.ExpiredSignatureError:
|
||
msg = '认证令牌已过期,请重新登陆!'
|
||
logger.error(msg)
|
||
raise
|
||
except Exception as e:
|
||
msg = '认证失败,请检查用户是否存在'
|
||
logger.error(msg)
|
||
raise
|
||
|
||
|
||
# 2. 获取当前文件所在的目录路径 (假设此文件与各个 app 文件夹同级,或者在特定配置目录下)
|
||
# 如果你的此文件在项目根目录,而 app 文件夹也在根目录:
|
||
from django.conf import settings
|
||
|
||
project_root = settings.BASE_DIR
|
||
|
||
|
||
# 如果 app 文件夹是 Django 的已安装应用,可能需要通过 settings.INSTALLED_APPS 获取,
|
||
# 但通常直接扫描物理目录更灵活。这里假设扫描当前目录下的所有子文件夹。
|
||
|
||
# 3. 自动发现并注册路由
|
||
def auto_load_routers(base_path, package_name_prefix=None):
|
||
"""
|
||
base_path: 物理路径,例如 /path/to/project
|
||
package_name_prefix: 包名前缀,例如 'myproject.' (如果这些文件夹是包的一部分)
|
||
"""
|
||
|
||
# 获取该路径下所有子模块/包
|
||
# 注意:这需要这些文件夹是合法的 Python 包 (即包含 __init__.py),或者在 Python 路径中
|
||
for importer, modname, ispkg in pkgutil.iter_modules([base_path]):
|
||
# 过滤条件:
|
||
# 1. 必须是包 (文件夹)
|
||
# 2. 不以 '_' 开头 (排除 __pycache__, _private 等)
|
||
# 3. 排除当前文件所在的脚本名 (防止递归或错误)
|
||
if ispkg and not modname.startswith('_'):
|
||
|
||
# 构造完整的模块路径:例如 "authorize.views"
|
||
# 如果这些文件夹直接位于根路径且不是大包的一部分,package_name_prefix 可能为空
|
||
if package_name_prefix:
|
||
full_module_path = f"{package_name_prefix}{modname}.views"
|
||
else:
|
||
full_module_path = f"{modname}.views"
|
||
|
||
try:
|
||
# 动态导入 views 模块
|
||
module = importlib.import_module(full_module_path)
|
||
|
||
# 检查是否存在 'router' 变量
|
||
if hasattr(module, 'router'):
|
||
router = getattr(module, 'router')
|
||
|
||
# 使用文件夹名称 (modname) 作为 URL 前缀
|
||
# 例如: 文件夹 'authorize' -> 前缀 '/authorize'
|
||
url_prefix = f"/{modname}"
|
||
|
||
api_v1.add_router(url_prefix, router)
|
||
print(f"✅ 自动注册: {url_prefix} (来自 {full_module_path})")
|
||
else:
|
||
print(f"⚠️ 跳过: {modname} (在 {full_module_path} 中未找到 'router' 变量)")
|
||
|
||
except ImportError as e:
|
||
# 如果文件夹里没有 views.py,会报 ImportError,这是正常的,跳过即可
|
||
if "No module named" in str(e) and "views" in str(e):
|
||
print(f"ℹ️ 跳过: {modname} (没有 views.py 模块)")
|
||
else:
|
||
print(f"❌ 导入错误 {modname}: {e}")
|
||
except Exception as e:
|
||
print(f"❌ 注册失败 {modname}: {e}")
|
||
|
||
|
||
api_v1 = NinjaAPI(version='1.0.0', auth=GlobalAuth())
|
||
auto_load_routers(project_root)
|
||
|
||
|
||
@api_v1.exception_handler(ValidationError)
|
||
def validation_errors(request, exc):
|
||
logger.error(request.body)
|
||
logger.error(exc)
|
||
logger.debug(traceback.format_exc())
|
||
return JsonResponse(data=CommonResponse.error(msg=f"输入参数验证失败!").to_dict(), status=422, safe=False)
|
||
|
||
|
||
@api_v1.exception_handler(exceptions.PyJWKError) # 捕获 HttpBearer 认证异常
|
||
def custom_http_bearer_exception_handler(request, exc):
|
||
logger.error(exc)
|
||
return JsonResponse(data=CommonResponse.error(msg=f"用户Token认证失败:{exc}").to_dict(), status=401)
|
||
|
||
|
||
@api_v1.exception_handler(exceptions.ExpiredSignatureError) # 捕获 HttpBearer 认证异常
|
||
def custom_http_bearer_exception_handler(request, exc):
|
||
logger.error(exc)
|
||
return JsonResponse(data=CommonResponse.error(msg=f"用户登录信息已过期,请重新登录!!").to_dict(), status=401)
|
||
|
||
|
||
urlpatterns = [
|
||
path('', include("front.urls")),
|
||
path('admin/', admin.site.urls),
|
||
path("api/", api_v1.urls),
|
||
path("ckeditor5/", include('django_ckeditor_5.urls')),
|
||
path("upload/", custom_upload_file, name="custom_upload_file"),
|
||
|
||
]
|
||
urlpatterns += staticfiles_urlpatterns()
|
||
if settings.DEBUG:
|
||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|