微信小程式非開放介面開發,token使用
前言:
之前介紹了些小程式使用者登入獲取伺服器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('儲存地址失敗'); } }
那麼今天的非開放介面就介紹到這裡了。有哪些不對的地方,希望大神能夠指正,我共同學習。
以上