From aadcf6c6613a1f9a81d3fb99b217b39deb247090 Mon Sep 17 00:00:00 2001 From: ctexthuang Date: Thu, 23 Jan 2025 16:56:24 +0800 Subject: [PATCH] feat: place order --- app/Cache/Redis/Api/SiteCache.php | 67 +++++++ app/Cache/Redis/Common/CommonRedisKey.php | 16 ++ app/Constants/ApiCode.php | 2 +- app/Controller/Api/OrderController.php | 8 + app/Exception/Handler/ErrExceptionHandler.php | 1 - app/Model/Site.php | 13 +- app/Service/Api/Order/BaseOrderService.php | 158 +++++++++++++++ app/Service/Api/Order/CheckCartService.php | 102 +--------- .../Api/Order/ConfirmationOrderService.php | 34 ++++ app/Service/Api/Order/PlaceOrderService.php | 25 ++- app/Service/ServiceTrait/Api/OrderTrait.php | 189 +++++++++++++++++- 11 files changed, 504 insertions(+), 111 deletions(-) create mode 100644 app/Cache/Redis/Api/SiteCache.php create mode 100644 app/Service/Api/Order/BaseOrderService.php create mode 100644 app/Service/Api/Order/ConfirmationOrderService.php diff --git a/app/Cache/Redis/Api/SiteCache.php b/app/Cache/Redis/Api/SiteCache.php new file mode 100644 index 0000000..4f705a3 --- /dev/null +++ b/app/Cache/Redis/Api/SiteCache.php @@ -0,0 +1,67 @@ +redis->exists($siteKey) && $this->redis->exists($LngLatKey)) return; + + $siteList = $this->siteModel->getAllSiteList(); + + $this->redis->delete($siteKey); + $this->redis->delete($LngLatKey); + + foreach ($siteList as $site) { + $this->redis->geoAdd($LngLatKey, $site->lng,$site->lat,$site->id,RedisCode::SYSTEM_DB); + } + + $this->redis->set($siteKey,json_encode(array_column($siteList, null, 'id'), JSON_UNESCAPED_UNICODE),RedisCode::SYSTEM_DB); + } + + /** + * @param int $siteId + * @return array|null + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + public function getSiteInfo(int $siteId): array|null + { + $this->setSiteRedis(); + + $siteKey = CommonRedisKey::siteListKey(); + + $siteList = json_decode($this->redis->get($siteKey), true); + + return $siteList[$siteId] ?? null; + } +} \ No newline at end of file diff --git a/app/Cache/Redis/Common/CommonRedisKey.php b/app/Cache/Redis/Common/CommonRedisKey.php index 9f6ac32..26fc0a0 100644 --- a/app/Cache/Redis/Common/CommonRedisKey.php +++ b/app/Cache/Redis/Common/CommonRedisKey.php @@ -86,4 +86,20 @@ class CommonRedisKey { return '__cycle:list'; } + + /** + * @return string + */ + public static function siteListKey(): string + { + return '__system:site:list'; + } + + /** + * @return string + */ + public static function siteLngLatKey(): string + { + return '__system:site:lngLat:list'; + } } \ No newline at end of file diff --git a/app/Constants/ApiCode.php b/app/Constants/ApiCode.php index f73c881..299c545 100644 --- a/app/Constants/ApiCode.php +++ b/app/Constants/ApiCode.php @@ -20,7 +20,7 @@ class ApiCode extends ReturnCode /** * @Message("商品不存在") */ - public const int ORDER_GOOD_INEXISTENCE = 20001; + public const int ORDER_GOOD_IN_EXISTENCE = 20001; /** * @Message("商品库存不足") diff --git a/app/Controller/Api/OrderController.php b/app/Controller/Api/OrderController.php index 359687a..07cce0e 100644 --- a/app/Controller/Api/OrderController.php +++ b/app/Controller/Api/OrderController.php @@ -7,6 +7,7 @@ namespace App\Controller\Api; use App\Controller\AbstractController; use App\Middleware\Api\JwtAuthMiddleware; use App\Service\Api\Order\CheckCartService; +use App\Service\Api\Order\ConfirmationOrderService; use App\Service\Api\Order\PlaceOrderService; use Hyperf\HttpServer\Annotation\Controller; use Hyperf\HttpServer\Annotation\Middlewares; @@ -26,6 +27,13 @@ class OrderController extends AbstractController return (new CheckCartService)->handle(); } + #[RequestMapping(path: 'confirmation_order',methods: 'post')] + #[Scene(scene: 'confirmation_order')] + public function confirmationOrder() + { + return (new ConfirmationOrderService)->handle(); + } + #[RequestMapping(path: 'place_order',methods: 'post')] #[Scene(scene: 'place_order')] public function placeOrder() diff --git a/app/Exception/Handler/ErrExceptionHandler.php b/app/Exception/Handler/ErrExceptionHandler.php index 55d9e8c..cf5f2e4 100644 --- a/app/Exception/Handler/ErrExceptionHandler.php +++ b/app/Exception/Handler/ErrExceptionHandler.php @@ -35,7 +35,6 @@ class ErrExceptionHandler extends ExceptionHandler { if ($throwable instanceof ErrException) { $urlArr = explode('/',$this->request->path()); - var_dump($throwable); $result = match ($urlArr[0]) { 'api' => $this->apiReturn->error($throwable->getMessage(),$throwable->getCode()), 'admin', 'common' => $this->adminReturn->error($throwable->getMessage(),$throwable->getCode()), diff --git a/app/Model/Site.php b/app/Model/Site.php index 4b70cc1..4be5895 100644 --- a/app/Model/Site.php +++ b/app/Model/Site.php @@ -58,7 +58,7 @@ class Site extends Model * @param int $kitchenId * @return int */ - public function disableStatusByKitchenId(int $kitchenId) + public function disableStatusByKitchenId(int $kitchenId): int { return $this ->where('kitchen_id',$kitchenId) @@ -96,4 +96,15 @@ class Site extends Model ->where('delivered_id',$driverId) ->get(['id','name', 'sequence']); } + + /** + * @return Builder[]|Collection + */ + public function getAllSiteList(): Collection|array + { + return $this + ->where('is_del',SiteCode::SITE_NO_DEL) + ->where('status',SiteCode::SITE_ENABLE) + ->get(['id','name','kitchen_id','city_id','address','lng','lat']); + } } diff --git a/app/Service/Api/Order/BaseOrderService.php b/app/Service/Api/Order/BaseOrderService.php new file mode 100644 index 0000000..082132b --- /dev/null +++ b/app/Service/Api/Order/BaseOrderService.php @@ -0,0 +1,158 @@ +cycleId = (int)$this->initTodayCycleId(); + $this->cartFirstData = []; + $this->cartSecondData = []; + $this->skuArr = []; + $this->goodIds = []; + $this->copies = 0; + $this->couponId = 0; + $this->siteId = 0; + + $this->orderRes = [ + 'total_price' => '0.00', //总价 + 'total_good_price' => '0.00', //商品总价 + 'favorable_good_price' => '0.00', //商品优惠价格 + 'good_after_discount_price' => '0.00', //商品优惠后价格 + 'total_sundry_price' => '0.00', //服务总费用 + 'sundry_price' => '0.00', //服务费单价 + 'sundry_num' => 0, //服务费数量 + 'favorable_sundry_price' => '0.00', //服务费优惠价格 + 'sundry_after_discount_price' => '0.00', //服务费优惠后价格 + 'actual_price' => '0.00', //实际支付价格 + 'coupon_id' => 0, + 'coupon' => [], + 'site_id' => 0, + 'site' => [], + 'good_ids' => [], + 'good' => [], + 'optional_copies' => 0, + 'meal_copies' => 0, + ]; + } + + /** + * 检测 + * @return void + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + public function check(): void + { + $kitchenId = $this->checkSite($this->siteId); + + $this->getGoodInfo($kitchenId); + + $this->buildCartData(json_decode($this->request->input('cart'), true)); + + $this->checkGood(); + + $this->checkStock(); + } + + /** + * 计算 + * @return void + */ + public function compute(): void + { + + $this->computePrice(); + + $this->computeSundryPrice(); + + $this->computeFavorable(); + + $this->computeFinallyPrice(); + } + +} \ No newline at end of file diff --git a/app/Service/Api/Order/CheckCartService.php b/app/Service/Api/Order/CheckCartService.php index 374d9c7..999547a 100644 --- a/app/Service/Api/Order/CheckCartService.php +++ b/app/Service/Api/Order/CheckCartService.php @@ -10,72 +10,11 @@ declare(strict_types=1); namespace App\Service\Api\Order; -use App\Cache\Redis\Api\ApiRedisKey; -use App\Cache\Redis\Api\GoodCache; -use App\Cache\Redis\RedisCache; -use App\Constants\ApiCode; -use App\Exception\ErrException; -use App\Service\Api\BaseService; -use App\Service\ServiceTrait\Api\OrderTrait; -use App\Service\ServiceTrait\Common\CycleTrait; -use Hyperf\Di\Annotation\Inject; use Psr\Container\ContainerExceptionInterface; use Psr\Container\NotFoundExceptionInterface; -use function Hyperf\Config\config; -class CheckCartService extends BaseService +class CheckCartService extends BaseOrderService { - use CycleTrait,OrderTrait; - - /** - * @var int 周期id - */ - private int $cycleId; - - /** - * @var array - */ - private array $goodIds = []; - - /** - * @var string 库存key - */ - private string $stockKey; - - /** - * @var RedisCache $redis - */ - #[Inject] - protected RedisCache $redis; - - /** - * @var array - */ - private array $cartSecondData; - - /** - * @var array - */ - private array $cartFirstData; - - /** - * @var int - */ - private int $copies; - - /** - * @throws ContainerExceptionInterface - * @throws NotFoundExceptionInterface - */ - public function __construct() - { - parent::__construct(); - $this->cycleId = (int)$this->initTodayCycleId(); - $this->cartFirstData = []; - $this->cartSecondData = []; - $this->copies = 0; - } - /** * @return array * @throws ContainerExceptionInterface @@ -83,43 +22,10 @@ class CheckCartService extends BaseService */ public function handle(): array { - throw new ErrException(ApiCode::getMessage(ApiCode::LOGIN_ERROR),ApiCode::LOGIN_ERROR); - $kitchenId = (int)$this->request->input('kitchen_id'); + $this->siteId = (int)$this->request->input('site_id'); - $this->getGoodInfo($kitchenId); + $this->check(); - $this->buildCartData(json_decode($this->request->input('cart'), true)); - - $this->checkGood($this->goodIds); - - $this->checkStock(); - - $this->return->success(); - } - - /** - * 获取商品信息 - * @param int $kitchenId - * @return void - * @throws ContainerExceptionInterface - * @throws NotFoundExceptionInterface - */ - private function getGoodInfo(int $kitchenId): void - { - $this->stockKey = ApiRedisKey::goodStockKey($this->cycleId,$kitchenId); - $mealGoodKey = ApiRedisKey::mealGoodListKey($this->cycleId,$kitchenId); - $optionalGoodKey = ApiRedisKey::optionalGoodListKey($this->cycleId,$kitchenId); - - $mealGood = $this->redis->get($mealGoodKey); - $optionalGood = $this->redis->get($optionalGoodKey); - - if (empty($mealGood) || empty($optionalGood)) throw new ErrException('商品不存在'); - - if ($this->redis->exists($this->stockKey)) throw new ErrException('库存不足'); - - $mealGood = json_decode($mealGood, true); - $optionalGood = json_decode($optionalGood, true); - - $this->goodIds = array_merge(array_column($mealGood, 'id'), array_column($optionalGood, 'id')); + return $this->return->success(); } } \ No newline at end of file diff --git a/app/Service/Api/Order/ConfirmationOrderService.php b/app/Service/Api/Order/ConfirmationOrderService.php new file mode 100644 index 0000000..5c31944 --- /dev/null +++ b/app/Service/Api/Order/ConfirmationOrderService.php @@ -0,0 +1,34 @@ +siteId = (int)$this->request->input('site_id'); + $this->couponId = (int)$this->request->input('coupon_id',0); + + $this->check(); + + $this->compute(); + + return $this->return->success('success',$this->orderRes); + } +} \ No newline at end of file diff --git a/app/Service/Api/Order/PlaceOrderService.php b/app/Service/Api/Order/PlaceOrderService.php index d80c336..a90d279 100644 --- a/app/Service/Api/Order/PlaceOrderService.php +++ b/app/Service/Api/Order/PlaceOrderService.php @@ -10,16 +10,31 @@ declare(strict_types=1); namespace App\Service\Api\Order; -use App\Service\Api\BaseService; +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\NotFoundExceptionInterface; -class PlaceOrderService extends BaseService +class PlaceOrderService extends BaseOrderService { /** - * @var int 1=微信支付 2=支付宝支付 3=余额支付 + * @return array + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface */ - private int $type; - public function handle() + { + $this->siteId = (int)$this->request->input('site_id'); + $this->couponId = (int)$this->request->input('coupon_id',0); + + $this->check(); + + $this->compute(); + + $this->placeOrder(); + + return $this->return->success('success',$this->orderRes); + } + + private function placeOrder() { } diff --git a/app/Service/ServiceTrait/Api/OrderTrait.php b/app/Service/ServiceTrait/Api/OrderTrait.php index d11a0ac..23d8595 100644 --- a/app/Service/ServiceTrait/Api/OrderTrait.php +++ b/app/Service/ServiceTrait/Api/OrderTrait.php @@ -10,14 +10,31 @@ declare(strict_types=1); namespace App\Service\ServiceTrait\Api; +use App\Cache\Redis\Api\ApiRedisKey; +use App\Cache\Redis\Api\SiteCache; +use App\Cache\Redis\RedisCache; use App\Constants\ApiCode; use App\Exception\ErrException; +use Hyperf\Di\Annotation\Inject; use Psr\Container\ContainerExceptionInterface; use Psr\Container\NotFoundExceptionInterface; trait OrderTrait { /** + * @var SiteCache + */ + #[Inject] + protected SiteCache $siteCache; + + /** + * @var RedisCache $redis + */ + #[Inject] + protected RedisCache $redis; + + /** + * 生成购物车数据 * @param array $data * @return void */ @@ -40,19 +57,20 @@ trait OrderTrait } /** - * @param array $goodIds + * 检测商品 * @return void */ - protected function checkGood(array $goodIds): void + protected function checkGood(): void { foreach ($this->cartFirstData as $key => $one) { - if (in_array($key, $goodIds)) continue; + if (in_array($key, $this->goodIds)) continue; - throw new ErrException('商品不存在'); + throw new ErrException('商品不存在',ApiCode::ORDER_GOOD_IN_EXISTENCE); } } /** + * 检测库存 * @return void * @throws ContainerExceptionInterface * @throws NotFoundExceptionInterface @@ -62,7 +80,168 @@ trait OrderTrait foreach ($this->cartFirstData as $key => $one) { if (($this->redis->zScore($this->stockKey,$key) ?? 0) >= $one) continue; - throw new ErrException('商品库存不足'); + throw new ErrException('商品库存不足',ApiCode::ORDER_GOOD_INSUFFICIENT_STOCK); } } + + /** + * 检测地点 + * @param int $siteId + * @return int + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + protected function checkSite(int $siteId): int + { + $siteInfo = $this->siteCache->getSiteInfo($siteId); + if (empty($siteInfo)) throw new ErrException('该地点暂时不支持点餐'); + + $kitchenId = (int)($siteInfo['kitchen_id'] ?? 0); + if ($kitchenId <= 0) throw new ErrException('该地点暂时不支持点餐'); + + $this->orderRes['site_id'] = $siteId; + $this->orderRes['site_info'] = $siteInfo; + + return $kitchenId; + } + + /** + * 获取商品信息 + * @param int $kitchenId + * @return void + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + protected function getGoodInfo(int $kitchenId): void + { + $this->stockKey = ApiRedisKey::goodStockKey($this->cycleId,$kitchenId); + $mealGoodKey = ApiRedisKey::mealGoodListKey($this->cycleId,$kitchenId); + $optionalGoodKey = ApiRedisKey::optionalGoodListKey($this->cycleId,$kitchenId); + + $mealGood = $this->redis->get($mealGoodKey); + $optionalGood = $this->redis->get($optionalGoodKey); + + if (empty($mealGood) || empty($optionalGood)) throw new ErrException('商品不存在'); + + if (!$this->redis->exists($this->stockKey)) throw new ErrException('库存不足'); + + $mealGood = json_decode($mealGood, true); + $optionalGood = json_decode($optionalGood, true); + + $skuArr = []; + foreach ($mealGood as $one){ + $skuArr = array_merge($skuArr,$one['sku_list']); + } + + foreach ($optionalGood as $one){ + $skuArr = array_merge($skuArr,$one['sku_list']); + } + + $this->skuArr = array_column($skuArr,null,'id'); + $this->goodIds = array_column($skuArr,'id'); + unset($skuArr); + } + + /** + * @return int + */ + protected function getAutoCouponId(): int + { + return 0; + } + + /** + * 计算价格 + * @return void + */ + protected function computePrice(): void + { + $this->orderRes['good_ids'] = $this->cartSecondData; + + foreach ($this->cartSecondData as $oneCopies) { + $oneCopiesTotalPrice = '0.00'; + + $copiesType = 1; + + $oneCopiesGoodInfo = []; + foreach ($oneCopies as $oneGood) { + if (empty($oneCopiesGoodInfo[$oneGood])) + { + $oneCopiesGoodInfo[$oneGood] = [ + 'num' => 1, + 'good_name' => '1', + 'good_url' => '1', + 'unit_price' => $this->skuArr[$oneGood]['price'] + ]; + } + + $oneCopiesTotalPrice = bcadd($oneCopiesTotalPrice, $this->skuArr[$oneGood]['price'],2); + + if ($copiesType == 1 && $this->skuArr[$oneGood]['type'] == 2) { + $copiesType = 2; + } + } + + $this->orderRes['good'][] = [ + 'good_ids' => $oneCopies, + 'good_info' => $oneCopiesGoodInfo, + 'price' => $oneCopiesTotalPrice, + ]; + + if ($copiesType == 1) { + $this->orderRes['optional_copies']++; + } else { + $this->orderRes['meal_copies']++; + } + + $this->orderRes['total_good_price'] = bcadd($this->orderRes['total_good_price'],$oneCopiesTotalPrice,2); + } + } + + /** + * 计算服务费 + * @return void + */ + protected function computeSundryPrice(): void + { + $this->orderRes['sundry_num'] = $this->orderRes['optional_copies']; + $this->orderRes['sundry_price'] = '1.00'; //todo 设置 + $this->orderRes['total_sundry_price'] = bcmul($this->orderRes['sundry_price'],$this->orderRes['sundry_num'],2); + } + + /** + * 计算优惠 + * @return void + */ + protected function computeFavorable(): void + { + if ($this->couponId <= 0) { + $this->couponId = $this->getAutoCouponId(); + } + + if ($this->couponId <= 0) { + return; + } + + $couponInfo = []; // todo 优惠券信息 + + $this->orderRes['coupon_id'] = $this->couponId; + $this->orderRes['coupon'] = $couponInfo; + + //todo 优惠计算 + $this->orderRes['favorable_good_price'] = '1.00'; + $this->orderRes['favorable_sundry_price'] = '1.00'; + } + + /** + * 计算最终价格 + * @return void + */ + protected function computeFinallyPrice(): void + { + $this->orderRes['good_after_discount_price'] = bcsub($this->orderRes['total_good_price'],$this->orderRes['favorable_good_price'],2); + $this->orderRes['sundry_after_discount_price'] = bcsub($this->orderRes['total_sundry_price'],$this->orderRes['favorable_sundry_price'],2); + $this->orderRes['total_price'] = bcadd($this->orderRes['total_good_price'],$this->orderRes['total_sundry_price'],2); + $this->orderRes['actual_price'] = bcadd($this->orderRes['good_after_discount_price'],$this->orderRes['sundry_after_discount_price'],2); + } } \ No newline at end of file