微信支付
Scope權限作用域的應用
1模擬枚舉
重構service/UserToken下的prepareCachedValue方法
//準備緩存中value的數據
private function prepareCachedValue($wxResult,$uid){
$cachedValue = $wxResult;
$cachedValue['uid'] = $uid;
//scope=16代表App用戶的權限數值
$cachedValue['scope']=ScopeEnum::User;//作用域 數字越大,權限越大
//scope = 32代表CMS用戶(管理員)的權限數值
// $cachedValue['scope']=32;
return $cachedValue;
}
前置方法
對Address接口做權限控制
controller/Address:
protected $beforeActionList = [
'checkPrimaryScope' => ['only' => 'createOrUpdateAddress']//關聯數組
];
protected function checkPrimaryScope(){
$scope =TokenService::getCurrentTokenVar('scope');//從緩存中獲取scope
if($scope){//判空
if($scope>=ScopeEnum::User){
return true;
}
else{
throw new ForbiddenException();//拋出異常就終止 不會再執行createOrUpdateAddress()
}
}
else{
throw new TokenException();
}
}
下單與支付的業務流程
class Order
{
//用戶在選擇商品後,向API提供包含他所選擇商品的相關信息
//API在接收到信息後,需要檢測訂單商品的庫存量(檢測的原因主要是客戶端和服務端信息不是實時同步)
//服務器如果檢測到有庫存,把訂單數據存入數據庫中=下單成功了,再返回客戶端消息,告訴客戶端可以支付了
//客戶端調用支付接口,進行支付
//還需要再次檢測商品的庫存量
//服務器就可以調用微信的支付接口進行支付
//微信會返回給我們一個支付的結果(異步調用)
//即使成功,也需要再進行一次庫存量檢測
//根據結果 成功。進行庫存量扣除。
//返回支付是否成功的結果(不是服務器返回,因爲是異步,無法實時同步,所以是微信會返回客戶端一個結果)
}
重構權限控制前置方法
面向對象編程:
將業務邏封裝到service/Token中
//需要用戶和CMS管理員都可以訪問的權限
public static function needPrimaryScope(){
$scope =self::getCurrentTokenVar('scope');//從緩存中獲取scope
if($scope){//判空
if($scope>=ScopeEnum::User){
return true;
}
else{
throw new ForbiddenException();//拋出異常就終止 不會再執行createOrUpdateAddress()
}
}
else{
throw new TokenException();
}
}
//只有用戶纔可以訪問的權限
public static function needExclusiveScope(){
$scope =self::getCurrentTokenVar('scope');//從緩存中獲取scope
if($scope){//判空
if($scope==ScopeEnum::User){
return true;
}
else{
throw new ForbiddenException();//拋出異常就終止 不會再執行createOrUpdateAddress()
}
}
else{
throw new TokenException();
}
}
創建控制器的基類BaseController:
class BaseController extends Controller
{
protected function checkPrimaryScope(){
TokenService::needPrimaryScope();
}
protected function checkExclusiveScope(){
TokenService::needExclusiveScope();
}
}
控制器Address和Order分別使用前置方法:
Address:
protected $beforeActionList = [//檢查權限 用戶以上級別都可以管理地址信息
'checkPrimaryScope' => ['only' => 'createOrUpdateAddress']//關聯數組
];
Order:
protected $beforeActionList = [//檢查權限 只有用戶纔可以下訂單
'checkExclusiveScope' => ['only' => 'placeOrder']
];
OrderPlace驗證器
客戶端傳入的下單商品數據結構如下:
OrderPlace(驗證二維數組的方法):
class OrderPlace extends BaseValidate
{
protected $rule = [//整個數組驗證規則
'products' => 'checkProducts'
];
protected $singleRule = [//每個子項的驗證規則(裏層數組)
'product_id' => 'require|isPositiveInteger',//下單商品id
'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;
}
private function checkProduct($value)
{
$validate = new BaseValidate($this->singleRule);//手動傳入自己定義的驗證規則
$result = $validate->check($value);
if(!$result){
throw new ParameterException([
'msg' => '商品列表參數錯誤',
]);
}
}
}
最後在controller/Order中調用
下單接口業務模型
service/Order:
<?php
namespace app\api\service;
use app\api\model\Product;
use app\lib\exception\OrderException;
class Order
{
//訂單的商品列表,也就是客戶端傳入的products參數
protected $oProducts;
//數據表中真實的商品信息(包括庫存量) 用前兩個變量做對比來檢測庫存量是否足夠
protected $products;
protected $uid;
public function place($uid, $oProducts)
{
//oProducts和products做對比
//products從數據庫查出來
$this->oProducts = $oProducts;
$this->products = $this->getProductsByOrder($oProducts);
$this->uid = $uid;
}
//根據訂單信息查找真實的商品信息(product_id)
private function getProductsByOrder($oProducts)
{
// foreach ($oProducts as $product){
// //循環地查詢數據庫 很容易搞崩數據庫 所以要避免循環
// }
$oPIDs = [];//存儲product_id
foreach ($oProducts as $item) {
array_push($oPIDs, $item['product_id']);
}
// 爲了避免循環查詢數據庫
$products = Product::all($oPIDs)
->visible(['id', 'price', 'stock', 'name', 'main_img_url'])
->toArray();//將默認的數據集轉爲數組 因爲$oProducts是數組 將$products轉爲數組可以更好地對比
return $products;
}
//做對比 數組對比 判斷Order裏所有Product狀態
private function getOrderStatus()//返回訂單的最終狀態
{
$status = [
'pass' => true,//庫存是否足夠 足夠則通過 某一個商品庫存不足就不通過
'orderPrice' => 0,//所有商品價格總和
'pStatusArray' => []//訂單詳情 查看歷史訂單用
];
foreach ($this->oProducts as $oProduct) {
$pStatus =
$this->getProductStatus(
$oProduct['product_id'], $oProduct['count'], $this->products);
if (!$pStatus['haveStock']) {
$status['pass'] = false;
}
$status['orderPrice'] += $pStatus['totalPrice'];
array_push($status['pStatusArray'], $pStatus);
}
return $status;
}
//判斷一個Product狀態
private function getProductStatus($oPID, $oCount, $products)
{
$pIndex = -1;
//保存訂單裏某一個商品的詳細信息 pStatusArray的子元素
$pStatus = [
'id' => null,
'haveStock' => false,
'count' => 0,
'name' => '',
//一類商品的總價:單價*數量
'totalPrice' => 0
];
//根據$oPID查詢products裏對應的序號(數組下標)
for ($i = 0; $i < count($products); $i++) {
if ($oPID == $products[$i]['id']) {
$pIndex = $i;
}
}
if ($pIndex == -1) {
// 客戶端傳遞的productid有可能根本不存在
throw new OrderException(
[
'msg' => 'id爲' . $oPID . '的商品不存在,訂單創建失敗'
]);
} else {
$product = $products[$pIndex];
$pStatus['id'] = $product['id'];
$pStatus['name'] = $product['name'];
$pStatus['count'] = $oCount;
$pStatus['totalPrice'] = $product['price'] * $oCount;
if ($product['stock'] - $oCount >= 0) {
$pStatus['haveStock'] = true;
}
}
return $pStatus;
}
}
訂單快照
記錄用戶的下單情況,不能動態存儲,要用快照思想,訂單已經完成就不能再更改參數了。
order表和order_product表是多對多關係。
order表中存儲快照信息: