全栈微信小程序商城 学习笔记之八 专题、分类、商品详情接口

Banner相关表分析

banner指banner位,banner_item是具体的banner项
一个banner位有多个banner_item,一个banner_item是能属于一个banner,所以是一对多的关系
两者通过banner_item的banner_id外键关联

banner表

banner_item表

banner_item 还有一个字段img_id与image表关联

image表

模型关联

applicationapimodelBanner.php

<?php

namespace appapimodel;

class Banner extends Model
{
    public function items()
    {
        //参数:1.关联的模型BannerItem2.关联BannerItem的外键3.当前模型的主键
        return $this->hasMany('BannerItem', 'banner_id', 'id');
    }
}

使用
applicationapicontrollerv1Banner.php

<?php

namespace appapicontrollerv1;


use appapivalidateIDMustBePositiveInt;
use appapimodelBanner as BannerModel;

/**
 * Banner资源
 */ 
class Banner
{
    public function getBanner($id)
    {
        $validate = new IDMustBePositiveInt();
        $validate->goCheck();
        $banner = BannerModel::with('items')->find($id);
        if (!$banner) {
            throw new BannerMissException();
        }
        return $banner;
    }
}

查看返回结果

使用命令新建Image模型

php think make:model api/Image
<?php

namespace appapimodel;

use thinkModel;

class Image extends Model
{

}

关联到Image模型
applicationapimodelBannerItem.php

<?php

namespace appapimodel;

use thinkModel;

class BannerItem extends Model
{

    public function img()
    {
        //一对一关系使用
        //参数:1.关联模型名2.关联外键3.关联模型主键
        return $this->belongsTo('Image', 'img_id', 'id');
    }
}

使用
applicationapicontrollerv1Banner.php

class Banner
{
    public function getBanner($id)
    {
        $validate = new IDMustBePositiveInt();
        $validate->goCheck();
        //嵌套关联查询
        $banner = BannerModel::with(['items', 'items.img'])->find($id);
        return $banner;
    }
}

返回的数据

从规范性来看,把查询的代码封装下更好
applicationapimodelBanner.php

class Banner extends Model
{
    public function items()
    {
        return $this->hasMany('BannerItem', 'banner_id', 'id');
    }
    public static function getBannerById($id)
    {
        $banner = self::with(['items','items.img'])
            ->find($id);

        return $banner;
    }
}

controller/v1/banner.php

class Banner
{
    public function getBanner($id)
    {
        $validate = new IDMustBePositiveInt();
        $validate->goCheck();
        $banner = BannerModel::getBannerById($id);
        if (!$banner) {
            throw new BannerMissException();
        }
        return $banner;
    }
}

隐藏模型字段

返回的因为是受保护的类型,不能直接访问其成员,要先转成数组

class Banner
{
    public function getBanner($id)
    {
        $validate = new IDMustBePositiveInt();
        $validate->goCheck();
        $banner = BannerModel::getBannerById($id);
        $data = $banner->toArray();
        unset($data['dalete_time']); //删除delete_time字段
        if (!$banner) {
            throw new BannerMissException();
        }
        return $data;
    }
}

上面的方法不推荐使用,建议用模型的方法

class Banner
{
    public function getBanner($id)
    {
        $validate = new IDMustBePositiveInt();
        $validate->goCheck();
        $banner = BannerModel::getBannerById($id);
        if (!$banner) {
            throw new MissException([
                'msg' => '请求banner不存在',
                'errorCode' => 40000
            ]);
        }
        //传进一个要隐藏的数组
        $banner->hidden(['delete_time', 'update_time']) 
        //只显示数组内的字段
        // $banner->visible(['id']) 
        return $banner;
    }
}

查看返回结果

在模型内部隐藏字段

applicationapimodelBanner.php

class Banner extends Model
{
    protected $hidden = ['id']; // visible方法也可用
}

查看返回结果

图片资源url配置

如果图片存在本地服务器上,在数据库url字段用相对路径,可以适应不同的根目录配置。
在application新建文件夹extra,这里面的配置文件会被tp5自动加载
applicationextrasetting.php

<?php
return [
    //将图片资源images文件夹放在zergpublic文件夹下
    'img_prefix' => 'http://127.0.0.1:8000/public/images'
];

读取器的妙用

applicationapimodelImage.php

<?php

namespace appapimodel;

use thinkModel;

class Image extends Model
{
    protected $hidden = ['delete_time', 'id', 'from'];
    //这个命名是特定的,表示是读取器,get和attr是固定式,url是字段名
    public function getUrlAttr($value, $data) {
        $finalUrl = $value;
        if ($data['from'] == 1) {
            //本地图片
            //使用了助手函数config这里可以成功拿到图片的基地址
            $finalUrl = config('setting.img_prefix').$value;
        }
        //如未通过以上判断说明是网络图片
        return $finalUrl;
    }
}

查看返回结果

自定义模型基类

提取一个基类优化结构
命令
php think make:model api/BaseModel

applicationapimodelBaseModel.php

<?php

namespace appapimodel;

use thinkModel;

class BaseModel extends Model
{
    protected $hidden = ['delete_time'];
    protected function prefixImgUrl($value, $data){
        $finalUrl = $value;
        if($data['from'] == 1){
            $finalUrl = config('setting.img_prefix').$value;
        }
        return $finalUrl;
    }
}

重新定义读取器
Img.php

<?php

namespace appapimodel;

use thinkModel;

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

    public function getUrlAttr($value, $data)
    {
        return $this->prefixImgUrl($value, $data);
    }
}

让所有的模型都继承自己写的BaseModel

定义API版本号

能更好适应业务变化
复制applicationapicontrollerv1Banner.php到
applicationapicontrollerv2Banner.php (仅用于测试)

新增路由
application oute.php

Route::get('api/:version/banner/:id', 'api/:version.Banner/getBanner');

此时可以通过更改url调用接口的不同版本

专题接口模型分析

一个theme下面有多个商品product,如果用一个字段表示拓展性就差了,这里可以新增中间表theme_product表示

theme表

product表

theme_product表

创建Theme和Product模型类
applicationapimodelTheme.php

class Theme extends BaseModel
{
    //
}
class Product extends BaseModel
{
    //
}

没必要建立中间表的模型类,tp5已经自动处理

一对一关系

applicationapimodelTheme.php

class Theme extends BaseModel
{
    public function topicImg()
    {
        return $this->belongsTo('Image', 'topic_img_id', 'id');
    }

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

一对一也有主从关系
主题Theme有关联Image表的外键,而Image表中并没有此类字段,如果用Image关联Theme则要用hasOne

theme表

image表

theme接口验证与重构

定义路由
application oute.php

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

applicationapicontrollerv1Theme.php

class Theme
{
    //返回一组theme模型
    public function getSimpleList($ids='') {

    }
}

applicationapivalidateIDCollection.php

<?php
namespace appapivalidate;


class IDCollection extends BaseValidate
{
    // 千万不要在require|checkIDS中加空格
    // 不然你会哭的
    // 源码中是没有去处多余空格的判断的
    // 这将导致验证不执行
    protected $rule = [ 
        'ids' => 'require|checkIDs'
    ];
    //这个message和rule一样也是内置的,如果规则未通过则用此信息通知
    protected $message = [ 
        'ids' => 'ids参数必须为以逗号分隔的多个正整数,仔细看文档啊'
    ];

