Django(6)路由

2023-05-16

文章目录

  • 一、路由概述
  • 二、正则路径中的分组
    • (1)正则路径中的无名分组
    • (2)正则路径中的有名分组
    • (3)路由分发
  • 三、反向解析(使用reverse)
    • (1)普通路径
    • (2)正则路径——无名分组
    • (3)正则路径——无名分组
  • 四、反向解析(使用模板)
    • (1)普通路径
    • (2)正则路径——无名分组
    • (3)正则路径——有名分组
  • 五、命名空间

此文章参考菜鸟教程:Django 路由 | 菜鸟教程 (runoob.com)

Django版本:

>>> django.VERSION  
(4, 1, 0, 'final', 0)

PS:基于前几章的进度进行修改

一、路由概述

  • 路由简单的说就是根据用户请求的URL连接来判断对应的处理程序,并且返回处理结果

  • 路由主要用于建立URL与Django视图之间的映射关系。路由在urls.py文件配置,urls.py文件中每一条配置都对应了处理程序

  • 不同版本的Django,urls.py文件的配置也有点差异

  • Django1.1.X版本:

url():普通路径和正则路径都可以使用,需要自己手动添加正则首位限制符号,例如:

from django.conf.urls import url # 用 url 需要引入

urlpatterns = [
    url(r'^admin/$', admin.site.urls),
    url(r'^index/$', views.index), # 普通路径
    url(r'^articles/([0-9]{4})/$', views.articles), # 正则路径
]
  • Django2.2.X之后的版本:

path():代替1.1版本的url()方法,不需要手动添加正则首位限制符号,底层已经添加

re_path():用于正则路径,需要自己手动添加正则首位限制符号

from django.urls import re_path # 用re_path 需要引入

urlpatterns = [
    path('admin/', admin.site.urls),
    path('index/', views.index), # 普通路径
    re_path(r'^articles/([0-9]{4})/$', views.articles), # 正则路径
]

二、正则路径中的分组

(1)正则路径中的无名分组

  • 无名分组按照位置传参,根据顺序一一对应
  • views中除了request参数,其他形参的数量要与urls中的分组数量一致,否则会报错
- 修改urls.py文件
#-*- coding: utf-8 -*-
from django.urls import re_path

from . import views

urlpatterns = [
    re_path("^test/([0-9]{4})/$",views.test),  #匹配以test开头,/结尾,例如/test/1234/
]


- 修改views.py文件
# -*- coding: utf-8 -*-
from django.http import HttpResponse

def test(request,year):
    print(year)
    return HttpResponse("Test")
  • 访问127.0.0.1:8000/test/2022,观察终端输出,最终print输出的是2022,这是因为方法的一个形参,代表路径中一个分组的内容,按顺序进行匹配,year参数匹配的是第二个分组([0-9]{4}),所以最终输出2022

在这里插入图片描述
在这里插入图片描述

(2)正则路径中的有名分组

  • 语法:
(?P<组名>正则表达式)  #大写的P
  • 有名分组按照关键字传参,与位置顺序没有关系
  • views中除了request参数,其他形参的数量要与urls中的分组数量一致,并且views中的形参名称要与urls中的组名相对应
- 修改urls.py文件
#-*- coding: utf-8 -*-
from django.urls import re_path

from . import views

urlpatterns = [
    re_path("^test/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$",views.test),
]
#分别指定year参数与month参数


- 修改views.py文件
# -*- coding: utf-8 -*-
from django.http import HttpResponse

def test(request,year,month):
    print(year,month)
    return HttpResponse("Test")
  • 访问127.0.0.1:8000/test/2022/08,查看终端输出

在这里插入图片描述
在这里插入图片描述

注意:无名分组和有名分组不能混用!!

(3)路由分发

  • Django项目中多个app目录共用一个urls非常容易混淆,后期维护也不方便,可以使用路由分发,让每个app目录都单独拥有自己的urls,实际步骤为:
  1. 在每个app目录中都创建一个urls.py文件
  2. 在项目名称目录下的urls.py文件中,统一将路径分发给各app目录
  • 创建app目录,在项目目录下创建test1test2

