1-1

理工寝室商店-微信小程序

疑问小结

  1. 当时在XAMMP下mysql目录下的bin下 php -v 不起作用.到php目录下就可以出现版本号.
    已经解决: 因为没有配置php全局变量.
  2. z.cn不成功 在 3-2
  3. 在开发阶段可以info.php保留 但是在生产阶段就要将info.php删除(info.php位置在public/info.php)
image.png

AOP:面向切面编程
ThinkPHP 5(web框架): 编写业务逻辑 访问数据库 向客户端提供数据
MySQL : 数据存储 数据表设计 与业务紧密集合
微信: 支付 善于借鉴与模仿,学习微信接口设计
小程序: 直接与用户交互 体验很重要

1.服务器:
Thinkphp 5 + MySQL构建REST API
2.客户端(客户端的行为逻辑是非常复杂的):
向服务器请求数据,完成自身行为逻辑
3.CMS:(直接对数据库的增删改查,通常CMS相对客户端要简单,因为面对的用户一般就是运维的人)
向服务端请求数据,实现发货与发送微信消息
CMS的功能:
1.基础数据的增删改查,比如添加商品 删除商品
2.特殊操作,比如我们要实现的发送微信消息

三端分离思想: 服务端 客户端 CMS


image.png
image.png

目前流行的就是使用ORM方式访问数据库
ORM框架有
Sequelize-Node
SQLAlchemg(python)-功能相当强大
Entity Framework

知识与技术
@ThinkPHP
Web框架三大核心知识(路由,控制器,与模型)
路由--url就是我们访问api的地址
控制器--算一个入口,相当于javaweb控制层 调用对应业务层的model
模型--通过ORM方式操作数据库

验证器,读取器,缓存,与全局异常
ORM:模型与关联模型(模型对应的是一张表 关联模型就是对象与对象之间的关联 放到数据库就是表与表之间的关联)

知识与技术
@微信
微信小程序
微信登录
微信支付(预支付,支付,库存量检测,与回调处理)
微信模板消息(就是用户在小程序点击了某些选项才会向用户推送消息,无法主动向用户推送消息)

知识与技术
@MySQL
数据表设计
数据冗余(rongyu)的合理利用--在数据越来越多的时候就体现出来了
事务与锁在订单(库存量)检测中的应用--解决并发访问数据库出现问题 ,比如商品就剩下一件了 2人同时下了订单 同时支付成功............

学习:
1.代码本身并不难 静下心学习就好
2.一关一关过 一级一级升 啃下这门课
3.遇见不懂得要返回去学习基础的知识

