微信小程序電商實戰(二)

一、架構體系

Model一般放的是粒度比較細的業務
而Service是對Model層粒度的組裝
但是Model和Service統稱爲業務層,就是業務複雜的時候要用到Service 不復雜的時候直接走Model層 因爲Model和Service是平行關係.
業務層是通過Think DB 框架去調用Mysql 從而獲得相關的業務數據
這個圖適合中小型項目
在這裏插入圖片描述
在這裏插入圖片描述

二、Banner

Banner數據表設計分析

banner表:記錄banner位,即將程序中用到輪播圖的地方編碼並記錄在此表中,此項目中雖然只有一個地方有輪播圖,即只有一個banner,但是這種邏輯想法還是要有的,以備以後添加新的banner。
banner_item表:記錄某一個banner中的具體內容,用banner_id來標識此banner_item屬於哪一個banner。
banner和banner_item是屬於一對多的關係,即一個banner可以有多個banner_item,某個banner_item只能屬於一個banner。

Banner接口定義以及自定義控制器多級目錄

目錄
在這裏插入圖片描述
Banner.php

<?php
namespace app\api\controller\v1;
class Banner
{
    /**
     * 獲取指定id的banner信息
     * @url /banner/:id 訪問接口的路徑
     * @http GET
     * @id banner的id號
     */
    public  function getBanner($id){
    
    }
}

route.php

Route::get('banner/:id','api/v1.Banner/getBanner');//三段式(模塊/控制器/方法)

三、構建驗證層

使用Validate類構建參數校驗層的兩種方法

校驗客戶端傳來的參數
驗證:內置規則
1、獨立驗證

<?php
namespace app\api\controller\v1;

use think\Validate;

class Banner
{
    /**
     * 獲取指定id的banner信息
     * @url /banner/:id 訪問接口的路徑
     * @http GET
     * @id banner的id號
     */
     //1數據2規則3校驗
    public  function getBanner($id){
        $data = [
            'name'=>'vendor111111111',
            'email'=>'vendorqq.com'
        ];
        $validate = new Validate([
            'name'=>'require|max:10',
            'email'=>'email'
        ]);
        $result = $validate->batch()->check($data);//batch批量驗證
        //echo $validate->getError();
        var_dump($validate->getError());
    }
}

在這裏插入圖片描述
2、驗證器
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

自定義驗證規則

若手冊中沒有自己需要的內置規則,則需要自定義規則

class IDMustBePositiveint extends Validate
{
    protected $rule = [
        'id'=>'require|isPositiveInteger'
    ];
    //自定義規則
    protected function isPositiveInteger($value,$rule='',$data='',$field=''){
        if(is_numeric($value) && is_int($value+0) && ($value+0)>0){//value是string類型
            return true;
        }
        else{
            return $field.'必須是正整數';
        }
    }
}
構建接口參數校驗層

項目會隨後定義一些列的驗證器,所以將所有驗證器寫在同一目錄下,在處理業務邏輯時實現複用,減少代碼量。所有的驗證器否會有一個goCheck()方法,具體有兩個功能:一是獲取http傳入的參數,二是對參數校驗。將這兩個功能的代碼封裝在一個公共基類BaseValidate中,BaseValidate繼承Validate類,其他驗證器繼承BaseValidate類,如此一來,在Banner.php中只需要寫入下面代碼即可實現校驗。

 public  function getBanner($id){
        (new IDMustBePositiveint())->goCheck();
    }

四、REST與RESTFul

REST與RESTFul API
在這裏插入圖片描述
在這裏插入圖片描述

五、AOP與全局異常處理

構建model層處理業務邏輯

在controller目錄下的Banner.php中將獲取的id校驗後,下一步則是通過獲取的id號來獲取Banner信息,這個業務邏輯需要寫在model層,所以新建一個model文件夾用來處理業務邏輯。
在這裏插入圖片描述
在model目錄下的Banner.php寫需要的業務邏輯

 public static function getBannerByID($id){
        //TODO:根據Banner ID號 獲取Banner信息
    }

然後在controller目錄下的Banner.php中調用處理此業務邏輯的函數,因爲兩個目錄下的Banner.php重名,所以可以在控制器調用前取個別名,然後調用。

use app\api\model\Banner as BannerModel;
 $banner = BannerModel::getBannerByID($id);
異常處理流程

在這裏插入圖片描述
全局異常處理用來處理兩種情況:一是不想逐級向上拋出異常,二是一些無法提前預測的異常。全局異常處理:1記錄日誌,2返回統一的錯誤信息格式到客戶端

固有的處理異常的思維模式與流程

常規的異常處理 不夠靈活且複用性比較低
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

//這裏捕獲的異常是調用getBannerByID方法時getBannerByID方法內部拋出的異常
  try{
     $banner = BannerModel::getBannerByID($id);
     }catch (Exception $ex) {
            $err = [
                'error_code' => 10001,
                'msg' => $ex->getMessage()
            ];
            //如果再拋出異常則是拋給TP5自帶的異常處理類
            //利用TP5的json函數將數組轉化爲json
            //400表示的是返回的狀態碼
            return json($err,400);
        }

在這裏插入圖片描述

異常分類

在這裏插入圖片描述

自定義全局異常處理

在這裏插入圖片描述
讓異常類和模塊相互獨立,以類庫的形式存在,利於複用
在這裏插入圖片描述
model下的Banner.php拋出異常給controller下的Banner.php,controller下的Banner.php無法處理,再拋給自定義異常處理類,其中render方法會接受所有的異常,並且按照自定義的格式返回到客戶端。注意,要想讓異常拋給自定義異常處理類,需要在配置文件config.php中修改exception_handle參數,將參數設置成自定義異常處理類的命名空間。
config.php

// 異常處理handle類 留空使用 \think\exception\Handle
    'exception_handle'       => 'app\lib\exception\ExceptionHandler',

model下的Banner.php

<?php
namespace app\api\model;
use think\Exception;
class Banner
{
    public static function getBannerByID($id){
        //TODO:根據Banner ID號 獲取Banner信息
        try {
            1 / 0;
        }
        catch (Exception $e){
            //TODO:可以記錄日誌
            throw $e;
        }
        return 'this is banner info';
    }
}

controller下的Banner.php

<?php
namespace app\api\controller\v1;
use app\api\validate\IDMustBePositiveint;
use app\api\model\Banner as BannerModel;
class Banner
{
    /**
     * 獲取指定id的banner信息
     * @url /banner/:id 訪問接口的路徑
     * @http GET
     * @id banner的id號
     */
    public  function getBanner($id){
        (new IDMustBePositiveint())->goCheck();//$this換成了IDMustBePositiveint()實例
         $banner = BannerModel::getBannerByID($id);
         return $banner;
    }
}

自定義異常處理類ExceptionHandler.php

<?php
namespace app\lib\exception;
use Exception;
use think\exception\Handle;
class ExceptionHandler extends Handle//重寫某個類的最好方法就是繼承並且覆蓋默認方法
{
    public function render(Exception $e)
    {
        return json('111111111111111111');//試驗能否順利調用重寫的render方法
    }
}

Postman
在這裏插入圖片描述
驗證成功後進一步寫自定義異常處理類ExceptionHandler.php的業務邏輯

<?php

namespace app\lib\exception;

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

class ExceptionHandler extends Handle//重寫某個類的最好方法就是繼承並且覆蓋默認方法
{
    private $code;
    private $msg;
    private $errorCode;
    //需要返回客戶端當前請求的url路徑

    public function render(Exception $e)
    {
        if($e instanceof BaseException){//第一類異常
            //如果是自定義的異常
            $this->code = $e->code;
            $this->msg = $e->msg;
            $this->errorCode = $e->errorCode;
        }
        else{//第二類異常
            $this->code = 500;
            $this->msg = '服務器內部錯誤';
            $this->errorCode = 999;
        }
        $request = Request::instance();
        $result = [
            'msg' => $this->msg,
            'error_code'=>$this->errorCode,
            'request_url'=>$request->url()
        ];
        return json($result,$this->code);
    }
}

在controller下的Banner.php編寫拋出異常,即拋出BannerMissException(banner不存在)這個異常類(這裏的BannerMissException需要繼承BaseException公共異常類,同時BaseException要繼承框架自帶的Exception類,繼承的時候進行重寫屬性和方法,簡明的說,任何自定義異常類都要繼承框架自帶的異常類才能讓自定義的異常處理類來接收自定義異常類,因爲自定義異常處理類也 有繼承框架自帶的異常處理類)

ThinkPHP5中的日誌系統

TP5具有默認記錄日誌功能,目錄:
在這裏插入圖片描述
修改日誌目錄:
在index.php入口文件裏修改

define('LOG_PATH', __DIR__ . '/../log/');

爲什麼需要修改日誌目錄(+自定義日誌格式)?
TP5默認記錄日誌功能會將所有的記錄存放起來,但是有些日誌記錄是不需要的,這些無意義的記錄會耗費資源去存儲、排查,比如一些因爲用戶操作不當而產生的異常記錄。我們只需要記錄服務器內部產生的異常,所以需要修改日誌目錄並自定義日誌格式

在全局異常處理中加入日誌記錄

首先修改config.php文件

 'log'                    => [
        // 日誌記錄方式,內置 file socket 支持擴展
        'type'  => 'test',//改爲test
        // 日誌保存目錄
        'path'  => LOG_PATH,
        // 日誌記錄級別
        'level' => [],
    ]

再修改自定義異常處理類ExceptionHandler.php

 private function recordErrorLog(Exception $e){
        //初始化日誌
        Log::init([
            'type'=>'File',
            'path'=>LOG_PATH,
            'level'=>['error']
        ]);

        Log::record($e->getMessage(),'error');
    }