在这里插入图片描述

  • 分别在两个目录下创建文件urls.pyviews.py文件

在这里插入图片描述

  • 先修改helloworld主app目录下的urls.py文件
#-*- coding: utf-8 -*-
from django.urls import re_path,path,include
from . import views

urlpatterns = [
    path('Year/',include("test1.urls")),   #分发给两个app目录下的urls.py文件
    path('Month/',include("test2.urls")),
]
  • 修改两个usrl.py文件
- test1/urls.py
#-*- coding: utf-8 -*-
from django.urls import re_path
from . import views

urlpatterns = [
    re_path('test1/(?P<year>[0-9]{4})/$',views.test1),
]

- test2/urls.py
#-*- coding: utf-8 -*-
from django.urls import re_path
from . import views

urlpatterns = [
    re_path('test2/(?P<month>[0-9]{2})/$',views.test2),
]
  • 修改两个views.py文件
- test1/views.py
# -*- coding: utf-8 -*-
from django.http import HttpResponse

def test1(request,year):
    print(year)
    return HttpResponse("Test1")

- test2/views.py
# -*- coding: utf-8 -*-
from django.http import HttpResponse

def test2(request,month):
    print(month)
    return HttpResponse("Test2")
  • 访问测试test1,需要访问127.0.0.1:8000/Year/test1/2022/,观察终端输出

注意:访问的资源路径是主app的urls路径加分发的app的urls路径,所以最终是/Year/test1/2022
在这里插入图片描述

在这里插入图片描述

  • 访问测试test2,同理访问127.0.0.1:8000/Month/test2/08/,观察终端输出
    在这里插入图片描述
    在这里插入图片描述

三、反向解析(使用reverse)

  • 随着功能的增加、路由层url的变化,通常需要去修改对应的视图层和模板层的url,每次一变化就去修改,不便于维护
  • 上述情况可以使用反向解析,当路由层url发生变化时,只需在视图层和模板层动态反向解析出更改后的url,这样就免去了修改的操作
  • 反向解析一般作用于模板中的超链接以及视图中的重定向

(1)普通路径

  • 在urls.py文件中给路由起“别名”,语法为:name="别名",实例:
- 修改urls.py文件
#-*- coding: utf-8 -*-
from django.urls import path
from . import views

urlpatterns = [
    path('index/',views.index),
    path('login/',views.login,name="login"),  #别名login
]


- 在views.py文件中,从urls引入reverse,利用reverse("别名")进行反向解析
# -*- coding: utf-8 -*-
from django.urls import reverse
from django.http import HttpResponse
from django.shortcuts import redirect,render
from django.views.decorators.csrf import csrf_exempt

@csrf_exempt
def index(request):
    if request.method == "GET":
        return HttpResponse("请使用POST")
    else:
        username = request.POST.get('username')
        pwd = request.POST.get('pwd')
        if username == 'zhangsan' and pwd == '123456':
            return HttpResponse("登录成功!!")
        else:
            return redirect(reverse('login')) #反向解析

def login(request):
        return HttpResponse('请传入 username 和 pwd 参数')
    
  • 直接访问127.0.0.1:8000/index/

在这里插入图片描述

  • 使用postman不加参数进行访问127.0.0.1:8000/index/,查看终端输出,可以看到302跳转,并且跳转是通过GET方式访问的login资源

在这里插入图片描述

在这里插入图片描述

  • 使用postman访问,添加usernamepwd参数

在这里插入图片描述

  • 不使用正确参数

在这里插入图片描述

(2)正则路径——无名分组

  • 使用正则路径,需要使用re_path方法,同样通过name='别名'配置路径别名,实例:
- 修改urls.py文件
#-*- coding: utf-8 -*-
from django.urls import path,re_path
from . import views

urlpatterns = [
    path('index/',views.index),
    re_path('^login/([0-9]{4})/$',views.login,name="login"), 
]

- 修改views.py
# -*- coding: utf-8 -*-
from django.urls import reverse
from django.http import HttpResponse
from django.shortcuts import redirect,render
from django.views.decorators.csrf import csrf_exempt

