ショッピングモールAPI開発の注文インタフェース


前言:
一つのショッピングモールの中で最も複雑な業務は何なのか、みんな自分の見方を持っているかもしれませんが、私から見れば注文が最も複雑で慎重にしなければならないところです.今日は私の注文インタフェースを紹介します.自分で整理してあげることもできます.まず需要を説明する必要がある.
私の注文
オーダー詳細
私のニーズは、注文の生成と同時に、注文のスナップショットを生成し、注文の注文時の注文情報を保持することです.その後、商品の改名や値下げなどの変化があっても、注文のデータには影響しません.
では、今回の注文インタフェースの考え方を整理してみましょう.
単一インタフェースプロセス
  • クライアントから渡された商品と数量のデータ
  • を受信する.
  • 検証データ
  • 在庫チェック
  • 在庫が十分であれば、注文および注文スナップショット
  • を作成します.
    これは比較的大まかな考え方で、私たちは一歩一歩これらのビジネスロジックを整理します.
    1.データを受信して検証する
    私が前に書いた検証器を見たことがある友达は、私のプロジェクトで使用されている独立した検証器を知っているに違いありません.ただし,今回検証したデータは比較的異なり,2次元配列である.クライアントから渡されたデータ構造を見てみましょう
    products=>[
            ['product_id'=>1,'count'=>5],
            ['product_id'=>3,'count'=>2]
    ];
    

    今回クライアントから渡されたのは実際には2次元配列であり、その中のproductであることを検証しています.idとcount.では、このような検証器はどのように書くのでしょうか.
    次にコードを見て、まず2つの検証ルールを書きます.この名前はruleというメンバー変数がcheckの時に自動的に導入されるので、名前を変えることはできません.singleRuleについては、私が考えている名前です.私たち自身に導入されているので、どんな名前を呼んでも構いません.
        protected $singleRule = [
            'product_id' => 'require|positiveInt',
            'count' => 'require|positiveInt'
        ];
        protected $rule = [
            'products' => 'require|checkProducts'
        ];
    

    まず、コントローラが私が前に書いた汎用検証方法を呼び出すと、ruleに従ってproductsという2次元配列を検証します.検証ルールは2つあります.1つはrequireです.私は紹介しません.もう1つは私が書いた自己検証方法checkProductsです.
        protected function checkProducts($value)
        {
            if (!is_array($value)) {
                throw new ParameterException(['message' => 'products must be array']);
            }
            foreach ($value as $v) {
                $this->checkProduct($v);
            }
            return true;
        }
    

    ベリファイアはproductsの2次元配列を私たちが書いたカスタムメソッドに転送します.まず,クライアントが配列ではない場合を排除し,データ構造が正しくなければパラメータエラーの異常を直接投げ出す.次にforeachを用いて配列を遍歴し,遍歴後の1次元配列$vの構造は
    ['product_id'=>1,'count'=>5]
    

    この時私はまたこの1次元配列をcheckProductという方法に伝えました.この名前を見て、ある商品を単独で検証する検証方法だと思います.
        protected function checkProduct($v)
        {
            //    new BaseValidate ?          singleRule       ,          BaseValidate   
            $validateObj = new BaseValidate($this->singleRule);
            $result = $validateObj->batch()->check($v);
            if (!$result) {
                throw new ParameterException(['msg'=>$validateObj->getError()]);
            }
            return true;
        }
    

    ここでは,ベリファイアの別の使い方を用いて,直接newのベリファイアを用いて,ルールを伝達する.このオブジェクト上のcheckメソッドを呼び出して検証します.このルールは、私たちが前に書いたメンバー変数singleRuleです.(ここでnewは私たち自身が書いたbaseValidateクラスです.主な原因は私たちが検証ルールにカスタムの検証ルール【positiveInt】を書いて正の整数を検証しているからです.カスタム検証ルールを使用しなければ、new Validateクラスも可能です)
    では、ここで検証器を書き終わります.コントローラと他のインタフェースと同じように、注文検証器オブジェクトを使用してgoCheckメソッドを呼び出すだけでいいです.一気に完成します.
    2.在庫検査
    これが今日のポイントですが、なぜポイントなのかというと、在庫をチェックするのは注文時だけでなく、支払い時にも使うからです.まず複雑な論理で、私は一般的にサービス層にカプセル化します.明らかに注文したサービスクラスが必要です.では、このクラスではまず3つのメンバー変数を作成します.
    
    class Order
    {
        protected $oProducts;//           ,o  order
        protected $products;//       id,            
        protected $uid;//   id
    }
    

    なぜこのメンバー変数を定義するのですか?まず、私たちの最も重要な在庫検査は、実は、注文商品の数量がデータベースの対応する商品在庫データと比較されているだけです.では、$oProductsと$productsの2つのデータを保存します.メソッド間で繰り返し伝達することなく、より直感的で呼び出しやすくなります.
    では、私たちは今直接OrderService層の注文方法を見て、私の習慣は1つのサービス層で、1つの対外公開の方法しか提供していません.できるだけこの方法を複数の私有の方法を抽象化して、論理をもっとはっきりさせて、もっと立体的にします.
        public function place($uid, $oProducts)
        {
            $this->oProducts = $oProducts;
            $this->uid = $uid;
            $this->products = $this->getProductByOrder($oProducts);
            //         
            $status = $this->getOrderStatus();
            //         
            if (!$status['pass']) {
                //             ,      order_id
                $status['order_id'] = -1;
                //           ,       
                return $status;
            }
            //    ,    
            //         ,        
            $snap = $this->snapOrder($status);
            //        ,         
            $result = $this->createOrder($snap);
            $result['pass'] = true;
            return $result;
        }
    

    これらの方法がどのように実現されているかを焦らずに、まずこの注文方法の流れを見てみましょう.
    発注方法フロー
    1.まず最初に見て、注文商品からデータベース商品データを取得する方法
        /**
         *       (    )
         * @param $oProducts
         * @return mixed
         * @throws \think\exception\DbException
         */
        private function getProductByOrder($oProducts)
        {
            //          id (                )
            $oPids = array_column($oProducts, 'product_id');
            // ids        
            $products = Product::all($oPids)
                //        
                ->visible(['id', 'name', 'price', 'main_img_url', 'stock']);
            //               ,    collection     ,    toArray    
            return $products->toArray();
        }
    

    2.注文データとデータベース商品の比較方法(つまり在庫検査方法)
        /**
         *              ,       
         * @return array
         * @throws OrderException
         */
        private function getOrderStatus()
        {
            //             
            $status = [
                //    
                'pass' => true,
                //     
                'orderPrice' => 0,
                //      
                'orderCount' => 0,
                //        (        )
                'pStatusArray' => []
            ];
            //        ,       $oProduct
            foreach ($this->oProducts as $oProduct) {
                //              ,                           
                $pStatus = $this->getProductStatus($oProduct['product_id'], $oProduct['count'], $this->products);
                //              ,     pass      false
                if ($pStatus['haveStock'] == false) {
                    $status['pass'] = false;
                }
                //       
                $status['orderPrice'] += $pStatus['totalPrice'];
                //         
                $status['orderCount'] += $oProduct['count'];
                //                    
                array_push($status['pStatusArray'], $pStatus);
            }
            return $status;
        }
    

    3.個別商品在庫検査
    実はこの方法はデータの上で1つの在庫のサブメソッドを検出するべきで、私達が注文を提出する時、注文の中で多種の商品があるかもしれなくて、それでは私達は在庫を検出して、すべての商品を単独で引き出して在庫を検出しなければならなくて、もし注文の中のすべての商品が在庫があるならばやっと注文の在庫が十分であると計算します
        /**
         *           
         * @param $oPid
         * @param $oCount
         * @param $products
         * @return array
         * @throws OrderException
         */
        private function getProductStatus($oPid, $oCount, $products)
        {
            //        
            $pStatus = [
                //     id
                'id' => 0,
                //       
                'name' => '',
                //       
                'count' => 0,
                //           bool
                'haveStock' => false,
                //              *  
                'totalPrice' => 0
            ];
            //     (        ,                        )
            $pIndex = -1;
            //                
            for ($i = 0; $i < count($products); $i++) {
                //   id             id   
                if ($oPid == $products[$i]['id']) {
                    //               
                    $pIndex = $i;
                }
            }
            if ($pIndex == -1) {
                //      id      all               products  ,             ,  
                //products            , for         。  $pIndex       ,     -1
                throw new OrderException(
                    ['msg' => '  ' . $oPid . '       ']
                );
            }
    
            //                  
            $pStatus['id'] = $oPid;
            $pStatus['name'] = $products[$pIndex]['name'];
            $pStatus['count'] = $oCount;
            //                    ,     true,  false
            $pStatus['haveStock'] = $oCount <= $products[$pIndex]['stock'] ? true : false;
            //        
            $pStatus['totalPrice'] = $oCount * $products[$pIndex]['price'];
            return $pStatus;
        }
    

    4.商品在庫検査に合格した場合、注文スナップショットを作成する必要があります.
    この注文のスナップショットは何に使いますか?注文書が注文されたとき、商品の情報を記録して、これを保存して、お客様が自分の購入記録をめくることができます.また、これらの注文データは、注文時に完全に保存するデータベースに保存される.これは,後に購入した商品の値下げや改名など,注文スナップショットの内容に影響を及ぼさない注意下の注文詳細データやユーザのアドレスが,シーケンス化配列の形で格納されていることを意味する.
        /**
         *       
         * @param $status
         * @return array
         * @throws
         */
        private function snapOrder($status)
        {
            //         
            $snap = [
                //     
                'total_price' => 0,
                //       
                'total_count' => 0,
                //        
                'snap_name' => null,
                //        
                'snap_img' => '',
                //         
                'snap_item' => [],
                //      
                'snap_address' => []
            ];
            //             
            $snap['total_price'] = $status['orderPrice'];
            //             
            $snap['total_count'] = $status['orderCount'];
            //                        
            $snap['snap_name'] = $this->products[0]['name'];
            //  ,      url
            $snap['snap_img'] = $this->products[0]['main_img_url'];
            //  uid       
            $snap['snap_address'] = serialize($this->getUserAddress());
            //                
            $snap['snap_items'] = serialize($status['pStatusArray']);
            //       ,        1       .    
            $snap['total_count'] > 1 && $snap['snap_name'] .= ' ';
            return $snap;
        }
    

    4.1ユーザアドレスの取得方法
    まず、これは簡単なビジネスロジックです.ユーザーがアドレスデータを持っていない場合は、注文をさせないべきです.取得も簡単ですが、ユーザーIDで表裏を調べるだけです
        /**
         *      id          
         * @return array
         * @throws
         */
        private function getUserAddress()
        {
            $userAddress = UserAddress::where(['user_id' => $this->uid])->find();
            if (!$userAddress) {
                throw new UserException([
                    'msg' => 'UserAddress is not found place order is fail',
                    'errCode' => 80001
                ]);
            }
            return $userAddress->toArray();
        }
    

    5.オーダー保存
    オーダーの保存は2つのステップで行います.
  • オーダーマスターテーブルのデータ
  • を保存
  • サブオーダーのデータを保存するため、ここではトランザクションを使用してデータの整合性を保証します.
           /**
         *           
         * @param $nsap
         * @return array
         * @throws
         */
        private function createOrder($nsap)
        {
            //    
            Db::startTrans();
            try {
                //     ,      
                $orderNo = makeOrderNo();
                $orderObj = new OrderModel();
                //   
                $orderObj->order_no = $orderNo;
                //  id
                $orderObj->user_id = $this->uid;
                //    
                $orderObj->total_price = $nsap['total_price'];
                //      
                $orderObj->snap_img = $nsap['snap_img'];
                //    ,    
                $orderObj->snap_address = $nsap['snap_address'];
                //    ,      
                $orderObj->snap_items = $nsap['snap_items'];
                //    ,       
                $orderObj->snap_name = $nsap['snap_name'];
                //    
                $orderObj->total_count = $nsap['total_count'];
                
                $orderObj->save();
                //       
                $orderId = $orderObj->id;
                $create_time = $orderObj->create_time;
                //          (                ,            )
                $orderProductObj = new OrderProduct();
                //     order_id    Oproducts 
                foreach ($this->oProducts as &$oProduct) {
                    $oProduct['order_id'] = $orderId;
                }
                //        
                $orderProductObj->saveAll($this->oProducts);
                //    
                Db::commit();
                return [
                    'order_no' => $orderNo,
                    'order_id' => $orderId,
                    'create_time' => $create_time
                ];
            } catch (Exception $ex) {
                //    
                Db::rollback();
                throw $ex;
            }
    
        }
    

    コントローラの呼び出し
    すべての業務をパッケージ化すると、コントローラが楽になります
        /**
         *     
         * @url http://local.jxshop.com/api/v1/order/place
         * @http POST
         */
        public function placeOrder()
        {
            OrderPlace::instance()->goCheck();
            $uid = TokenService::getCurrentId();
            //              /a
            $oProducts = input('post.products/a');
            $orderServiceObj = new OrderService();
            $result = $orderServiceObj->place($uid, $oProducts);
            return $result;
        }
    

    まとめ:今回のコードは複雑で、このブログの可読性が足りないかもしれません.でも大丈夫です.私はどうせ私のこのインタフェースコードをオープンソースにしました.興味のある友人がいれば、完全なコードを直接クローンするとより多くの収穫が得られる可能性があります.私も友達が私の間違いを指摘してくれることをとても望んでいます.先にプロジェクトの住所を謝りました:コードクラウド
    以上