再在服務器出錯的業務邏輯裏調用此方法

$this->recordErrorLog($e);//調用
全局異常處理的應用

出於當發生錯誤時客戶端希望看到json結構體,但是服務器管理人員希望看到發生錯誤的具體原因,只看到json結構體的內容,不足以去排查錯誤。所以需要設置一個開關switch,用來控制兩者的轉換,將switch換成配置文件config.php‘中的app_debug,開時是調試模式,關則是生產模式。所以修改ExceptionHandler.php

 public function render(Exception $e)
    {
        if($e instanceof BaseException){//第一類異常
            //如果是自定義的異常
            $this->code = $e->code;
            $this->msg = $e->msg;
            $this->errorCode = $e->errorCode;
        }
        else{//第二類異常
            //$switch = true;
            //if($switch){
            //Config::get('app_debug');
            if(config('app_debug')){
                //return default error page
                return parent::render($e);
                //調用父類的render 還原TP5的默認rennder
            }
            else{
                $this->code = 500;
                $this->msg = '服務器內部錯誤';
                $this->errorCode = 999;
                $this->recordErrorLog($e);//調用
            }
        }

進一步來看
BaseValidate:

class BaseValidate extends Validate
{
    public function goCheck(){
        //獲取http傳入的參數
        //對參數校驗
        $request = Request::instance();
        $param = $request->param();//獲取所有參數
        $result= $this->check($param);//校驗
        if(!$result){
            $error = $this->error;
            throw new Exception($error);//Exception不屬於BaseException,所以屬於第二類異常
        }
        else{
            return true;
        }
    }
}

如果校驗不通過,則會返回錯誤,通過app_debug的開關,來觀察發現無論是開還是關返回給客戶端的信息都不滿足我們想要的
具體例子:將輸入的id改爲0.1
app_debug關:
在這裏插入圖片描述
app_debug開:
在這裏插入圖片描述
所以新創建一個參數異常類ParameterException,修改BaseValidate,如下:

class BaseValidate extends Validate
{
    public function goCheck(){
        //獲取http傳入的參數
        //對參數校驗
        $request = Request::instance();
        $param = $request->param();//獲取所有參數
        $result= $this->check($param);//校驗
        if(!$result){
            $e = new ParameterException();//自定義 屬於第一類異常
            $e->msg = $this->error;//覆蓋msg屬性的內容 數據來自validate的驗證結果
            throw $e;
            //$error = $this->error;
           // throw new Exception($error);
        }
        else{
            return true;
        }
    }
}

在這裏插入圖片描述
優化1:
在BaseValidate中如果從外部訪問成員變量的方式給成員變量賦值也未嘗不可,但是我們建議有更好的一種面向對象寫法,我們在new一個對象的時候就完成了賦值,而不是在new一個對象之後再賦值。可以通過構造函數來初始化賦值操作,更加符合面向對象的基本特性。則實例化的時候即可傳入參數,相應的構造函數(父類中創建即可)會進行處理。
BaseValidate

if(!$result){
            $e = new ParameterException([
                'msg'=>$this->error
            ]);

在BaseException(ParameterException的父類)中添加構造方法:

public function __construct($params = [])//構造函數
    {
        if(!is_array($params)){
            return ;
            //throw Exception('參數必須是數組');
        }
        if(array_key_exists('code',$params)){
            $this->code = $params['code'];
        }
        if(array_key_exists('msg',$params)){
            $this->msg = $params['msg'];
        }
        if(array_key_exists('errorCode',$params)){
            $this->errorCode = $params['errorCode'];
        }
    }

優化2:
在goCheck()中加入批量驗證處理

$result = $this->batch()->check($param);
補充錯誤-瞭解Exception的繼承關係

當輸入路由錯誤時,頁面如下:
在這裏插入圖片描述
由於拋出的異常HttpException和render方法中需要傳入的參數爲think的Exception不存在父子類關係(可以查找並分析這兩個類),嘗試將HttpException傳入到本應該傳think的Exception時就會出現報錯。所以需要將傳入的參數修改爲兩者的基類Exception。Exception
修改後:
在這裏插入圖片描述
或者:
在這裏插入圖片描述

本章小結與AOP思想

1學會重構代碼,考慮代碼的複用性和層次結構
2AOP思想
例子1: 校驗層和異常處理層
我們處理異常的時候,我們並不會把異常分散到具體的每一個業務代碼中,而我們提供了一個類似於橫切面的東西,這個橫切面就是我們的ExceptionHandler.php下的render方法,它會統一的處理所有的異常.
例子2:我們去看電影,電影院有一個檢票口,也許你的票在貓眼,美團…上買的.不管你在哪買的,最後我們都要在檢票口看你的票能不能入我的電影院.我們不能給每一個觀影人都配一個檢票員.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章