feat : rank
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
// +--------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
12
app/Constants/Common/LeaderboardHistoryCode.php
Normal file
12
app/Constants/Common/LeaderboardHistoryCode.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Constants\Common;
|
||||
|
||||
class LeaderboardHistoryCode
|
||||
{
|
||||
/**
|
||||
* 排行榜类型 1=厨师评分榜 2=厨师销量榜
|
||||
*/
|
||||
const int TYPE_CHEF_SCORE = 1;
|
||||
const int TYPE_CHEF_SALE = 2;
|
||||
}
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace App\Controller\Admin;
|
||||
|
||||
use App\Controller\AbstractController;
|
||||
use App\Extend\DateUtil;
|
||||
use App\Request\Admin\LoginRequest;
|
||||
use App\Service\Admin\Login\LoginService;
|
||||
use Hyperf\HttpServer\Annotation\Controller;
|
||||
@@ -32,4 +33,14 @@ class LoginController extends AbstractController
|
||||
$service = new LoginService();
|
||||
return $service->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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
219
app/Cron/Chef/RankTask.php
Normal file
219
app/Cron/Chef/RankTask.php
Normal file
@@ -0,0 +1,219 @@
|
||||
<?php
|
||||
/**
|
||||
* This crontab file is part of item.
|
||||
*
|
||||
* @author ctexthuang
|
||||
* @contact ctexthuang@qq.com
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Cron\Chef;
|
||||
|
||||
use App\Constants\Admin\UserCode;
|
||||
use App\Constants\Common\LeaderboardHistoryCode;
|
||||
use App\Extend\DateUtil;
|
||||
use App\Lib\Log;
|
||||
use App\Model\Chef;
|
||||
use App\Model\ChefStatement;
|
||||
use App\Model\Evaluation;
|
||||
use App\Model\Kitchen;
|
||||
use App\Model\LeaderboardHistory;
|
||||
use Exception;
|
||||
use Hyperf\Crontab\Annotation\Crontab;
|
||||
use Hyperf\DbConnection\Db;
|
||||
use Hyperf\Di\Annotation\Inject;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
|
||||
#[Crontab(rule: "0 12 * * 6", name: "RankTask", singleton: true , callback: "execute", memo: "厨师排行榜数据生成")]
|
||||
class RankTask
|
||||
{
|
||||
/**
|
||||
* @var ChefStatement
|
||||
*/
|
||||
#[Inject]
|
||||
protected ChefStatement $chefStatementModel;
|
||||
|
||||
/**
|
||||
* @var Evaluation
|
||||
*/
|
||||
#[Inject]
|
||||
protected Evaluation $evaluationModel;
|
||||
|
||||
/**
|
||||
* @var LeaderboardHistory
|
||||
*/
|
||||
#[Inject]
|
||||
protected LeaderboardHistory $leaderboardHistoryModel;
|
||||
|
||||
/**
|
||||
* @var Log
|
||||
*/
|
||||
#[Inject]
|
||||
protected Log $log;
|
||||
|
||||
/**
|
||||
* @var Chef
|
||||
*/
|
||||
#[Inject]
|
||||
protected Chef $chefModel;
|
||||
|
||||
/**
|
||||
* @var Kitchen
|
||||
*/
|
||||
#[Inject]
|
||||
protected Kitchen $kitchenModel;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $thisWeekInfo;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $chefInfo;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $kitchenSiteList;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $insertData;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected string $nowDate;
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
public function execute(): void
|
||||
{
|
||||
try {
|
||||
$this->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,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
];
|
||||
|
||||
}
|
||||
}
|
||||
35
app/Model/LeaderboardHistory.php
Normal file
35
app/Model/LeaderboardHistory.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
use Hyperf\DbConnection\Model\Model;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $board_type
|
||||
* @property int $time_key
|
||||
* @property int $kitchen_id
|
||||
* @property int $city_id
|
||||
* @property int $chef_id
|
||||
* @property int $score
|
||||
* @property string $create_time
|
||||
*/
|
||||
class LeaderboardHistory extends Model
|
||||
{
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*/
|
||||
protected ?string $table = 'leaderboard_history';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*/
|
||||
protected array $fillable = [];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to native types.
|
||||
*/
|
||||
protected array $casts = ['id' => 'integer', 'board_type' => 'integer', 'time_key' => 'integer', 'kitchen_id' => 'integer', 'city_id' => 'integer', 'chef_id' => 'integer', 'score' => 'integer'];
|
||||
}
|
||||
115
app/Service/Api/System/GetLeaderboardService.php
Normal file
115
app/Service/Api/System/GetLeaderboardService.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
/**
|
||||
* This service file is part of item.
|
||||
*
|
||||
* @author ctexthuang
|
||||
* @contact ctexthuang@qq.com
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service\Api\System;
|
||||
|
||||
use App\Cache\Redis\Api\ApiRedisKey;
|
||||
use App\Cache\Redis\RedisCache;
|
||||
use App\Constants\Common\LeaderboardHistoryCode;
|
||||
use App\Extend\DateUtil;
|
||||
use App\Model\AdminUser;
|
||||
use App\Model\LeaderboardHistory;
|
||||
use App\Model\SystemCity;
|
||||
use App\Service\Api\BaseService;
|
||||
use App\Service\ServiceTrait\Common\OssTrait;
|
||||
use Hyperf\Di\Annotation\Inject;
|
||||
|
||||
class GetLeaderboardService extends BaseService
|
||||
{
|
||||
use OssTrait;
|
||||
|
||||
/**
|
||||
* @var SystemCity
|
||||
*/
|
||||
#[Inject]
|
||||
protected SystemCity $systemCityModel;
|
||||
|
||||
/**
|
||||
* @var RedisCache
|
||||
*/
|
||||
#[Inject]
|
||||
protected RedisCache $redis;
|
||||
|
||||
/**
|
||||
* @var LeaderboardHistory
|
||||
*/
|
||||
#[Inject]
|
||||
protected LeaderboardHistory $leaderboardHistoryModel;
|
||||
|
||||
/**
|
||||
* @var AdminUser
|
||||
*/
|
||||
#[Inject]
|
||||
protected AdminUser $adminUserModel;
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$cityId = (int)$this->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]);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user