    protected function checkIDs($value)
    {
        $values = explode(',', $value);
        if (empty($values)) {
            return false;
        }
        foreach ($values as $id) {
            if (!$this->isPositiveInteger($id)) {
                // 必须是正整数
                return false;
            }
        }
        return true;
    }
}

上面的如果要生效还要更改下验证器基类
applicationapivalidateBaseValidate.php

class BaseValidate extends Validate
{
    // 检测所有客户端发来的参数是否符合验证类规则
    public function goCheck()
    {
        $request = Request::instance();
        $params = $request->param();
        //同时校验
        $result = $this->batch()->check($params);
        if (!$result) {
            $e = new ParameterException([
                'msg' => $this->error,
                'code' => 400,
                'errorCode' => 10002
            ]);
          throw $e;
        } else {
            return true;
        }
    }
    //把applicationapivalidateIDMustBePositiveInt.php的方法移动到这
    protected function isPositiveInteger($value, $rule='', $data='', $field='')
    {
        if (is_numeric($value) && is_int($value + 0) && ($value + 0) > 0) {
            return true;
        }
        //不能返回字符串,否则会被认为通过验证
        //return $field . '必须是正整数';
        return false;
    }
}

applicationapicontrollerv1Theme.php

class Theme
{
    public function getSimpleList($ids='') 
    {
        (new IDCollection())->goCheck();
        return 'success'
    }
}

测试,如果返回success表示通过验证

完成Theme简要信息接口

applicationapicontrollerv1Theme.php

class Theme
{
    public function getSimpleList($ids='') {
        (new IDCollection())->goCheck();
        $ids = explode(',', $ids);
        $result = ThemeModel::with('topicImg,headImg')->select($ids);

        if (!$result) {
            throw new ThemeException();
        }
        return $result;
    }
}

applicationlibexceptionThemeException.php

<?php

namespace applibexception;


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

把不需要返回给客户端的字段隐藏
applicationapimodelTheme.php

class Theme extends BaseModel
{
    protected $hidden = ['delete_time', 'topic_img_id', 'head_img_id'];
    //...
}

theme详情接口

多对多关系
applicationapimodelTheme.php

class Theme extends BaseModel
{
    public function products()
    {
        //1.关联模型名2.中间表名3.中间表的当前模型外键4.中间表的当前模型关联键
        return $this->belongsToMany(
            'Product', 'theme_product', 'product_id', 'theme_id'); 
    }
}

applicationapicontrollerv1Theme.php

class Theme
{
    public function getSimpleList($ids='') {
        (new IDCollection())->goCheck();
        $ids = explode(',', $ids);
        $result = ThemeModel::with('topicImg,headImg')->select($ids);

        if (!$result) {
            throw new ThemeException();
        }
        return $result;
    }
    //theme详情接口
    public function getComplexOne($id)
    {
        return 'success';
    }
}

定义路由
application oute.php

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

使用时发现路由不能正常使用,会被上一个theme路由匹配成功

路由使用完整匹配
applicationconfig.php

return [
    //...
    'route_complete_match'   => true,
    //...
];

测试,返回success

Theme详情

applicationapicontrollerv1Theme.php

class Theme
{
    public function getSimpleList($ids='') {
        (new IDCollection())->goCheck();
        $ids = explode(',', $ids);
        $result = ThemeModel::with('topicImg,headImg')->select($ids);

        if (!$result) {
            throw new ThemeException();
        }
        return $result;
    }
    public function getComplexOne($id)
    {
        (new IDMustBePositiveInt())->goCheck();
    }
}

applicationapimodelTheme.php

class Theme extends BaseModel
{
    public static function getThemeWithProducts($id)
    {
        $themes = self::with('products,topicImg,headImg')
            ->find($id);
        return $themes;
    }
}

applicationapicontrollerv1Theme.php

public function getComplexOne($id)
{
    (new IDMustBePositiveInt())->goCheck();
    $theme = ThemeModel::getThemeWithProducts($id);
    if(!$theme){
        throw new ThemeException();
    }
    return $theme;
}

REST的合理利用

新建一个读取器
applicationapimodelProduct.php

class Product extends BaseModel
{
    //pivot如果是多对多关系会tp5自动带上的中间表属性,这里不需要
    protected $hidden = [
        'delete_time', 'main_img_id', 'pivot', 'from', 'category_id',
        'create_time', 'update_time']; 

