firstOrFail([ 'id', 'uid', 'school', 'address', 'home', 'height', 'star', 'introduce', 'expect', 'sex', 'photo_src', 'check_photo', 'is_sell', 'photo_1', 'photo_1_check', 'photo_2', 'photo_2_check', 'photo_3', 'photo_3_check', 'photo_4', 'photo_4_check', 'voice', 'voice_check', 'praises', 'age', 'star', 'is_recommend', 'is_push_feed', 'feed_push_type', 'black_at', 'last_three_day_feed', ]); $data->black_at = $data->black_at < time() ? 0 : $data->black_at; } else { /** @var PartnerModel $arg */ $data = $arg; } $data->photo_src = "https://oss.pocketuniversity.cn{$data->photo_src}"; $data->photo_1 = "https://oss.pocketuniversity.cn{$data->photo_1}"; $data->photo_2 = "https://oss.pocketuniversity.cn{$data->photo_2}"; $data->photo_3 = "https://oss.pocketuniversity.cn{$data->photo_3}"; $data->photo_4 = "https://oss.pocketuniversity.cn{$data->photo_4}"; if (1 != $data->check_photo) { unset($data->photo_src); } if (1 == $data->photo_1_check) { } else { unset($data->photo_1); } if (1 == $data->photo_2_check) { } else { unset($data->photo_2); } if (1 == $data->photo_3_check) { } else { unset($data->photo_3); } if (1 != $data->photo_4_check) { unset($data->photo_4); } if (1 != $data->voice_check) { unset($data->voice); } unset($data->check_photo); unset($data->photo_1_check); unset($data->photo_2_check); unset($data->photo_3_check); unset($data->photo_4_check); unset($data->voice_check); /** @var UserModel $user */ $user = UserModel::findOrFail($data->uid, [ 'uid', 'headimgurl', 'nickname', 'be_vip_at', 'supvip_endat', 'weixin', 'qq', 'age', 'star', 'introduce', 'expect', 'height', 'sex', 'school', 'address', 'home', 'tag_1', 'tag_2', 'tag_3', 'tag_4', 'login_at', 'location', 'identity_auth', 'wx_auth', 'claim_tag', 'work_state', 'logoff_at', ]); /** @var PartnerModel $data */ $key = "{beta:paint}"; $bool = Redis::Sismember($key, $uid); if (($inviteConf = InviteConfigModel::find($data->uid))) { $user->task_question = $inviteConf->task_question; $user->task_sing = $inviteConf->task_sing; $user->task_question_data = $inviteConf->task_question_data ? true : false; $user->setAttribute('task_paint', $bool ? true : $inviteConf->task_paint); $user->setAttribute('task_sing_data', $inviteConf->task_sing_data ? true : false); } else { $user->task_question = false; $user->task_sing = false; $user->task_question_data = false; $user->setAttribute('task_paint', $bool); $user->setAttribute('task_sing_data', false); } $user->setAttribute('invite_cnt', InvitationCardModel::where('invite_uid', $data->uid)->count()); // 与self的关系处理 $self = array( 'friend' => false, 'praise' => false, 'invite' => [], 'superlike' => false, ); if (is_int($uid)) { $friend = FriendsModel::where([['uid', $uid], ['friend_uid', $data->uid]])->first(); if ($uid != $data->uid && collect($friend)->isEmpty()) { !empty($user->weixin) && $user->weixin = mb_substr($user->weixin, 0, 1) . "****" . mb_substr( $user->weixin, -1, 1 ); !empty($user->qq) && $user->qq = mb_substr($user->qq, 0, 1) . "****" . mb_substr($user->qq, -1, 1); } /** @var array $self */ $self['friend'] = $friend; if (PraiseModel::where([['uid', $uid], ['partner_id', $data->id], ['type', 1]])->exists()) { $self['praise'] = true; } $invite = InvitationCardModel::where([ ['uid', $uid], ['invite_uid', $data->uid], ['expired_at', '>', time()], ])->groupBy('question_type')->get(['question_type'])->pluck('question_type'); $self['invite'] = $invite->toArray(); if (SuperLikeModel::where([['uid', $uid], ['partner_id', $data->id]])->exists()) { $self['superlike'] = true; } } $data->setAttribute('user', $user); $data->setAttribute('self', $self); return $data; } /** * 获取用户算法方案和算法推荐结果 * @param int $uid * @return FeedAlgorithmModel|Builder|mixed|null * @throws AlertException */ public function randAlgorithm(int $uid) { $algorithms = FeedAlgorithmModel::where([ ['open_at', '<', date('Y-m-d H:i:s')], ['end_at', '>', date('Y-m-d H:i:s')], ])->get(); $total = $algorithms->sum('rate'); $rand = rand(1, $total); $value = 0; foreach ($algorithms as $algorithm) { /** @var FeedAlgorithmModel $algorithm */ $value = $value + $algorithm->rate; if ($rand <= $value) { UserStateModel::set($uid, 'algorithm', $algorithm->name); return $algorithm; } } return null; } /** * 获取算法推荐的集合 * @param int $uid * @param int $sex 性别1男2女 * @return array|null * @throws AlertException */ public function algorithmRecommend(int $uid, int $sex = 1) { $current = UserStateModel::get($uid, 'algorithm'); $selectAlgo = null; if (is_null($current)) { $selectAlgo = $this->randAlgorithm($uid); } else { $algorithm = FeedAlgorithmModel::where('name', $current)->first(); if (collect($algorithm)->isEmpty() || !Carbon::now()->isBetween($algorithm->open_at, $algorithm->end_at)) { $selectAlgo = $this->randAlgorithm($uid); } else { $selectAlgo = $algorithm; } } if (is_null($selectAlgo)) { // 用户方案, 算法结果 return ['N', null]; } else { if ('N' == $selectAlgo->name) { // 用户方案, 算法结果 return ['N', null]; } else { if (1 == $sex) { $api = trim($selectAlgo->man_url . $uid . "?number=464656"); } else { $api = trim($selectAlgo->woman_url . $uid . "?number=464656"); } $resp = Curl::to($api)->withTimeout(1)->asJsonResponse(true)->get(); return [$selectAlgo->name, $resp]; } } } /** * 信息流-对检索的结果进行组合输出 * @param $feedArr * @param int $sxo * @param int $take * @return array * @throws AlertException */ public function feed($feedArr, int $sxo, int $take = 2) { // 不同用户层级分配的卡片层级比例 $levelRates = array( 'A' => [ 1 => [ [ 'pool' => 'likeme', 'weight' => 5, ], [ 'pool' => 'pool', 'weight' => 95, 'attribute' => [ [ 'pool' => 'low', 'weight' => 10, ], [ 'pool' => 'normal', 'weight' => 90, 'attribute' => [ [ 'pool' => 'select', 'weight' => 80, ], [ 'pool' => 'recommend', 'weight' => 20, ], [ 'pool' => 'new', 'weight' => 0, ], ], ], ], ], ], 2 => [ [ 'pool' => 'likeme', 'weight' => 5, ], [ 'pool' => 'pool', 'weight' => 95, 'attribute' => [ [ 'pool' => 'low', 'weight' => 5, ], [ 'pool' => 'normal', 'weight' => 95, 'attribute' => [ [ 'pool' => 'select', 'weight' => 80, ], [ 'pool' => 'recommend', 'weight' => 20, ], [ 'pool' => 'new', 'weight' => 0, ], ], ], ], ], ], ], 'B' => [ 1 => [ [ 'pool' => 'likeme', 'weight' => 5, ], [ 'pool' => 'pool', 'weight' => 95, 'attribute' => [ [ 'pool' => 'low', 'weight' => 25, ], [ 'pool' => 'normal', 'weight' => 75, 'attribute' => [ [ 'pool' => 'select', 'weight' => 40, ], [ 'pool' => 'recommend', 'weight' => 60, ], [ 'pool' => 'new', 'weight' => 0, ], ], ], ], ], ], 2 => [ [ 'pool' => 'likeme', 'weight' => 5, ], [ 'pool' => 'pool', 'weight' => 95, 'attribute' => [ [ 'pool' => 'low', 'weight' => 20, ], [ 'pool' => 'normal', 'weight' => 80, 'attribute' => [ [ 'pool' => 'select', 'weight' => 40, ], [ 'pool' => 'recommend', 'weight' => 60, ], [ 'pool' => 'new', 'weight' => 0, ], ], ], ], ], ], ], 'C' => [ 1 => [ [ 'pool' => 'likeme', 'weight' => 5, ], [ 'pool' => 'pool', 'weight' => 95, 'attribute' => [ [ 'pool' => 'low', 'weight' => 25, ], [ 'pool' => 'normal', 'weight' => 75, 'attribute' => [ [ 'pool' => 'select', 'weight' => 20, ], [ 'pool' => 'recommend', 'weight' => 80, ], [ 'pool' => 'new', 'weight' => 0, ], ], ], ], ], ], 2 => [ [ 'pool' => 'likeme', 'weight' => 5, ], [ 'pool' => 'pool', 'weight' => 95, 'attribute' => [ [ 'pool' => 'low', 'weight' => 20, ], [ 'pool' => 'normal', 'weight' => 80, 'attribute' => [ [ 'pool' => 'select', 'weight' => 20, ], [ 'pool' => 'recommend', 'weight' => 80, ], [ 'pool' => 'new', 'weight' => 0, ], ], ], ], ], ], ], 'D' => [ 1 => [ [ 'pool' => 'likeme', 'weight' => 5, ], [ 'pool' => 'pool', 'weight' => 95, 'attribute' => [ [ 'pool' => 'low', 'weight' => 25, ], [ 'pool' => 'normal', 'weight' => 75, 'attribute' => [ [ 'pool' => 'select', 'weight' => 10, ], [ 'pool' => 'recommend', 'weight' => 90, ], [ 'pool' => 'new', 'weight' => 0, ], ], ], ], ], ], 2 => [ [ 'pool' => 'likeme', 'weight' => 5, ], [ 'pool' => 'pool', 'weight' => 95, 'attribute' => [ [ 'pool' => 'low', 'weight' => 20, ], [ 'pool' => 'normal', 'weight' => 80, 'attribute' => [ [ 'pool' => 'select', 'weight' => 10, ], [ 'pool' => 'recommend', 'weight' => 90, ], [ 'pool' => 'new', 'weight' => 0, ], ], ], ], ], ], ], 'E' => [ 1 => [ [ 'pool' => 'likeme', 'weight' => 5, ], [ 'pool' => 'pool', 'weight' => 95, 'attribute' => [ [ 'pool' => 'low', 'weight' => 25, ], [ 'pool' => 'normal', 'weight' => 75, 'attribute' => [ [ 'pool' => 'select', 'weight' => 20, ], [ 'pool' => 'recommend', 'weight' => 50, ], [ 'pool' => 'new', 'weight' => 30, ], ], ], ], ], ], 2 => [ [ 'pool' => 'likeme', 'weight' => 5, ], [ 'pool' => 'pool', 'weight' => 95, 'attribute' => [ [ 'pool' => 'low', 'weight' => 20, ], [ 'pool' => 'normal', 'weight' => 80, 'attribute' => [ [ 'pool' => 'select', 'weight' => 20, ], [ 'pool' => 'recommend', 'weight' => 50, ], [ 'pool' => 'new', 'weight' => 30, ], ], ], ], ], ], ], 'F' => [ 1 => [ [ 'pool' => 'likeme', 'weight' => 5, ], [ 'pool' => 'pool', 'weight' => 95, 'attribute' => [ [ 'pool' => 'low', 'weight' => 25, ], [ 'pool' => 'normal', 'weight' => 75, 'attribute' => [ [ 'pool' => 'select', 'weight' => 10, ], [ 'pool' => 'recommend', 'weight' => 60, ], [ 'pool' => 'new', 'weight' => 30, ], ], ], ], ], ], 2 => [ [ 'pool' => 'likeme', 'weight' => 5, ], [ 'pool' => 'pool', 'weight' => 95, 'attribute' => [ [ 'pool' => 'low', 'weight' => 20, ], [ 'pool' => 'normal', 'weight' => 80, 'attribute' => [ [ 'pool' => 'select', 'weight' => 10, ], [ 'pool' => 'recommend', 'weight' => 60, ], [ 'pool' => 'new', 'weight' => 30, ], ], ], ], ], ], ], 'G' => [ 1 => [ [ 'pool' => 'likeme', 'weight' => 5, ], [ 'pool' => 'pool', 'weight' => 95, 'attribute' => [ [ 'pool' => 'low', 'weight' => 25, ], [ 'pool' => 'normal', 'weight' => 75, 'attribute' => [ [ 'pool' => 'select', 'weight' => 5, ], [ 'pool' => 'recommend', 'weight' => 65, ], [ 'pool' => 'new', 'weight' => 30, ], ], ], ], ], ], 2 => [ [ 'pool' => 'likeme', 'weight' => 5, ], [ 'pool' => 'pool', 'weight' => 95, 'attribute' => [ [ 'pool' => 'low', 'weight' => 20, ], [ 'pool' => 'normal', 'weight' => 80, 'attribute' => [ [ 'pool' => 'select', 'weight' => 5, ], [ 'pool' => 'recommend', 'weight' => 65, ], [ 'pool' => 'new', 'weight' => 30, ], ], ], ], ], ], ], ); $disRates = array( 'likeme' => array( [ 'min' => 0, 'max' => 20, 'weight' => 30, ], [ 'min' => 20, 'max' => 50, 'weight' => 30, ], [ 'min' => 50, 'max' => 100, 'weight' => 15, ], [ 'min' => 100, 'max' => 200, 'weight' => 10, ], [ 'min' => 200, 'max' => 300, 'weight' => 7, ], [ 'min' => 300, 'max' => 400, 'weight' => 5, ], [ 'min' => 400, 'max' => INF, 'weight' => 3, ], ), 'low' => array( [ 'min' => 0, 'max' => 20, 'weight' => 30, ], [ 'min' => 20, 'max' => 50, 'weight' => 30, ], [ 'min' => 50, 'max' => 100, 'weight' => 15, ], [ 'min' => 100, 'max' => 200, 'weight' => 10, ], [ 'min' => 200, 'max' => 300, 'weight' => 7, ], [ 'min' => 300, 'max' => 400, 'weight' => 5, ], [ 'min' => 400, 'max' => INF, 'weight' => 3, ], ), 'new' => array( [ 'min' => 0, 'max' => 20, 'weight' => 30, ], [ 'min' => 20, 'max' => 50, 'weight' => 30, ], [ 'min' => 50, 'max' => 100, 'weight' => 15, ], [ 'min' => 100, 'max' => 200, 'weight' => 10, ], [ 'min' => 200, 'max' => 300, 'weight' => 7, ], [ 'min' => 300, 'max' => 400, 'weight' => 5, ], [ 'min' => 400, 'max' => INF, 'weight' => 3, ], ), 'recommend' => array( [ 'min' => 0, 'max' => 20, 'weight' => 30, ], [ 'min' => 20, 'max' => 50, 'weight' => 30, ], [ 'min' => 50, 'max' => 100, 'weight' => 15, ], [ 'min' => 100, 'max' => 200, 'weight' => 10, ], [ 'min' => 200, 'max' => 300, 'weight' => 7, ], [ 'min' => 300, 'max' => 400, 'weight' => 5, ], [ 'min' => 400, 'max' => INF, 'weight' => 3, ], ), 'select' => array( [ 'min' => 0, 'max' => 20, 'weight' => 30, ], [ 'min' => 20, 'max' => 50, 'weight' => 30, ], [ 'min' => 50, 'max' => 100, 'weight' => 15, ], [ 'min' => 100, 'max' => 200, 'weight' => 10, ], [ 'min' => 200, 'max' => 300, 'weight' => 7, ], [ 'min' => 300, 'max' => 400, 'weight' => 5, ], [ 'min' => 400, 'max' => INF, 'weight' => 3, ], ), ); // dump("开始:".microtime(true)); $res = array(); for ($i = 0; $i < $take; $i++) { // 滑卡奖励 $flowerLog = new FlowerLogModel(); if (is_int($feedArr['uid']) && $redpack = $flowerLog->redpack($feedArr['uid'])) { Redis::hincrby("session_msy_{$feedArr['uid']}", "feed_limit", 1); $res[] = array( 'type' => 1, 'data' => $redpack, ); continue; } // dump(" 输出单元{$i}:" . microtime(true)); $levelCnt = 0; do { // dump(" 输出单元{$i}定义检索条件:" . microtime(true)); $poolList = $levelRates[$feedArr['feed_level']][$sxo]; ++$levelCnt; if (0 == $feedArr['partner_id']) { $pool = "select"; // 新用户只看精选的 - 正常卡片-标记精选池分配比例 } else { $poolArray = $this->getRandomList($poolList, 'weight'); if ('pool' == $poolArray['pool']) { $poolArray = $this->getRandomList($poolArray['attribute'], 'weight'); if ('normal' == $poolArray['pool']) { $poolArray = $this->getRandomList($poolArray['attribute'], 'weight'); $pool = $poolArray['pool']; } else { $pool = $poolArray['pool']; } } else { $pool = $poolArray['pool']; } } $disList = $disRates[$pool]; $disArray = $this->getRandomList($disList, 'weight'); $reduceFeed = Redis::get("reduce_feed"); if ($reduceFeed && $reduceFeed > 0) { $disArray = array( 'min' => 0, 'max' => INF, ); } // dump(" 得到检索条件:性别:{$sxo};池:{$pool};min:{$disArray['min']};max:{$disArray['max']};" .microtime(true)); $feed = $this->feedpartner($feedArr, $sxo, $pool, $disArray['min'], $disArray['max']); // Ntodo 移除没有的$poolList和$disList } while (collect($feed)->isEmpty() && $levelCnt < 3); if (collect($feed)->isEmpty()) { continue; } else { Redis::hincrby("session_msy_{$feedArr['uid']}", "feed_limit", 1); $res[] = array( 'type' => $feed->feed_push_type, 'data' => $feed, ); continue; } } return $res; } /** * 检索feed流卡片 * @param array $feedUser 检索者信息 * @param int $sxo 输出性别 * @param string $pool 卡片等级 * @param int $minDis 最近距离 * @param float $maxDis 最远距离 * @return PartnerModel * @throws AlertException */ public function feedpartner(array $feedUser, int $sxo, string $pool, int $minDis = 0, float $maxDis = INF) { // LBS缓存key $cacheLbskey = ($sxo == 1) ? "fpdx:user:locations:sell:boy" : "fpdx:user:locations:sell:girl"; // 看过的卡片id缓存key $cacheIsSeedPartnerIds = "charge_feed_{$feedUser['uid']}"; // 信息流所以卡片分数缓存key $cachePartnerScore = "feed:push:partner:score"; // 卡片等级缓存key $searchUidskey = "feed:pool:{$pool}:sex:{$sxo}"; // 某人某条件搜索的缓存key $cacheKey = "feed:user:{$feedUser['uid']}:sex:{$sxo}:pool:{$pool}:mindis:{$minDis}:maxdis:{$maxDis}"; // 某人某条件搜索的结果缓存key $cacheResult = "feed:user:{$feedUser['uid']}:sex:{$sxo}:pool:{$pool}:mindis:{$minDis}:maxdis:{$maxDis}:result"; $feed = new PartnerModel(); $whereUid = Redis::smembers($cacheKey); if (empty($whereUid)) { // dump("未命中缓存:".microtime(true)); // 可查询的用户uid集合A $where = array(['sex', $sxo], ['is_sell', 1], ['score', '>=', 0]); switch ($pool) { case 'likeme': // 喜欢我的 $useAlgorithm = false; $uids = PraiseModel::where([ ['partner_id', $feedUser['partner_id']], ['type', 1], ])->get(['uid'])->pluck('uid')->toArray(); $searchUids = PartnerModel::where(array(['sex', $sxo], ['is_sell', 1]))->whereIn( 'uid', $uids )->get(['uid'])->pluck('uid')->toArray(); break; case 'select': // 正常卡片-标记精选池分配比例 $useAlgorithm = false; // 是否运用算法 $where[] = array('is_select', 1); $searchUids = PartnerModel::where($where)->get(['uid'])->pluck('uid')->toArray(); break; case 'low': // 低保池 $useAlgorithm = false; // 是否运用算法 $searchUids = Redis::Smembers($searchUidskey); break; case 'recommend': // 正常卡片-标记推荐池分配比例 $useAlgorithm = false; // 是否运用算法 true $searchUids = Redis::Smembers($searchUidskey); break; case 'new': // 正常卡片-新卡片(未标记)池分配比例 $useAlgorithm = false; // 是否运用算法 true $searchUids = Redis::Smembers($searchUidskey); break; default: return $feed; } $whereUid = $searchUids; // 看过的用户集合B $hideIds = Redis::zrange($cacheIsSeedPartnerIds, 0, -1); if (empty($hideIds)) { $hideIds = array(); } $isSeeUids = PartnerModel::whereIn('id', $hideIds)->get(['uid'])->pluck('uid')->toArray(); $whereUid = array_diff($whereUid, $isSeeUids); if (empty($whereUid)) { Redis::sadd($cacheKey, ...[-1]); Redis::setex($cacheResult, 3600 * 2, 0); Redis::expire($cacheKey, 3600 * 2); return collect([]); } // LBS用户集合D if (INF == $maxDis) { $maxDisUids = $whereUid; } else { $maxDisUids = Redis::geoRadius( $cacheLbskey, $feedUser['lng'], $feedUser['lat'], $maxDis, "km", array('ASC') ); } if (0 == $minDis) { $minDisUids = array(); } else { $minDisUids = Redis::geoRadius( $cacheLbskey, $feedUser['lng'], $feedUser['lat'], $minDis, "km", array('ASC') ); } $disUids = array_diff($maxDisUids, $minDisUids); $whereUid = array_intersect($whereUid, $disUids); // 算法卡片用户集合C $isAlgorithm = false; if (is_integer($feedUser['uid'])) { list($selectPlan, $algorithm) = $this->algorithmRecommend($feedUser['uid'], $feedUser['sex']); } else { list($selectPlan, $algorithm) = array('N', null); } if ($useAlgorithm && !is_null($algorithm)) { $algorithmIds = array_column($algorithm, 'ItemId'); $algoHash = array_combine($algorithmIds, $algorithm); $algorithmUids = PartnerModel::whereIn('id', $algorithmIds)->get(['id', 'uid'])->toArray(); $algorithmUids = array_column($algorithmUids, 'uid'); $byFeed = $selectPlan; } else { $algorithmUids = []; $algoHash = []; $byFeed = 'N'; } // 结果集合E $whereAUid = array_intersect($whereUid, $algorithmUids); if (empty($whereAUid)) { $algoHash = []; $byFeed = 'N'; } else { $whereUid = $whereAUid; } } else { if (1 == abs($whereUid[0])) { // dump("未命中缓存".microtime(true)); return collect([]); } // dump("命中缓存".microtime(true)); // 看过的用户集合B $hideIds = Redis::zrange("charge_feed_{$feedUser['uid']}", 0, -1); if (empty($hideIds)) { $hideIds = array(); } $isSeeUids = PartnerModel::whereIn('id', $hideIds)->get(['uid'])->pluck('uid')->toArray(); $whereUid = array_diff($whereUid, $isSeeUids); if (is_integer($feedUser['uid'])) { list($selectPlan, $algorithm) = $this->algorithmRecommend($feedUser['uid'], $feedUser['sex']); } else { list($selectPlan, $algorithm) = array('N', null); } if (!is_null($algorithm)) { $algorithmIds = array_column($algorithm, 'ItemId'); $algoHash = array_combine($algorithmIds, $algorithm); } else { $algoHash = []; } $isAlgorithm = Redis::get($cacheResult) ?? 0; if ($isAlgorithm) { $byFeed = $selectPlan; } else { $byFeed = "N"; } } if (empty($whereUid)) { Redis::sadd($cacheKey, ...[-1]); Redis::setex($cacheResult, 3600 * 2, 0); Redis::expire($cacheKey, 3600 * 2); return collect([]); } $scoreRandomUids = array(); foreach ($whereUid as $wu) { $scoreRandomUids[] = array( 'uid' => $wu, 'score' => rand(1, 100), ); } // dump(" 得到可用集合" . microtime(true)); // $whereUidScores = array_combine($whereUid, array_pad([], count($whereUid), 0)); // Redis::zadd($cachePartnerScoreWhereUid, $whereUidScores); // Redis::zinterstore($cachePartnerScoreResult, [$cachePartnerScore, $cachePartnerScoreWhereUid]); // $scoreUids = Redis::zrange($cachePartnerScoreResult, 0, -1, ['withscores' => true]); // Redis::del([$cachePartnerScoreWhereUid, $cachePartnerScoreResult]); // if (empty($scoreUids)) { // Redis::sadd($cacheKey, ...[-1]); // Redis::setex($cacheResult, 3600 * 2, 0); // Redis::expire($cacheKey, 3600 * 2); // return collect([]); // } else { // $scoreRandomUids = array(); // foreach ($scoreUids as $sk => $sv) { // $scoreRandomUids[] = array( // 'score' => $sv, // 'uid' => $sk // ); // } // } $resultUid = $this->getRandomList($scoreRandomUids, 'score'); // dump(" 加权随机" . microtime(true)); $cacheWhereUid = array_diff($whereUid, [$resultUid['uid']]); if (!empty($cacheWhereUid)) { Redis::sadd($cacheKey, ...$cacheWhereUid); Redis::setex($cacheResult, 3600 * 2, $isAlgorithm ? 1 : 0); Redis::expire($cacheKey, 3600 * 2); } $feed = PartnerModel::whereIn('uid', $resultUid)->where('is_sell', 1)->first(); if (!$feed) { return collect([]); } $feed = $this->getPartner(/** @var PartnerModel $feed */ $feed, $feedUser['uid']); if ($pool == 'low') { $feed->setAttribute('feed_push_type', 5); } $attach = array( 'belong_feed' => $selectPlan, // 用户所属方案 'by_feed' => $byFeed, // 卡片的输出方案 'algorithm_score' => 0, // 算法分数 'user_level' => $feedUser['feed_level'], // 用户分级 'partner_level' => $pool, // 卡片分级 ); if ($isAlgorithm) { $attach['algorithm_score'] = $algoHash[$feed->id]['Score']; } $feed->setAttribute('attach', json_encode($attach)); Redis::zadd("charge_feed_{$feedUser['uid']}", [$feed->id => time()]); Redis::expire("charge_feed_{$feedUser['uid']}", 86400 * 60); // dump(" 输出:".microtime(true)); return $feed; } /** * 加权随机数 * @param array $list 随机项 * @param string $weightKey 权重字段 * @return int|string|null */ public function getRandomList(array $list, string $weightKey = "score") { $sum = 0; $listPoint = array(0); foreach ($list as $key => $value) { if (0 > $value[$weightKey]) { $value[$weightKey] = 1; } $sum += $value[$weightKey]; array_push($listPoint, $sum); } $num = rand(0, $sum); for ($i = 0; $i < count($listPoint) - 1; $i++) { if ($num >= $listPoint[$i] && $num <= $listPoint[$i + 1]) { $elem = array_slice($list, $i, 1); return array_pop($elem); } } return array_pop(array_slice($list, 0, 1)); } /** * 获取用户和卡片之间的分数 * @param int $uid * @param int $partner_id * @return array */ public function getScoreUid2Partner(int $uid, int $partner_id): array { $info = array(); $user = UserModel::find($uid); /** @var UserModel $puser */ $puser = UserModel::where('partner_id', $partner_id)->firstOrFail(); $score = array( 'score' => 0, 'show' => "not_show", ); // 预需求 $user_pre = explode(',', $user->claim_tag); $puser_pre = explode(',', $puser->claim_tag); $pre_request = count(array_intersect($user_pre, $puser_pre)) * 20; if ($pre_request > $score['score']) { $score['score'] = $pre_request; $score['show'] = "pre_request"; } // 工作状态 $work_state = 0; if (!empty($puser->school)) { switch ($user->work_state) { case "工作党": $work_state = 40; break; case "高中党": case "大学党": $work_state = 80; break; default: $work_state = 60; break; } } if ($work_state > $score['score']) { $score['score'] = $work_state; $score['show'] = "work_state"; } // 兴趣 $puser_tag2 = explode(',', $puser->tag_2); $user_tag2 = explode(',', $user->tag_2); $puser_tag3 = explode(',', $puser->tag_3); $user_tag3 = explode(',', $user->tag_3); $puser_tag4 = explode(',', $puser->tag_4); $user_tag4 = explode(',', $user->tag_4); $intersect_tag2 = array_intersect($puser_tag2, $user_tag2); $intersect_tag3 = array_intersect($puser_tag3, $user_tag3); $intersect_tag4 = array_intersect($puser_tag4, $user_tag4); $info['tags'] = compact('intersect_tag2', 'intersect_tag3', 'intersect_tag4'); $cnt = count($intersect_tag2) + count($intersect_tag3) + count($intersect_tag4); $tag = $cnt * 10; if ($tag > $score['score']) { $score['score'] = $tag; $score['show'] = "tag"; } // 身高 if ($user->height != 0 && $puser->height != 0) { if ($user->sex == 1) { $height = ceil(100 / abs($user->height / 1.09 - $puser->height)); } else { $height = ceil(100 / abs($puser->height / 1.09 - $user->height)); } if ($height > $score['score']) { $score['score'] = $height; $score['show'] = "height"; } } // 家乡 $home = 0; if (!empty($puser->home)) { if ($puser->home == $user->home) { $home = 100; } if ($home > $score['score']) { $score['score'] = $home; $score['show'] = "home"; } } // 距离 $geo = new Geohash(); $dis = $geo->getDistance($user->lat, $user->lng, $puser->lat, $puser->lng) + rand(1000, 5000); return array($puser->location, $dis, $score, $info); } /** * feed流每日滑卡片限制 * @param int $uid * @return bool */ public function limit(int $uid) { $last_time = Redis::hget("session_msy_{$uid}", "last_feed_time") ?? time(); if ($last_time < mktime(0, 0, 0)) { Redis::hset("session_msy_{$uid}", "feed_limit", 0); Redis::hset("session_msy_{$uid}", "flower_limit", 0); Redis::hset("session_msy_{$uid}", "hd_cnt", 0); Redis::hset("session_msy_{$uid}", "init_recommend_queues", 0); $count = Redis::zcount("fpdx:feed:thumb:{$uid}", 0, "+inf"); if ($count <= 10) { Redis::hset("session_msy_{$uid}", "thumbme_limit", 2); } elseif ($count > 10 && $count < 50) { Redis::hset("session_msy_{$uid}", "thumbme_limit", 5); } else { Redis::hset("session_msy_{$uid}", "thumbme_limit", 10); } } $limit = Redis::hget("session_msy_{$uid}", "hd_cnt"); $low_limit = Redis::hget("session_msy_{$uid}", "feed_limit"); Redis::hset("session_msy_{$uid}", "last_feed_time", time()); Redis::expire("session_msy_{$uid}", 86400); if (($limit > 200 || $low_limit > 400) && !in_array($uid, [94302, 98047, 10771042])) { return false; } else { if (0 == Redis::hget("session_msy_{$uid}", "init_recommend_queues")) { FeedRecommendJob::dispatch($uid); Redis::hset("session_msy_{$uid}", "init_recommend_queues", 1); } Redis::hincrby("session_msy_{$uid}", "feed_count", 1); return true; } } /** * 获取点赞队列存量 * @param int $uid * @return int */ public function getHasThumb(int $uid) { $count = Redis::zcount("fpdx:feed:thumb:{$uid}", 0, "+inf"); return $count; } /** * 提醒TA补全信息 * @param int $uid * @param int $remind_uid * @param $type * @return bool */ public function remindType4(int $uid, int $remind_uid, $type): bool { // 我提醒过 if ( FeedType4RemindModel::where([ ['uid', $uid], ['is_remind_uid', $remind_uid], ['is_update_feedback_at', 0], ['type', $type], ])->exists() ) { return false; } FeedType4RemindModel::create([ 'uid' => $uid, 'is_remind_uid' => $remind_uid, 'type' => $type, ]); // 被提醒过两次以上则不发送 if (FeedType4RemindModel::where(['is_remind_uid' => $remind_uid, 'type' => $type])->count() > 2) { return false; } switch ($type) { case '1': // 发起提醒事件 $nickname = UserModel::find($uid)->value('nickname'); event(new CompleteInfoRemind(UserModel::find($remind_uid), $nickname)); break; case '2': // 通知队列 $to_user = UserModel::find($remind_uid); $from_user = UserModel::find($uid); $payload = [ 'to_user' => $to_user->getAuth(), 'user' => $to_user->toArray(), 'from_user' => $from_user->toArray(), ]; dispatch(new RegistEventJob(100009, $payload))->onQueue("{push}"); break; case '3': // 通知队列 $to_user = UserModel::find($remind_uid); $from_user = UserModel::find($uid); $payload = [ 'to_user' => $to_user->getAuth(), 'user' => $to_user->toArray(), 'from_user' => $from_user->toArray(), ]; dispatch(new RegistEventJob(100008, $payload))->onQueue("{push}"); break; } return true; } }