微信小程序后端笔记(PHP)

1,022 阅读7分钟

微信小程序商城构建全栈应用

  • php+微信小程序全栈应用

软件/素材

  • mac os 10.13.3
  • PhpStorm 2018
  • Postman
  • XAMPP 7.0.2-1
  • ThinkPHP 5.0.7

项目目录结构

├─application           应用目录
  ├─api                公共模块目录(可以更改)
  │  │-controller      控制器目录  (版本以及业务)
  │  │-model           模型目录  (关联模型处理)
  │  │-service         模型服务层(相对复杂的业务处理)
  │  └─validate        验证层     (客户端数据验证)
  ├─extra              自定义公共资源层(tp5自带的)
  ├─lib                模块目录
  │  ├─enum            枚举
  │  └─exception       全局异常处理目录
  │
  ├─command.php        命令行工具配置文件
  ├─common.php         公共函数文件
  ├─config.php         公共配置文件
  ├─route.php          路由配置文件
  ├─tags.php           应用行为扩展定义文件
  └─database.php       数据库配置文件

笔记

第八章

数据表关系分析 (写着写着就绕了)

  1. 数据表之间的关系: 1 对 1 1 对多 多对多
    • 如何判断数据表之间的结构
  2. 首先确立是否是一个多对多的关系
    • 查看表与表之间是否存在双方的外建均能被多个表调用,如果不是那就去除多对多关系
  3. 1 对 1 1 对多
    • 在 thinkphp 中问题不大
    • 如何去分析 1 对多或 1 对 1
    • 1 对 1 的关系中, 两个表直接同时并且单次被执行,就是说一个关联请求中,表 1 一次只可以调用一个表 2 的元素,并且表 2 也只是被调用了一次
    • 1 对多 的关系中, 表 1 通过一个外建,调用了多个表 2 的数据,并且表 2 的数据不能属于多个表 1,这样就是 1 对多的表现了

模型关联(我们确立了 er 关系再来做这么的一个关联)

  1. 模型关联查询

    • 在我们的 model 是作为一个 ORM 模式的模型结构
    • 在这之前我们就已经定义了模型了
    • 我们有两个模型 Banner 与 BannerItem
    • tp5 对我们提供了关联查询的方法 hasMany
    • 定义关联查询
        // 在当前模型 Banner 新建类  类名自定义喜欢什么来什么
        // 函数体要写在 Banner 这个主模型中,BannerItem是被关联模型
        // 调用模型关联时要清晰的知道 外键 以及主建(某程度下是不用写后面两个,不建议)
        public function items () {
                // 关联查询方法hasMany 关联模型      外建      当前模型 banner id主建
               return $this->hasMany('BannerItem','banner_id','id');
        }
    
    • 调用关联查询
      //  在调用 模型的时候加上 with这么个方法 (括号内填写的就是刚才定义的函数名)
    $banner = BannerModel::with('items')->find($id);
    
  2. 模型嵌套关联查询

    • 在我们的 查询中 会存在被关联体中还关联着变得关联体,在 tp5 中就形成了嵌套查询
    • 当然 tp5 也给我们提供了方法:belongsTo
    • 嵌套关系 Banner -> BannerItem -> Image (这里就存在了多重的嵌套)
    • 模型 Banner BannerItem Image
    • 是 BannerItem 关联 Image 所以关联函数我们写在 BannerItem 中
    • 定义嵌套查询
      public function img() {
    //        处理方法名其他都是一样的,这里就不多说了
          return $this->belongsTo('Image','img_id','id');
      }
    
    • 调用查询 (这个比较关键,不过还是很简单的)
    // with 可以是字符串也可以是数组(嵌套关联时就会用数组)
    // 为什么是items.img 而不是 直接img呢,因为是嵌套关系,在模型中可以嵌套这里也是可以的
    // 但是在 嵌套时 是items 关联的 img ,这里就会用.来链接
    // 这个解释比较绕但是,知道方法就是要这样去用的就好啦
    $banner = BannerModel::with(['items','items.img'])->find($id);
    

