From 9b7390129da37d3b1ed976ae79e5ea79425f7999 Mon Sep 17 00:00:00 2001 From: ctexthuang Date: Wed, 22 Jan 2025 11:04:12 +0800 Subject: [PATCH] feat: spu --- app/Cache/Redis/Api/ApiRedisKey.php | 32 ++ app/Cache/Redis/Api/GoodCache.php | 291 ++++++++++++++++++ app/Cache/Redis/Common/CycleCache.php | 16 +- app/Constants/Common/GoodCode.php | 6 + app/Controller/Api/GoodController.php | 4 +- app/Model/Cycle.php | 9 + app/Model/Spu.php | 22 +- app/Service/Admin/Good/SpuService.php | 5 + app/Service/Api/Good/OptionalListService.php | 30 +- .../ServiceTrait/Common/CycleTrait.php | 21 +- 10 files changed, 426 insertions(+), 10 deletions(-) create mode 100644 app/Cache/Redis/Api/GoodCache.php diff --git a/app/Cache/Redis/Api/ApiRedisKey.php b/app/Cache/Redis/Api/ApiRedisKey.php index afe8ac2..df00fa2 100644 --- a/app/Cache/Redis/Api/ApiRedisKey.php +++ b/app/Cache/Redis/Api/ApiRedisKey.php @@ -23,4 +23,36 @@ class ApiRedisKey { return 'login:or:register:code:'.$code; } + + /** + * 自选商品列表 + * @param int $cycleId + * @param int $kitchenId + * @return string + */ + public static function optionalGoodListKey(int $cycleId, int $kitchenId): string + { + return 'good:list:optional:cycle_id:'.$cycleId.':kitchen_id:'.$kitchenId; + } + + /** + * 套餐商品列表 + * @param int $cycleId + * @param int $kitchenId + * @return string + */ + public static function mealGoodListKey(int $cycleId, int $kitchenId): string + { + return 'good:list:meal:cycle_id:'.$cycleId.':kitchen_id:'.$kitchenId; + } + + /** + * @param int $cycleId + * @param int $kitchenId + * @return string + */ + public static function goodStockKey(int $cycleId, int $kitchenId): string + { + return 'good:list:stock:cycle_id:'.$cycleId.':kitchen_id:'.$kitchenId; + } } \ No newline at end of file diff --git a/app/Cache/Redis/Api/GoodCache.php b/app/Cache/Redis/Api/GoodCache.php new file mode 100644 index 0000000..e97b5df --- /dev/null +++ b/app/Cache/Redis/Api/GoodCache.php @@ -0,0 +1,291 @@ +expireTime = DateUtil::DAY; + } + + /** + * @param int $id + * @return bool|float|int|\Redis + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + public function getStock(int $id): float|bool|int|\Redis + { + $stockKey = ApiRedisKey::goodStockKey($this->cycleId,$this->kitchenId); + return $this->redis->zScore($stockKey, $id) ?? 0; + } + + /** + * @return void + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + private function initGoodCacheByCycleId(): void + { + $this->mealKey = ApiRedisKey::mealGoodListKey($this->cycleId,$this->kitchenId); + $this->optionalKey = ApiRedisKey::optionalGoodListKey($this->cycleId,$this->kitchenId); + + if ($this->checkMealGoodCache() && $this->checkOptionalGoodCache()) return; + + $this->setOptionalGoodCache(); + + $this->setMealGoodCache(); + + if (!empty($this->stockArr)) { + $stockKey = ApiRedisKey::goodStockKey($this->cycleId,$this->kitchenId); + foreach ($this->stockArr as $one) { + $this->redis->zAdd($stockKey,$one['id'],$one['stock']); + $this->redis->expire($stockKey,$this->expireTime); + } + } + } + + /** + * @return void + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + private function setMealGoodCache(): void + { + $list = $this->spuModel->getListByCycleIdAndType($this->cycleId, $this->kitchenId,GoodCode::SPU_TYPE_MEAL); + + if (empty($list)) return; + + $list = $list->toArray(); + + $this->buildData($list); + + $this->redis->set($this->mealKey, json_encode($list)); + $this->redis->expire($this->mealKey,$this->expireTime); + } + + /** + * @param $list + * @return mixed + */ + private function buildData(&$list): mixed + { + $spuIds = array_column($list, 'id'); + + $skuList = $this->skuModel + ->whereIn('spu_id',$spuIds) + ->where('is_del',GoodCode::SKU_IS_NO_DEL) + ->get(); + if (empty($skuList)) return $list; + + $skuList = $skuList->toArray(); + + $imageIdArr = array_column($skuList,'image_ids'); + $imageIds = array_unique(explode(',',implode(',',$imageIdArr))); + $imageList = $this->getOssObjects($imageIds); + + $skuListArr = []; + $imageArr = []; + $stockArr = []; + foreach ($skuList as $sku) { + $imageOneArr = []; + foreach (explode(',',$sku['image_ids']) as $imageId) { + $imageOneArr[] = [ + 'id' => $imageId, + 'url' => $imageList[$imageId]['url'] + ]; + } + + $sku['image_list'] = $imageOneArr; + + if (empty($skuListArr[$sku['spu_id']])) { + $skuListArr[$sku['spu_id']] = []; + } + + $skuListArr[$sku['spu_id']][] = $sku; + $imageArr[$sku['spu_id']] = array_merge($imageArr[$sku['spu_id']] ?? [],$imageOneArr); + + $stockArr[] = [ + 'id' => $sku['id'], + 'stock' => $sku['surplus_stock'] + ]; + } + + $this->stockArr = $stockArr; + + foreach ($list as &$item) { + $item['sku_list'] = $skuListArr[$item['id']] ?? []; + $item['image_list'] = $imageArr[$item['id']] ?? []; + } + + return $list; + } + + /** + * @return void + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + private function setOptionalGoodCache(): void + { + $list = $this->spuModel->getListByCycleIdAndType($this->cycleId, $this->kitchenId,GoodCode::SPU_TYPE_OPTIONAL); + + if (empty($list)) return; + + $list = $list->toArray(); + + $this->buildData($list); + + $this->redis->set($this->optionalKey, json_encode($list)); + $this->redis->expire($this->optionalKey,$this->expireTime); + } + + /** + * @return bool + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + private function checkMealGoodCache(): bool + { + if ($this->redis->exists($this->mealKey)) return true; + + $this->redis->delete($this->mealKey); + + return false; + } + + /** + * @return bool + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + private function checkOptionalGoodCache(): bool + { + if ($this->redis->exists($this->optionalKey)) return true; + + $this->redis->delete($this->optionalKey); + + return false; + } + + /** + * @return array|mixed + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + public function getOptionalGoodList(): mixed + { + $this->initGoodCacheByCycleId(); + + $data = $this->redis->get($this->optionalKey); + + if (empty($data)) return []; + + $data = json_decode($data,true); + + $this->buildData($data); + + return $data; + } + + /** + * @param $data + * @return mixed + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + private function buildStockData(&$data): mixed + { + foreach ($data as &$spu) { + foreach ($spu as &$sku) { + $sku['stock'] = $this->getStock((int)$sku['id']); + } + } + + return $data; + } + + /** + * @return array|mixed + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + public function getMealGoodList(): mixed + { + $this->initGoodCacheByCycleId(); + + $data = $this->redis->get($this->mealKey); + + if (empty($data)) return []; + + $data = json_decode($data,true); + + $this->buildData($data); + + return $data; + } +} \ No newline at end of file diff --git a/app/Cache/Redis/Common/CycleCache.php b/app/Cache/Redis/Common/CycleCache.php index 5d34178..62c2821 100644 --- a/app/Cache/Redis/Common/CycleCache.php +++ b/app/Cache/Redis/Common/CycleCache.php @@ -46,8 +46,16 @@ class CycleCache } } -// public function getCycleCache(string $key) -// { -// $this->redis->zScore() -// } + /** + * @param string $date + * @return bool|float|\Redis + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + public function getCycleCache(string $date): float|bool|\Redis + { + $key = CommonRedisKey::getCycleList(); + + return $this->redis->zScore($key,$date,RedisCode::SYSTEM_DB); + } } \ No newline at end of file diff --git a/app/Constants/Common/GoodCode.php b/app/Constants/Common/GoodCode.php index 9488351..9d83000 100644 --- a/app/Constants/Common/GoodCode.php +++ b/app/Constants/Common/GoodCode.php @@ -15,4 +15,10 @@ class GoodCode */ const int SKU_IS_NO_DEL = 1; const int SKU_IS_DELETE = 2; + + /** + * @var int 1=自选 2=套餐 + */ + CONST INT SPU_TYPE_OPTIONAL = 1; + CONST INT SPU_TYPE_MEAL = 2; } \ No newline at end of file diff --git a/app/Controller/Api/GoodController.php b/app/Controller/Api/GoodController.php index 09f27d5..502d9a6 100644 --- a/app/Controller/Api/GoodController.php +++ b/app/Controller/Api/GoodController.php @@ -12,13 +12,13 @@ use Hyperf\Validation\Annotation\Scene; #[Controller(prefix: 'api/good')] class GoodController extends AbstractController { - #[RequestMapping(path: 'optional',methods: 'post')] + #[RequestMapping(path: 'optional',methods: 'GET')] public function optional() { return (new OptionalListService)->handle(); } - #[RequestMapping(path: 'meal',methods: 'post')] + #[RequestMapping(path: 'meal',methods: 'GET')] public function meal() { return (new MealListService)->handle(); diff --git a/app/Model/Cycle.php b/app/Model/Cycle.php index 9015f05..177ae01 100644 --- a/app/Model/Cycle.php +++ b/app/Model/Cycle.php @@ -51,4 +51,13 @@ class Cycle extends Model { return $this->where('id',$id)->first(); } + + /** + * @param string $date + * @return int + */ + public function getIdByDate(string $date): int + { + return $this->where('dates',$date)->value('id') ?? 0; + } } diff --git a/app/Model/Spu.php b/app/Model/Spu.php index d923c84..8e42199 100644 --- a/app/Model/Spu.php +++ b/app/Model/Spu.php @@ -6,6 +6,7 @@ namespace App\Model; use App\Constants\Common\GoodCode; use Hyperf\Database\Model\Builder; +use Hyperf\Database\Model\Collection; use Hyperf\DbConnection\Model\Model; /** @@ -18,6 +19,8 @@ use Hyperf\DbConnection\Model\Model; * @property string $sub_title * @property int $category_id * @property int $saleable + * @property int $type + * @property int $sort * @property int $is_del * @property string $create_time * @property string $update_time @@ -39,7 +42,7 @@ class Spu extends Model /** * The attributes that should be cast to native types. */ - protected array $casts = ['id' => 'integer', 'cycle_id' => 'integer', 'city_id' => 'integer', 'kitchen_id' => 'integer','chef_id' => 'integer', 'category_id' => 'integer', 'saleable' => 'integer']; + protected array $casts = ['id' => 'integer', 'cycle_id' => 'integer', 'city_id' => 'integer', 'kitchen_id' => 'integer','chef_id' => 'integer', 'category_id' => 'integer', 'saleable' => 'integer','type' => 'integer','sort' => 'integer']; const string CREATED_AT = 'create_time'; const string UPDATED_AT = 'update_time'; @@ -63,4 +66,21 @@ class Spu extends Model { return $this->where('id', $id)->where('is_del',GoodCode::SPU_IS_NO_DEL)->first(); } + + /** + * @param int $cycleId + * @param int $kitchenId + * @param int $type + * @return Builder[]|Collection + */ + public function getListByCycleIdAndType(int $cycleId, int $kitchenId, int $type): Collection|array + { + return $this + ->where('cycle_id',$cycleId) + ->where('kitchen_id',$kitchenId) + ->where('is_del',GoodCode::SPU_IS_NO_DEL) + ->where('type',$type) + ->orderBy('sort') + ->get(); + } } diff --git a/app/Service/Admin/Good/SpuService.php b/app/Service/Admin/Good/SpuService.php index c5b7e74..a26440d 100644 --- a/app/Service/Admin/Good/SpuService.php +++ b/app/Service/Admin/Good/SpuService.php @@ -71,6 +71,7 @@ class SpuService extends BaseService ->where('city_id',$cityId) ->where('kitchen_id',$kitchenId) ->where('is_del',GoodCode::SPU_IS_NO_DEL) + ->where('type',$this->request->input('type')) ->paginate($limit) ->toArray(); @@ -152,6 +153,8 @@ class SpuService extends BaseService $insertModel->sub_title = $this->request->input('sub_title',''); $insertModel->category_id = $this->request->input('category_id'); $insertModel->saleable = $this->request->input('saleable'); + $insertModel->type = $this->request->input('type'); + $insertModel->sort = $this->request->input('sort'); if (!$insertModel->save()) throw new ErrException('添加菜品失败'); @@ -214,6 +217,8 @@ class SpuService extends BaseService $info->sub_title = $this->request->input('sub_title',''); $info->category_id = $this->request->input('category_id'); $info->saleable = $this->request->input('saleable'); + $info->type = $this->request->input('type'); + $info->sort = $this->request->input('sort'); if (!$info->save()) throw new ErrException('修改菜品失败'); diff --git a/app/Service/Api/Good/OptionalListService.php b/app/Service/Api/Good/OptionalListService.php index 64dc3cd..72bafec 100644 --- a/app/Service/Api/Good/OptionalListService.php +++ b/app/Service/Api/Good/OptionalListService.php @@ -10,12 +10,38 @@ declare(strict_types=1); namespace App\Service\Api\Good; +use App\Cache\Redis\Api\GoodCache; use App\Service\Api\BaseService; +use App\Service\ServiceTrait\Common\CycleTrait; +use Hyperf\Di\Annotation\Inject; +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\NotFoundExceptionInterface; class OptionalListService extends BaseService { - public function handle() + use CycleTrait; + + /** + * @var GoodCache + */ + #[Inject] + protected GoodCache $goodCache; + + /** + * @return array + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + public function handle(): array { - //todo Write logic + $cycleId = $this->initTodayCycleId(); + + if (empty($cycleId)) return ['list' => []]; + + $this->goodCache->cycleId = (int)$cycleId; + $this->goodCache->kitchenId = (int)$this->request->input('kitchen_id'); + $data = $this->goodCache->getOptionalGoodList(); + + return $this->return->success('success', ['list' => $data]); } } \ No newline at end of file diff --git a/app/Service/ServiceTrait/Common/CycleTrait.php b/app/Service/ServiceTrait/Common/CycleTrait.php index d5225df..4bb8de3 100644 --- a/app/Service/ServiceTrait/Common/CycleTrait.php +++ b/app/Service/ServiceTrait/Common/CycleTrait.php @@ -5,7 +5,12 @@ namespace App\Service\ServiceTrait\Common; use App\Cache\Redis\Common\ConfigCache; use App\Cache\Redis\Common\CycleCache; use App\Constants\ConfigCode; +use App\Constants\RedisCode; +use App\Model\Cycle; use Hyperf\Di\Annotation\Inject; +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\NotFoundExceptionInterface; +use Redis; trait CycleTrait { @@ -15,13 +20,24 @@ trait CycleTrait #[Inject] protected CycleCache $cycleCache; + /** + * @var Cycle + */ + #[Inject] + protected Cycle $cycleModel; + /** * @var ConfigCache */ #[Inject] protected ConfigCache $configCache; - protected function initTodayCycleId() + /** + * @return bool|float|Redis + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + protected function initTodayCycleId(): float|bool|Redis { $TodayCutOffTime = $this->configCache->getConfigValueByKey(ConfigCode::TODAY_CUT_OFF_TIME_KEY); @@ -31,6 +47,9 @@ trait CycleTrait $day = date('Y-m-d',strtotime('today')); } + $cycleCacheId = $this->cycleCache->getCycleCache($day); + if (!empty($cycleCacheId)) return $cycleCacheId; + return $this->cycleModel->getIdByDate($day); } } \ No newline at end of file