diff --git a/app/Cache/Redis/Api/ApiRedisKey.php b/app/Cache/Redis/Api/ApiRedisKey.php index 6f17782..74ded81 100644 --- a/app/Cache/Redis/Api/ApiRedisKey.php +++ b/app/Cache/Redis/Api/ApiRedisKey.php @@ -73,4 +73,15 @@ class ApiRedisKey { return 'lock:refund:user_id:'. $userId; } + + /** + * @param int $cityId + * @param int $type + * @param int $timeKey + * @return string + */ + public static function getLeaderboardByCityIdAndType(int $cityId, int $type, int $timeKey): string + { + return 'chef:leaderboard:city_id:'.$cityId.':type:'.$type.':time_key:'.$timeKey; + } } \ No newline at end of file diff --git a/app/Cache/Redis/RedisCache.php b/app/Cache/Redis/RedisCache.php index 8f87070..17dc47d 100644 --- a/app/Cache/Redis/RedisCache.php +++ b/app/Cache/Redis/RedisCache.php @@ -560,6 +560,21 @@ class RedisCache { return $this->getRedis($poolName)->zRank($key, $value); } + + /** + * @param $key + * @param int $start + * @param int $end + * @param bool $score + * @param string $poolName + * @return array|false|Redis + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + public function zRevRange($key, int $start = 0, int $end = -1, $score = false, string $poolName = RedisCode::DEFAULT_DB): false|array|Redis + { + return $this->getRedis($poolName)->zRevRange($key, $start, $end,$score); + } // +-------------------------------------------------------------------------------------------------------------------------------------------- // | geo // +-------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/app/Constants/Common/LeaderboardHistoryCode.php b/app/Constants/Common/LeaderboardHistoryCode.php new file mode 100644 index 0000000..25063d6 --- /dev/null +++ b/app/Constants/Common/LeaderboardHistoryCode.php @@ -0,0 +1,12 @@ +handle(); } + + #[RequestMapping(path: "test", methods: "GET")] + #[Scene(scene: "test")] + public function test(LoginRequest $request) + { + $res = DateUtil::getLastWeekInfo(); + $res2 = DateUtil::getThisWeekInfo(); + var_dump($res); + var_dump($res2); + } } diff --git a/app/Controller/Api/SystemController.php b/app/Controller/Api/SystemController.php index c38767f..abe9537 100644 --- a/app/Controller/Api/SystemController.php +++ b/app/Controller/Api/SystemController.php @@ -8,6 +8,7 @@ use App\Controller\AbstractController; use App\Middleware\Api\JwtAuthMiddleware; use App\Service\Api\System\AliStsService; use App\Service\Api\System\CityListService; +use App\Service\Api\System\GetLeaderboardService; use App\Service\Api\System\MiniWxConfigService; use App\Service\Api\System\SiteListService; use App\Service\Api\System\SystemConfigService; @@ -71,4 +72,12 @@ class SystemController extends AbstractController { return (new AliStsService)->handle(); } + + #[RequestMapping(path: "leaderboard", methods: "GET")] + #[Scene(scene: 'leaderboard')] +// #[Middleware(JwtAuthMiddleware::class)] + public function getLeaderboardHistory() + { + return (new GetLeaderboardService)->handle(); + } } diff --git a/app/Cron/Chef/RankTask.php b/app/Cron/Chef/RankTask.php new file mode 100644 index 0000000..c66f772 --- /dev/null +++ b/app/Cron/Chef/RankTask.php @@ -0,0 +1,219 @@ +thisWeekInfo = DateUtil::getThisWeekInfo(); + + if ( + empty($this->thisWeekInfo) || + empty($this->thisWeekInfo['start_date']) || + empty($this->thisWeekInfo['end_date']) || + empty($this->thisWeekInfo['week_number']) || + empty($this->thisWeekInfo['year']) + ) throw new Exception(__CLASS__.'数据生成失败,获取上周信息失败'); + + $this->nowDate = date("Y-m-d H:i:s"); + + $flag = $this->leaderboardHistoryModel->where('time_key',$this->thisWeekInfo['year'].$this->thisWeekInfo['week_number'])->first(); + if (!empty($flag)) throw new Exception(__CLASS__.'数据生成失败,上周数据已生成'); + + $chefInfo = $this->chefModel + ->join('admin_user', function ($join) { + $join->on('admin_user.id', '=', 'chef.user_id') + ->where('admin_user.is_del', '=', UserCode::IS_NO_DEL) + ->select([ + 'admin_user.id', + 'chef.kitchen_id', + ]); + }) + ->get(); + if ($chefInfo->isEmpty()) throw new Exception(__CLASS__.'数据生成失败,获取厨师信息失败'); + $this->chefInfo = $chefInfo->toArray(); + + $kitchenIds = array_column($this->chefInfo, 'kitchen_id'); + $this->kitchenSiteList = $this->kitchenModel->whereIn('id',$kitchenIds)->pluck('city_id','id')->toArray(); + if (empty($this->kitchenSiteList)) throw new Exception(__CLASS__.'数据生成失败,获取厨房城市信息失败'); + + $this->buildChefEvaluationRankData(); + + $this->buildChefStatementRankData(); + + if (empty($this->insertData)) throw new Exception(__CLASS__.'数据生成失败,获取厨师排行榜数据失败'); + Db::transaction(function () { + $res = (new LeaderboardHistory)->insert($this->insertData); + + if (!$res) throw new Exception(__CLASS__.'数据生成失败,插入数据失败'); + }); + + + }catch (Exception $e){ + $this->log->error($e->getMessage()); + } + } + + /** + * @return void + */ + private function buildChefEvaluationRankData(): void + { + $data = $this->chefStatementModel + ->whereBetween('date',[$this->thisWeekInfo['start_date'],$this->thisWeekInfo['end_date']]) + ->select('chef_id', Db::raw('SUM(`sale`) as total_sale')) + ->groupBy('chef_id') + ->get(); + + if ($data->isEmpty()) $this->buildChefStatementNullData(LeaderboardHistoryCode::TYPE_CHEF_SALE); + $data = array_column($data->toArray(),'total_sale','chef_id'); + + foreach ($this->chefInfo as $v){ + $this->insertData[] = [ + 'chef_id' => $v->id, + 'kitchen_id' => $v->kitchen_id, + 'city_id' => $this->kitchenSiteList[$v->kitchen_id] ?? 0, + 'time_key' => $this->thisWeekInfo['year'].$this->thisWeekInfo['week_number'], + 'board_type' => LeaderboardHistoryCode::TYPE_CHEF_SALE, + 'score' => $data[$v->id] ?? 0, + 'create_time' => $this->nowDate, + ]; + } + + } + + private function buildChefStatementNullData(int $type): void + { + foreach ($this->chefInfo as $v) { + $this->insertData[] = [ + 'chef_id' => $v->id, + 'kitchen_id' => $v->kitchen_id, + 'city_id' => $this->kitchenSiteList[$v->kitchen_id] ?? 0, + 'time_key' => $this->thisWeekInfo['year'].$this->thisWeekInfo['week_number'], + 'board_type' => $type, + 'score' => 0, + 'create_time' => $this->nowDate, + ]; + } + } + + /** + * @return void + */ + private function buildChefStatementRankData(): void + { + $data = $this->evaluationModel + ->whereBetween('date',[$this->thisWeekInfo['start_date'],$this->thisWeekInfo['end_date']]) + ->select('chef_id',Db::raw('AVG(`score`) as total_score')) + ->groupBy('chef_id') + ->get(); + + if ($data->isEmpty()) $this->buildChefStatementNullData(LeaderboardHistoryCode::TYPE_CHEF_SCORE); + $data = array_column($data->toArray(),'total_score','chef_id'); + + foreach ($this->chefInfo as $v){ + $this->insertData[] = [ + 'chef_id' => $v->id, + 'kitchen_id' => $v->kitchen_id, + 'city_id' => $this->kitchenSiteList[$v->kitchen_id] ?? 0, + 'time_key' => $this->thisWeekInfo['year'].$this->thisWeekInfo['week_number'], + 'board_type' => LeaderboardHistoryCode::TYPE_CHEF_SCORE, + 'score' => $data[$v->id] ?? 0, + 'create_time' => $this->nowDate, + ]; + } + } +} \ No newline at end of file diff --git a/app/Extend/DateUtil.php b/app/Extend/DateUtil.php index 6d9c2a1..92fccaa 100644 --- a/app/Extend/DateUtil.php +++ b/app/Extend/DateUtil.php @@ -2,6 +2,9 @@ namespace App\Extend; +use DateMalformedStringException; +use DateTime; + class DateUtil { public const MINUTE = 60; // 分 @@ -126,4 +129,63 @@ class DateUtil return $date; } + + static function getThisWeekInfo(): array + { + // 获取当前时间 + $now = new DateTime(); + + // 获取开始日期 (周一) + $startOfWeek = clone $now; + $startOfWeek->modify('Monday this week'); + + // 获取上周的结束日期 (周日) + $endOfWeek = clone $now; + $endOfWeek->modify('Sunday this week'); + + // 获取上周是当年的第几周 + $weekNumber = $now->format('W'); + $year = $now->format('o'); + + return [ + 'start_date' => $startOfWeek->format('Y-m-d'), + 'end_date' => $endOfWeek->format('Y-m-d'), + 'week_number' => (int)$weekNumber, + 'year' => (int)$year, + ]; + } + + /** + * @return array + * @throws DateMalformedStringException + */ + static function getLastWeekInfo(): array + { + // 获取当前时间 + $now = new DateTime(); + + // 获取上周的时间 + $lastWeek = clone $now; + $lastWeek->modify('-1 week'); + + // 获取上周的开始日期 (周一) + $startOfWeek = clone $lastWeek; + $startOfWeek->modify('Monday this week'); + + // 获取上周的结束日期 (周日) + $endOfWeek = clone $lastWeek; + $endOfWeek->modify('Sunday this week'); + + // 获取上周是当年的第几周 + $weekNumber = $lastWeek->format('W'); + $year = $lastWeek->format('o'); + + return [ + 'start_date' => $startOfWeek->format('Y-m-d'), + 'end_date' => $endOfWeek->format('Y-m-d'), + 'week_number' => (int)$weekNumber, + 'year' => (int)$year, + ]; + + } } \ No newline at end of file diff --git a/app/Model/LeaderboardHistory.php b/app/Model/LeaderboardHistory.php new file mode 100644 index 0000000..be80b06 --- /dev/null +++ b/app/Model/LeaderboardHistory.php @@ -0,0 +1,35 @@ + 'integer', 'board_type' => 'integer', 'time_key' => 'integer', 'kitchen_id' => 'integer', 'city_id' => 'integer', 'chef_id' => 'integer', 'score' => 'integer']; +} diff --git a/app/Service/Api/System/GetLeaderboardService.php b/app/Service/Api/System/GetLeaderboardService.php new file mode 100644 index 0000000..c3af89e --- /dev/null +++ b/app/Service/Api/System/GetLeaderboardService.php @@ -0,0 +1,115 @@ +request->input('city_id'); + $type = (int)$this->request->input('type'); + $cityInfo = $this->systemCityModel->find($cityId); + + if (empty($cityInfo)) return $this->return->success('success',['list' => []]); + + $lastInfo = DateUtil::getLastWeekInfo(); + + if ( + empty($lastInfo) || + empty($lastInfo['start_date']) || + empty($lastInfo['end_date']) || + empty($lastInfo['week_number']) || + empty($lastInfo['year']) + ) return $this->return->success('success',['list' => []]); + + $timeKey = (int)($lastInfo['year'].$lastInfo['week_number']); + + $redisKey = ApiRedisKey::getLeaderboardByCityIdAndType($cityId,$type,$timeKey); + + if (!$this->redis->exists($redisKey)) { + $info = $this->leaderboardHistoryModel->where('board_type',$type)->where('city_id',$cityId)->where('time_key',$timeKey)->pluck('score','chef_id')->toArray(); + foreach ($info as $k=>$v) { + if ($type == LeaderboardHistoryCode::TYPE_CHEF_SCORE) { + if ($v == 0) $v = 0; + + if ($v < 4) { + $normalized = $v / 4; + $v = 4 + 1 - exp(-2 * $normalized); + } + + $v = round($v,2); + } + + $this->redis->zAdd($redisKey,$v,$k); + $this->redis->expire($redisKey,86400); + } + } + + $leaderboardList = $this->redis->zRevRange($redisKey,0,-1,true); + + $chefIds = array_keys($leaderboardList); + $chefList = $this->adminUserModel->whereIn('id',$chefIds)->select('id','chinese_name','avatar')->get(); + if ($chefList->isEmpty()) return $this->return->success('success',['list' => []]); + $chefList = $chefList->toArray(); + + $avatarIds = array_column($chefList,'avatar'); + $avatarList = $this->getOssObjects($avatarIds); + + $res = []; + foreach ($leaderboardList as $k=>$v) { + $res[] = [ + 'id' => $k, + 'name' => $chefList[$k]['chinese_name'], + 'avatar' => $avatarList[$chefList[$k]['avatar']]['url'] ?? '', + 'score' => $v, + ]; + } + + return $this->return->success('success',['list' => $res]); + } +} \ No newline at end of file diff --git a/sync/http/admin/address.http b/sync/http/admin/address.http index 1562e82..df00727 100644 --- a/sync/http/admin/address.http +++ b/sync/http/admin/address.http @@ -8,6 +8,12 @@ account=13632877014&password=123456 client.global.set("admin_token", response.body.data.token); %} + +### admintest +GET {{host}}/admin/login/test +Content-Type: application/x-www-form-urlencoded + + ### test GET {{host}}/admin/third/sts/test Content-Type: application/x-www-form-urlencoded