@csrf_exempt
def index(request):
    if request.method == "GET":
        return HttpResponse("请使用POST")
    else:
        username = request.POST.get('username')
        pwd = request.POST.get('pwd')
        if username == 'zhangsan' and pwd == '123456':
            return HttpResponse("登录成功!!")
        else:
            return redirect(reverse('login',args=('2022',)))

@csrf_exempt
def login(request,year):  #添加一个参数
        print(year)
        return HttpResponse('Test')
  • 使用postman访问127.0.0.1:8000/index,观察终端

在这里插入图片描述
在这里插入图片描述

(3)正则路径——无名分组

  • 实例:
- 修改urls.py文件
#-*- coding: utf-8 -*-
from django.urls import path,re_path
from . import views

urlpatterns = [
    path('index/',views.index),
    re_path('^login/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$',views.login,name="login"), 
]

- 修改views.py文件
# -*- coding: utf-8 -*-
from django.urls import reverse
from django.http import HttpResponse
from django.shortcuts import redirect,render
from django.views.decorators.csrf import csrf_exempt

@csrf_exempt
def index(request):
    if request.method == "GET":
        return HttpResponse("请使用POST")
    else:
        username = request.POST.get('username')
        pwd = request.POST.get('pwd')
        if username == 'zhangsan' and pwd == '123456':
            return HttpResponse("登录成功!!")
        else:
            return redirect(reverse('login',kwargs={"year":"2022","month":"09"}))

@csrf_exempt
def login(request,year,month):
        print(year + month)
        return HttpResponse('Test')
  • 使用postman访问127.0.0.1:8000/index,观察终端
    在这里插入图片描述
    在这里插入图片描述

四、反向解析(使用模板)

(1)普通路径

  • 在模板文件(html文件)中使用反向解析,利用{% url '别名' %}

  • urls.py文件

#-*- coding: utf-8 -*-
from django.urls import path
from . import views

urlpatterns = [
    path('index/',views.index),
    path('login/',views.login,name="login"),  #别名login
]
  • views.py文件
# -*- coding: utf-8 -*-
from django.urls import reverse
from django.http import HttpResponse
from django.shortcuts import redirect,render
from django.views.decorators.csrf import csrf_exempt


def index(request):  #index资源返回html页面,页面使用post方法反向解析到login资源
        return render(request,"test.html")

@csrf_exempt       #因为html使用post方法,所以需要使用装饰器将此函数可以接受post方式
def login(request):
        username = request.POST.get('username')
        pwd = request.POST.get('pwd')
        if username == "zhangsan" and pwd == "123456":
                return HttpResponse("登录成功")
        else:
                return HttpResponse("登录失败")
  • templates/test.html文件
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h3>用户登录</h3>
    <form action="{% url 'login' %}" method="post">
        {% csrf_token %}   #使用post方式,需要添加
        <p>用户名:<input type="text" name="username"></p>
        <p>密码:<input type="text" name="pwd"></p>
        <input type="submit">
    </form>
</body>
</html>
  • 使用浏览器访问127.0.0.1:8000/index/,先输出正确的usernamepwd,查看终端输出,然后输出错误的参数

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

(2)正则路径——无名分组

  • 使用正则路径时,模板中利用{% url "别名" 符合正则匹配的参数 %}来实现反向解析

  • urls.py文件

#-*- coding: utf-8 -*-
from django.urls import path,re_path
from . import views

urlpatterns = [
    path('index/',views.index),
    re_path('^login/([0-9]{4})/$',views.login,name="login"),  #别名login
]
  • views.py文件
# -*- coding: utf-8 -*-
from django.urls import reverse
from django.http import HttpResponse
from django.shortcuts import redirect,render
from django.views.decorators.csrf import csrf_exempt


def index(request):
        return render(request,"test.html")

@csrf_exempt     
def login(request,year):   #增加参数year
        username = request.POST.get('username')
        pwd = request.POST.get('pwd')
        if username == "zhangsan" and pwd == "123456":
                return HttpResponse("登录成功,当前年份:" + year)
        else:
                return HttpResponse("登录失败,当前年份:" + year)
    
  • templates/test.html文件
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h3>用户登录</h3>
    <form action="{% url 'login' '2022' %}" method="post">    #无名分组,添加参数
        {% csrf_token %}
        <p>用户名:<input type="text" name="username"></p>
        <p>密码:<input type="text" name="pwd"></p>
        <input type="submit">
    </form>