隐藏模型字段 (模型自带)

  1. hidden 方法隐藏字段
// 数据      方法      字段名
 $banner->hidden(['字段名例:id'])
  1. visible 只显示的字段
$banner->visible(['字段名例:id','update_time])

模型内部隐藏字段 (自定义模型的内部隐藏,把一些前端不需要的字段隐藏了)

  • hidden 隐藏
  • 直接在 model 定义的模型内添加方法 (以 Banner 为例)
namespace app\api\model;

use think\Model;

class Banner extends Model
{
     // 直接添加 $hidden的数组填入要隐藏的字段即可
     // visible 等方法用法一样,那个模型内部的字段要隐藏就在那个模型内部设置
    protected $hidden = ['id'];

    public function items () {
                                // 关联模型           外建                当前模型 banner id主建
        return $this->hasMany('BannerItem','banner_id','id');
    }
    public static function getBannerByID($id) {
        $banner = self::with(['items','items.img'])->find($id);

        return $banner;
    }
}

自定义配置

  • /application/extra (extra 自己新建的,凡是放在这里面的配置文件都会被自动加载)
  • 手动配置一个本地的 img 图片路径
  1. 在 extra 下 新建 setting.php
 return [
     //  名称                 域名   路径(直接放在public下的images就是这样写就可以了)
     'img_prefix' => 'http://zerg.cn/images'
 ];
  1. 使用自定义变量
    • 因为是在 extra 内部定义的所以会自动调用,那么我们用 config 就可以去掉用到了
          // 配置文件名.变量名
    config('setting.img_prefix');
    

静态文件存放

  • 静态的外部文件,例如图片啊文本啊等的文件,必须放在 public 这个公共目录下
  • 并不是放在 application 的这个开发目录下,因为 tp5 的架构里面只有 public 这个目录是对外开放的
  • 所以文件都必须是要放在 public 目录下

tp 模型读取器 (数据拼合)

  • 为了获取数据/修改数据,tp5 给出了一个读取器的方法
  • 用来给我们读取数据修改数据用的
  • 那个模型要修改数据就在哪个模型定义
  1. 定义读取器(其实也是一个函数方法)
    • 读取器命名规范 开头 get 必须有 + 读取数据的名称并且开头要大写例 Url + Attr 必须加的(利用驼峰命名法)
    • getUrlAttr (完整的编写,除了中间的那个数据,其他都是必须有的,中间数据名开头必须大写)
    • 传入一个值,名字自定义 (这个传入的数据其实就是我们要获取到要修改的数据)
    • 每一次传入一个数据,有多个输出就会重复的执行读取器
    • 因为在我们的业务逻辑中会调用到当前模型的其他数据,但是第一个参数只是获取到的是当前读取器的数据,并无法读取到其他的数据
    • 所以添加了第二个参数 (这个参数会给我们返回一个这个模型的数据,就是所有的数据)
 public function getUrlAttr ($value,$data) {

 }
  1. 使用读取器 (做数据的修改然后返回)
    public function getUrlAttr ($value) {
        // 这里我们只是做了一个自定义的 变量和url路径的拼接
        return config('setting.img_prefix').$value;
    }
  1. 业务逻辑添加
 public function getUrlAttr ($value,$data) {
 $finalUrl = $value;
     // 判断是否要拼接
 if ($data['from'] === 1) {
     $finalUrl = config('setting.img_prefix') . $value;
 }
 return $finalUrl;
 }

自定义基类 (面向对象,提取模型读取器)

  • 一开始这样做会觉得好像代码还多了啊,这么不就是做无用功吗,在业务不断增加的时候,后期修改就可以看出来好处了
  • 集中业务逻辑
  • 创建 BaseModel.php 作为模型基类
  • 把让所有的模型都继承这个基类
  1. 把读取器提取到 模型基类 (这样做是一个面向对象的思想)
    • 但是提取了模型基类后我们所有的子模型都会自动的去执行模型
    • 这样可能会造成一些数据的变更和错误,比如说,两个命名一样但是代表的数据不同是就会出现错误
    • 所以我们把它封装为一个自调用的方法
    // BaseModel
    // 读取器
    protected function prefixImgUrl ($value,$data) {
        $finalUrl = $value;
        if ($data['from'] === 1) {
            $finalUrl = config('setting.img_prefix') . $value;
        }
        return $finalUrl;
    }
    
  2. 子模型调用基类方法
    • Image
    public function getUrlAttr ($value,$data) {
        return $this->prefixImgUrl($value,$data);
    }
    

定义 api 版本号

  • 在互联网的项目中,我们会对项目版本对升级,以及业务逻辑改变和变更
  • 同时也是需要去兼容旧版本,所以会保留旧版本的 api
  1. 开发开闭原则
    • 代码对拓展开发,对修改封闭
    • 添加功能直接以拓展的方式添加就可以,不需要去改变代码
    • 修改是封闭的,业务变更上升版本
    • 不可以修改原来的版本代码,会破坏了原版本的代码,和影响功能调用的风险
    • 需要修改就要添加新的版本
  2. 多版本
    • 版本的分离,新旧版本不发生冲突
    • 新老版本的兼容问题
    • 给用户缓冲时间,也不能兼容太多的版本,成本太高
    • v1 做 v1 版本层
    • v2 做 v2 版本层

路由 api 动态变更

//              动态版本 实现传什么就调用什么版本的api,同时也是要修改版本指向接口
//                 传 v1 就是 v1
//                 传 v2 就是 v2           动态写入
Route::get('api/:version/banner/:id','api/:version.Banner/getBanner');

一对一关系选择关联方法

  1. belongsTo
    • 在有外建的表内请求就用 belongsTo
  2. hasOne
    • 在没有外建的表亲求就用 hasOne

多对多查询 (belongsToMany)

  • 多对多的查询呢 就比一对多和 1 对 1 的查询要多了一个参数
  • 在参数中第二个是放入第三个表也就是中间表
public function products () {
                                    // 关联表名        中间表名                 关联表id            主建
    return $this->belongsToMany('Product','theme_product','product_id','theme_id');
}

开启路由完整匹配模式

  • 开我们开发的过程中难免会有 api 相同当是请求的方式以及传参的不同,但是又需要相同的 api 名称
  • 在我们的 tp5 中,会自动追寻一个半路径的匹配,所以当匹配到了相关的路由时就会停止匹配
  • 但是这样返回的结果肯定不是我们要的,所以就要开启这个完整的路由匹配模式
  • 在 config.php 配置文件中,我们就可以来更改了
 //  只有找到这句话改变就可以了   false -> true
 // 路由使用完整匹配
 'route_complete_match'   => true,

合理利用数据冗余

  • 在查询量上来的时候避免数据量大多表查询之间耗时
  • 合理的利用数据冗余来减少联合表的查询减少查询时间
  • 但不要太过多但使用,只是为了减少数据库压力
  • 在数据库中做相关的优化

collection 字符集

  • 我们使用获取到的数据是字符集更方便让我们来修改数据
  1. tp5 修改获取返回数据 (/application/database.php)
 // 找到这个吧 arr改为 collection
 // 数据集返回类型
 'resultset_type'  => 'collection',
  1. 使用字符集就可以轻松的临时隐藏字段

    • 当我们在开发的过程中,不是所有业务逻辑都需要隐藏的字段,我们就不可以在关联模型中直接就隐藏字段
    • 我们会使用临时隐藏字段
    • 当然数组我们是不可以直接这样来隐藏的,但是使用字符集的话就可以直接的去使用函数进行数据的隐藏
    // 使用hidden进行隐藏
    $products = $products->hidden(['summary']);
    
  2. 字符集判空

    • isEmpty 内置函数
     // 判断空抛出异常
     if ($products->isEmpty()) {
         throw new ProductException();
     }

##第九章

service (建立在 model 上的,用来处理复制的业务)

  • 在我们的 tp5 中,我们的 model 代表的一个很重要的位置
  • 可以写业务逻辑,也访问数据库
  • 但是 service 不可以用来访问数据库,因为上建立在 model 之上的
  • 我们都会把复杂的业务逻辑放在 service 层中

公共应用文件 common.php

  • 编写公共的 http 请求
/*
* @param string $url get 请求地址
* @param int $httpCode 返回状态码
* @return mixed
*/
function curl_get ($url,&$httpCode = 0) {
 $ch = curl_init();
 curl_setopt($ch,CURLOPT_URL,$url);
 curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);

//    不做证书校验,部署在linux环境下请改为true
 curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,false);
 curl_setopt($ch,CURLOPT_CONNECTTIMEOUT,10);
 $file_contents = curl_exec($ch);
 $httpCode = curl_getinfo($ch,CURLINFO_HTTP_CODE);
 curl_close($ch);
 return $file_contents;
}

