ActivityController.php 9.03 KB
<?php

namespace App\Http\Container\AppSection\Controllers\Recommend;

use App\Enums\ActivityAuditStatusEnum;
use App\Enums\ActivityCreateFormEnum;
use App\Enums\ActivitySongTypeEnum;
use App\Enums\ActivityStatusEnum;
use App\Enums\ActivityWorkStatusEnum;
use App\Enums\ActivityWorkTypeEnum;
use App\Helpers\ConfigHelper;
use App\Helpers\JsonResource;
use App\Models\Activity;
use App\Models\ActivityUser;
use App\Models\Pivots\ActivityTagPivot;
use App\Models\Pivots\UserActivityCollectionPivot;
use App\Models\Pivots\UserActivityViewPivot;
use App\Models\User;
use Arr;
use Auth;
use Cache;
use Hikoon\LaravelApi\Support\ApiController;
use Illuminate\Cache\TaggedCache;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Str;

class ActivityController extends ApiController
{
    private TaggedCache $cache;

    protected ?User $user;

    public function __construct()
    {
        $this->middleware(['auth.check:app', 'emptyToNull']);
        $this->cache = Cache::tags('recommend_activity');
    }

    /**
     * @return int
     */
    protected function getUserKey(): int
    {
        return $this->user?->getKey() ?? 0;
    }

    /**
     * @throws \Psr\SimpleCache\InvalidArgumentException
     * @throws \Random\RandomException
     */
    public function __invoke(Request $request): JsonResponse
    {
        $page       = $request->integer('page', 1);
        $pageSize   = $request->integer('size', 20);
        $identifier = $request->get('identifier') ?? Str::uuid()->toString();

        $this->user = Auth::user();

        $page === 1 && $this->cache->delete($identifier);

        $recommendIds = $this->getRecommendIds($identifier);
        $pageIds      = $recommendIds->forPage($page, $pageSize);
        $pageCount    = $recommendIds->count();

        return JsonResource::success(JsonResource::SUCCESS, [
            'identifier'    => $identifier,
            'count'         => $pageCount,
            'has_activitys' => $pageIds->isEmpty() ? [] : $this->get($pageIds->toArray()),
            'publish_count' => $this->getProjectCount()
        ]);
    }

    /**
     * @param array $fieldIds
     * @return \Illuminate\Database\Eloquent\Collection
     */
    public function get(array $fieldIds): Collection
    {
        return Activity::orderByField('id', $fieldIds)
            ->with(['tags:id,name', 'project:id,name,cover', 'linkArranger:id,nick_name', 'comfirmTime:project_id,average_day,updated_at'])
            ->select([
                'id', 'song_name', 'sub_title', 'cover', 'project_id', 'is_official', 'sex', 'lang',
                'publish_at', 'estimate_release_at', 'status', 'mark', 'speed', 'expand',
                'lyric', 'clip_lyric', 'guide', 'guide_clip', 'karaoke', 'karaoke_clip'
            ])
            ->addSelect(['is_view' => $this->getViewQuery(), 'is_collection' => $this->getCollectionQuery(), 'is_take' => $this->getTakeQuery(),])
            ->get()
            ->tap(fn(Collection $collection) => ConfigHelper::formatManyActivityDictionary($collection))
            ->each(function (Activity $activity) {
                $activity->setAttribute('status_tag', $activity->getAttribute('is_view') === 1 ? 'listened' : 'new');
                $activity->setAttribute('project', [$activity->getRelation('project')?->toArray()]);
                unset($activity->linkArranger, $activity->expand);
                $activity->unsetRelation('project');
            });
    }

    /**
     * @return int
     */
    public function getProjectCount(): int
    {
        return Activity::query()
            ->where('song_type', ActivitySongTypeEnum::SONG)
            ->where('audit_status', ActivityAuditStatusEnum::SUCCESS)
            ->whereIn('status', [ActivityStatusEnum::MATCH, ActivityStatusEnum::SEND])
            ->count();
    }


    /**
     * @throws \Random\RandomException
     */
    protected function getRecommendIds(string $key): Collection
    {
        $builder = Activity::filter([
            'tag'  => Arr::wrap(request('tag', [])),
            'sex'  => array_filter(explode(',', request('sex', ''))),
            'lang' => array_filter(explode(',', request('lang', ''))),
        ])
            ->select(['id', 'lang', 'sex'])
            ->addSelect(['is_listen' => UserActivityViewPivot::selectRaw('IFNULL(COUNT(*),0)')->whereColumn('activitys.id', 'activity_id')->where('user_id', $this->getUserKey())])
            ->where('audit_status', ActivityAuditStatusEnum::SUCCESS)
            ->where('song_type', ActivitySongTypeEnum::SONG)
            ->whereIn('created_form', [ActivityCreateFormEnum::ADMIN, ActivityCreateFormEnum::PROJECT])
            ->where('status', ActivityStatusEnum::UP)
            ->when(request('text'), fn(Builder $query, $name) => $query->where(fn(Builder $query) => $query->where('song_name', 'like', '%' . $name . '%')->orWhereRelation('project', 'name', 'like', '%' . $name . '%')))
            ->when(request()?->integer('filter_listen') === 1, fn(Builder $query) => $query->having('is_listen', 0));


        if (!$this->cache->has($key)) {
            $this->cache->put($key, request('sort_field') === 'latest' ? $this->getLastIds($builder) : $this->getRandomIds($builder), now()->addHour());
        }

        return $this->cache->get($key, Collection::make());
    }