</body>
</html>
  • 访问127.0.0.1:8000/index/,使用正确的usernamepwd,查看终端输出,然后使用错误的参数进行访问

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

(3)正则路径——有名分组

  • 使用正则路径时,模板中利用{% url "别名" 分组名=符合正则匹配的参数 %}来实现反向解析,多个分组,使用空格分割

  • urls.py文件

#-*- coding: utf-8 -*-
from operator import indexOf
from django.urls import path,re_path
from . import views

urlpatterns = [
    path('index/',views.index),
    re_path('^login/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$',views.login,name="login"),  #别名login
]
  • views.py文件
# -*- coding: utf-8 -*-
from django.urls import reverse
from django.http import HttpResponse
from django.shortcuts import redirect,render
from django.views.decorators.csrf import csrf_exempt


def index(request):
        return render(request,"test.html")

@csrf_exempt     
def login(request,year,month):  #添加新参数
        username = request.POST.get('username')
        pwd = request.POST.get('pwd')
        if username == "zhangsan" and pwd == "123456":
                return HttpResponse("登录成功,当前年份:" + year + "月份" + month)
        else:
                return HttpResponse("登录失败,当前年份:" + year + "月份" + month)
  • templates/test.html文件
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h3>用户登录</h3>
    <form action="{% url 'login' year='2022' month='09' %}" method="post">
        {% csrf_token %}
        <p>用户名:<input type="text" name="username"></p>
        <p>密码:<input type="text" name="pwd"></p>
        <input type="submit">
    </form>
</body>
</html>
  • 访问127.0.0.1:8000/index/,使用正确的usernamepwd,查看终端输出,然后使用错误的参数进行访问

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

五、命名空间

  • 命名空间(namespace),表示标识符的可见范围,一个标识符可以在多个命名空间定义,不同命名空间的标识符含义不会相干

  • 一个新的命名空间可以定义任意标识符,并且不会与其他命名空间的相同标识符发生冲突

  • 使用原因:

路由别名没有作用域,而Django在反向解析URL时会进行全局搜索,搜索到的第一个路由别名,会直接返回,当在不同app目录下定义相同的路由别名时,可能会导致反向解析错误,而命名空间就可以解决这个问题

  • helloworld同等目录下创建test1test2,并且分别在两个目录下创建urls.pyviews.py

在这里插入图片描述

  • 编写两个目录的urls.pyviews.py
- test1的urls.py
#-*- coding: utf-8 -*-
from django.urls import path
from . import views


urlpatterns = [
    path('index1/',views.test1),
]



- test1的views.py
# -*- coding: utf-8 -*-
from django.http import HttpResponse

def test1(request):
    return HttpResponse("Test1")



- test2的urls.py
#-*- coding: utf-8 -*-
from django.urls import path
from . import views


urlpatterns = [
    path('index2/',views.test2),
]


- test2的views.py
# -*- coding: utf-8 -*-
from django.http import HttpResponse

def test2(request):
    return HttpResponse("Test2")

  • 修改helloworld/urls.py,这个是主app
#-*- coding: utf-8 -*-
from django.urls import path,include #导入include
#from . import views


urlpatterns = [
    path('test1/',include("test1.urls")),   #导入两个app
    path('test2/',include("test2.urls")),
]

include参数直接传一个元组,元组的第一个参数是指定APP的urls,第二个参数是指定命名空间

  • 现在分别访问127.0.0.1:8000/test1/index1127.0.0.1:8000/test2/index2

在这里插入图片描述
在这里插入图片描述

  • 已经可以成功访问,现在为两个app的urls都加上别名,名称都是index
- test1的urls.py
#-*- coding: utf-8 -*-
from django.urls import path
from . import views


urlpatterns = [
    path('index1/',views.test1,name='index'),
]


- test2的urls.py
#-*- coding: utf-8 -*-
from django.urls import path
from . import views