模型插入数据(create)

  • 在 tp5 中如何向数据库插入数据
  • tp5 模型给我们准备了 create 的方法
     //  模型名   create方法 数组传入要添加的字段和数据
 $user = UserModel::create([
         'openid' => $openid
     ]);

动态传入数值随机生成字符串方法

/*
 * 生成随机字符串
 */
function getRandChar ($length) {
    $str = null;
    $strPol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
    $max = strlen($strPol) - 1;

    for ($i=0;$i < $length; $i++) {
        $str .= $strPol[rand(0,$max)];
    }

    return $str;
}

文件缓存 chache

  • 使用 cache 写入缓存
  • 使用文件存储的方式
  • 缓存的地址在目录文件/runtime/cache 文件内
$request = cache($key,$value,$expire_in);

路由分组

  • 由于我们 api 接口的不断增加
  • 在一个分类中会有很多的相同的接口路由
  • 这个时候如果我们业务的变更修改起来就会很麻烦
  • 所以我们是用来路由分组来实现
  • group 方法
  • 第一个是公共的路由部分,第二个是一个闭包(也就是一个 function 的方法)
  • 在里面还是安装路由一样去定义就可以了
  • 也能提高路由的效率

//Route::get('api/:version/product/recent','api/:version.Product/getRecent');
//Route::get('api/:version/product/by_category','api/:version.Product/getAllInCategory');
//Route::get('api/:version/product/:id','api/:version.Product/getOne',[],['id'=>'\d+']);

Route::group('api/:version/product', function () {
    Route::get('/recent','api/:version.Product/getRecent');
    Route::get('/by_category','api/:version.Product/getAllInCategory');
    Route::get('/:id','api/:version.Product/getOne',[],['id'=>'\d+']);
});

关联模型下个关联数据排序(tp5 没有的,重点)

  • 使用 模型+query 添加排序
 // 关联模型 imgs  properties 查询
 // 模型的嵌套 imgurl
 public static function getProductDetail ($id) {
     // 在 with 中 嵌套function
     // 在内部添加 query
     $product = self::with(['imgs' => function ($query) {
         $query->with(['imgUrl'])->order('order','asc');
     }])->with(['properties'])->find($id);
     return $product;
 }

使用 关联模型 添加/更新数据

  • 添加数据的方法有很多,我们来使用一下关联模型的方法
  • 两个的区别在于 修改操作的 关联 不可以用括号
    // 调用 user 中的 address 关联 使用 save方法添加数据
    $user->address()->save($dataArray);
    // 调用 user 中的 address 关联 使用 save方法修改数据
    $user->address->save($dataArray);

第十章

前置操作

  • 在我们编写 api 业务逻辑的时候,我们会想在调用 api 接口之前,需要满足某些条件
  • 这样才可以去访问我们的接口中的业务逻辑
  • 所以我们要在做一个前置操作,抵挡不满足条件的抛出异常
  1. tp5 中使用前置操作需要基础自带的一个基类 Controller
  2. 定义一个名为 $beforeActionList 的数组
