在yii里的restful实践总结

1.金融类型设计和计算

2.用户表、认证表设计

3.Yii里管理Api代码

4.Yii的restful配置

1.金融类型设计和计算

因为涉及金融产品计算,所以使用decimal数据类型,如果是简单的余额精度没必要这么高了,另外,如果是单一货币——如RMB,可以使用整型,单位是分,在显示的时候除100,接收的适合乘100,整型的好处是方便计算(加减)。

decimal(19,4) 余额

decimal(10,4) 利息

decimal(10,6) 利率

在PHP里进行浮点计算为了进一步精确,采用了PHP BC函数来进行精度计算,值得注意的是在进行惩罚和除法计算的时候,一定要提前指定精度,指定精度用bcscale(6)函数,此处是指定小位数位为6位。另外注意,BC函数是用字符串进行计算,结果也是字符串,所以还需要用doubleval()转换成浮点型。

http://php.net/manual/zh/ref.bc.php

  • bcadd — 2个任意精度数字的加法计算
  • bccomp — 比较两个任意精度的数字
  • bcdiv — 2个任意精度的数字除法计算
  • bcmod — 对一个任意精度数字取模
  • bcmul — 2个任意精度数字乘法计算
  • bcpow — 任意精度数字的乘方
  • bcpowmod — Raise an arbitrary precision number to another, reduced by a specified modulus
  • bcscale — 设置所有bc数学函数的默认小数点保留位数
  • bcsqrt — 任意精度数字的二次方根
  • bcsub — 2个任意精度数字的减法

2.用户表、认证表设计

db

user表存用户基本信息,用户表里有个uid的字段,用来在公开地方作为用户唯一标识,防止通过ID爆破等。

authentication表存存登录凭证,因为现在应用会采用很多的登录方式(邮箱、手机号、用户名)以及第三方登录方式(微信、QQ、新浪)等。

identity_type: 登录类型,枚举值,每个值对应一个登录类型。包括重置密码的token。

identifier: 登录用户名,如手机号、邮箱、微信openid等。

credential:登录凭证,如密码、第三方的token等。

可以发现,这种方式,邮箱、手机号、用户名等登录方式分别对应了一条密码,如果需要统一,需要在模型层进行统一控制。

session表对应登录的会话管理,每次成功登录需要生成一个系统的token。

login_log记录每一次登录的日志,可以用来做登录控制,如错误次数等。

这种设计方式的好处是,在restful设计的时候,可以讲登录相关的业务抽象成如下操作:

1.POST /user  注册

2.PATCH /user 更新用户信息

3.POST /authentication 绑定登录方式

4.PATCH /authentication 更换手机号等

5.DELETE /authentication 解绑登录方式

6.POST /session 登录

7.PATCH /session 刷新token

8.DELETE /session 登出

3.Yii里管理Api代码

在Yii框架里管理Api代码(即怎么创建Api文件、怎么管理代码版本号)有两个思路:

一是在控制器controllers下建立api文件夹,在api里在根据版本号建立文件夹,最终就像controllers/api/v1_0/UserController.php这样,这样的好处是简单方便,url访问就是r=api/v1_0/user这样。不方便的地方是这只是控制器的一个扩展,如果api模型和web模型不通用的话就比较麻烦了。

二是使用模块,模块的好处是每个独立模块里面是独立的MVC结构,这样的结构可以是modules/api/v1_0/controllers/UserController.php,url访问就是r=api/v1_0/user,跟上面是一样的。api如果对模型有定制化需求的话,比如不同版本的api的模型有变化,则此种方式比较适用。

总结来说,如果是小型项目,可以使用第一种,管理起来更方便。如果是中大型项目,建议用第二种,以后扩展起来更方便。

4.Yii的restful配置

Yii Framework提供了restful模块,\yii\rest\ActiveController包含了对模型的增删查改操作,具体方法在\vendor\yiisoft\yii\rest\下面有

CreateAction.php

DeleteAction.php

IndexAction.php

OptionsAction.php

UpdateAction.php

ViewAction.php。

文档: 官方  中文

接入步骤:

1.在config下建立api.php配置文件,主要用于接口专有配置,附上参考代码

<?php
$config = [
    'components' => [
        'request' => [
            'parsers' => [
                'multipart/form-data' => 'yii\web\MultipartFormDataParser',
                'application/json' => 'yii\web\JsonParser',
            ],
        ],
        'response' => [
            'format' =>  \yii\web\Response::FORMAT_JSON,
            'class' => 'yii\web\Response',
        ],
        'user' => [
            'identityClass'   => 'app\models\ApiIdentity',
            'enableAutoLogin' => false,
            'enableSession'   => false,
            'loginUrl'        =>null,
        ],
        'urlManager' => [
            'enablePrettyUrl'     => true,
            'enableStrictParsing' => false,
            'showScriptName'      => false,
            'rules' => [
                [
                    'class' => 'yii\rest\UrlRule',
                    'controller' => [
                        'api/v1_0/user',
                        'api/v1_0/authentication',
                        'api/v1_0/session',
                        'api/v1_0/verification',
                        'api/v1_0/project',
                        'api/v1_0/notice',
                        'api/v1_0/message',
                    ],
                    'tokens'=>[
                        '{id}'    => '<id:\\d[\\d,]*>', //default
                        '{uid}'   => '<uid:\\w{32}>',
                        '{token}' => '<token:[\\w-_]{128}>',
                    ],
                    'extraPatterns'=>[
                        'PUT,PATCH {uid}'                  => 'update',
                        'GET,HEAD {uid}'                   => 'view',
                        'GET,HEAD {id}/projects'           => 'projects',
                        'GET,HEAD received'                => 'received',
                        'GET,HEAD sent'                    => 'sent',
                        'GET,HEAD {id}/comments'           => 'comments',
                        'GET,HEAD {id}/replies'            => 'replies',
                        'POST {id}/vote'                   => 'vote',
                    ]
                ],
            ],
        ]
    ],
];