    /**
     * @param \Illuminate\Database\Eloquent\Builder $builder
     * @return \Illuminate\Support\Collection
     */
    protected function getLastIds(Builder $builder): Collection
    {
        return $builder->latest('audit_at')->pluck('id');
    }

    /**
     * @param \Illuminate\Database\Eloquent\Builder $builder
     * @return \Illuminate\Support\Collection
     * @throws \Random\RandomException
     */
    protected function getRandomIds(Builder $builder): Collection
    {
        return $builder
            ->addSelect([
                'tag'          => ActivityTagPivot::selectRaw('CONCAT("[",IFNULL(GROUP_CONCAT(tag_id),""),"]")')->whereColumn('activitys.id', 'activity_id'),
                'view_count'   => UserActivityViewPivot::selectRaw('count(*)')->whereColumn('activitys.id', 'activity_id'),
                'submit_count' => ActivityUser::selectRaw('count(*)')->whereColumn('activitys.id', 'activity_id')->where('type', ActivityWorkTypeEnum::SUBMIT)
            ])
            ->withCasts(['tag' => 'array', 'is_listen' => 'int', 'submit_count' => "int", 'view_count' => 'int'])->get()
            ->each(fn(Activity $activity) => $this->computeScore($activity))
            ->sortByDesc('score')->values()->pluck('id');
    }

    /**
     * @param \App\Models\Activity $activity
     * @return void
     * @throws \Random\RandomException
     */
    protected function computeScore(Activity $activity): void
    {
        $userLang      = $this->user?->getAttribute('lang');
        $userSex       = Arr::get([1 => 'male', 2 => 'female'], $this->user?->getAttribute('sex'), 'unrestricted');
        $userTag       = $this->user?->getAttribute('styleTags')?->modelKeys();
        $tagMatchCount = collect($userTag)->merge($activity->getAttribute('tag'))->duplicates()->count();
        $submitRate    = $activity->getAttribute('view_count') === 0 ? 0 : bcdiv($activity->getAttribute('submit_count'), $activity->getAttribute('view_count'), 2);
        $activity->setAttribute('rate', $submitRate);

        $score = random_int(80, 160);
        $score += ($activity->getAttribute('sex') === 'unrestricted' || $activity->getAttribute('sex') === $userSex) ? random_int(40, 100) : random_int(20, 60);
        $score += $activity->getAttribute('is_listen') === 0 ? random_int(60, 180) : random_int(20, 60);
        $score += in_array($userLang, $activity->getAttribute('lang'), true) ? random_int(120, 200) : random_int(20, 60);
        $score += random_int(100, 800) * $submitRate;
        $score += $tagMatchCount ? random_int(100, 160) * $tagMatchCount : random_int(40, 80);

        $activity->setAttribute('score', $score);
    }

    /**
     * @return \Illuminate\Database\Query\Builder
     */
    protected function getViewQuery(): \Illuminate\Database\Query\Builder
    {
        return UserActivityViewPivot::selectRaw('IF(COUNT(*) = 0, 0, 1)')
            ->where('user_id', $this->getUserKey())
            ->whereColumn('activity_id', 'activitys.id')
            ->getQuery();
    }

    /**
     * @return \Illuminate\Database\Query\Builder
     */
    protected function getCollectionQuery(): \Illuminate\Database\Query\Builder
    {
        return UserActivityCollectionPivot::selectRaw('IF(COUNT(*) = 0, 0, 1)')
            ->where('user_id', $this->getUserKey())
            ->whereColumn('activity_id', 'activitys.id')
            ->getQuery();
    }

    /**
     * @return \Illuminate\Database\Query\Builder
     */
    protected function getTakeQuery(): \Illuminate\Database\Query\Builder
    {
        return ActivityUser::selectRaw('IF(COUNT(*) = 0, 0, 1)')
            ->where('user_id', $this->getUserKey())->whereColumn('activity_id', 'activitys.id')
            ->where('type', ActivityWorkTypeEnum::SUBMIT)->where('status', ActivityWorkStatusEnum::SUCCESS)
            ->getQuery();
    }
}