use think\Controller

 class Address extends Controller
 {
     // 定义前置属性
     // 第一个字段是 访问api接口前 需要 访问的一个前置方法
     // 箭指的 是一个数组
     // 数组内部定义一个箭指数据,也可以直接是一个字符串(内部填入api接口函数就可以了)
     // 否则向下面这样写
     // 多api编写
     protected $beforeActionList = [
         'first' => ['only' => 'second,third']
     ];

     // 触发api前 执行的前置函数
     protected function first () {
         echo 'first';
     }

     // api接口
     public function second () {
         echo 'second';
     }

     // api接口
     public function third () {
         echo 'third';
     }
 }

重构前置验证操作 (实现面向对象)

  • 提取验证业务逻辑到 service 的基类中
  • 提取前置方法到 BaseController 的基类中
  • 继承基类,执行前置方法
  1. 提取出一个前置的基类 BaseController (继承内置 Controller)
use app\api\service\Token as TokenService;

// 继承
class BaseController extends Controller
{
 // 前置方法
 // 验证初级权限作用域,用户和cms都可以访问
 protected function checkPrimaryScope () {
     // 向Token调用验证方法
     TokenService::needPrimaryScope();
 }

 // 验证权限,只有用户可以访问,cms无法访问
 protected function checkExclusiveScope () {
     TokenService::needExclusiveScope();
 }
}
  1. 提取验证业务逻辑(因为是 token 相关的就归并到 token 的 service 业务层中)
 // 重构前置方法,验证权限
 // 用户和cms管理员都可以访问的权限
 public static function needPrimaryScope () {
     // 调用token中的方法获取scope
     $scope = self::getCurrentTokenVar('scope');
     // 判断是否存在
     if ($scope) {
         // 判断 scope的权限大小
         if ($scope >= ScopeEnum::User) {
             return true;
         } else {
             throw new ForbiddenException();
         }
     } else {
         throw new TokenException();
     }
 }
  1. 继承 BaseController 基类使用前置方法
         // 继承基类
class Address extends BaseController
{
 // 调用前置的方法
 protected $beforeActionList = [
     // 前置验证的方法名                   需要前置验证的函数
     'checkPrimaryScope' => ['only' => 'createOrUpdateAddress']
 ];

 /*
  * @url api/v1/address
  */
 public function createOrUpdateAddress () {

 }
}

验证器数据自定义子项验证

  • 自定义子项验证,通过自定义的方法调用实现
  • 当我们在验证时,传入的是一个二维数组,就可以使用来验证子项
  • 我们就自定义一个验证的方法,通过基类的验证的调用

 // 整体验证
 protected $rule = [
   'products' => 'checkProducts'
 ];

 // 数据子项的验证
 protected $singleRule = [
     'product_id' => 'require|isPositiveInteger',
     'count' => 'require|isPositiveInteger'
 ];

 /*
  * 自定义整体验证
  */
 protected function checkProducts ($values) {
     // 验证是不是数组
     if (!is_array($values)) {
         throw new ParameterException([
             'msg' => '商品参数不正确'
         ]);
     }

     // 验证不为空
     if (empty($values)) {
         throw new ParameterException([
             'msg' => '商品列表不能为空'
         ]);
     }

     // 循环对每一项进行验证
     foreach ($values as $value) {
         $this->checkProduct($value);
     }

     return true;
 }

 // 基础调用子项验证
 protected function checkProduct ($value) {
     $validate = new BaseValidate($this->singleRule);
     $result = $validate->check($value);
     if (!$result) {
         throw new ParameterException([
             'msg' => '商品参数不正确'
         ]);
     }
 }