urlpatterns = [
    path('index2/',views.test2,name='index'),
]
  • 修改两个app的views.py,设置反向解析,都重定向到index
- test1的views.py
# -*- coding: utf-8 -*-
from django.http import HttpResponse
from django.urls import reverse

def test1(request):
    return HttpResponse(reverse('index'))  #输出reverse('index')会返回反向解析的资源路径



- test2的views.py
# -*- coding: utf-8 -*-
from django.http import HttpResponse
from django.urls import reverse

def test2(request):
    return HttpResponse(reverse('index'))
  • 这样配置之后,我们想要达到的目的访问不同的app时,都可以返回自己相应的资源路径,现在来访问观察效果

在这里插入图片描述
在这里插入图片描述

  • 可以发现访问两个资源,返回的都是test2/index2也就是test2的资源路径,这样肯定是不符合需求的,现在来加上命名空间

造成这样的结果,是因为别名是没有作用域的,所以在django反向解析时,会在项目全局顺序搜索,当查找到第一个时,就会直接返回

  • 修改两个app的urls
- test1的urls.py
#-*- coding: utf-8 -*-
from django.urls import path
from . import views

app_name = 'test1'   #设置命名空间名称
urlpatterns = [
    path('index1/',views.test1,name='index'),
]

- test2的urls.py
#-*- coding: utf-8 -*-
from django.urls import path
from . import views

app_name = 'test2'
urlpatterns = [
    path('index2/',views.test2,name='index'),
]
  • 修改helloworld/urls.py
#-*- coding: utf-8 -*-
from django.urls import path,include
#from . import views


urlpatterns = [
    path('test1/',include("test1.urls",namespace='test1')),  #指定命名空间
    path('test2/',include("test2.urls",namespace='test2')),
]
  • 修改两个app的views.py
- test1的views.py
# -*- coding: utf-8 -*-
from django.http import HttpResponse
from django.urls import reverse

def test1(request):
    return HttpResponse(reverse('test1:index'))  #test1表示命名空间,index表示别名



- test2的views.py
# -*- coding: utf-8 -*-
from django.http import HttpResponse
from django.urls import reverse

def test2(request):
    return HttpResponse(reverse('test2:index'))

  • 现在再来访问
    在这里插入图片描述

在这里插入图片描述

  • 成功达到的目的!!!

  • 模板使用命名空间和使用reverse相似,同样也是在指定别名时,前面添加命名空间名称,例如:

    {% url '命名空间名称:别名' %}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Django(6)路由 的相关文章

