前言:
之前介紹了些小程序用戶登錄獲取服務器token〕氩洌現在來介紹下用戶拿到token后請求一些權限接口的時候怎么,我們服務器端應該如何處理鳍烁。今天就用一個商城里非常常見的地址添加接口來舉例叨襟。
目錄:
- 思路圖
- token的接收和使用
- 接受地址數據,過濾地址數據
- 判斷是新增還是修改幔荒,保存數據
一:思路圖
二: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了
三:接受地址數據,過濾地址數據
這里的接受數據過濾數據唉工,其實主要是關于安全方面的考慮研乒。因為我們要將用戶發(fā)來的數據直接存入數據庫是非常危險的。現在我們將接收數據淋硝,驗證數據告嘲,過濾數據〗钡兀總結到一起。方便今后使用赋焕。這里介紹的也是一個非常通用的方法参歹。
大家一定知道在接收數據后我們都會使用驗證器來驗證這些數據。
AddressValidate::instance()->goCheck();
就像這樣隆判,就是我之前寫過的獨立驗證器犬庇。驗證如果不符合規(guī)則就會拋出異常。那么驗證通過的數據就一定安全嗎侨嘀?我們現在來看看我的驗證規(guī)則
因為用戶id我們從token對應的值中取了臭挽,所以我們不會從客戶端傳遞的數據中獲取用戶id也不會信任用戶傳遞的數據的。
那么我如果驗證通過我就去取得用戶傳遞的所有數據這樣安全嗎咬腕?不安全欢峰。一個不起眼的小細節(jié)就在這里。首先我們的確不從客戶端取用戶id涨共,也不取驗證用戶id纽帖。但是我們不能保證客戶端不會傳用戶id呢。如果他傳了所有的地址數據举反,通過驗證懊直,還多傳了一個用戶id怎么辦。所以我們不能全盤接收用戶傳遞的數據火鼻∈夷遥可是我也不想一個字段一個字段的去接收。那么就要寫個通用的方法
這個通用的方法我就寫到驗證器的基類里中的goCheck方法中魁索,因為每次接收用戶數據融撞,都會去驗證的,也就會去調用goCheck方法蛾默,那么我們就寫在這里面讓它功能更強大.
先說下這個過濾的思路懦铺。我們在寫驗證器的時候是根據我們要接收哪些字段我們就會驗證哪些字段對吧。那么我們要過濾出來的字段也就是我們驗證規(guī)則數組中的key支鸡,不知道大家注意到了沒有
那么我們只需將$rule中的key值作為過濾依據冬念,驗證規(guī)則中有的我們就保留趁窃,驗證規(guī)則之外的不保留
/**
* 根據驗證器來獲取客戶端傳遞的信息
* @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 = [];
//遍歷驗證規(guī)則數組
foreach ($this->rule as $key => $value) {
//規(guī)則中有的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對象麦锯,就是我創(chuàng)建的UserAddress類的對象。
我的理解是琅绅,當關聯地址表中沒有這個用戶的數據扶欣,返回的hasOne中的data屬性就會是個空數組。來判斷數據是否存在。我們就可以調用關聯對象也就是(hasOne)對象上的save方法料祠。這里是創(chuàng)建一個關聯上user的數據對象的意思骆捧。
當返回的關聯對象含有數據,那么使用user模型上的address屬性髓绽,address屬性上存儲的其實就是UserAddress模型對象敛苇,我們打印出來也印證了這個觀點。那么既然是模型對象model的子類顺呕,那么直接使用model中最常用的save方法枫攀,將數據存入,就可以完成修改了株茶。
這里比較繞哈来涨,雖然可以用模型直接新增,傳入用戶id的方法完成這個業(yè)務启盛,但是這個方法我之前沒有怎么用過扫夜,還是給大家介紹下一個新的思路。
下面我附上完整的控制器代碼
/**
* 添加或者更新地址
* @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('保存地址失敗');
}
}
那么今天的非開放接口就介紹到這里了棍厂。有哪些不對的地方,希望大神能夠指正超陆,我共同學習牺弹。
以上