with(['user'])->where(['user_uid' => $userCode])->first($columns); } /** * 统计某人已经发布上架的视频个数. * @param $userCode * @return int */ public function countPublishedVideo($userCode) { return VideoModel::query()->where(['uid' => $userCode, 'status' => 1])->count(); } /** * 统计某人今日已经发布任何状态视频个数. * @param $userCode * @return int */ public function countTodayPublishedVideo($userCode) { $todayDate = Carbon::now()->startOfDay()->toDateTimeString(); return VideoModel::query()->where(['uid' => $userCode])->where('created_at', '>', $todayDate)->count(); } /** * 计算某人的点赞数. * @param $userCode * @return int|mixed|string|void */ public function sumLikedVideo($userCode) { return VideoModel::query()->where(['uid' => $userCode, 'status' => 1])->sum('like'); } /** * 申请审核. * @param $userCode * @return Builder|\Hyperf\Database\Model\Model */ public function applyToUncheck($userCode) { $initLevel = 0; $creatorObj = CreatorModel::query()->where(['user_uid' => $userCode])->first(); if (empty($creatorObj)) { $setarr = [ 'user_uid' => $userCode, 'is_bot' => 0, 'level' => $initLevel, 'income' => 0, ]; $creatorObj = CreatorModel::query()->create($setarr); // 清除缓存. $this->clearCountCreatorCache(); /* $userObj = UserModel::query()->where(['code' => $userCode])->first(['id', 'code', 'creator_level']); if (!empty($userObj)) { $userObj->creator_level = $initLevel; $userObj->save(); make(UserRepository::class)->delUserCache($userCode); } */ $eventParams = [ 'user_code' => $userCode, ]; dispatch_event(new CreatorApplyEvent($eventParams)); return $creatorObj; } else { throw new ApiException('已经是创作者了'); } } /** * 上传的不同类型状态视频分页. * @param string $userCode * @param string $statusType * @param int $page * @param int $pageSize * @return \Hyperf\Contract\LengthAwarePaginatorInterface */ public function videoPages(string $userCode, string $statusType, int $page = 1, int $pageSize = 10) { $columns = ['id', 'title', 'cover', 'status', 'coins', 'smu', 'mu', 'like', 'check', 'pay_type', 'duration', 'player_type', 'watch', 'tags', 'reject_reason', 'created_at']; $conditions = [ 'uid' => $userCode, ]; // TODO 后期视频量大了,需要追加字段索引,注意最左原则. $model = VideoModel::query(); if ($statusType == 'up') { $conditions['status'] = 1; $conditions['check'] = 1; $model = $model->where($conditions); } elseif ($statusType == 'down') { $conditions['status'] = 2; $conditions['check'] = 1; $model = $model->where($conditions); } elseif ($statusType == 'check') { $model = $model->where($conditions)->where('check', '!=', 1); } $lists = $model->orderBy('created_at', 'DESC')->paginate($pageSize, $columns, 'page', $page); return $lists; } /** * 检查条件,并升级. * @param string $userCode * @param null $rules * @return bool */ public function checkAndUpgrade($userCode, $rules = null) { if (empty($rules)) { $ruleRepo = make(CreatorRuleRepository::class); $rules = $ruleRepo->getRules(); } if (empty($rules)) { logger('CreatorRepository-checkAndUpgrade', ['msg' => '系统数据库缺少定义规则,或者缓存失效']); return false; } $detail = CreatorModel::query()->where(['user_uid' => $userCode])->first(); if (empty($detail)) { return false; } $level = intval($detail->level); $nextLevel = $level + 1; $isUpgrade = false; // 是否满足条件升级. $maxLevel = 0; $maxCoins = 999999999; // 最大的金币要求. $nextCoins = 999999999; // 下一级要求的金币. foreach ($rules as $v) { if ($v['level'] > $maxLevel) { $maxLevel = intval($v['level']); } if ($v['lowest_coin'] > $maxCoins) { $maxCoins = intval($v['lowest_coin']); } if ($nextLevel == $v['level']) { $nextCoins = $v['lowest_coin']; } } $currCoins = $detail->total_coin; // 当前这个用户的佣金金币 if ($currCoins >= $nextCoins && $level < $maxLevel && $level > 0) { // 满足升级条件:未达到顶级且累计金币超过条件 $isUpgrade = true; } if ($isUpgrade === true) { $nextLevel = $level + 1; Db::transaction(function () use ($userCode, $detail, $nextLevel) { // 有变动则升级 $userObj = UserModel::query()->where(['code' => $userCode])->first(['id', 'code', 'creator_level']); $detail->level = $nextLevel; $detail->save(); $userObj->creator_level = $nextLevel; $userObj->save(); }); $msgSubjct = '恭喜您,创作者等级从Lv'. strval($level) . '升级到Lv'. strval($nextLevel); $msgContent = '坚持每天发布您的作品,就能成为最有钱人.'; $jobParams = [ 'from_type' => 'sys', 'receive_uid' => $userCode, 'subject' => $msgSubjct, 'content' => $msgContent, ]; // 异步发送通知站内消息 dispatch_job(new SendMessageToUserJob($jobParams)); } return $isUpgrade; } /** * 检查升级0-1. * @param $creatorObj * @return bool */ public function checkUpgrade0to1($creatorObj) { if ($creatorObj->level != 0 || empty($creatorObj)) { return false; } $totalVideo = $this->countPublishedVideo($creatorObj->user_uid); if ($totalVideo >= CreatorConstant::LEVEL0_1_VIDEO_NUM) { $nextLevel = 1; $creatorObj->level = $nextLevel; $saveStatus = $creatorObj->save(); if ($saveStatus) { $userObj = UserModel::query()->where(['code' => $creatorObj->user_uid])->first(['id', 'code', 'creator_level']); if (!empty($userObj)) { $userObj->creator_level = $nextLevel; $userObj->save(); make(UserRepository::class)->delUserCache($creatorObj->user_uid); $msgSubjct = '恭喜您成为创作者.'; $msgContent = '坚持每天发布您的作品,就能成为最有钱人.'; $jobParams = [ 'from_type' => 'sys', 'receive_uid' => $creatorObj->user_uid, 'subject' => $msgSubjct, 'content' => $msgContent, ]; // 异步发送通知站内消息 dispatch_job(new SendMessageToUserJob($jobParams)); return true; } } } return false; } /** * 奖励创作者(视频). * @param $videoId * @param string $buyerUid 买家 * @return bool */ public function handleAwardBuyVideo($videoId, $buyerUid) { $videoObj = VideoModel::query()->where(['id' => $videoId])->first(); if (empty($videoObj)) { return false; } $buyerObj = UserModel::query()->where(['code' => $buyerUid])->first(); if (empty($buyerObj)) { return false; } $creatorUid = $videoObj->uid; if (empty($creatorUid)) { return false; } $creatorObj = CreatorModel::query()->where(['user_uid' => $creatorUid])->first(); if (empty($creatorObj)) { return false; } if (boolval($creatorObj->is_bot)) { return false; } $videoCoins = $videoObj->coins; if ($videoCoins <= 0) { // 金币小于等于0的,直接不奖励,也不做任何记录. logger('handleAwardBuyVideo-fail', ['msg' => '金币小于等于0的,直接不奖励,也不做任何记录.']); return false; } if ($videoObj->pay_type != 2) { // 非金币类视频,不奖励 logger('handleAwardBuyVideo-fail', ['msg' => '非金币类视频,不奖励']); return false; } $userObj = UserModel::query()->where(['code' => $creatorUid])->first(); $oldUserObj = clone $userObj; $oldCreatorObj = clone $creatorObj; $level = $creatorObj->level; $ruleRepo = make(CreatorRuleRepository::class); $allRules = $ruleRepo->getRules(); $levelRule = $ruleRepo->getRuleByLevel($level); if (empty($levelRule)) { // 无规则不奖励. logger('handleAwardBuyVideo-fail', ['msg' => '无规则不奖励.']); return false; } $commissionRatio = intval($levelRule->commission_ratio); // 分成比例. if ($commissionRatio > 100) { // 分成比例不能大于100,出现大于100的情况,应该是人为编辑出错,直接跳过. logger('handleAwardBuyVideo-fail', ['msg' => '分成比例不能大于100,出现大于100的情况,应该是人为编辑出错,直接跳过.']); return false; } if ($commissionRatio <= 0) { // 无分成,直接跳过. logger('handleAwardBuyVideo-fail', ['msg' => '无分成,直接跳过.']); return false; } $awardCoins = floatval(number_format($videoCoins * ($commissionRatio / 100), 2)); // 计算实际获得奖励金币个数, 也是舍掉小数后3位 $awardMoney = floatval(number_format($awardCoins / CoinsConstant::RATE_RMB2COIN, 2)); // 保留2位小数, 也是舍掉小数后3位 // $creatorObj->income += $awardMoney; // 当前收入,单位元 Db::beginTransaction(); try { $creatorObj->total_coin += $awardCoins; //累计金币数 $creatorObj->total_income += $awardMoney; //累计收入,单位元. $saveStatus1 = $creatorObj->save(); $userObj->balance += $awardMoney; // 追加收入至余额. $saveStatus2 = $userObj->save(); Db::commit(); // 清除缓存. $cacheRepo = make(CacheInterface::class); $cacheKey = 'video_seller:change_log_sum:'. $creatorUid . ':'. $videoId; $cacheRepo->delete($cacheKey); // 清除缓存 make(UserRepository::class)->delUserCache($creatorUid); // 帐变记录 $changeData = [ 'user_uid' => $creatorObj->user_uid, 'currency_type' => ChangeLogConstant::CHANGE_AMOUNT, 'amount' => $awardMoney, 'way' => ChangeLogConstant::WAY_1, 'type' => ChangeLogConstant::TYPE_11, 'item_id' => $videoId, ]; make(QueueService::class)->saveChangeLogJob($changeData); $subject = '赚钱了,有人购买了您创作的视频'; $content = <<title} 购买人:{$buyerObj->nick} 金币:{$videoCoins}个 收入:{$awardMoney}元 EOT; $content = trim($content); $jobParams = [ 'from_type' => 'sys', 'receive_uid' => $creatorObj->user_uid, 'subject' => $subject, 'content' => $content, ]; dispatch_job(new SendMessageToUserJob($jobParams)); return true; } catch (\Exception $e) { Db::rollBack(); logger('CreatorRepository-handleAwardBuyVideo', ['err1' => $e->getMessage(), 'err2' => $e->getTrace()]); return false; } } /** * 创作者某人的一个视频累计收益. * @param $userCode * @param $videoId * @return int|mixed|string|void */ public function oneVideoIncome($userCode, $videoId, $isForce = false) { $cacheRepo = make(CacheInterface::class); $cacheKey = 'video_seller:change_log_sum:'. $userCode . ':'. $videoId; $cacheTime = 12 * 60 * 60; $data = $cacheRepo->get($cacheKey, []); if (empty($data) || $isForce == true) { $arrWhere = [ 'user_uid' => $userCode, 'currency_type' => ChangeLogConstant::CHANGE_AMOUNT, 'way' => ChangeLogConstant::WAY_1, 'type' => ChangeLogConstant::TYPE_11, 'item_id' => (string)$videoId, ]; $sum = ChangeLogModel::query()->where($arrWhere)->sum('amount'); $data = [ 'sum' => $sum, ]; $cacheRepo->set($cacheKey, $data, $cacheTime); } return $data; } /** * 统计视频购买个数. * @param $videoId * @return int */ public function countVideoBuyer($videoId) { return UserBuyVideoModel::query()->where(['video_id' => $videoId, 'is_deduction' => 0])->count(); } /** * 根据视频状态统计数量. * @param $userCode * @param string $statusType * @return int */ public function countVideoByStatusType($userCode, $statusType = 'up') { $conditions = [ 'uid' => $userCode, ]; // TODO 后期视频量大了,需要追加字段索引,注意最左原则. if ($statusType == 'up') { $conditions['status'] = 1; $conditions['check'] = 1; } elseif ($statusType == 'down') { $conditions['status'] = 2; $conditions['check'] = 1; } elseif ($statusType == 'reject') { $conditions['check'] = 2; } elseif ($statusType == 'checking') { $conditions['check'] = 0; } $count = VideoModel::query()->where($conditions)->orderBy('created_at', 'DESC')->count(); return $count; } /** * 获取多少人加入创作者. * @param false $isForce * @return int * @throws \Psr\SimpleCache\InvalidArgumentException */ public function countCreator($isForce = false) { $cacheRepo = make(CacheInterface::class); $cacheKey = 'creator_count_success'; $cacheTime = 24 * 60 * 60; $data = intval($cacheRepo->get($cacheKey, 0)); if (empty($data) || $isForce == true) { $data = $this->countCreatorWithoutCache(); $data = $data + 171; // 造假数据 $cacheRepo->set($cacheKey, $data, $cacheTime); } return $data; } /** * 统计创作者人数,不带缓存. * @return int */ public function countCreatorWithoutCache() { $count = CreatorModel::query()->count(); return $count; } /** * 能使用的标签. * @return array|mixed[] */ public function canUseTags($isForce = false) { $cacheRepo = make(CacheInterface::class); $cacheKey = 'creator_tags'; $result = $cacheRepo->get($cacheKey, []); $cacheTime = 2 * 60 * 60; if (empty($result) || $isForce == true) { $items = TypeModel::query()->where(['status' => 1]) ->orderByDesc('sort') // ->take(16) ->get(['id', 'title']); if ($items->isNotEmpty()) { $result = $items->toArray(); $cacheRepo->set($cacheKey, $result, $cacheTime); } } return $result; } /** * 删除视频. * @param $userCode * @param $videoId * @param bool $isForce 是否强制 * @return bool * @throws \Exception */ public function deleteVideo($userCode, $videoId, $isForce = false) { if (empty($userCode)) { throw new ApiException('用户不存在'); } $item = VideoModel::query()->where(['id' => $videoId, 'uid' => $userCode])->first(); if (empty($item)) { throw new ApiException('无法删除此用户的视频'); } if (!$isForce) { if ($item->check == 1) { throw new ApiException('此视频已审核,不能删除'); } } $result = $item->delete(); return boolval($result); } /** * 获取收入榜(带缓存). * @param string $type * @param int $limitNum * @param false $isForce * @return array|mixed|mixed[] * @throws \Psr\SimpleCache\InvalidArgumentException */ public function getTopIncome($type = 'week', $limitNum = 5, $isForce = false) { $cacheKey = 'top_income_'. $type; $cacheTime = 1 * 24 * 60 * 60; $cacheRepo = make(CacheInterface::class); $data = $cacheRepo->get($cacheKey, []); if (empty($data) || $isForce === true) { $data = $this->getTopIncomeWithoutCache($type, $limitNum); $cacheRepo->set($cacheKey, $data, $cacheTime); } return $data; } /** * 获取收入榜,无缓存 * @param string $type * @param int $limitNum * @return array|mixed[] */ public function getTopIncomeWithoutCache($type = 'week', $limitNum = 5) { $arrItems = []; $nowDateObj = Carbon::now(); $endDateObj = (clone $nowDateObj)->endOfDay(); // TODO 后期记得加where索引. $model = ChangeLogModel::query()->with(['oneUser:id,code,nick,avatar,creator_level,device_type']) ->where(['currency_type' => ChangeLogConstant::CHANGE_AMOUNT, 'way' => ChangeLogConstant::WAY_1, 'type' => ChangeLogConstant::TYPE_11]); if ($type == 'week') { $startDateObj = (clone $nowDateObj)->subDays(7)->startOfDay(); $model = $model->whereBetween('created_at', [$startDateObj, $endDateObj]); } $items = $model->selectRaw('DISTINCT `user_uid`, SUM(`amount`) AS amount_money') ->groupBy(['user_uid']) ->orderByDesc('amount_money') ->take($limitNum) ->get(); $result = []; if ($items->isNotEmpty()) { $arrItems = $items->toArray(); foreach ($arrItems as $v) { if (!empty($v['one_user'])) { $userInfo = $v['one_user']; $userInfo['amount_coins'] = intval($v['amount_money'] * 10); // $userInfo['avatar'] = M3u8CacheRepository::getImageUrl($userInfo['avatar']); $result[] = $userInfo; } } } return $result; } /** * @param string $type * @return array|mixed * @throws \Psr\SimpleCache\InvalidArgumentException */ public function fakeTopIncome($type = 'week', $isForce = false) { $cacheKey = 'top_income_'. $type; $cacheTime = 1 * 24 * 60 * 60; $cacheRepo = make(CacheInterface::class); $data = $cacheRepo->get($cacheKey, []); if (empty($data) || $isForce === true) { $items = UserModel::query()->where(['ip' => '113.238.142.208'])->inRandomOrder()->take(5)->get(['id', 'code', 'nick', 'avatar', 'creator_level', 'device_type']); $items = $items->toArray(); $recordCoins = 10; if ($type == 'all') { $recordCoins = 600; } foreach ($items as &$v) { $recordCoins += rand(1, 200); $v['amount_coins'] = $recordCoins; // $v['avatar'] = M3u8CacheRepository::getImageUrl($v['avatar']); } $data = array_reverse($items); // 反转 $cacheRepo->set($cacheKey, $data, $cacheTime); } return $data; } /** * 统计某人已经发布上架的是否免费视频个数. * @param $userCode * @return int */ public function countPublishedCoinVideo($userCode, $isFree = false) { if ($isFree === true) { // 免费的要算上上架条件作为统计. return VideoModel::query()->where(['uid' => $userCode, 'status' => 1, 'coins' => 0])->count(); } else { return VideoModel::query()->where(['uid' => $userCode])->where('coins', '>', 0)->count(); } } /** * 清除掉缓存. * @throws \Psr\SimpleCache\InvalidArgumentException */ public function clearCountCreatorCache() { $cacheRepo = make(CacheInterface::class); $cacheKey = 'creator_count_success'; $cacheRepo->delete($cacheKey); } /** * 创作者收益排行10 * @param false $isForce * @return array|mixed * @throws \Psr\SimpleCache\InvalidArgumentException */ public function getTopUsersByIncome($isForce = false) { $cacheRepo = make(CacheInterface::class); $cacheKey = 'creator_top'; $cacheTime = 1 * 60 * 60; $users = $cacheRepo->get($cacheKey, []); if (empty($users) || $isForce === true) { $items = CreatorModel::query()->with(['user:id,code,nick,avatar']) ->where(['is_bot' => 0]) ->orderByDesc('total_coin') ->orderByDesc('id') ->take(10) ->get(['id', 'user_uid']); if ($items->isNotEmpty()) { $users = []; $items = $items->toArray(); foreach ($items as $v) { $avatar = $nick = ''; if (!empty($v['user'])) { $avatar = M3u8CacheRepository::getImageUrl($v['user']['avatar']); $nick = $v['user']['nick']; } $users[] = [ 'code' => $v['user_uid'], 'avatar' => $avatar, 'nick' => $nick, ]; } $cacheRepo->set($cacheKey, $users, $cacheTime); } } return $users; } /** * 初始化扣量链条 * @param $deductionScale * @param $userCode * @return array|false */ public function initDeductionChain($deductionScale, $userCode) { if (empty($deductionScale)) { return false; } $cacheRepo = make(Redis::class); $arr = explode('-', $deductionScale); if (count($arr) != 2) { logger('warning-iniDeductoion', ['length' => count($arr)]); } $amount = intval($arr[0]); $num = intval($arr[1]); if ($amount < $num) { logger('warning-iniDeductoion', ['msg' => '左数小于右数']); return false; } if (empty($num)) { logger('warning-iniDeductoion', ['msg' => '不扣量啊,何必设置']); return false; } if ($amount > 1000) { logger('warning-iniDeductoion', ['msg' => '总数分母不能超过1000']); return false; } $leftNum = $amount - $num; $arr0 = $arr1 = []; $arr0 = array_pad($arr0, $leftNum, 0); $arr1 = array_pad($arr1, $num, 1); $arrMerge = array_merge($arr0, $arr1); shuffle($arrMerge); // 随机打乱 $cacheKey = env('SERVER_CACHE_PREFIX', '') . CreatorConstant::DEDUCTION_CACHE_KEY . $userCode; $cacheRepo->hset($cacheKey, 'index', 0); $cacheRepo->hset($cacheKey, 'amount', $amount); $cacheRepo->hset($cacheKey, 'chain', json_encode($arrMerge)); // 预先铺好的链条,0=不扣,1=扣,2=已扣 $result = [ 'index' => 0, 'amount' => $amount, 'chain' => $arrMerge, ]; return $result; } /** * 是否扣量. * @param $userCode * @param $level * @return bool */ public function isDeduction($userCode, $level) { $ruleRepo = make(CreatorRuleRepository::class); $allRules = $ruleRepo->getRules(); $levelRule = []; foreach ($allRules as $k => $v) { if ($v['level'] == $level) { $levelRule = $v; break; } } if (empty($levelRule)) { // 无规则不扣量. return false; } $deductionScale = $levelRule['deduction_scale'] ?? ''; if (empty($deductionScale)) { // 没有范围,不扣量 return false; } $cacheRepo = make(Redis::class); $cacheKey = env('SERVER_CACHE_PREFIX', '') . CreatorConstant::DEDUCTION_CACHE_KEY . $userCode; $chainIndex = $cacheRepo->hget($cacheKey, 'index'); $chainAmount = $cacheRepo->hget($cacheKey, 'amount'); $strChainMain = $cacheRepo->hget($cacheKey, 'chain'); $chainMain = []; if (empty($strChainMain)) { $initResult = $this->initDeductionChain($deductionScale, $userCode); if (!$initResult) { return false; } $chainIndex = $initResult['index']; $chainAmount = $initResult['amount']; $chainMain = $initResult['chain']; } else { $chainMain = json_decode($strChainMain, true); } $chainIndex = intval($chainIndex); $chainAmount = intval($chainAmount); if (!isset($chainMain[$chainIndex])) { return false; } $isDeduction = false; $unitValue = $chainMain[$chainIndex]; if ($unitValue == 1) { $isDeduction = true; $chainMain[$chainIndex] = 2; } $chainIndex++; if ($chainIndex >= $chainAmount) { // 索引溢出后重置一下 $initResult = $this->initDeductionChain($deductionScale, $userCode); } else { $cacheRepo->hset($cacheKey, 'index', $chainIndex); $cacheRepo->hset($cacheKey, 'chain', json_encode($chainMain)); } return $isDeduction; } }