自动添加时间戳(TP5 内置添加时间戳)

  • 在我们的操作中,我们的数据中会带有数据,tp5 为我们提供了自动添加时间戳
  1. 找到自己要添加的时间戳的模型 我是在 order 添加那我就去 orde 人的模型中
  2. $autoWriteTimestamp 添加为 true,需要是模型的方式才可以使用的
  3. 创建 修改 删除
  4. 默认为 create_time update_time delete_time
  5. 修改方法名 在模型下修改
    // 自动写入时间戳
    protected $autoWriteTimestamp = true;
    // 修改字段名
    //         内置名称           自定义修改的名称
    protected $createTime = 'create_timestamp';

Tp5 事务应用

  • 在我们的应用中可能会出现分步的操作,可能会本地与服务端出现不一致
  • 所以我们使用事务来做处理
  • 在中间出现错误就会把数据回滚保持数据的一致性
 // 开头加入开始
     Db::startTrans();
     try {
         $orderNo = $this->makeOrderNo();
         $order = new \app\api\model\Order();
         $order->user_id = $this->uid;
         $order->order_no = $orderNo;
         $order->total_price = $snap['orderPrice'];
         $order->total_count = $snap['totalCount'];
         $order->snap_img = $snap['snapImg'];
         $order->snap_name = $snap['snapName'];
         $order->snap_address = $snap['snapAddress'];
         $order->snap_items = json_encode($snap['pStatus']);

         $order->save();

         $orderID = $order->id;
         $create_time = $order->create_time;

         foreach ($this->oProducts as &$p) {
             $p['order_id'] = $orderID;
         }

         $orderProduct = new OrderProduct();
         $orderProduct->saveAll($this->oProducts);

         // 结尾加上结束
         Db::commit();

         return [
             'order_no' => $orderNo,
             'order_id' => $orderID,
             'create_time' => $create_time
         ];
     } catch (Exception $ex) {
         // 异常出现回滚
         Db::rollback();
         throw $ex;
     }

引入没有命名空间的文件与调用(Loader),手动引入微信支付 php

  • 使用 loader 的 import 方法
  • extend/WxPay/WePay.Api.php
    //         文件开头的第一个  文件路径       // 类的名称
Loader::import('WxPay.WxPay',EXTEND_PATH,'.Api.php');
// 调用
// 调用的时候前面要加反斜杠
$wxOrderData = new \WxPayUnifiedOrder();

TP5 模型实现数据减少 setDec

                                            // 前面是查询  第一个数是写要改变的字段  第二个是要减少的数量
Product::where('id','=',$singlePStatus['id'])->setDec('stock',$singlePStatus['count']);

数据库锁与事务锁的区别

  • 数据库模型->lock(true)
  • 事务锁 Db
  1. 事务锁是等待整个事务提交才会执行第二次事务,但是数据库模型锁只是单步的锁着了数据库查询语句
  2. 在后面的操作还没有执行时,数据库模型锁已经放开了

外部网址使用

  • 要从根目录一直到 index.php
  • 后面才是路由
  • www.yhf7/zerg/public/index.php/api/v1/pay/notify

模型分页查询(paginate)

  • 第一个参数是分类数
  • 第二个数是否简洁模式
  • 第三个是数组填入分页数

 public static function getSummaryByUser ($uid,$page=1,$size=15) {
     $paginData = self::where('user_id','=',$uid)->order('create_time desc')->paginate($size,true,['page' => $page]);
     return $paginData;
 }

后记

  • 这是学习微信小程序开发后端PHP时候的笔记,欢迎更多的同行大哥指导交流
  • 欢迎进入我的博客:https://yhf7.github.io/
  • 如果有什么侵权的话,请及时添加小编微信以及qq也可以来告诉小编(905477376微信qq通用),谢谢!