随机推荐

  • 你们要的网页版matlab来了,速领!

    近来有不少小伙伴在公众号里面问网页版matlab xff0c 之前公众号确实是在菜单中给大家分享过一款网页版matlab编译环境 xff0c 不过由于公众号菜单更新换代 xff0c 已经将相关分享入口给取消了 xff0c 为了让大家更加方便
  • RHEL本地登录login出现permission denied的可能的解决办法

    OS xff1a RHEL7 6 开机后发现在本地使用root和非root用户登录 xff0c 输入用户名密码后 xff0c 都会出现permission denied xff0c 没有办法进入到系统 在网上查了很多资料 xff0c 看到这
  • Updating crates.io index 速度慢的解决办法

    Rust社区公开的第三方包都集中在crates io网站上面 xff0c 他们的文档被自动发布到doc rs网站上 Rust提供了非常方便的包管理器cargo xff0c 它类似于Node js的npm和Python的pip 但cargo不
  • SecureCRT 密码解密

    SecureCRT xff08 8 以上 xff09 配置的密码存放在 Config Sessions下面的ini文件中 xff0c 内容如下 xff1a S Username 61 S Monitor Password V2 61 S P
  • AD10 四层板经验(层的性质)

    层的性质 xff1a 内部层有两种 xff1a plane和layer xff0c 他们的区别如下 xff1a plane是所有Layer的其中一个 xff0c Layer是指层 xff0c 例如有常见的信号层Signal Layers 顶
  • Centos7下httpd最新版本安装或者更新至最新版本

    查找Centos上软件库里的httpd版本 yum info httpd yum info httpd 已加载插件 xff1a fastestmirror Loading mirror speeds from cached hostfile
  • 修改git远程仓库地址

    由于git仓库的地址发生了变化 xff0c 需要修改仓库地址才能push 查看本地工程git仓库关联的远程仓库地址 xff1a span class token function git span remote v 删除本地关联的远程仓库地
  • INSTALL_FAILED_NO_MATCHING_ABIS 的解决办法

    在Android模拟器上安装apk的时候出现 xfeff xfeff INSTALL FAILED NO MATCHING ABIS 这个错误提示的解决办法 是由于使用了native libraries 该native libraries
  • IntelliJ IDEA 控制台中文乱码

    1 预热 刚刚接触IntelliJ IDEA几天 xff0c 在易用性方面的确比Eclipse好很多 xff0c 比较智能 xff0c 各种插件 工具都已经集成 xff0c 和Mac OS X类似 开箱即用 但是还是老大难问题 中文乱码 x
  • unable to start the monitor on 4454 ,an other instance is problaly using the same port

    在打开idea studio后 xff0c 再打开android studio xff0c 或者反过来 xff0c 打开项目的时候就会报 xff1a unable to start the monitor on 4454 xff0c an
  • windows中为cmd设置代理

    在CMD环境下设置代理可能不是很常用 xff0c 但是某些情况下还是可能会用到 xff0c 比如有些资源网站被墙了 xff0c 此时你如果想访问这些资源时 xff0c 只能通过代理来访问相应的资源 xff0c 而你需要在CMD环境下下载墙外
  • version `GLIBCXX_3.4.21' not found 解决办法

    在安装cmake3 5 1运行 bootstrap的时候出现如下提示 xff1a gmake cmake 是最新的 span class hljs header usr soft cmake 3 5 1 Bootstrap cmk cmak
  • Oracle死锁查询及处理

    一 数据库死锁的现象 程序在执行的过程中 xff0c 点击确定或保存按钮 xff0c 程序没有响应 xff0c 也没有出现报错 二 死锁的原理 当对于数据库某个表的某一列做更新或删除等操作 xff0c 执行完毕后该条语句不提 交 xff0c
  • Ubuntu 18.04添加中文输入法

    找到设置 xff1a 点击 Manager Installed Languages 出现下图提示 xff1a Keyboard input method system 里面有Ibus XIM fcitx none 三种输入架构 xff0c
  • KEIL问题【打开文件太多造成任何按钮都不可点】【Keil4 编译时出现RL-ARM is not allowed with this license 】【 局部变量仿真显示not in scope】

    SYD8801是一款低功耗高性能蓝牙低功耗SOC xff0c 集成了高性能2 4GHz射频收发机 32位ARM Cortex M0处理器 128kB Flash存储器 以及丰富的数字接口 SYD8801片上集成了Balun无需阻抗匹配网络
  • Django(2)模板、标签

    文章目录 一 使用Django模板修改页面二 Django模板标签 变量 列表 字典 过滤器1 default2 length3 filesizeformat4 date5 truncatechars6 safe if else标签 for
  • Django(3)模型

    文章目录 一 Django 模型 ORM二 数据库配置三 定义模型 xff08 创建数据表 xff09 四 数据库基本操作 插入数据 获取数据 xff08 1 xff09 查询所有的数据行 xff08 2 xff09 where条件查询 x
  • Django(4)表单

    文章目录 一 概述二 GET方法三 POST方法四 Request对象五 QueryDict对象 此文章参考菜鸟教程 xff1a Django 表单 菜鸟教程 runoob com Django版本 xff1a span class tok
  • Django(5)视图

    文章目录 一 视图概述二 请求对象HttpRequest xff08 1 xff09 GET xff08 2 xff09 POST xff08 3 xff09 body xff08 4 xff09 path xff08 5 xff09 me
  • Django(6)路由

    文章目录 一 路由概述二 正则路径中的分组 xff08 1 xff09 正则路径中的无名分组 xff08 2 xff09 正则路径中的有名分组 xff08 3 xff09 路由分发 三 反向解析 xff08 使用reverse xff09