return $config;

每新增一个控制器,需要在配置rules里声明。request定义了接收数据的解析器,response定义了输出数据的格式。

2.在web下建立api.php,复制index.php内容到api.php里,并加上上门的配置文件

<?php

// comment out the following two lines when deployed to production
defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'dev');

require(__DIR__ . '/../vendor/autoload.php');
require(__DIR__ . '/../vendor/yiisoft/yii2/Yii.php');

$config = yii\helpers\ArrayHelper::merge(
    require(__DIR__ . '/../config/web.php'),
    require(__DIR__ . '/../config/api.php')
);

(new yii\web\Application($config))->run();

3.建立一个接口控制器父类如RestController.php,继承\yii\rest\ActiveController,所有的接口都继承RestController,这样可以在RestController里进行一些定制化的内容而不用修改框架代码。

<?php

namespace app\modules\api\controllers;

use \app\models\ApiIdentity;
use \yii\filters\auth\CompositeAuth;
use \yii\filters\auth\HttpBasicAuth;
use \app\components\AccessFilter;

class RestController extends \yii\rest\ActiveController
{
    /**
     * 数据级别的访问控制
     * 
     * @param string $action the ID of the action to be executed
     * @param object $model the model to be accessed. If null, it means no specific model is being accessed.
     * @param array $params additional parameters
     * @throws ForbiddenHttpException if the user does not have access
     */
    public function checkAccess($action, $model = null, $params = [])
    {
        if($model==null) $model=$this->modelClass;
        $modelName=(new \ReflectionClass($model))->getShortName();
        $checkParams = $model ? [$modelName=>$model] : null;
        if(!\Yii::$app->user->can($action.$modelName,$checkParams)) {
            throw new \yii\web\ForbiddenHttpException("Forbidden $action the $modelName");
        }   
    }

    public function behaviors()
    {
        $behaviors = parent::behaviors();
        $behaviors['authenticator'] = [
            'class' => CompositeAuth::className(),
            'authMethods' => [
                [
                    'class'=>HttpBasicAuth::className(),
                    //'auth'=>[ApiIdentity::className(),'validateUidAndToken']
                    'auth'=>[ApiIdentity::className(),'findIdentityByAccessToken']
                ]                
            ],
        ];
        /*
        $behaviors['access']=[
            'class'=>AccessFilter::className(),
            'level'=>AccessFilter::LEVEL_NONE,//模型级别访问控制
            'model'=>$this->modelClass,
        ];
        */
        return $behaviors;
    }
}

checkAccess方法是用来做访问控制,rest默认的增删查改会调用checkAccess来判断是否有操作当前模型的权限。

behaviors里定义了一些行为,在我的代码里定义了一个认证类,用的BasicAuth认证

4.创建接口控制器,继承上门的基类

<?php

namespace app\controllers\api\v1_0;

use \yii;
use \yii\db\Exception;
use \app\models\SignupForm;
use \app\models\User;

class UserController extends \app\modules\api\controllers\RestController
{
    public $modelClass = 'app\models\User';

    public function actions()
    {
        $actions=parent::actions();
        unset($actions['create']);//禁用默认的ceate操作
        return $actions;
    }

    public function behaviors()
    {
        $behaviors=parent::behaviors();
        //unset($behaviors['authenticator']);//不做登录验证
        //unset($behaviors['access']);
        $behaviors['authenticator']['optional']=['create'];
        //$behaviors['access']['except']=['create'];//create操作不做访问控制
        return $behaviors;
    }

    public function actionCreate()
    {
        $data  = \yii::$app->getRequest()->getBodyParams();
        $model = new SignupForm;
        if($model->load($data,'') && $model->signup()){
            $response = \Yii::$app->getResponse();
            $response->setStatusCode(201);
            $user = $model->getUser();
            $response->getHeaders()->set('Location', \yii\helpers\Url::toRoute(['view', 'uid' => $user->uid], true));
            return $user;
        }elseif (!$model->hasErrors()) {
            throw new \yii\web\ServerErrorHttpException('Failed to create the object for unknown reason.');
        }
        return $model;
    }
}

在\yii\rest\ActiveController里的actions方法里引用了create update delete view options等操作,所以在此处可以通过unset来控制默认的restful操作。

在behaviors里可以通过$behaviors[‘authenticator’][‘optional’]来控制是否要求当前操作认证。

因为注册操作涉及的模型不只User一个,所以需要重写create操作。

发表评论

电子邮件地址不会被公开。 必填项已用*标注