    public function getMainImgUrlAttr($value, $data)
    {
        return $this->prefixImgUrl($value, $data);
    }
}

查看返回结果

最近新品

使用模型插入数据库,update_time、create_time、delete_time会自动更新
新建路由
application oute.php

Route::get('api/:version/product/recent', 'api/:version.Product/getRecent');

applicationapicontrollerv1Product.php

class Product 
{
    public function getRecent($count = 15)
    {
        (new Count())->goCheck();
        return 'success';
    }
}

新建验证器
applicationapivalidateCount.php

<?php
namespace appapivalidate;

class Count extends BaseValidate
{
    protected $rule = [
        'count' => 'isPositiveInteger|between:1,15',
    ];
}

applicationapimodelProduct.php

class Product extends BaseModel
{
    public static function getMostRecent($count)
    {
        $products = self::limit($count)
            ->order('create_time desc') //排序
            ->select();
        return $products;
    }
}

applicationapicontrollerv1Product.php

<?php

namespace appapicontrollerv1;

use appapimodelProduct as ProductModel;
use appapivalidateCount;
use applibexceptionProductException;

class Product
{
    public function getRecent($count = 15)
    {
        (new Count())->goCheck();
        $products = ProductModel::getMostRecent($count);
        if (!$products)
        {
            throw new ProductException();
        }
        return $products;
    }
}

applicationlibexceptionProductException.php

<?php

namespace applibexception;


class ProductException extends BaseException
{
    public $code = 404;
    public $msg = '指定商品不存在,请检查商品ID';
    public $errorCode = 20000;
}

使用数据集还是数组?

观察接口的返回结果,有一个summary字段是不需要的

applicationapimodelProduct.php

class Product extends BaseModel
{   
    //这里不能直接加summary,否则所有与Product相关联的接口都不会显示这个字段
    protected $hidden = [
        'delete_time', 'main_img_id', 'pivot', 'from', 'category_id',
        'create_time', 'update_time']; 
}

在接口中对summer做隐藏

applicationapicontrollerv1Product.php

class Product
{
    public function getRecent($count = 15) {
        (new Count())->goCheck();
        $products = ProductModel::getMostRecent($count);
        if (!$products)
        {
            throw new ProductException();
        }
        //将数组转成数据集
        $collection = collection($products)
        $products = $collection->hidden(['summary'])
        return $products
    }
}

数据集包含了许多对数据的有用操作,可以在配置文件配置,让数据集不再以二维数组的方式返回而是以数据集类的方式返回
applicationdatabase.php

return [
    //...
    'resultset_type'  => 'collection',
    //...
];

applicationapicontrollerv1Product.php

class Product
{
    (new Count())->goCheck();
    $products = ProductModel::getMostRecent($count);
    //返回数据集类,原来的判空方式不再适用,这里使用数据集类的isEmpty方法判空
    //其他接口使用了模型的select方法返回数据集的地方也要改
    if ($products->isEmpty())
    {
        throw new ProductException();
    }
    $products = $products->hidden(['summary']);
    return $products;
}

查看结果

分类接口

application oute.php

Route::get('api/:version/category/all', 'api/:version.Category/getAllCategories');

applicationapimodelCategory.php

class Category extends BaseModel
{
    public function img()
    {
        return $this->belongsTo('Image', 'topic_img_id', 'id');
    }
}

applicationapicontrollerv1Category.php

class Category
{
    public function getAllCategories()
    {
        //[]代表查询所有
        //以下语句相当于CategoryModel::with('img')->select()
        $categories = CategoryModel::all([], 'img'); 
        if($categories -> isEmpty()){
           throw new CategoryException();
        }
        return $categories;
    }
}

定义异常
applicationlibexceptionCategoryException.php

<?php

namespace applibexception;


class CategoryException extends BaseException
{
    public $code = 404;
    public $msg = '指定类目不存在,请检查商品ID';
    public $errorCode = 20000;
}

分类商品接口

新建路由
application oute.php

Route::get('api/:version/product/by_category', 'api/:version.Product/getAllInCategory');

applicationapicontrollerv1Product.php

class Product
{
    public function getAllInCategory($id)
    {
        (new IDMustBePositiveInt())->goCheck();
        $products = ProductModel::getProductsByCategoryID(
            $id);
        if ($products->isEmpty()) {
            throw new ProductException();
        }
        $products = $products->hidden(['summary']);
        return $products;
    }
}

applicationapimodelProduct.php

class Product extends BaseModel
{
    public static function getProductsByCategoryID($categoryID)
    {
        $products = self::where('category_id', '=', $categoryID) -> select();
        return $products;
    }
}
原文地址:https://www.cnblogs.com/Qyhg/p/14106689.html