面向对象的三大特性: 封装性 **继承性 多态性(在java c#表现的精彩 在PHP就稍低)

前置知识:
1.PHP与PHP面向对象的相关知识
2.ThinkPHP基本知识 (对控制器和路由有一定的了解)
3.了解关系数据库(MySQL)的基本使用,写过SQL语句,在项目中使用ORM操作数据库
4.小程序常用API
5.一个小程序账号

理解技术
1.语言和框太多了 ,它们就是工具 我们使用'它们'解决问题
先有想法 再寻找'工具' 而不是反过来

讲些什么
1.泛化的web(我们说做一个产品难道真的就是在做一个产品吗?)不过它们都有一个共同的点:它们需要的api接口是相同的.


image.png

2.能写出代码和能写出易维护的代码不是一回事儿.(提高代码的可读性-复用性)
3.前端绝不等于做界面和特效,前端与服务器在编程思想上的差异已经越来越小了.
4.前端已经变得与服务器一样需要处理大量的业务逻辑.
MVC MVP MVVM 之间的区别http://www.ruanyifeng.com/blog/2015/02/mvcmvp_mvvm.html
为什么会出现MVVM 是因为前端的代码变得越来越复杂了.因此前端也需要类似服务器的框架来支撑前端代码的复杂性.

课程内容与产品特点:
1.依赖或者包管理
composer(php里面依赖包管理工具)
npm(nodejs)
pip(python)
mavenen(java)

使用外部包的注意事项:
在使用外部包的时候,要看是否自己真的需要?就比如说php的laravel框架中的Passport包,就是对用户登录做处理的,但是你需要寻找包,下载包,阅读api......最后它还会在你数据库生成它自己的表,会使 你的代码冗,学习代价大,还不如自己去写这方便的业务逻辑.但是一些强大的第三方包还是值得去使用的.
合理的使用第三方组件
选与不选: 这个业务逻辑太难实现了.就像数据库的访问层.这时候就选第三方组件来实现. 看这个第三方组件会不会破坏你的代码结构,就像passport会自动在你数据库生成表,选一些比较独立的组件.

维护与提问
1.课程代码会一直维护下去
3.代码更新会在慕课手记,知乎专栏(小楼昨夜又秋分)

环境,工具与准备工作

环境工具

Web框架
ThinkPHP 5.07

基础语言,环境
PHP 5.6(比较稳定)
MySQL
Apache(Web服务器)
XAMPP https://www.apachefriends.org/zh_cn/index.html 我下载的是5.6.30

安装XAMPP集成了PHP,MySQL,Apache

开发工具
PHPStorm(编写php代码的IDE) 破解安装https://www.jianshu.com/p/3a4a856c4490
微信Web开发者工具(VS Code)
PostMan(测试接口的,非常有必要的,类似的软件Fiddler)
Navicat(数据库的可视化管理工具)

环境与工具
ThinkPHP只是Web框架就是java的ssm ssh
ThinkPHP还需要PHP运行环境和Web服务器
常见的Web服务器:Apache Nginx(常用于web整个体系的反向代理的这样一个结构)

在win下查看端口占用情况:
win+R --> cmd --> netstat -ano //然后进行查看
如果杀死占用端口 win任务管理器下详细信息 --> 根据查看的pid进行杀死

验证XAMMP下Apache配置成功,在XAMMP内开启Apache 然后在浏览器输入 localhost 如果出现内容则说明Apache配置成功.
验证XAMMP下MySQL配置成功, 第一次进入密码默认为空 就是空格就ok, 出现Welcome to the MariaDB monitor就说明成功了.
C:\Users\彬>Z:
Z:>cd XAMMP
Z:\XAMMP>cd mysql
Z:\XAMMP\mysql>cd bin
Z:\XAMMP\mysql\bin>mysql -uroot -p
Enter password:
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 2
Server version: 10.1.21-MariaDB mariadb.org binary distribution
Copyright (c) 2000, 2016, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

验证XAMMP下php配置成功, 版本为5.6.28

注释
show databases; //显示数据库信息,要记得后面加 ";"

非要进入XAMMP下的php目录才 php -v 才有效

获取ThinkPHP5源文件
1.通过Composer安装
2.通过Git安装
3.直接下载

3-1.直接下载需要下载2个东西:https://www.kancloud.cn/manual/thinkphp5/118003
应用项目 5.0.7
核心框架 5.0.7
然后解压刚在自己 Z:\微信小程序商城开发工具\ThinkPHP
然后将 解压好的 think-5.0.7 framework-5.0.7 剪切到 Z:\XAMMP\htdocs //htdocs就是我们网站的根目录

然后开始视频2-4
三端命名:
服务器程序: Zerg
客户端程序: Protoss
CMS: Terran

将 think-5.0.7改名为zerg 将framework-5.0.7改名为thinkphp并剪贴到zerg目录下
于是TP5框架就算是安装成功了.接下来测试一下.

浏览器输入: http://localhost/zerg/public/
出现:

image.png

启动:然后在phpstroms下打开 Z:\XAMMP\htdocs \zerg

激活PhpStrom 需要在 C:\Windows\System32\drivers\etc\HOSTS 下添加代码 http://idea.lanyus.com/

PhpStrom 快捷键:
ctrl+E //recent file
ctrl+alt+s //设置键
ctrl+shift+/ //块注释
ctrl+shift+r //替换
ctrl+shift+n //全局查找
alt + insert //快速创建文件
双击alt //显示structure与project
ctrl+~ //选择小技巧 快速切换主题

TP5层次结构

image.png

image.png

详细可以查看https://www.kancloud.cn/manual/thinkphp5/118008

Apache 与 Nginx 是静态服务器,我个人认为写API用静态就行,因为任何人任何时候访问都是相应想用的结果.
Tomcat则是动态服务器.
TP5自带的Web Server

image.png

它这个服务器只是我们做测试的时候用一下

PhpStrom调试-xdebug安装

在PhpStrom下的断点调试, 要想设置断点调试,就需要安装一个插件 xdebug
安装地址: https://xdebug.org/download.php
我安装的是: php_xdebug-2.5.1-5.6-vc11.dll
安装的版本要与之php版本对应.
1.首先在zerg下的public目录下创建info.php 内容为: phpinfo();
2.打开XAMMP 打开apache, 在浏览器: http://localhost/zerg/public/info.php 就出现了信息.
3.右键->查看源码信息 复制所有源码粘贴到: https://xdebug.org/wizard.php 然后点击按钮.

image.png

4.根据给出的指示进行配置.

手动配置:
1.我的php是5.6.30 下载的php_xdebug-2.5.1-5.6-vc11.dll
2.将php_xdebug-2.5.1-5.6-vc11.dll 粘贴到Z:\XAMMP\php\ext目录下.
3.XAMMP下apache对应的Config快速修改配置文件,在php.ini文件的最下面加入:
zend_extension 根据自己的情况而定.

[Xdebug]
zend_extension = Z:\XAMMP\php\ext\php_xdebug-2.5.1-5.6-vc11.dll
xdebug.remote_enable=1
xdebug.remote_handler=dbgp
xdebug.remote_mode=req
xdebug.remote_host=localhost
xdebug.remote_port=9000
xdebug.idekey="PHPSTORM"

安装完成

去PhpStorm下配置,点击

image.png

image.png
image.png

点击爬虫就会跳转到页面.

TP5 URL路径格式 (param与value不是必须的,只是参数)
官方将以下的url格式成为PATH_INFO

PATH_INFO

Url不区别大小写 如果想让url区别大小写,修改application下config.php的 'url_convert' => true, 将true改为false就ok.

URL格式实际写法

当正常的格式不支持的时候,可以采用以下兼容格式(不过基本不采用)


URL兼容格式

URL路径格式缺点

  • 太长
  • URL路径暴露了服务器文件结构
  • 不够灵活
  • 不能很好的支持URL语义化(最大缺陷). URL语义化就是一看你的url就知道干什么的.

解决缺点,我们可以使用url路由的方式其余解决.

创建一个模块
1.创建controller(即PHP Class)

image.png

自动填写命名空间,不用手动去[app\sample\controller]


自动命名空间

模块相对比较大的,所以不要一个功能就去写一个模块.

url http://localhost/zerg/public/index.php 简化

Z:\XAMMP\apache\conf\extra\httpd-vhosts.conf 文件添加下列

<VirtualHost *:80>
    DocumentRoot "Z:\XAMMP\htdocs\zerg\public"
    ServerName z.cn
</VirtualHost>

编辑 C:\Windows\System32\drivers\etc\HOSTS
加入

127.0.0.1       z.cn

PostMan

测试接口

开始3-4
编写TP5路由
1.路由是在zerg/application下的route.php下编写的.
2.TP5路由的编写有2种方式 配置式编写 动态注册编写 我使用的是动态注册这种方式,因为配置式易读性比较差.
3.一旦对一个操作方法定义了路由,那么原有的就不起作用了
一个简单的路由
application/route.php文件

//所有的动态注册都是根据Route这个类而来的.
use think\Route;
//开始编写路由
Route::rule('hello', 'sample/Test/hello');
//以上就是一个简单的路由
//http://localhost/zerg/public/index.php/hello能访问
//http://localhost/zerg/public/index.php/sample/Test/hello 就不能访问了

3种url路径格式

  • PATH_INFO
  • 混合模式 (就是一个方法可以是路由,另一个方法可以是PATH_INFO)
  • 强制使用路由模式 (如果设置强制使用路由,那么PATH_INFO就不起作用了)

这三个格式是在application/config.php下配置

    // 是否开启路由
    'url_route_on'           => true,
    // 是否强制使用路由
    'url_route_must'         => false,

建议: 如果编写标准的API 那么就开始强制使用路由

路由:

use think\Route;
//1.0开始编写路由
//Route::rule('hello', 'sample/Test/hello','GET',['https'=>false]);
//以上就是一个简单的路由

//1.1即支持GET也支持POST
//Route::rule('hello','sample/Test/hello','GET|POST');

//1.2简写的get路由注册
//Route::get('hello','sample/Test/hello');

//1.3简写的post路由注册
//Route::post('hello','sample/Test/hello');

//1.4简写的any路由注册 就是GET POST DELETE PUT都ok
Route::any('hello','sample/Test/hello');

在路由里面如何传递参数
有2种
1.第一种是将参数写在方法货号里面 然后引用

    public  function  hello($id) {
  echo $id;
}

2.第二种是用Request类,调用param方法

        $id = Request::instance()->param('id');
        $name = Request::instance()->param('name');
        $age = Request::instance()->param('age');

        如果你想获取所有的参数
        Request::instance->param();
        如果你想获取url内参数
        Request::instance()->route();
        如果你想获取?后面的参数
        Request::instance()->get();

      简化写法
      $name = input('param.name');
      $all = input('param.');  //记得param后面有一个点
依赖注入

3.7 over

在Navicat设置本地数据库密码
点击用户->localost>
密码为123456


image.png

点击 root@localhost 修改密码

导入数据库 先新建数据库


image.png

然后右键zerg数据库 >> 运行SQL文件 >> 选择文件其他默认

外键约束:其实一直是业内人员经常讨论的一个话题 到底使用不使用外键约束
不使用的原因是因为业务有的大的项目 业务需要几乎2周就要更新一次

使用的原因...

只能这么说 用有用的好处 不用有不用的好处 关于这方面的讨论 网上也是有很对资料的 大家可以下去去了解一下.那些资料会详细的阐述了用外键约束的好用与坏处 与不用外键约束的好处以及坏处,我个人偏向于不用外键约束.

3.8over

现在一般用户删除都是假删除 因为提高容错率 也有是用于数据分析

开始3.9 讲解banner
在banner下我们不是没外键 我们只是么有外键约束
在 banner 与 banner_item中
是一对多的关系 banner每一个记录在banner_item中对应多条记录 而banner_item每一个记录只对应一个banner记录 所以是一对多的关系

4.2开始编写banner接口


接口结构
<?php
/**
 * Created by PhpStorm.
 * User: 彬
 * Date: 2018/1/31
 * Time: 11:14
 * QQ:739367755
 * 17603779649
 */

namespace app\api\controller\v1;
use think\Request;

class Banner
{
    /**
     * 获取指定id的banner信息
     * @id 指明的是banner表的id号.
     * @url /banner/:id
     * @http GET
     */
    public function getBanner ($id) {
//        路由是在route下编写的
//        http://localhost/zerg/public/index.php/banner/50 这是我访问的路径  /banner/50是在route下编写的路由.
        $id = Request::instance()->param('id');
        echo $id;
    }
}

客户端传过来的参数都是不可信的,因为校验客户端传过来的参数是非常重要的.

4.3Validate独立校验
如何使用TP5的 validate 类 来构建我们的参数校验层.

在Banner.php下编写validate实例

在TP5中 validate有2种用法.
1.独立验证
2.验证器
要想使用validate首先就要引入它. use think\Validate;

快速debug
将XDEBUG_SESSION_START=16721添加到rul路径后面,不过XDEBUG_SESSION_START=16721前面要增加 ?

独立验证


//        编写验证规则
        $validate = new Validate([
           'name' => 'require|max:10',
            'email' => 'email'
        ]);
//       执行验证 下面是单个验证
//        $result = $validate->check($data);
//        不过我们需要批量验证 先调用batch方法 再调用check方法
        $result = $validate->batch()->check($data);
//        打印验证出错原因
//        返回的是一个报错数组 因此就不能用 echo  而需要用var_dump()
//        echo  $validate->getError();
        var_dump($validate->getError());
//        以上就是独立验证大方法 独立验证还分为单个验证和批量验证 批量验证需要在check之前调用batch方法 输出的时候也不能用echo

TP5的validate提供了很多写好的验证方法 比如 require max:10 email .... 更多可见文档 https://www.kancloud.cn/manual/thinkphp5/129356

当内置的验证规则不能使用的时候我们就需要自定义验证规则

验证器的验证方式是官方推荐的验证方法

4.4 验证器
验证器相对于独立验证的优点是做了更好的封装
4.4.1 首先在api目录下新建一个validate文件夹.
4.4.2 然后在validate文件夹新建一个名为TestValidate.php 类.
4.4.3 class TestValidate 需要继承 Validate

use think\Validate;

class TestValidate extends Validate
{
//    $rule 是固定的数组 名字不能改
    protected $rule = [
        'name' => 'require|max:10',
        'email' => 'email'
    ];
//    以上就定义完毕了
//    然后就可以去Banner去使用这个验证器

}

Banner.php

        $data = [
            'name' => 'sdasdsazhangbin',
            'email' => '739367755qq.com'
        ];

//        这样是用了验证器 而不是独立验证 这里要主要 new的是刚才编写的class 而不是Validate
        $validate = new TestValidate();
//       执行验证 下面是单个验证
//        $result = $validate->check($data);
//        不过我们需要批量验证 先调用batch方法 再调用check方法
        $result = $validate->batch()->check($data);
//        打印验证出错原因
//        返回的是一个报错数组 因此就不能用 echo  而需要用var_dump()
//        echo  $validate->getError();
        var_dump($validate->getError());

表面看 独立验证 和 验证器 没有什么大的区别,底层原理就是一样的,但是在大的项目复杂的逻辑下才能体会到验证器的好处.

Model一般放的是粒度比较细的业务
而Service是对Model层粒度的组装
但是Model和Service统称为业务层,就是业务复杂的时候要用到Service 不复杂的时候直接走Model层 因为Model和Service是平行关系.

业务层是通过Think DB 框架去调用Mysql 从而获得相关的业务数据

这个图适合中小型项目
4.5over 但是我觉得挺重要的.

4.6自定义验证规则
IDMustBePostiveInt.php

<?php
/**
 * Created by PhpStorm.
 * User: 彬
 * Date: 2018/1/31
 * Time: 14:51
 * QQ:739367755
 * 17603779649
* 自定义验证方法
 */

namespace app\api\validate;


use think\Validate;

class IDMustBePostiveInt extends Validate
{
    protected $rule = [
      'id' => 'require|isPositiveInteger'
    ];

    //这里写的isPositiveInteger其实就是扩展Validate的内置规则
    protected function isPositiveInteger($value, $rule = '', $data = '', $field = '') {
//        判断value是不是数字 是不是整形 value需要大于0
        if(is_numeric($value) && is_int($value + 0) && ($value + 0) > 0) {
            return true;
        }else{
            return $field.'必须是正整数';
        }
    }

}
//    自定义验证
    public function  test($id) {
        $data = [
          'id' => $id
        ];

//        $validate = new Validate([
//           'id' => '',
//        ]);

        $validate = new IDMustBePostiveInt();
        $result = $validate->batch()->check($data);

        if($result) {

        }else {
        }

    }

4.7 工欲善其事必先利其器 - 构建接口参数校验层
http://localhost/zerg/public/index.php/api/v1.Banner/test/id/5?XDEBUG_SESSION_START=13387
BaseValidate.php

<?php
/**
 * Created by PhpStorm.
 * User: 彬
 * Date: 2018/2/1
 * Time: 11:08
 * QQ:739367755
 * 17603779649
 */

namespace app\api\validate;

use think\Exception;
use think\Request;
use think\Validate;

class BaseValidate extends Validate
{
    public function goCheck() {
//        获取http传入的参数
//        对这些参数校验
        $request = Request::instance();
//        获取所有的参数
        $params = $request->param();

        $result =$this->check($params);
        if(!$result) {
            $error = $this->error;
            throw new Exception($error);
        }else {
            return true;
        }
    }
}

IDMustBePostiveInt.php

<?php
/**
 * Created by PhpStorm.
 * User: 彬
 * Date: 2018/1/31
 * Time: 14:51
 * QQ:739367755
 * 17603779649
* 自定义验证方法
 */

namespace app\api\validate;


use think\Validate;

class IDMustBePostiveInt extends BaseValidate
{
    protected $rule = [
      'id' => 'require|isPositiveInteger'
    ];

    //这里写的isPositiveInteger其实就是扩展Validate的内置规则
    protected function isPositiveInteger($value, $rule = '', $data = '', $field = '') {
//        判断value是不是数字 是不是整形 value需要大于0
        if(is_numeric($value) && is_int($value + 0) && ($value + 0) > 0) {
            return true;
        }else{
            return $field.'必须是正整数';
        }
    }

}

Banner.php

    public function  test($id) {
        (new IDMustBePostiveInt())->goCheck();
    }

5.1
介绍REST
什么是REST?
中文意思 表述性状态转移, 是一种风格,约束或者说设计理念
SOAP vs REST
在REST出现之前,一直就是SOAP的天下
@SOAP Simple Object Access Protocol 通常来说,使用XML来描述数据,比较重,相对于轻量级来说.REST提倡用json来描述数据.

REST给我们提供了更加轻量级的思维方式.
就目前的形式来看,大多数互联网的产品在提供接口的时候还是会优先选择REST,但是并不意味着SOAP就没有人去用,在传统的企业级时候还是占一席之地.

RESTFul API 基于REST的API设计理论
特点
1.轻
2.通常来说,使用JSON描述数据
3.无状态 (也是REST一个很大的特点),可以这样理解,发送了2个http请求,它们之间没有任何关系,第二个也不会依赖第一个http请求.
@RESTFul API 基于资源,增删改查都只是对于资源状态的改变
使用http动词来操作资源.
使用url来表示资源.

在传统开发中和在REST中 get,post的区别
从技术层次来讲没有区别,但是从意义上来讲是有区别的,在传统开发中我们选择get,post的选择依据并不是根据资源的增删改查,比如你又可能使用get方法去删除一个数据,这在传统的web开发中是没有区别的,但是在REST开发中是绝对不行的,因为在REST里面get表示的是一个查询的操作,因此你在REST中用get删除一个数据语义是不明确的,也是非常不合理的.

总结:在传统的开发中,不管你是增伤改查,只要你的参数足够简单就可以使用get方法.而你的参数比较复杂,比如你要提交一个表单,在这种情况下我们就会使用post
但是对于REST服务来说,选择get或者post的依据是到底你是查询操作还是新增操作.

@RESTFul API
/getmovie/:mid 不推荐
GET: /movie/:mid 推荐

5.2 over

5.3
RESTFul API最佳实践
@HTTP动词 (幂等性 资源安全性, 一般用不到除非安全性非常高)
POST : 创建
PUT : 更新
GET: 查询
DELETE: 删除

状态码:
404 当前请求的页面没有找到
400 参数错误
200 一个查询操作 get请求成功
201 post创建资源成功
202 put更新成功
401 未授权
403 当前这个资源是被禁止的
500 服务器的未知错误

错误码:
自定义的错误ID号 就像开发微信时给你个报错码 你去微信官网去查找对应错误码的信息

统一描述错误: 错误码 错误信息 当前URL

使用Token令牌来授权和验证身份
Cookie和Token在本质上没有太大的区别,但是在实现的机制上有一些区别,因为Cookie一般是浏览器上的行为,每一次访问浏览器会自动携带cookie,而Token通常我们自己存储和管理的.所以我认为Token会更加灵活一点.

Token是一个重点,也会详细讲明它的应用.

版本控制

测试与生产环境分开
api.xxx.com
dev.api.xxx.com

URL语义要明确,最好可以'望文知意'
最好有一份比较标准的文档

5.4
学习RESTFul API最佳方式

模仿(豆瓣开发api 非常标准的:GitHub开发者API)
5.4 over

ctrl + alt + o 快速删除无用的命名空间

6.1


image.png

开启关闭debug模式: 在config.php文件下 'app_debug' => false,
常规的异常处理 不够灵活切复用性比较低
model下的Banner.php

 public static function getBannerByID($id){
        //TODO: 根据id号, 获取Banner信息
        try{
            1/0;
        }catch (Exception $ex) {
            //TODO:可以记录日志
            throw $ex;
        }
        return 'this is Banner InFO';
    }

controller下的 Banner.php

  try{
//            这里捕获的异常是调用getBannerByID方法时候 getBannerByID方法内部抛出的异常
            //        调用BannerModel下的静态方法getBannerByID()
            $banner = BannerModel::getBannerByID($id);
        }catch (Exception $ex) {
            $err = [
                'error_code' => 10001,
                'msg' => $ex->getMessage()
            ];
//            利用TP5的json函数将数组转化为json
//            400表示的是返回的状态码
            return json($err,400);
        }

代码越抽象 代码的复用性就越高

异常的分类:
1.由于用户行为导致的异常(比如没有通过验证器,没查询到结果)--通常不需要记录日志,需要向用户返回具体信息.
2.服务器自身的异常(代码错误,调用外部接口错误)--通常需要记录日志,不向用户通知具体信息.

抛出异常,如果没有在特定的地方捕获异常,那么这个异常就会抛到全局异常处理哪里.最后交给Handle类下的render方法去处理异常.
我们可以重写render方法,不过在重写的同时需要修改config.php文件
ExceptionHandler.php

<?php
/**
 * Created by PhpStorm.
 * User: 彬
 * Date: 2018/2/2
 * Time: 15:20
 * QQ:739367755
 * 17603779649
 */

namespace app\lib\exception;


use think\Exception;
use think\exception\Handle;

class ExceptionHandler extends Handle
{
    public function render(Exception $ex) {
        return json('~~~');
    }

}

config.php

 // 异常处理handle类 留空使用 \think\exception\Handle
  'exception_handle'       => 'app\lib\exception\ExceptionHandler',

6.4 over
6.5 over
6.6自定义日志格式
日志在生产阶段的时候会用的多一些
TP5默认会记录日志异常,但是我们很多时候需要自己去定义日志的格式.所以我们要去把TP5默认的日志关闭掉.配置主要在config.php和database.php下.
TP5默认生成的日志在runtime->log目录下
1.更改Log目录位置,在public\index.php下
index.php 改变了LOG_PATH的路径

// 定义应用目录
define('APP_PATH', __DIR__ . '/../application/');
define('LOG_PATH', __DIR__ . '/../log');

重新一运行,如果有异常就会在zerg下生成一个新的目录log
2.为什么要自定义日志格式,因为日志也有的是保存的是用户操作不当而产生的异常,这样的异常非常多切不是服务器的问题,我们只需要记录服务器内部产生的异常,因为我们需要自定义日志格式.
因此我们开始关闭默认的日志格式,编写自己的日志格式.
关闭:在aplication\config.php下,将默认的file改为test

    'log'                    => [
        // 日志记录方式,内置 file socket 支持扩展
        'type'  => 'test',
        // 日志保存目录
        'path'  => LOG_PATH,
        // 日志记录级别
        'level' => [],
    ],

因为我们刚才关闭了log因此我们再使用log的时候需要初始化
ExceptionHandler.php

 private function recordErrorLog(Exception $e) {
//        我们已经在config.php哪里关闭了log 因此需要在这路初始化log
        Log::init([
            'type' => 'File',
            'path' => LOG_PATH,
            'level' => ['error']
        ]);
        Log::record($e->getMessage(),'error');
    }

如果在开发阶段的时候,我们将config.php的app_debug设置为true 当转为生产模式的时候我们应该将它改为false
ctrl+b 跳转到声明处

6.8 over

6.11本章小结与AOP
代码不是一次性就能写好的,要不要的重构代码才能使代码更精炼,有人说代码写着很简单.那是因为没有考虑到代码的复用性还有代码的层次结构,仅仅的还是停留在写业务的层次上,如果不考虑多那么自己的代码不会得到提高,因为代码你一直写的很直白.比如说你验证层,你直接写业务没有想到把这些抽象为一个层.
大家拿一个语言,一个框架吃透之后,其他的语言和框架在思路上都是大同小异的.所以说学习,不是去学习具体的一门语言和一种框架,更重要的去学习编程的思路.逐步培养自己的抽象的编程思维,

@AOP 面向切面编程
AOP的运用是非常广泛的,AOP是一种思想,并不是具体的框架也不是具体的代码.
是一个好的程序员和一般程序员的分水岭

AOP的理论知识很容易让人迷乱,不要死扣理论

通过写的实例,自己写的validate和exception就是很好的AOP编程
尤其是exception

我们要站在更高的角度,用比较抽象的方式来统一的总体的来处理一类问题.这是对AOP一个通俗的解释.

我们处理异常的时候,我们并不会把异常分散到具体的每一个业务代码中,而我们提供了一个类似于横切面的东西,这个横切面就是我们的ExceptionHandler.php下的render方法,它会统一的处理所有的异常.
举例:我们去看电影,电影院有一个检票口,也许你的票在猫眼,美团...上买的.不管你在哪买的,最后我们都要在检票口看你的票能不能入我的电影院.我们不能给每一个观影人都配一个检票员.
这个就是AOP思想的举例.

面向对象的3大特性: 封装性 继承性 多态性
java的spring框架的核心思想也是AOP思想.

6.11 over

7数据库操作
7.1.1数据库的配置
database.php 只需要配置下面就可以连接

    // 数据库类型
    'type'            => 'mysql',
    // 服务器地址
    'hostname'        => '127.0.0.1',
    // 数据库名
    'database'        => 'zerg',
    // 用户名
    'username'        => 'root',
    // 密码
    'password'        => '123456',
    // 端口
    'hostport'        => '3306',

7.1.2 TP5查询数据库的方式分为三种
(1)使用原生的SQL语句来查询数据库.
(2)使用构造器来操作数据库
(3)使用模型和关联模型来操作数据库(这是我们将要使用的,前2种是第三种的基石)
7.1.3 TP5提供的Db类操作数据库

return返回的时候一定要是json();

ctrl + alt + o 快速移除没有使用的类
7.2
ExceptionHandler类里面使用的render方法和recordErrorLog方法传入的是基类 \Exception

    private function recordErrorLog(\Exception $e) 
    public function render(\Exception $e) 

HttpException和think\Exception他们不是一个继承关系,所以不能自动的做类型转换.

找错误是一个很好的学习习惯
找错误就是一个寻宝的过程,错误信息就是藏宝图,你不断的发现信息查找信息...........

7.3 TP5 数据库架构

image.png

Db 是操作数据库的入口对象(换一种说话,我们增删改查都能通过DB来实现的)
Collection数据库连接器,并不是真正的连接数据库,而是起一种待命的状态.连接器是惰性的,好处是能够节约服务器的资源.它是执行sql语句的.
查询器其实最后也会被转化为原生的sql语句,相当于封装了sql,它翻译成原生的sql语句就是利用Builder生成器来实现的.一个大的作用,就是隐藏细节.支持不同数据库的查询.
drivers驱动 提供了几个不同的类 每一个类负责了一个不同的连接
总结:sql直接被Collection执行 Query被Builder翻译成sql,sql再被Collection执行.

设计模式的理解来源于日常的编码工作中的,当你遇见困难的时候再看去设计模式的书你才会茅塞顿开.23种设计模式不是死记硬背就可以的.

7.3 over 比较抽象

7.4 使用query查询器来构造我们数据库的操作
7.4.1 为什么不使用原生的sql?
(1)原生的没有查询器简洁方便.
(2)最重要的是查询操作器封装了对不同数据库的操作.它提供了我们统一的数据库操作方法.不用关心不同数据库在原生sql语句的差异.所以这个是使用查询器最主要的原因.
query查询器不仅包含对数据库查询操作还具有写的操作,查询器名字只是一个泛写.因为不管是读写最终都会翻译成原生的sql语句.

在中小型的项目中还是推荐使用ORM

7-4,5,6

<?php
/**
 * Created by PhpStorm.
 * User: 彬
 * Date: 2018/2/2
 * Time: 11:51
 * QQ:739367755
 * 17603779649
 */

namespace app\api\model;


use think\Db;
use think\Exception;

class Banner
{
    public static function getBannerByID($id){
        //TODO: 根据id号, 获取Banner信息
        //使用查询器不是直接返回的结果 而是返回的一个对象. 只有加上find函数,不过find()只能返回一条数据
        //将find()替换为select()方法才可以返回所有查询的结果.
        //find()返回的是一维数组 select()返回的是二维数组
        $result = Db::table('banner_item')->where('banner_id','=',$id)->select();
        return $result;

        //为什么称作为链式方法 因为它们像链子一样连在一起 但是链式方法不会真正去执行sql语句,需要类似于select find update delete insert
        //类似的select() 还有 update() delete() insert() find()
        //一运行select update delete insert find 以后 之前的 Db::....都被清除了

        //where('字段名','表达式','查询条件');      where('banner_id','=',$id);
        //where一共有3种写法. [表达式 , 数组法 , 闭包(最灵活的)]
        //闭包写法
//        $result = Db::table('banner_item')->where(function ($query) use ($id){
//            $query->where('banner_id','=',$id);
//        })->select();

        //以下是原生的sql操作
        //当执行Db的query方法时候,会自动将 $id 填充到 sql 语句内.
        //$result =  Db::query('select * from banner_item WHERE banner_id=?',[$id]);
        //return $result;
    }
}

7.7 开启SQL日志记录
配置
(1)database.php下的 'debug' => true,
(2)config.php下的 'app_debug' => true,
(3)config.php下的

    'log'                    => [
        // 日志记录方式,内置 file socket 支持扩展
        'type'  => 'test',
        // 日志保存目录
        'path'  => LOG_PATH,
        // 日志记录级别
        'level' => ['sql'],
    ],

(4)在index.php下添加
加这段的原因是我们在config.php下禁止了默认的日志,所以我们要在入口处初始化sql日志.

\think\Log::init([
   'type' => 'File',
    'path' => LOG_PATH,
    'level' => ['sql']
]);

建议:一般在生产模式下,把sql的日志关闭.

每一个URL请求都过经过index.php文件

7-8 ORM与模型
@ORM Obeject Relation Mapping 对象关系映射(用面对对象的思维去思考数据表)
用ORM我们尽量把每一个表看成是一个对象
ORM不是一种具体的语言也不是一种具体的框架,它也只是一种思想
举例:最著名的就是java里面的Hibernate就是ORM思想
@模型
//ORM Obeject Relation Mapping 对象关系映射
//模型 特指TP5的模型 在TP5中的模型是ORM实现的一个机制 它不仅是对数据库的查询 还包含了一些业务逻辑
//不要把模型想的太过单一了 把它想的大一点 不要仅仅理解为一个对象 业务的一个集合 也许很多对象合在一起也是一个模型
//模型主要是处理一些比较复杂的业务逻辑
//更直白的来说 ORM是把一个表当成一个对象来看待 但是模型可能是对应多个对象的 同时也可能对应对个表的
//这个模型中 表和对象没有必然的联系 模型是根据自己的业务逻辑而划分的-简单的来说就是根据自己的功能来划分的
//总结:不要把模型看做是数据库的查询 模型更多关注的是业务逻辑 不要把模型和数据库的表一一对应起来. 简单的业务逻辑对造成一个假象
//假象就是:一个模型对应一个数据表 但是复杂的模型和复杂的业务逻辑不是这样的 有可能横跨多个表 模型是个业务逻辑相关的

    //模型不仅仅是model层 还有service .. 这2都是写业务逻辑的 

7-9 初识模型
我们已经学会了2中数据库操作的方式
(1)原生SQL (2)Db
Db不是能很好的包含和处理我们的业务逻辑.

开始实现第一个model模型
7.9.1 继承Model类
model下的Banner.php

class Banner extends Model

这样Banner摇身一变就成为了一个模型

修改config.php下 将html修改为json

  // 默认输出类型
    'default_return_type'    => 'json',

v1下的Banner.php

        $banner = BannerModel::get($id); //返回的是模型对象 get方法是在BannerModel继承的Mode类里面定义的 我们不用手动去写getBannerByID
//        $banner = BannerModel::getBannerByID($id);

为什么要用模型? 因为Banner继承了Model类,摇身一变为模型,在Model类中已经自动提供了get方法代替了自己写的getBannerByID方法.
7.10
7.10.1模型都是继承Model这个类
7.10.2业务逻辑简单的时候,一张表对应一个模型
7.10.3在数据库里面有主表和从表这个概念的,TP5的关联模型对应数据库的主从表.
7.10.4为什么调用get就知道去banner表去查询数据?因为在默认情况下,数据库表的名字和我们的模型类名是一一对应的.因此在调用get查询的时候TP5就知道该去哪个表去查询.但是表名字也不一定要和类名一样的,如果不想采用默认方式就需要在模型类下指明对应的哪一个数据库表.
只需要在对应的模型下添加 protected $table = 'banner_item';

   class Banner extends Model
{
    protected $table = 'banner_item';

7.10.5 快速创建模型的方法
TP5提供了一个自动生成模型的命令
(1)打开PHPStorm下的terminal(快捷键alt+f12)
(2)输入: php think make:model api/BannerItem (api为模块名 BannerItem对应的表名)
(3)此刻model目录下就多出一个新的模型-BannerItem.php

7.11 静态调用还是实例调用
TP5还是推荐静态调用的

        //下面三句话 第一句是静态调用 后2句是实例对象调用 不过TP5推荐使用静态的调用方式
        $banner = BannerModel::get($id); //返回的是模型对象 get方法是在BannerModel继承的Mode类里面定义的 我们不用手动去写getBannerByID

//        $banner = new BannerModel();
//        $banner = $banner->get($id);

为什么要使用静态调用?
(1)调用起来更加简洁

面向对象下的对象与类之间的关系?
(1)BannerModel对应的是数据库的一张表
(2)new 出来的实例对象对应的是表内的一条记录
(3)类是描述一个对象的相关属性和行为的.只有把类new出来之后,才能具体的代表一个事务.
(4)类可以看成是生产对象的一个模板

7.12 几种查询动词的总结 与 ORM性能问题
查询动词

//查询方式 get find all select  (get find)只能返回一条数据 (all select)是返回的一组记录,或者说一组模型对象
        //get all 是模型特有的方法  find select是Db特有的方法
        //模型中也可以使用Db的方法 但是Db中不可以使用模型的get all方法
        //为什么模型中也可以使用Db方法?因为Db是模型的基石 他们2者是不能分离的,在模型中最终访问的数据库还是使用数据库层的Db方法

@ORM
为什么使用ORM?
1.我们编写代码很多时候就是解决现实世界所遇到的问题,编码很多时候就是现=现实世界事务的抽象.所以我能使用面向对象的思维去解决问题就使用面向对象的思维,

模型和数据库访问层是不同的2个概念,它们的职责是不同的.
模型主要是用来处理业务的.
而Db数据库访问层是用来查询数据库的.
但是模型是建立在Db的基础上的.
不要因为模型的性能较差就放弃使用模型
要用面向对象的思维去设计模型
模型的底层仍然是数据库访问抽象层

原则:好的代码第义原则是什么?
不能代码的性能,而是代码的可读性.
只有设计框架它们的性能就会超微差点,不能像c c++ 汇编语言一样,不过这种性能可以忽略,不如说我们的TP5,虽然性能是有损耗的,如果你使用一些非高级语言,以及不使用框架.那么你的开发效率和周期是有多长?那么你的性能损耗的价值和你的开发效率所损失的时间相比较起来,哦轻孰重?
如果你发现你的产品访问的很慢,真的是ORM引起的吗?
不要把访问速度慢直接就归因于ORM上,因为ORM所产生的性能问题客户是察觉不到的.
在经验来看,一般慢的原因都是sql语句写的不够好.
ORM其实没做什么,就是把原生的sql语句封装了一下.
如果项目比较大的时候,需要考虑的高并发的时候建议还是需要考虑使用原生的sql语句.但是绝大多情况的时候ORM就够了.

第8章 专题、分类、商品详情接口编写
8.1 Banner相关表分析(数据表关系分析)
(1)banner 与 banner_item之间是依靠外键来联系到一起的.
(2)模型关联,我们就需要将banner与banner_item之间的表关联起来.它们之间的关联项就是banner_id,banner_id就是外键.
(3)banner表与banner_item之间是一对多的关系,就是一个banner可以对应多个banner_item.但是一个banner_item表只能对应一个banner表. 如果一个banner_item能对应多个banner表,那么它们之间就是多对多的关系.
banner_item与image之间是一对一的关系.

banner banner_item imgage 表之间都是靠外键联系在一起的.
banner 与 banner_item 是靠 banner_id关联在一起的
banner_item 与 image 是靠image_id 关联在一起的

8.2 模型关联
8.2.1 要在Banner模型里面表示 Banner模型与BannerItem模型之间的一对多的关系.(学习模型关联,最重要的就是学会如何定义模型与模型之间的关联关系),我们的目的是让Banner模型包含BannerItem模型
所以
Banner.php模型

    public function items(){
        //这不是一个普通的函数 我们将它成为 关联
        //参数 第一个是关联模型的明星 第二个是外键 第三个是当前模型的主键
        //当前模型是Banner 关联模型是BannerItem
        //这里的$this就是Banner 调用下面的方法.
        return $this->hasMany('BannerItem','banner_id','id');
    }

Banner.php控制层

   //   $banner = BannerModel::find($id); //返回的是模型对象 get方法是在BannerModel继承的Mode类里面定义的 我们不用手动去写getBannerByID
        //如果没有with就是查询的banner表 如果有with就是查询banner表和它所关联的表.
        //解释这面一句话 BannerModel这个模型类 关联一个items 去找到id为1的banner
        $banner = BannerModel::with('items')->find($id); //返回时的banner表的id记录 和对应的banner_item的记录.

8.3 模型关联-(嵌套查询)-如果banner banner_item还与其他的表有关联呢?
8.3.1 如果banner banner_item image之间都有关系如果一次性将所有的关联都查询出来?
8.3.2新建image模型

 php think make:model api/Image

然后在BannerItem模型内创建与Image模型的关联模型
BannerItem .php模型

class BannerItem extends Model
{
    public function img(){
        //一对一之间的关系是用
        //foreignKey localKey 都是可以省略的,但是建议不要省略!!!
        return $this->belongsTo('Image','img_id','id');
    }
}

最后在Banner控制层调用.

//with函数就是代表BannerModel模型内含有的,img没在BannerModel模型内,所以需要嵌套关系.
 $banner = BannerModel::with(['items','items.img'])->find($id);

有一个疑问
为什么不在image模型中创建关联关系?
这样理解,当A模型调用B模型数据时,在A模型创建关联关系.
ORM模型的优势,以为是sql语句日志,业务越来越复杂,我们只需要写几句模型语句,就可以代替下列大量的sql语句.

[ 2018-02-05T13:18:00+08:00 ] ::1 ::1 GET /zerg/public/index.php/api/v1/banner/1?XDEBUG_SESSION_START=11139
[ log ] localhost/zerg/public/index.php/api/v1/banner/1?XDEBUG_SESSION_START=11139 [运行时间:0.659942s][吞吐率:1.52req/s] [内存消耗:2,667.41kb] [文件加载:44]
[ sql ] [ DB ] CONNECT:[ UseTime:0.003107s ] mysql:dbname=zerg;host=127.0.0.1;port=3306;charset=utf8
[ sql ] [ SQL ] SHOW COLUMNS FROM `banner` [ RunTime:0.015226s ]
[ sql ] [ SQL ] SELECT * FROM `banner` WHERE  `id` = 1 LIMIT 1 [ RunTime:0.000967s ]
[ sql ] [ SQL ] SHOW COLUMNS FROM `banner_item` [ RunTime:0.010269s ]
[ sql ] [ SQL ] SELECT * FROM `banner_item` WHERE  `banner_id` = 1 [ RunTime:0.000978s ]
[ sql ] [ SQL ] SELECT * FROM `banner_item` WHERE  `banner_id` = 1 [ RunTime:0.001932s ]
[ sql ] [ SQL ] SHOW COLUMNS FROM `image` [ RunTime:0.020076s ]
[ sql ] [ SQL ] SELECT * FROM `image` WHERE  `id` IN (65,2,3,1) [ RunTime:0.029929s ]

8.4 隐藏模型字段
Model主要是编写业务逻辑.
隐藏模型字段意思就是客户端有时候不需要太多返回的信息,因此把原来很多的信息隐藏以后才返回给客户端.
8.4.1删除Banner模型的delete_time字段
首先要明白数据返回的格式是一个对象
Banner.php的控制层

        $banner = BannerModel::getBannerByID($id);
        $banner->hidden(['delete_time','update_time']); //隐藏$banner下delete_time update_time属性

//      $banner->visible(['id','items']); //只显示$banner下的id属性与items属性

//模型的好处就是提供了很多内置的方法,可以直接处理返回的数据

8.5 对8.4隐藏字段做进步一的优化
隐藏字段名,本应让模型类自己管理的,因此去Model下的Banner去优化
只需要在Banner.php 模型类添加

    protected $hidden = ['delete_time'];
    //如果想要隐藏多个字段名,只需要 protected $hidden = ['delete_time','update_time'];

想要隐藏BannerItem表的字段名,只需要在BannerItem模型类下

    protected $hidden = ['delete_time'];

隐藏字段的原因
(1)出于安全性的考虑,有一些的字段不应该返回到客户端的
(2)为了保证你返回客户端的json简洁.

8.6 图片资源的URL配置
8.6.1在数据库的图片处配置相对路径
8.6.2自己写一个配置文件,并且会被TP5框架自动加载,在application目录下新建一个extra的目录(这个目录会自动被TP5加载),然后在extra目录下新建一个php file名字为setting.
将images目录放到public目录下,因为在TP5中,只有public是一个公开的目录.
在setting.php下

return [
  'img_prefix' =>  'http://localhost/zerg/public/images'
];

于是在控制层 Banner.php下读取配置文件setting的信息

        $c = config('setting.img_prefix');

接下来将数据库的url与我们基地址拼接
8.7-读取器的巧妙运用
就是用于模型的读取器.
我们需要改模型Image的数据,因此我们就需要在Image模型内定义一个读取器.
Image.php

class Image extends Model
{
    protected $hidden = ['delete_time','update_time','id','from'];

    //这是一个读取器,名字命名是固定的 get + 字段名(大写) + Attr
    //读取器可以接受一个参数
    public function getUrlAttr($value){
        return config('setting.img_prefix').$value;
    }
}

为什么我们要在控制器能保持源代码的整洁?
因为如果我们有读源码的习惯,我们发现去读的时候都是一层一层去读的.如果我们将业务逻辑封装到类里面.那么我们读的时候就会顺.会比较的方便.
进一步优化,图片分为本地与网络,网络不需要拼接,因此需要作出判断from!=1时候不拼接
模型Image.php

  public function getUrlAttr($value,$data){
         //这里的$value $data都不是我们自己传入的参数,系统会自动给我们 可以设置断点查看.
       $finalUrl = $value;
        //判断from == 1 是判断图片是网络的还是本地的,当为1的时候是本地的,需要拼接url
        if($data['from'] == 1) {
            //在自己的配置文件读取基地址然后拼接url 然后就自动将url拼接完整.
            $finalUrl = config('setting.img_prefix') . $value;
        }
        return $finalUrl;
    }

关键是要学习好框架提供给你的工具.
其实这个读取器也是AOP实现的思想.

8.8 自定义模型基类
8.8.1在官方中,读取器叫做获取器.
8.8.2读取器的触发是框架自动触发的,当你使用了该模型就会调用该模型读取器并且关联的模型也会触发读取器.
8.8.3如果其他表也有类似的url残缺字段.呢怎么办?现在我们将读取器写在Image模型下面的,如果我们其他的模型也有这个url字段呢.那么这个读取器就无法生效.我们将有更加面向对象的方式来处理这个问题.
我们创建一个新的BaseModel作为所有模型的基类,这个基类再继承Model
BaseModel.php


class BaseModel extends Model
{
    //这里已经不是一个读取器了,因为如果是读取器那么所有含有url的字段都会被处理.我们只需要在用的时候再子类调用,
    protected function prefixImgUrl($value,$data){
        $finalUrl = $value;
        if($data['from'] == 1) {
            $finalUrl = config('setting.img_prefix') . $value;
        }
        return $finalUrl;
    }
}

Image.php

    //这样写法是需要url时候处理,调用相应的父类相应的方法就行.
    public function getUrlAttr($value,$data){
        return $this->prefixImgUrl($value,$data);
    }

8.9 定义API版本号
8.9.1我们之所以能保证控制器的代码非常少,这有赖于我们做了很好的面向对象的封装.并且我们也利用了很多TP5自带的功能.从而才实现了代码这么简洁编写.
8.9.2为什么我们要支持多版本?
我们开发的业务,但是业务不可能是一成不变的.当业务变得时候,我们如何能够更好的适应业务的变化.
我们拿getBanner做一个讲解.下面这个是我们大多数程序员写的,但是不提倡,我们应该新建一个v2目录.

public function getBanner($id,$version){
    if(version==1){
    ******
    }
    if(version==2){
    ******
    }
}

@开闭原则就是我们要对代码的扩展是要开放的.而对修改是封闭的.
我们经常面临是代码的改变,为了解决不断变化的代码.才产生了这么多的设计模式,以及设计思想.
那么开闭原则就是告诉大家你要修改一个代码.最好通过扩展的形式.
扩展形式exception就是很好的扩展原则.
尽量不要修改以前的代码,因为修改就会产生应该其他代码的风险.我们应该在其他的地方增加新的扩展.

优化
在controller目录下新建一个v2的目录
在v2中写类似于v1的
为什么要分版本号?
因为软件产品要兼容老版本,因为有些用户不会更新最新的版本号.关于版本的支持一直是一个很麻烦的事情.一定要在规划产品的时候考虑最多兼容几个版本.
route.php

//编写route是三段式 模块,控制器,操作方法
// api/v1.Banner/getBanner 不区分大小写
//Route::get('api/v1/banner/:id','api/v1.Banner/getBanner');

//因版本号不同作出相应的判断
Route::get('api/:version/banner/:id','api/:version.Banner/getBanner');

这就做到了不同版本之间相互不影响.

8.10 专题接口模型分析
8.10.1分析theme表与product表之间关系. 一个theme可以包含多个product,一个product可以属于多个theme.因此theme与product之间是多对多之间的关系.
但是多对多之间的关系一般需要第三张表来表示.因此还有一个theme_product表.这个表做中转的关系表.不是一定要有这个第三张表,但是没有这个表这个数据库设计是不好的.
不好的设计就是在theme表字段内增添一个字段,这个字段相当于一个数组,这个数组的内容就是该theme包含的product.虽然这种设计容易让人理解.这样写的不好之处就是数据库的扩展性比较差,然后当你读取数据库相关字段的时候.代码会写的非常麻烦. 举例


假如你现在专题1包含1,3,5,8产品.如果你想扩展你需要1.先读取这个字段,2.然后增加这个字段3.最后更新.是很繁琐的.这还是一个简单的业务逻辑.
当你想要统计这个专题下有多少个产品.那么会更加比较麻烦.因此当多对多表的时候就要新建一张中转表.
多对多只建2张表,也违反了数据库设计思想.
因此theme product theme_product(中转表)构成了多对多之间的关系.

8.10.2
(1)开始编写theme控制器
使用命令来创建控制器类

php think make:controller api/v1/Theme

不过这里提倡自己手动创建,因为命令行创建很自动生成很多默认的方法.
因此Model可以使用命令行去创建.Controller适合自己手动去创建.
(2)创建Theme关联的模型类.
创建Theme模型类
创建Product模型类
但是不用创建ThemeProduct模型类 因为TP5框架内部会自动调用的.
Product.php 模型类

<?php
/**
 * Created by PhpStorm.
 * User: 彬
 * Date: 2018/2/6
 * Time: 12:06
 * QQ:739367755
 * 17603779649
 */

namespace app\api\model;


class Product extends BaseModel
{

}

Theme.php 模型类

<?php
/**
 * Created by PhpStorm.
 * User: 彬
 * Date: 2018/2/6
 * Time: 12:06
 * QQ:739367755
 * 17603779649
 */

namespace app\api\model;


class Theme extends BaseModel
{

}

8.11 一对一关系解析
因为theme与image有关系,所以需要分析
theme一个主题只对应一张图, 一张图只能是一个theme 因此是一对一之间的关系.
因此需要去Theme模型类下编写模型关联.
Theme.php

class Theme extends BaseModel
{
    public function topicImg(){
        //belongsTo 和 hasOne 都是一对一的关系时用的.
        //不过hasOne是用在Image模型类里面,这是作区分的.
        //如果一个表内含有外键,那么这个表模型内就定义belongsTo,如果一个表没有外键那么这个表模型就用hasOne 因此这里的Theme含有外键,所以用BELONGStO
        return $this->belongsTo('Image','topic_img_id','id');
    }

    public function headImg(){
        return $this->belongsTo('Image','head_img_id','id');
    }
}

为什么我们要在Theme.php模型定义关系关系,而不在Image.php模型类定义?
因为我们是通过Theme调用的,因此在该模型类下编写关联模型.
一对一之间也存在一个主从关系的.

8.12 Theme接口验证与重构
Theme.php

class Theme
{
    public function getSimpleList(){
        
    }
}

接下来去定义它的路由.
route.php

Route::get('api/:version/theme','api/:version.Theme/getSimpleList');

控制层theme.php

class Theme
{
    /**
     * @url /theme?ids=id1,id2,id3...
     * @return 一组theme模型
     */
    public function getSimpleList($ids=''){
        //验证传入的参数是否合法 因此去定义验证器.
    }
}

接下来写参数验证器
在validate目录下新建 IDCollection.php 并且让它继承基类的验证器

<?php
/**
 * Created by PhpStorm.
 * User: 彬
 * Date: 2018/2/6
 * Time: 13:09
 * QQ:739367755
 * 17603779649
 */

namespace app\api\validate;


class IDCollection extends BaseValidate
{
    
}

开始编写内容,可以模仿
IDCollection.php

<?php
/**
 * Created by PhpStorm.
 * User: 彬
 * Date: 2018/2/6
 * Time: 13:09
 * QQ:739367755
 * 17603779649
 */

namespace app\api\validate;


class IDCollection extends BaseValidate
{
    protected $rule = [
        'ids' => 'require|checkIDs'
    ];

    //这里必须是$message 不能自己定义
    //如果验证不通过返回的错误信息
    protected $message = [
      'ids' => 'ids必须是以为逗号分隔的多个正整数'
    ];

    // $values 就是传入的参数ids
    protected function checkIDs($value){
        //将ids字符串转化为数组
        $values = explode(',',$value);
        //如果$values是空的 那么不符合要求
        if(empty($values)){
            return false;
        }
        //判断是否都是正整数
        foreach ($values as $id){
            if(!$this->isPositiveInteger($id)){
                return false;
            }
        }

        return true;
    }

}

然后去测试, 我们做服务的人一定要有一个服务的心
在控制层Theme.php

class Theme
{
    /**
     * @url /theme?ids=id1,id2,id3...
     * @return 一组theme模型
     */
    public function getSimpleList($ids=''){
        //验证传入的参数是否合法 因此去定义验证器.
        (new IDCollection())->goCheck();

        return 'success';
    }
}

于是Theme参数的校验层就完成了.

8.13 完成Theme简要信息接口
Theme.php控制层

class Theme
{
    /**
     * @url /theme?ids=id1,id2,id3...
     * @return 一组theme模型
     */
    public function getSimpleList($ids=''){
        //验证传入的参数是否合法 因此去定义验证器.
        (new IDCollection())->goCheck();
        //接受参数,并将参数转换为数组的形式
        $ids = explode(',',$ids);
        $result = ThemeModel::with('topicImg,headImg')->select($ids);
        return $result;
    }
}

这样结果就能返回,下一步开始写 如果返回结果异常处理
在lib下的exception下创建ThemeException.php

<?php
/**
 * Created by PhpStorm.
 * User: 彬
 * Date: 2018/2/6
 * Time: 14:41
 * QQ:739367755
 * 17603779649
 */

namespace app\lib\exception;


class ThemeException extends BaseException
{
    //    覆盖
    public  $code = 404;
    public  $msg = '指定的主题不存在,请检查ID';
    public  $errorCode = 30000;
}

复杂的业务逻辑不应该写在控制层,应该写在Model层,但是太复杂的业务逻辑甚至不能写到Model层,应该新建一个Service层.Service层就是处理一些比较复杂的业务逻辑的地方.特别是多个模型涉及到相互关系的时候.

8.14开启路由完整匹配模式这个很重要
需要在config.php修改route_complete_match属性为true

    // 路由使用完整匹配
    'route_complete_match'   => true,

8.15 编写Theme详细接口
8.16数据库字段冗余的合理利用

一个数据库设计很重要的原理
product表下的img_id字段与main_img_url字段含义一样,代表的是同一张图片.这个就是数据冗余.我们知道数据库的设计就是不要出现数据的冗余.那么我们为什么要在这里出现数据的冗余,这个主要是出于查询性能的考虑.因为product关联了太多image,假如一个主题有100product,那么又需要循环100次查询image还不如在product内直接数据冗余.
为什么使用冗余
(1)这个数据结构在很多地方都要使用
(2)数量是不可控制的

8.17 REST合理使用
为上一节补充.为什么不推荐滥用数据冗余,对数据的完整性,以及一致性的维护来说是非常困难的, 比如,(1)当你写入数据的时候,你就需要同时对2处作出一样的操作.(2)最大的问题在于删除和更新的时候,特别是更新的时候,一个地方改变对应的也要改变.如果忘记就会产生数据的不一致,

REST是基于资源的.

8.18 开始编写最近新品
最近新品就是product表,根据上传数据库的时间倒序排列选出15条.
详细在控制层Product.php

8.19 使用数据集 还是 数组?
8.19.1特别提醒, 在TP5的字符串中,不要随便写空格!!!
使用数据集临时隐藏字段
Product.php

    public function getRecent($count=15){
        //1.方法定义完去route.php 定义路由
        //2.如果客户端没有传入参数 默认就为15
        //3.参数校验.
        //4.编写模型getMostRecent()
        //5.手动将模型引入进来 use app\api\model\Product as ProductModel;
        //6.编写异常  ProductException.php
        //7.ok
        (new Count())->goCheck();
        $products =  ProductModel::getMostRecent($count);
        if(!$products){
            throw  new ProductException();
        }
        //$collection是一个数据集它有一个默认的方法能够解决 临时隐藏字段的 函数
        $collection = collection($products);
        //临时隐藏字段
        $products =  $collection->hidden(['summary']);
        return $products;
    }

比如有一组数组,我们需要对它每一个做相同的处理.比较麻烦,TP5提供了一个数据集,将数组封装成一个collection对象,在对象上执行方法会对每一个数组item进行处理.
优化
在database.php下修改 将array改成collection

  // 数据集返回类型
    'resultset_type'  => 'collection',

Product.php 优化过的,因为在database.php下直接设置了返回的是数据集而不是数组.

    public function getRecent($count=15){
        //1.方法定义完去route.php 定义路由
        //2.如果客户端没有传入参数 默认就为15
        //3.参数校验.
        //4.编写模型getMostRecent()
        //5.手动将模型引入进来 use app\api\model\Product as ProductModel;
        //6.编写异常  ProductException.php
        //7.ok
        (new Count())->goCheck();
        $products =  ProductModel::getMostRecent($count);
        if(!$products){
            throw  new ProductException();
        }
        //临时隐藏字段
        $products =  $products->hidden(['summary']);
        return $products;
    }

不过上面的代码已经有bug了
if(!$products)是错误的了,因为返回的是数据集了,用!已经不起作用了,只能用isEmpty();

 if($products->isEmpty())

8.20 开始编写分类列表接口
分类一共分为2部分
(1)左边为分类列表
(2)某一个分类的相关信息

8.21扩展 接口粒度与接口分层
在首页下,我们如何能一次性将 banner category recent_product 请求返回到客户端,这样做的好处就是只用发送一次http请求.就可以搞定首页所有的数据.
接口的粒度一般是由架构师来考虑的.
架构师是做什么的?
接口设计,其实接口设计是非常难得.必须根据业务来决定接口的粒度.粒度过大的时候复用性不好,不够灵活.粒度如果过于太细小的话.客户端调用起来不方便,发送太多的hhtp请求,并且如果都是异步的呢.接口的设计确实是非常复杂的,设计的方方面面.
8.22 分类商品
url
http://localhost/zerg/public/index.php/api/v1/product/by_category?id=5
控制层Product.php下的getAllInCategory

推荐阅读更多精彩内容