1.路由存在的意义
????首先简单说一下路由是干什么的。之前我们在使用PHP函数的时候,是通过如下方式使用的:
localhost/index.php/index/hello/name/world
简单解释一下,localhost/index.php是入口文件,index表示的是控制器,hello表示的是index这个控制器里面的方法,name指的是前面提到的hello方法里面的参数名,world就是参数值。如果这个不直观的话,请看下图,对应的代码如下: 总结如下:在不使用路由的情况下调用函数的方式为:
localhost/index.php/控制器名称/函数名称/参数名称/参数值
你可能想问如果有多个参数应该怎么办呢?很简单在后面照加就行:
localhost/index.php/控制器名称/函数名称/param1/value1/param2/value2
????当你写完之后,你肯定有点不爽了,这要是我不知道名字,这怎么分辨呀,又是控制器名又是参数名,还加个参数值,他们没有什么区分方式也太难受了。其实,也是可以区分的,根据上面的规则,将index.php之后的第一个看做控制器,之后看成函数名,后面依次是参数和值交替出现。 ????虽然是这个道理,总觉得说了也白说。好,看如下两种种方式:
http:
http:
第一种方式就不用怎么说了,其实就是最常见的get提交方式,比如百度搜索就是这样的。对于第二种方式,其实是在参数前面加了一个“:” 方便区分参数。 ????那么问题来了,你平时见过这么长的url吗?太多了,比如CSDN的链接分享。额…确实,但是我们自己设计总不想把url设计成这么长吧。route的意义就在这里,它可以使我们的url更加简洁。
2.路由的定义方式
????这里我们不多说明了,起初接触路由的时候,你可能像我一样觉得有些不适应,倒想回到原始状态。这其实是还没有掌握规则,当你熟悉了之后,你就会发现它是真的香。使用规则大致分为两种:(1)指定方式路由;(2)附加方式路由; ????首先,我们必须要知道,浏览器的提交共有五种方式:get、post、delete、put、patch;前面两种是比较常用的,后面的三种可以先放一下^_^。简单说一下这个get和post,get一般用来获取服务器上的资源,post一般是向服务器提交资源,对于一个网站来说,get多用于路由的跳转,post多用于表单数据的提交,至于二者的详细区别,有兴趣可以看看这篇文章,讲的还挺详细,推荐给大家:
????GET和POST两种基本请求方法的区别
具体的路由定义方式为,在route/app.php文件中写入如下代码:
Route::get('simplify/:name/:index','Index/hello');
第一个参数是简写后的访问方式,第二个参数是路由位置(控制器名称/方法名称)至于其他的请求方式只需要将get方法改成post、delete等就行,但是得提醒一下的是不同的路由请求方式浏览器处理机制是不一样的。换句话说就是利用get请求的写法发送post请求报错是很正常的。再补充如下几点: (1)将get换成any表示任意一种方式的路由都包含; (2)浏览器对于路由的匹配是从上到下的,如果匹配就执行,对于重复的路由是不会报错的。 (3)已经定义路由之后,再使用原始的路由方式会报错,这是因为thinkPHP会默认去检测路由是否存在(这样也便于减少冲突)。 ????说完指定方式路由之后,附加就简单了,只是把路由方式放到了后面,具体写法,在route/app.php文件中写入如下代码:
Route::rule('simplify/:name/:index','Index/hello','get|post');
前两个参数同上,第三个参数表示访问方式,可以是‘any’,也可以是多个方式用“|” 连接。推荐使用第一种方法(更利于路由匹配)。
3.路由的限制规则
????有些时候我们可能需要限制用户传入的参数,还用pattern就行。紧跟上面的例子,具体写法如下:
Route::get('simplify/:name/:index','index/hello')->pattern([
'name'=>'[A-za-z]{6}',
'index'=>'\d+'
]);
对于正则规则的写法可以参照: 菜鸟教程正则表达式写法 通过数据测试发现,路由的检测将会顺着里面的正则向下检索,如果没能匹配,将会继续向下检索,如果实在没有,则采用原始方式将simplify当成控制器查询。同样做一些补充: (1)定义某参数的全局匹配规则(不管那种路由,只要出现该参数,就需要检测相应规则),方式如下:
Route::pattern(['name'=>'[A-za-z]{6}']);
(2)路由的参数除了使用“:” 区分参数外,还可以使用如下方式:
Route::get('simplify/<name>/<index>','index/hello');
(3)路由之间的访问方式比较灵活,可以使用特殊符号(只要不被浏览器解码就行),例如:
Route::get('simplify/<name>@_@<index>','index/hello');
(4)动态路由(即将控制器的名字使用变量代替),具体写法如下:
Route::get('simplify/<control>','<control>/index');
4.多级路由的访问
????这里还是举例说明吧,在实际应用中,当项目比较大的时候,可能需要对项目进行分组。例如下面的控制器结构: 这时有如下两种路由定义方式: (1)直接使用“.” 进行分级:
Route::get('passage','Group.Passage/index');
(2)使用全局路由:
Route::get('test','app\controller\Group\Passage@test');
注:最后的方法名之前使用“@” ,至于参数的传递,和上面一样。补充一点,建议使用第一种方式,在实际应用中发现:第二种由于使用的全局路径导致实际上的路径依然保留在index目录下,不会跳转到对应的二级目录下,按照常规方法使用facade门面类的时候会报错。
5.路由的函数筛选
????路由方法还具有很多辅助筛选函数,比较常用的函数如下: (1)ext 函数,用于强制url路由后缀,使用方法如下:
Route::get('passage','Group.Passage/index')->ext('html|css');
解释:上述表示的是,访问的url路径必须是.html或者.css为后缀的,否则将不会匹配此条路由规则。此处补充说明一点,我们平时使用原始方法访问控制器下面的方法的时候,会发现路径后面会默认加上.html的后缀,其实这是在config/route.php的 url_html_suffix 处设置的,当此参数为false或者是空的时候表示的是并不设置后缀,同时测试发现,如果对应路径后缀与实际不符时,默认路径会失效,应该是thinkPHP框架内部对于路径作了检测或者说是规则定义,可能说的不太直观,那么看到下一个路径便会明白了(设置该参数为hml,这个也是html文件中的一类,故而后缀可行,但是设置css等默认后缀将失效):
(2)https 函数,对于路由规则会筛选https的安全路由,如果使用的 不是https而是http,将不会予以匹配,用法举例如下:
Route::get('passage','Group.Passage/index')->https();
(3)denyExt 函数,禁止使用某些后缀,可用于防止访问图片资源等,例如如下写法:
Route::get('passage','Group.Passage/index')->denyExt('jpg|jpeg|gif|png');
(4)domain 函数,用于过滤域名或者子级域名,用法举例:
Route::get('passage','Group.Passage/index')->denyExt('nightowl.top');
解释:第一种表示的匹配对应的域名,第二种表示的匹配子级域名中是否含有相关的后缀。 (5)ajax 函数,用于筛选是否是通过Ajax发起请求,这个需要通过jQuery封装的Ajax函数库来进行测试。经过一番思索,发现可以使用如下代码进行测试:
<script>
function btnTest(){
console.log('直接跳转');
window.location.href='http://localhost/index.php/test';
}
</script>
Route::get('test','Group.Passage/test')->ajax();
解释:通过上面的两种方法和对应路由,测试发现,不使用->ajax()筛选路由时上述两种方法都能够使用。但是如果使用了筛选,将导致下面的方法由于路由不匹配而报错(因为该路由不是Ajax请求)。 (6)filter 函数,用于匹配必要参数,用法如下:
Route::get('test','Group.Passage/test')->filter([
'username'=>'凌空暗羽',
'age'=>20
]);
解释:上述表示的是url请求必须含有username和age两个参数并且这两个参数的值也需要一致,否则该路由不会被匹配。 (7)append 函数,用于附加参数,主要是用来页面间额外传值,用法如下:
Route::get('test','Group.Passage/test')->filter([
'hobby'=>'fishing'
]);
(8)补充说明,对于url的匹配筛选,同样遵循链式法则,也可以使用option 方法直接调用数组,用法如下:
Route::get('test','Group.Passage/test')->option([
'https'=>false,
'filter'=>['username'=>'凌空暗羽','age'=>20],
'append'=>['hobby'=>'fishing'],
'ajax'=>true
]);
此外,也可以使用如下写法进行筛选:
Route::domain(['test','origin'],function(){
Route::get('passage','Group.Passage/index')->ajax(false);
});
6.路由的跨域请求
????在实际请求时,我们可能会遇到如下问题:
解释:上述英文报错指的是,Cookie和XMLHttprequest层面的同源策略禁止 Ajax 直接发起跨域HTTP请求(其实可以发送请求,结果被浏览器拦截,不展示),同时 Ajax 请求不能携带与本网站不同源的 Cookie。这主要是因为浏览器为了安全起见,不支持不同域进行跨域请求,这里的域或者源指的是协议+域名+端口 。 ????首先,说一下为什么持跨域请求不安全,跨域请求容易被不法分子利用,进而进行跨域请求伪造,简单地说,指的是不法网站借用用户身份去登录他们想要去登录的网站,并执行相关操作(比如不法网站借用用户身份去转账),具体实现的过程如下: ????问题来了,有时候因为实际需要,我们还是需要发送跨域请求。说起跨域,我们很容易会想到之前我们网页引入百度图片的时候也是跨域,其实这里使用了JSONP 技术,JSONP 本质上是利用 <script>、<img>、<iframe> 等标签不受同源策略限制,可以从不同域加载并执行资源的特性,来实现数据跨域传输,但是JSONP只能使用get请求,无法被用来处理我们想要的灵活跨域。 ????这时,W3C站出来了,W3C提出CORS (跨域资源共享)标准,它新增的一组HTTP首部字段,允许服务端其声明哪些源站有权限访问哪些资源。它允许浏览器向指定的跨源服务器,发出XMLHttpRequest请求(js接口,用于网络通信),克服了AJAX只能同源使用的限制。 ????实际应用时,浏览器会先利用上面提到的option方法检测是否允许跨域请求。具体写法如下:
Route::get('test','Group.Passage/test')->allowCrossDomain([
'Access-Control-Allow-Origin'=>'http://test.night.com'
]);
经测试成功发送请求。
7.路由的分组与MISS
7.1路由的分组
????其实,路由的分组很好理解,主要是方便路由的管理。举例如下:
Route::group(function(){
Route::get('passage','Group.Passage/index');
Route::get('test','Group.Passage/test');
})->option([
'https'=>false,
'filter'=>['username'=>'凌空暗羽','age'=>20],
]);
????关于路由分组引申了如下几种常用方法: (1)路由的分组名称设置,也可以为每个分组主动设置名称,方便加以区分: (2)路由的自由匹配,除了设置分组明白,还可以结合pattern筛选方法对路由进行自由匹配,举例如下: (3)prefix 函数,统一前缀,主要是为了进一步简化路由方法的第二个route参数,改写上面的自由匹配路由得到如下实例: 最后,补充说明关于config/route.php文件里面的 url_lazy_route 参数,这个参数指的是路由的延时解析。通常情况下加载thinkPHP资源文件时就会进行路由的解析。有时候路由本身的定义规则比较多,这会增加资源负载。使用延时解析指的是,当路由匹配了才会进行路由解析。
7.2路由的MISS
????这个方法就有内味儿了,有时候用户输入的url并不匹配,实际应用一般是需要直接导向一个404页面,而MISS刚好能够解决这个需求。使用方法如下:
Route::miss('Error/miss');
????当然,结合之前的学习,相信接下来的操作应该很熟悉了。我再废话一遍吧,很简单,在controller下面新建一个Error.php(构建Error控制器),在里面写入namesapce并引入facade门面类,添加miss方法如下:
public function miss(){
return View::fetch('index');
}
接下来便是在view中创建error目录和index.html文件,在里面设计想要的404报错提示页面就行。 ????最后,再来补充一点,这里的miss是全局匹配页面丢失,也可以在分组里面设置miss路由以及相应的miss方法。
8.资源路由
????其实,对于资源路由的使用,之前我们在构建用户增删改查列表时也使用过。thinkPHP提供了快速创建一个资源控制器的方法,如下图所示,在tp文件夹下面打开cmd,使用如下指令就可以快速创建:
php think make:controller Blog(控制器名称)
解释:其实之前也创建过,但是并没有系统的提及资源控制器这个概念,系统默认方法介绍可见下表: 通过不同的请求方式以及url路径可以将不同的方法给区分开来。 ????常用的方法如下: (1)vars 函数,修改默认的参数名称,使用方法如下:
Route::resource('blog','Blog')->vars(['id'=>'blog_id']);
这样read、edit等带有参数的函数里面的默认参数名称$id都需要用$blog_id替换,否则会报错。 (2)only 函数,用于限定系统提供的资源方法,使用方法如下:
Route::resource('blog','Blog')->only(['index','save']);
(3)except 函数,用于排除系统提供的资源方法,使用方法如下:
Route::resource('blog','Blog')->except(['edit','delete']);
(4)rest 函数,用于重写系统提供的资源方法,使用方法如下:
Route::rest('create',['GET','/:id/add','create']);
解释:第一个参数为系统默认的资源方法名称,第二个参数是新建立的方法,在第二个参数数组里面,第一个是请求方式,第二个是url访问方式,第三个参数是新的资源访问的方式。需要注意的是,该rest方法需要写在resource之前才有效。 (5)资源嵌套路由,方便上级资源对下级资源进行操作。假设对于一篇文章存在多个评论,现在需要修改某文章对应的评论,这时使用资源嵌套路由就很方便。具体使用方法如下: 首先,需要新建控制器commit,并在其中写入下入方法:
function edit($commit_id,$blog_id){
return '评论id'.$commit_id.','.'从属文章id:'.$blog_id;
}
在路由文件中添加如下路由设置:
Route::resource('blog','Blog');
Route::resource('blog.commit','Commit')->vars(['blog'=>'blog_id','commit'=>'commit_id']);
对应的访问方式如下:
localhost/index.php/blog/:blog_id/commit/:commit_id/edit
9.注解路由与url的生成原理
9.1注解路由
????除了传统的路由方式外,还有一种比较新的路由方式,之所以叫注解路由,是因为路由路由定义以注释的形式出现。注解路由同样能够实现前面提到的路由的分组、筛选以及资源路由。 ????thinkPHP6.0默认没有提供注解路由,因此需要自行使用composer下载,首先转到tp目录,打开cmd输入如下命令:
composer require topthink/think-annotation
安装成功效果如下图所示: ????既然下载完毕,接下来便是使用了。为了更好的验证此功能,首先将route/app.php中定义的路由全部关闭。下面,以User控制器为例对注解路由的使用方式进行总结: ????首先,使用如下指令引入刚下载的注解路由:
use think\annotation\Route;
然后,点击右键,找到生成->PHPDOC块,然后选择你想要创建的注解路由方法即可。拿index为例,得到如下结果: 上面的两行就是注解路由增加的注解接下来添加路由方法: ????这里直接500报错了,说明内部有问题。经过反复尝试发现主要是因为安装了annotation这个扩展导致的,查阅大佬们的文章发现,一般使用的是1.0版本的,而这里却用的是最新版本(2.0版本),可能程序本身有一些问题。解决方法如下: ????找到composer.json文件,将里面的think-annotation版本手动改成1.0,如下图所示: 接下来回到tp6demo目录下,进入cmd,输入composer update 指令,执行之后composer会删除并重新下载annotation模块,修改annotation版本之后,在route/app.php文件中增加如下设置(用来显式开启注解路由):
'route_annotation' => true,
接下来回到User控制器中随便写一个方法进行测试,成功运行结果如下: 补充说明,这里的method="GET"是辅助筛选参数,还可以是其他的上述在路由筛选中提到的方法转变的参数,例如https=0,ext="html"等。其次,使用引号的地方不得使用单引号。 ????注解路由同样支持资源路由,写法如下: ????同样的注解路由也支持路由分组,分组路由的使用方法如下: 补充说明,group和route不能同时在一个注解里面,不然group会失效。除了上面的group方法外,还有middleware,middleware方法使用的时候和route同级。
9.2 url的生成
????thinkPHP 里面的 Url 支持所有的路由方式,以及完美解决了路由地址的反转解析,直白点就是把route的路由规则给返还成原始状态。url的生成方法:buildUrl 函数(使用时需要引入think\facade\route )。 ????为了进一步总结其特点,首先,搭建如下场景: 在index方法中写入如下代码:
return Route::buildUrl('test',['str'=>"来自index传入的参数"]);
返回便是生成的url链接: 需要补充说明的是,这里的url的第一个参数除了是直接访问规则外,还可以是路由的后缀名,举例说明如下:
Route::get("test/:str","Url/test")->name("newName");
不同的是,使用路由规则访问会会进行转码,使用路由名称则不会。 ????如果没有参数可传,可以直接用如下方法生成url:
Route::buildUrl('test/来自index传入的参数');
除此以外url的生成还有如下几个辅助函数: (1)domain 函数,用于补全url链接的协议和域名部分,用法如下:
Route::buildUrl("index",['id'=>'3'])->domain(true);
补充说明:domain参数还可以是二级域名或者完整域名,这种情况下等价于:
Route::buildUrl("index/3@create.nightowl.com");
(2)suffix 函数,用于给url添加后缀,其权限大于默认后缀;
Route::buildUrl("index",['id'=>'3'])->suffix('shtml');
(3)url 助手函数,可用于代替Route::buildUrl() ;
url("index",['id'=>'3'])->domain('create');
参考文献:
(1)菜鸟教程正则表达式写法
(2)浅谈CSRF攻击方式
(3)跨域资源共享 CORS 详解——阮一峰
(4)什么是跨域请求以及实现跨域的方案
(5)注解路由——ThinkPHP6.0完全开发文档
|