前言:
之前介绍了些小程序用户登录获取服务器 token. 现在来介绍下用户拿到 token 后请求一些权限接口的时候怎么, 我们服务器端应该如何处理. 今天就用一个商城里非常常见的地址添加接口来举例.
目录:
思路图
token 的接收和使用
接受地址数据, 过滤地址数据
判断是新增还是修改, 保存数据
一: 思路图
token 的使用. PNG
二: token 的接收和使用
我们就从客户端传来 token, 地址数据, 开始说起.
首先作为非开放接口, 我们第一步当然就是接收 token, 如果没有 token, 就直接抛出异常.
那么, 我们约定, token 是在 http 请求中的 headr 传递过来. 那我们首先取出 token, 然后到服务器缓存中查找是否有相对于的 value 值. 如果没有说明 token 不存在或者过期了, 抛出异常.
首先我们在我们的 service 层下的 token 服务中构建一个通用的获取 token 对应储存的值的数据方法. 因为我们 token 对应的值中有好几个数据, 有用户的 id, 微信的 openid,session_key. 我们需要哪个字段的值, 就传入这个 key 就可以了.
- /**
- * 获取 token 对应的某个数据
- * @param $key
- */
- protected static function getCurrentTokenValue($key)
- {
- // 取出 token
- $token = request()->header('token');
- // 到缓存中获取
- $value = Cache::get($token);
- // 如果没有这条数据
- if (empty($value)) {
- // 抛出异常
- throw new TokenException();
- }
- }
如果缓存中有值, 我们因为在存储的时候是将数组序列话存储的. 那么我这里就将数据反序列化一下. 当然, 我还加入了一点容错机制, 因为我是直接使用 tp5 提供的文件缓存, 所以只能存储字符串. 如果今后我换了 Redis 或者别的缓存驱动, 可以存储数组或者对象什么的. 就不用序列化了. 所以我在反序列化之前, 先判断下是不是数组, 如果不是数组那就反序列化
- protected static function getCurrentTokenValue($key)
- {
- // 取出 token
- $token = request()->header('token');
- $value = Cache::get($token);
- if (empty($value)) {
- throw new TokenException();
- }
- // 判断是否是以数组形式储存的
- if (!is_array($value)) {
- $value = unserialize($value);
- }
- }
最后一步就很简单了, 根据传入的 key 值, 去找 $value 中有没有, 如果有就返回, 如果没有就抛出异常
下面是完整代码
- /**
- * 获取 token 对应的某个数据
- * @param $key
- * @return mixed
- * @throws TokenException
- * @throws Exception
- */
- protected static function getCurrentTokenValue($key)
- {
- // 取出 token
- $token = request()->header('token');
- $value = Cache::get($token);
- if (empty($value)) {
- throw new TokenException();
- }
- // 判断是否是以数组形式储存的
- if (!is_array($value)) {
- $value = unserialize($value);
- }
- if (array_key_exists($key, $value)) {
- return $value[$key];
- } else {
- throw new Exception('尝试请求的参数并不存在');
- }
- }
好了 写好这个通用的获取 token 对应的值的方法之后呢, 我们就需要想想我们到底需要这个 token 中的什么数据呢? 既然是地址添加接口, 那么就一定绕不开用户 id, 不然怎么知道是谁的地址呢? 那么我们就需要写一个获取当前用户 id 的方法
- /**
- * 获取当前用户的 id
- * @return mixed
- */
- public static function getCurrentId()
- {
- $id = self::getCurrentTokenValue('id');
- return $id;
- }
方法非常简单啊, 就是之前的通用方法的具体实现. 这样我们在控制器中使用这个 getCurrenId 方法就可以拿到用户的 id 了
三: 接受地址数据, 过滤地址数据
这里的接受数据过滤数据, 其实主要是关于安全方面的考虑. 因为我们要将用户发来的数据直接存入数据库是非常危险的. 现在我们将接收数据, 验证数据, 过滤数据. 总结到一起. 方便今后使用. 这里介绍的也是一个非常通用的方法.
大家一定知道在接收数据后我们都会使用验证器来验证这些数据.
AddressValidate::instance()->goCheck();
就像这样, 就是我之前写过的独立验证器. 验证如果不符合规则就会抛出异常. 那么验证通过的数据就一定安全吗? 我们现在来看看我的验证规则
验证规则
因为用户 id 我们从 token 对应的值中取了, 所以我们不会从客户端传递的数据中获取用户 id 也不会信任用户传递的数据的.
那么我如果验证通过我就去取得用户传递的所有数据这样安全吗? 不安全. 一个不起眼的小细节就在这里. 首先我们的确不从客户端取用户 id, 也不取验证用户 id. 但是我们不能保证客户端不会传用户 id 呢. 如果他传了所有的地址数据, 通过验证, 还多传了一个用户 id 怎么办. 所以我们不能全盘接收用户传递的数据. 可是我也不想一个字段一个字段的去接收. 那么就要写个通用的方法
这个通用的方法我就写到验证器的基类里中的 goCheck 方法中, 因为每次接收用户数据, 都会去验证的, 也就会去调用 goCheck 方法, 那么我们就写在这里面让它功能更强大.
先说下这个过滤的思路. 我们在写验证器的时候是根据我们要接收哪些字段我们就会验证哪些字段对吧. 那么我们要过滤出来的字段也就是我们验证规则数组中的 key, 不知道大家注意到了没有
验证规则中的 key
那么我们只需将 $rule 中的 key 值作为过滤依据, 验证规则中有的我们就保留, 验证规则之外的不保留
- /**
- * 根据验证器来获取客户端传递的信息
- * @param $arrays 传入接收的客户端所有数据
- * @return array
- * @throws Exception
- */
- protected function getDataByRule($arrays)
- {
- // 如果传递过来的数据中包含 user_id 这个字段的话, 那就十有八九这人是个黑帽子了
- if (array_key_exists('user_id', $arrays)) {
- throw new Exception('本接口不支持你这个 user_id 的参数, 别想了兄弟');
- }
- $newArr = [];
- // 遍历验证规则数组
- foreach ($this->rule as $key => $value) {
- // 规则中有的 key 值, 对应客户端的所有数据中的 key 值. 保存在 $newArrr 中
- $newArr[$key] = $arrays[$key];
- }
- return $newArr;
- }
这个方法写好之后, 我么说过要将它写到 goCheck 方法中, 让验证数据的同时, 过滤了数据. 我们就在验证成功后调用过滤方法. 不清楚这个验证器的使用的可以回顾一下
- /**
- * 获取传递参数, 并验证
- * @return bool|array
- * @throws ParameterException
- */
- public function goCheck()
- {
- // 接收参数
- $request = Request::instance();
- // 通过 param 方法获取到所有的参数
- $params = $request->param();
- // 由哪个对象来调用 goCheck 方法, 就是由哪个对象来调用 check 方法, 将接收的所有参数传递进去
- $result = $this->batch()->check($params);
- if (!$result) {
- // 如果结果为 false, 调用 getError 方法获取错误信息
- $error = $this->getError();
- // 抛出参数错误异常
- throw new ParameterException(['msg' => $error]);
- } else {
- // 调用获取过滤参数的方法, 返回给控制器
- return $this->getDataByRule($params);
- }
- }
那么现在, 只需要在控制器中拿个变量接收下 goCheck 方法的返回值就行了
$data = AddressValidate::instance()->goCheck();
四: 判断是新增还是修改, 保存数据
由于新增地址和修改地址的逻辑很类似 完全可以写在一个接口中.
之前通过 token 获取到对应的用户 id, 我们还需要为程序的健壮性, 再去查询下这个用户是否是存在, 是不是被我们删除了或者怎么了.
所有在控制器中调用模型上的 getUserById 方法
- // 验证用户是否存在
- $user = User::getUserById($id);
模型方法也很简单
- /**
- * 通过用户 id, 查询用户
- * @param $id
- * @return bool|null|static
- */
- public static function getUserById($id)
- {
- $result = self::get($id);
- if (empty($result)) {
- return false;
- } else {
- // 用户存在, 返回数据模型对象
- return $result;
- }
- }
顺便在 User 模型中将地址 UserAddress 关联起来(关联就不详细介绍了. 有时间单独写)
- // 一对一关联, 外键在外
- public function address()
- {
- return $this->hasOne('UserAddress','user_id','id');
- }
好了, 现在我们通过判断用户是否存在顺便也取到了用户的数据模型对象. 也关联好了地址表
下一步我们就判断地址是否存在, 然后保存数据就好了
- // 判断用户是新增还是修改
- $address = $user->address();
- // 返回关联的对象 hasOne 对象
- if (empty($address)) {
- // 调用 hasOne 对象上的保存 (新增) 当前关联数据对象方法
- $result = $address->save($data);
- } else {
- //address 属性中保存的是 UserAddress 数据对象, 是 model 的子类. 调用 model 上的 save 方法
- $result = $user->address->save($data);
- }
- // 如果保存成功
- if (!empty($result)) {
- // 返回一个操作成功的对象, 对象里包含成功的信息
- return new SuccessMessage();
- } else {
- // 抛出异常
- throw new Exception('保存地址失败');
- }
细节
值得注意的是这里, 新增和修改的 save 方法是不同的. 我也将 $user->address()和 $user->address 打印出来看了. 带括号的是 hasOne 对象也就我们关联的时候返回的那个对象. 不带括号的是 model 对象, 就是我创建的 UserAddress 类的对象.
我的理解是, 当关联地址表中没有这个用户的数据, 返回的 hasOne 中的 data 属性就会是个空数组. 来判断数据是否存在. 我们就可以调用关联对象也就是 (hasOne) 对象上的 save 方法. 这里是创建一个关联上 user 的数据对象的意思.
当返回的关联对象含有数据, 那么使用 user 模型上的 address 属性, address 属性上存储的其实就是 UserAddress 模型对象, 我们打印出来也印证了这个观点. 那么既然是模型对象 model 的子类, 那么直接使用 model 中最常用的 save 方法, 将数据存入, 就可以完成修改了.
这里比较绕哈, 虽然可以用模型直接新增, 传入用户 id 的方法完成这个业务, 但是这个方法我之前没有怎么用过, 还是给大家介绍下一个新的思路.
下面我附上完整的控制器代码
- /**
- * 添加或者更新地址
- * @url http://local.jxshop.com/api/v1/address/add
- * @url http://local.jxshop.com/api/v1/address/update
- * @http GET
- *
- */
- public function createOrUpdateAddress()
- {
- $data = AddressValidate::instance()->goCheck();
- // 验证 token 真实性
- $id = TokenService::getCurrentId();
- // 验证用户是否存在
- $user = User::getUserById($id);
- if (!$user) {
- throw new UserException();
- }
- // 判断用户是新增还是修改
- $address = $user->address();
- // 返回关联的对象 hasOne 对象
- if (empty($address)) {
- // 调用 hasOne 对象上的保存 (新增) 当前关联数据对象方法
- $result = $address->save($data);
- } else {
- //address 属性中保存的是 UserAddress 数据对象, 是 model 的子类. 调用 model 上的 save 方法
- $result = $user->address->save($data);
- }
- // 如果保存成功
- if (!empty($result)) {
- // 返回一个操作成功的对象, 对象里包含成功的信息
- return new SuccessMessage();
- } else {
- // 抛出异常
- throw new Exception('保存地址失败');
- }
- }
那么今天的非开放接口就介绍到这里了. 有哪些不对的地方, 希望大神能够指正, 我共同学习.
以上
来源: http://www.jianshu.com/p/7c87592f8029