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) 破解安裝http://www.lxweimin.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

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容