Django多子项目按域名动态加载urlpatterns问题求助
Django多子项目域名动态路由解决方案
方案一:自定义URLResolver实现动态路由
Django的URL解析系统支持自定义Resolver,我们可以通过它根据请求域名动态加载对应子项目的urlpatterns。
步骤1:编写自定义DomainURLResolver
在项目根目录创建resolvers.py:
from django.urls.resolvers import URLResolver from django.conf import settings from django.http import HttpRequest, HttpResponseNotFound from django.urls import path class DomainURLResolver(URLResolver): def resolve(self, path): # 从全局属性获取当前请求(由中间件注入) request = getattr(HttpRequest, '_current_request', None) if not request: raise ValueError("Request not captured, check middleware setup") # 统一转小写避免大小写问题 domain = request.get_host().lower() # 从配置读取域名-子项目映射 domain_mapping = getattr(settings, 'DOMAIN_SERVICE_MAPPING', {}) target_urlconf = domain_mapping.get(domain) if not target_urlconf: # 处理未映射域名的情况 def domain_not_found(request): return HttpResponseNotFound("Domain not mapped to any service") temp_urls = [path('', domain_not_found)] self.urlconf_module = type('TempURLs', (), {'urlpatterns': temp_urls}) else: # 动态设置要加载的子项目urls self.urlconf_module = target_urlconf return super().resolve(path)
步骤2:编写请求捕获中间件
创建middleware.py,用于将当前请求临时存入全局属性,供Resolver调用:
class RequestCaptureMiddleware: def __init__(self, get_response): self.get_response = get_response def __call__(self, request): # 将请求绑定到HttpRequest类属性,供Resolver访问 setattr(request.__class__, '_current_request', request) response = self.get_response(request) # 请求处理完成后清理属性,防止内存泄漏 delattr(request.__class__, '_current_request') return response
步骤3:配置settings.py
在settings.py中注册中间件,并定义域名映射:
# 中间件列表最前面加入请求捕获中间件 MIDDLEWARE = [ 'your_project_name.middleware.RequestCaptureMiddleware', # 其他原有中间件... ] # 域名与子项目urls的映射,可后续改为从数据库读取 DOMAIN_SERVICE_MAPPING = { 'service1.domain.com': 'service1.urls', 'domainforservice1.com': 'service1.urls', 'service2.domain.com': 'service2.urls', 'domainforservice2.com': 'service2.urls', }
步骤4:根urls.py中使用自定义Resolver
from django.urls import path from your_project_name.resolvers import DomainURLResolver from django.conf import settings urlpatterns = [ # 全局通用路由(如admin)放在前面 path('admin/', admin.site.urls), # 使用自定义Resolver处理域名路由 DomainURLResolver(r'', 'service1.urls'), # 这里的默认值会被动态替换 ]
方案二:中间件直接转发请求到子项目
如果不想修改URL解析器,可以在中间件中直接根据域名调用对应子项目的URL分发逻辑。
编写路由中间件
from django.urls import get_resolver from django.http import HttpResponseNotFound class DomainRoutingMiddleware: def __init__(self, get_response): self.get_response = get_response # 预定义域名映射,可改为从数据库查询 self.domain_mapping = { 'service1.domain.com': 'service1.urls', 'domainforservice1.com': 'service1.urls', 'service2.domain.com': 'service2.urls', 'domainforservice2.com': 'service2.urls', } def __call__(self, request): domain = request.get_host().lower() target_urlconf = self.domain_mapping.get(domain) if not target_urlconf: return HttpResponseNotFound("Unsupported domain") # 使用子项目的urlconf解析请求路径 resolver = get_resolver(target_urlconf) try: match = resolver.resolve(request.path_info) view, args, kwargs = match return view(request, *args, **kwargs) except: # 解析失败时交给全局路由处理 return self.get_response(request)
注册中间件
将该中间件放在settings.py的MIDDLEWARE列表最前面,确保优先处理路由转发。
注意事项
- 动态映射更新:如果需要动态修改域名与子项目的绑定,可将
domain_mapping改为从数据库读取,并添加缓存逻辑避免重复查询。 - 本地开发配置:在本地
hosts文件中添加域名映射,例如127.0.0.1 service1.domain.com,才能正常测试。 - 生产环境Host头:确保Web服务器(Nginx/Apache)正确传递
Host请求头给Django,不要篡改该字段。 - 异常处理:需添加合适的异常捕获逻辑,处理子项目路由解析失败的情况。
内容的提问来源于stack exchange,提问作者Ömer Ekmen
相关产品推荐
相关产品推荐

