微信支付
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表中存储快照信息: