userService = $userService; $this->openService = $openService; $this->chapterOrderService = $chapterOrderService; } /** * 书籍列表 * * @param $data * @return mixed */ public function getBookList($data) { $distribution_channel_id = Site::getCurrentChannelId(); $gender = getProp($data, 'gender', 2); // 男女频 $book_name = trim(getProp($data, 'book_name')); // 书名 $charge_type = getProp($data, 'charge_type'); // 篇幅(长篇:CHAPTER 短篇:BOOK) $status = getProp($data, 'status'); // 连载状态(0.连载中 1.已完结) $roles = getProp($data, 'roles'); // 主角名称 $page_number = getProp($data, 'page_number'); $query = Book::leftJoin('book_configs', 'books.id', 'book_configs.bid')->whereIn('book_configs.is_on_shelf', [1, 2]) ->leftJoin('book_categories', 'book_categories.id', 'books.ncategory_id') ->leftJoin('channel_book_charge_type', function ($join) use ($distribution_channel_id) { $join->on('channel_book_charge_type.bid', '=', 'books.id')->where('channel_book_charge_type.distribution_channel_id', '=', $distribution_channel_id); }) ->leftJoin('books as b1', 'b1.id', 'book_configs.origin_bid') ->where('books.gender', $gender)->select('books.id', 'books.name', 'books.intro', 'books.cover', 'book_configs.origin_bid', 'books.author', 'books.category_name', 'books.gender', 'books.status', 'book_categories.category_name as ncategory_name', 'books.chapter_count', 'books.size', 'book_configs.book_name', 'book_configs.roles', 'book_configs.charge_type', 'books.created_at', 'b1.name as origin_book_name', 'channel_book_charge_type.book_calculate_price_type', 'channel_book_charge_type.book_coin'); if ($book_name) { $bid = BookConfig::where('book_name', $book_name)->value('bid'); if ($bid) { // 如果精准搜索到书籍 $query->where('books.id', $bid)->orWhere('book_configs.origin_bid', $bid)->orderBy('book_configs.origin_bid'); } else { $query->where('book_configs.book_name', 'like', '%' . $book_name . '%'); } } else { $query->where('book_configs.origin_bid', 0); } if ($charge_type) { $query->where('book_configs.charge_type', $charge_type); } if ($status !== '') { $query->where('books.status', $status); } if ($roles) { $query->where('book_configs.roles', 'like', '%' . $roles . '%'); } if ($page_number) { return $query->orderBy('books.created_at', 'desc')->paginate($page_number); } else { return $query->orderBy('books.created_at', 'desc')->paginate(); } } public function simpleBookConfigs($bids) { return BookConfig::select('bid', 'book_name')->whereIn('bid', $bids)->get(); } /** * 热门搜索 * * @return array */ public function getHotSearches() { $result = DB::table('hot_search_words')->get()->pluck('hot_word')->toArray(); $result = array_unique($result); shuffle($result); return $result; } /** * 书籍详情 * * @param $data * @return array */ public function getBookDetail($data) { Log::info('书籍详情页入参: ' . json_encode($data, 256)); $uid = Site::getUid(); $user_info = User::find($uid); $bid = getProp($data, 'bid'); if (mb_strlen($bid) === 32) { $bid = Hashids::decode($bid)[0]; } if (!$bid) { Utils::throwError(ErrorConst::DATA_EXCEPTION); } $result = Book::leftJoin('book_configs', 'book_configs.bid', 'books.id') ->where('books.id', $bid) ->whereIn('book_configs.is_on_shelf', [1, 2]) ->select('books.id as bid', 'books.name as book_name', 'books.category_name', 'books.category_id', 'books.size', 'books.status', 'books.cover', 'books.intro')->first(); if (!$result) return []; $book['detail'] = $result->toArray(); $book['user'] = [ 'balance' => getProp($user_info, 'balance', 0), 'charge_balance' => getProp($user_info, 'charge_balance', 0), 'reward_balance' => getProp($user_info, 'reward_balance', 0), ]; // 获取本书前两章内容 $front_double_chapters = Chapter::leftJoin('chapter_contents', 'chapter_contents.id', 'chapters.chapter_content_id') ->select('chapters.id as cid', 'chapters.name as chapter_name', 'chapter_contents.content as chapter_content') ->where('chapters.bid', $bid)->where(['chapters.is_check' => 1, 'chapters.is_deleted' => 0])->orderBy('sequence') ->limit(2)->get()->toArray(); $book['first_chapter'] = $book['second_chapter'] = []; $book['front_double_chapter_contents'] = ''; $i = 1; foreach ($front_double_chapters as &$item) { $book['front_double_chapter_contents'] .= $item['chapter_name'] . PHP_EOL . filterContent($item['chapter_content']) . PHP_EOL . PHP_EOL; $item['cid'] = Hashids::encode($item['cid']); $item['chapter_content'] = filterContent($item['chapter_content']); if ($i === 1) $book['first_chapter'] = $item; if ($i === 2) $book['second_chapter'] = $item; $i++; } // 获取最新章节 $book['latest_chapter'] = Chapter::where(['bid' => $bid, 'is_check' => 1, 'is_deleted' => 0])->orderBy('sequence', 'desc') ->select('id as cid', 'name as chapter_name')->first(); if ($book['latest_chapter']) { $book['latest_chapter'] = $book['latest_chapter']->toArray(); $book['latest_chapter']['cid'] = Hashids::encode($book['latest_chapter']['cid']); $book['latest_chapter']['status'] = $book['detail']['status']; } else { $book['latest_chapter'] = []; } // 格式化书籍数据 $book['detail']['cover'] = addPrefix($book['detail']['cover']); $book['detail']['size'] = round($result['size'] / 10000, 1) . '万'; $book['detail']['status_info'] = (int)$result['status'] === 1 ? '已完结' : '连载中'; $book['detail']['bid'] = Hashids::encode($bid); if (!$book['detail']['category_name']) $book['detail']['category_name'] = '其他'; // 获取充值排名前20的书籍(每次都随机排序) $charge_books = $this->getBookOrderByCharge($book['detail']['category_id']); $book['recommend_books'] = []; $max = DB::table('books')->count('id'); if ($max < 20) $max -= 1; else $max = 19; $arr_index = range(0, $max); shuffle($arr_index); $arr_index = array_slice($arr_index, 0, 6); foreach ($arr_index as $index) { if (isset($charge_books[$index])) { $arr = $charge_books[$index]; $book['recommend_books'][] = [ 'bid' => Hashids::encode($arr['id']), 'cover' => addPrefix($arr['cover']), 'book_name' => $arr['name'], ]; } } $book['promotion_code'] = ''; // 生成视频推广口令码 if ($uid) { $book['promotion_code'] = Utils::getPasswordCode($uid, $bid); } // 获取分享链接中的隐藏参数(如果存在隐藏参数邀请码则绑定) $invite_code = getProp($data, 'invite_code'); $from_uid = User::where('invite_code', $invite_code)->value('id'); if ($from_uid) { $info = $this->userService->bind($from_uid, $uid); // 绑定邀请码 Log::info('书籍详情页邀请码绑定结果-----from_uid: ' . $from_uid . ';uid: ' . $uid . ';info: ' . $info); } // 获取分享链接中的隐藏参数(如果存在隐藏参数派单id则绑定) $send_order_id = getProp($data, 'send_order_id'); if ($send_order_id && $uid) { $info = $this->userService->bindSendOrder($send_order_id); // 绑定派单链接 Log::info('书籍详情页派单链接绑定结果-----send_order_id: ' . $send_order_id . ';uid: ' . $uid . ';info: ' . $info); } return $book; } /** * 获取某本书的阅读记录 * * @param $data * @return array|false|mixed * @throws \App\Exceptions\ApiException */ public function recentChapter($data) { $uid = Site::getUid(); $bid = getProp($data, 'bid'); if (mb_strlen($bid) === 32) { $bid = Hashids::decode($bid)[0]; } if (!$bid) { Utils::throwError(ErrorConst::DATA_EXCEPTION); } $recent_book = UserCache::getRecentBooksByBid($uid, $bid); if ($recent_book) { $recent_book['bid'] = Hashids::encode($recent_book['bid']); $recent_book['cid'] = Hashids::encode($recent_book['cid']); } else { $chapter = Chapter::leftJoin('books', 'books.id', 'chapters.bid') ->leftJoin('book_configs', 'chapters.bid', 'book_configs.bid')->whereIn('book_configs.is_on_shelf', [1, 2]) ->where(['chapters.bid' => $bid, 'chapters.is_check' => 1, 'chapters.is_deleted' => 0]) ->orderBy('chapters.sequence')->select('chapters.bid', 'books.name as book_name', 'books.cover', 'chapters.id as cid', 'chapters.name as chapter_name', 'chapters.sequence', 'books.gender')->first(); if ($chapter) { $chapter = $chapter->toArray(); $chapter['bid'] = Hashids::encode($chapter['bid']); $chapter['cid'] = Hashids::encode($chapter['cid']); $chapter['read_time'] = ''; $recent_book = $chapter; } else { $recent_book = new \stdClass(); } } return $recent_book; } /** * 生成分享链接参数(小程序内部调用) * * @param $data * @return array|string * @throws \App\Exceptions\ApiException * @throws \GuzzleHttp\Exception\GuzzleException */ public function setUrlLink($data) { $type = getProp($data, 'type'); $send_order_id = getProp($data, 'send_order_id'); $distribution_channel_id = getProp($data, 'distribution_channel_id'); $template_id = getProp($data, 'template_id', env('SHARE_TEMPLATE_ID')); $uid = Site::getUid(); $invite_code = User::where('id', $uid)->value('invite_code'); if (!$invite_code) $invite_code = ''; $data['invite_code'] = $invite_code; Log::info('小程序内部调用分享链接参数: ' . json_encode($data, 256)); $bid = getProp($data, 'bid'); $cid = getProp($data, 'cid'); if (mb_strlen($bid) === 32) { $bid = Hashids::decode($bid)[0]; } if (mb_strlen($cid) === 32) { $cid = Hashids::decode($cid)[0]; } // 如果有派单链接,则替换bid和cid if ($send_order_id) { $send_order = DB::table('send_orders')->where('id', $send_order_id)->select('distribution_channel_id', 'book_id', 'chapter_id')->first(); if ($send_order) { $bid = getProp($send_order, 'book_id'); $cid = getProp($send_order, 'chapter_id'); $distribution_channel_id = getProp($send_order, 'distribution_channel_id'); } } $promotion_code = ''; if ($bid) $promotion_code = DB::table('user_book_code')->where(['bid' => $bid, 'uid' => $uid])->value('id'); $arr = [ 'path' => 'pages/index/index', 'bid' => $bid ? Hashids::encode($bid) : '', 'cid' => $cid ? Hashids::encode($cid) : '', 'invite_code' => $invite_code, 'promotion_code' => $promotion_code, 'send_order_id' => $send_order_id, 'distribution_channel_id' => $distribution_channel_id, ]; if ($send_order_id) $arr['jump'] = 'no'; // 增加阅读页跳转到选定章节的限制 switch ($type) { case 'friend_url': // 私信朋友 if ($bid) { if (!$cid) { // 未设置章节则默认第一章 $first_cid = Chapter::where('bid', $bid)->where(['is_check' => 1, 'is_deleted' => 0])->orderBy('sequence')->limit(1)->value('id'); $arr['cid'] = Hashids::encode($first_cid); } $arr['path'] = 'pages/reader/index'; } break; case 'promotion_url': $arr['path'] = 'pages/detail/index'; break; default: break; } // 统计(uv|pv) StatisticCache::setPV('set_url_link'); if ($uid) StatisticCache::setUV('set_url_link', $uid); $path = $arr['path']; unset($arr['path']); return '/' . $path . '?' . http_build_query($arr); } /** * 生成分享链接(外网) * * @param $data * @return array|string * @throws \App\Exceptions\ApiException * @throws \GuzzleHttp\Exception\GuzzleException */ public function setDyLink($data) { $type = getProp($data, 'type'); $send_order_id = getProp($data, 'send_order_id'); $distribution_channel_id = getProp($data, 'distribution_channel_id'); $template_id = getProp($data, 'template_id', env('SHARE_TEMPLATE_ID')); $uid = Site::getUid(); $invite_code = User::where('id', $uid)->value('invite_code'); if (!$invite_code) $invite_code = ''; $data['invite_code'] = $invite_code; Log::info('外网分享链接参数: ' . json_encode($data, 256)); $bid = getProp($data, 'bid'); $cid = getProp($data, 'cid'); if (mb_strlen($bid) === 32) { $bid = Hashids::decode($bid)[0]; } if (mb_strlen($cid) === 32) { $cid = Hashids::decode($cid)[0]; } // 如果有派单链接,则替换bid和cid if ($send_order_id) { $send_order = DB::table('send_orders')->where('id', $send_order_id)->select('distribution_channel_id', 'book_id', 'chapter_id')->first(); if ($send_order) { $bid = getProp($send_order, 'book_id'); $cid = getProp($send_order, 'chapter_id'); $distribution_channel_id = getProp($send_order, 'distribution_channel_id'); } } $promotion_code = ''; if ($bid) $promotion_code = DB::table('user_book_code')->where(['bid' => $bid, 'uid' => $uid])->value('id'); $arr = [ 'path' => 'pages/index/index', 'bid' => $bid ? Hashids::encode($bid) : '', 'cid' => $cid ? Hashids::encode($cid) : '', 'invite_code' => $invite_code, 'promotion_code' => $promotion_code, 'send_order_id' => $send_order_id, 'distribution_channel_id' => $distribution_channel_id, ]; if ($send_order_id) $arr['jump'] = 'no'; // 增加阅读页跳转到选定章节的限制 switch ($type) { case 'friend_url': // 私信朋友 if ($bid) { if (!$cid) { // 未设置章节则默认第一章 $first_cid = Chapter::where('bid', $bid)->where(['is_check' => 1, 'is_deleted' => 0])->orderBy('sequence')->limit(1)->value('id'); $arr['cid'] = Hashids::encode($first_cid); } $arr['path'] = 'pages/reader/index'; } break; case 'promotion_url': $arr['path'] = 'pages/detail/index'; break; default: break; } // 统计(uv|pv) StatisticCache::setPV('set_dy_link'); if ($uid) StatisticCache::setUV('set_dy_link', $uid); $path = $arr['path']; unset($arr['path']); $json = json_encode($arr, 256); // 调用抖音三方库生成分享链接 $instance = $this->openService->getInstance(['sandbox' => env('DOUYIN_APP_SANDBOX')]); $urlLink = $instance->generateShareLink($path, $json); if (isset($urlLink['url_link']) && $send_order_id) { DB::table('send_orders')->where('id', $send_order_id)->update([ 'send_order_url' => $urlLink['url_link'], 'updated_at' => date('Y-m-d H:i:s'), ]); } return $urlLink ? $urlLink : ['err_no' => 1, 'err_tips' => '', 'url_link' => '']; } /** * 轮播图 * * @param $gender * @return mixed */ public function getBanners($gender) { return Book::rightJoin('home_book_configs', 'home_book_configs.bid', 'books.zw_id') ->leftJoin('book_configs', 'books.id', 'book_configs.bid')->whereIn('book_configs.is_on_shelf', [1, 2]) ->where('home_book_configs.type', 'banner')->where('home_book_configs.gender', $gender) ->select('books.id', 'books.cover', 'books.name', 'books.intro')->get()->toArray(); } /** * 获取原创精选书籍 * * @param $gender * @return mixed */ public function getFeatureBooks($gender) { return Book::rightJoin('home_book_configs', 'home_book_configs.bid', 'books.zw_id') ->leftJoin('book_configs', 'books.id', 'book_configs.bid')->whereIn('book_configs.is_on_shelf', [1, 2]) ->where('home_book_configs.type', 'feature')->where('home_book_configs.gender', $gender) ->select('books.id', 'books.name', 'books.cover', 'books.intro', 'books.gender', 'books.category_name')->get()->toArray(); } /** * 获取当前阅读书籍 * * @return array|mixed */ public function getCurrentBook() { $uid = Site::getUid(); if (!$uid) return []; return UserCache::getCurrentChapter($uid); } /** * 获取猜你喜欢书籍 * * @param $gender * @return mixed */ public function getFavoriteBooks($gender) { $uid = Site::getUid(); $channel_type = Site::getChannelType(); $result = []; // 获取当前阅读的redis数据 $recent_books = UserCache::getRecentBooksKeys($uid); // 屏蔽首页banner图和精选好书模块的书籍(避免重复) $filter_bids = DB::table('home_book_configs')->where('gender', $gender)->select('bid')->get()->pluck('bid')->toArray(); if ($recent_books) { // 有分类则推荐分类书籍 $recent_categories = DB::table('books')->whereIn('id', $recent_books)->get()->pluck('category_id')->toArray(); $query = Book::leftJoin('book_configs', 'books.id', 'book_configs.bid') ->whereIn('book_configs.is_on_shelf', [1, 2])->whereNotIn('books.zw_id', $filter_bids) ->select('books.id', 'books.name', 'books.intro', 'books.cover', 'books.author', 'books.category_name', 'books.gender')->whereIn('books.category_id', array_unique($recent_categories)); // 会员制站点只展示短篇书籍 // if ($channel_type == 'PERIOD') { // $query->where('book_configs.charge_type', 'BOOK'); // } $result = $query->where('books.gender', $gender)->paginate(); } // 无阅读分类则推荐默认书单 if (!$result) { $query = Book::leftJoin('book_configs', 'books.id', 'book_configs.bid') ->whereIn('book_configs.is_on_shelf', [1, 2])->whereNotIn('books.zw_id', $filter_bids) ->select('books.id', 'books.name', 'books.intro', 'books.cover', 'books.author', 'books.category_name', 'books.gender'); // 会员制站点只展示短篇书籍 // if ($channel_type == 'PERIOD') { // $query->where('book_configs.charge_type', 'BOOK'); // } $result = $query->where('books.gender', $gender)->paginate(); } return $result; } /** * 获取充值前20书籍 * * @param $category_id * @return mixed */ public function getBookOrderByCharge($category_id) { $channel_type = Site::getChannelType(); $query = Book::leftJoin('book_configs', 'books.id', 'book_configs.bid') ->whereIn('book_configs.is_on_shelf', [1, 2]) ->select('books.id', 'books.name', 'books.intro', 'books.cover', 'books.author', 'books.category_name', 'books.gender', DB::raw("(select sum(price) from orders where from_bid = books.id and status = 'PAID') as all_price")); // 会员制站点只展示短篇书籍 // if ($channel_type == 'PERIOD') { // $query->where('book_configs.charge_type', 'BOOK'); // } return $query->where('books.category_id', $category_id)->orderBy('all_price', 'desc')->limit(20)->get()->toArray(); } /** * 章节目录 * * @param $data * @return mixed */ public function getChapterList($data) { $distribution_channel_id = Site::getCurrentChannelId(); $bid = getProp($data, 'bid'); if (!$bid) { Utils::throwError(ErrorConst::DATA_EXCEPTION); } $order_type = getProp($data, 'order_type', 'asc'); $page_number = getProp($data, 'page_number'); $query = Chapter::where(['bid' => $bid, 'is_check' => 1, 'is_deleted' => 0])->select('id', 'name', 'charge_length', 'size', 'is_vip', 'sequence'); if ($page_number) { $chapters = $query->orderBy('sequence', $order_type)->paginate($page_number); } else { $chapters = $query->orderBy('sequence', $order_type)->paginate(10); } if ($chapters) { $result['chapters'] = $chapters; } else { $result['chapters'] = []; } // 获取书籍信息 $result['book'] = Book::leftJoin('book_configs', 'book_configs.bid', 'books.id')->where(['books.id'=>$bid]) ->select('books.id as bid', 'book_configs.book_name', 'books.cover', 'books.size', 'books.chapter_count', 'books.intro', 'books.category_name', 'book_configs.feed_advertise_seq', 'book_configs.follow_seq')->first(); if (!$result['book']) { Utils::throwError(ErrorConst::BOOK_NOT_EXIST); } $result['book'] = $result['book']->toArray(); // 获取书籍收费方式 $book_charge_type = DB::table('channel_book_charge_type')->where('distribution_channel_id', $distribution_channel_id)->where('bid', $result['book']['bid'])->first(); if ($book_charge_type) { $result['book']['charge_info'] = [ 'distribution_channel_id' => getProp($book_charge_type, 'distribution_channel_id'), 'book_calculate_price_type' => getProp($book_charge_type, 'book_calculate_price_type'), 'book_coin' => getProp($book_charge_type, 'book_coin'), ]; }else { $result['book']['charge_info'] = []; } $result['book']['cover'] = addPrefix($result['book']['cover']); $result['book']['intro'] = filterIntro($result['book']['intro']); return $result; } /** * 设置书籍收费方式 * @param $data * @return bool * @throws \App\Exceptions\ApiException */ public function setBookChargeType($data) { $distribution_channel_id = Site::getCurrentChannelId(); $bid = getProp($data, 'bid'); $book_calculate_price_type = getProp($data, 'book_calculate_price_type'); $book_coin = getProp($data, 'book_coin'); if (!$bid || !$book_calculate_price_type || !$book_coin) { Utils::throwError(ErrorConst::DATA_EXCEPTION); } return DB::table('channel_book_charge_type')->updateOrInsert([ 'distribution_channel_id' => $distribution_channel_id, 'bid' => $bid ], [ 'book_calculate_price_type' => $book_calculate_price_type, 'book_coin' => $book_coin, 'created_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s'), ]); } /** * 章节信息 * * @param $data * @return array */ public function getChapterInfo($data) { $cid = getProp($data, 'cid'); if (!$cid) { Utils::throwError(ErrorConst::DATA_EXCEPTION); } $result = Chapter::leftJoin('chapter_contents', 'chapter_contents.id', 'chapters.chapter_content_id') ->leftJoin('books', 'books.id', 'chapters.bid') ->leftJoin('book_configs', 'book_configs.bid', 'chapters.bid') ->whereIn('book_configs.is_on_shelf', [1, 2]) ->where('chapters.id', $cid)->where(['chapters.is_check' => 1, 'chapters.is_deleted' => 0]) ->select('chapters.id as cid', 'chapters.bid', 'chapters.name as chapter_name', 'chapters.sequence', 'books.gender', 'chapter_contents.content as chapter_content', 'chapters.is_vip', 'books.name as book_name', 'books.cover', 'book_configs.charge_type')->first(); if (!$result) { Utils::throwError(ErrorConst::CHAPTER_NOT_EXIST); } $chapter = $result->toArray(); $chapter['cover'] = addPrefix($chapter['cover']); // 获取上一章和下一章cid $prev_cid = Chapter::where('bid', $chapter['bid'])->where(['is_check' => 1, 'is_deleted' => 0]) ->where('sequence', '<', $chapter['sequence'])->orderBy('sequence', 'desc')->limit(1)->value('id'); $next_cid = Chapter::where('bid', $chapter['bid'])->where(['is_check' => 1, 'is_deleted' => 0]) ->where('sequence', '>', $chapter['sequence'])->orderBy('sequence', 'asc')->limit(1)->value('id'); $chapter['prev_cid'] = $prev_cid ? $prev_cid : 0; $chapter['next_cid'] = $next_cid ? $next_cid : 0; $chapter['chapter_content'] = filterContent($chapter['chapter_content']); return $chapter; } /** * 获取全部一级分类 * * @param $data * @return mixed */ public function getCategoryList($data) { $gender = trim(getProp($data, 'gender')); $uid = Site::getUid(); // 获取当前阅读书籍 $current_chapter = UserCache::getCurrentChapter($uid); if (!$gender) $gender = $current_chapter['gender'] ? $current_chapter['gender'] : 1; return Category::where(['channel_id' => $gender, 'is_show' => 1])->where('pid', 0) ->select('id as category_id', 'category_name')->get()->toArray(); } /** * 获取分类书籍 * * @param $data * @return mixed */ public function getCategoryBooks($data) { $gender = trim(getProp($data, 'gender')); $category_id = getProp($data, 'category_id'); $search_key = getProp($data, 'search_key'); $status = getProp($data, 'status'); $page_number = getProp($data, 'page_number'); $uid = Site::getUid(); $channel_type = Site::getChannelType(); // 获取当前阅读书籍 $current_chapter = UserCache::getCurrentChapter($uid); if (!$gender) $gender = $current_chapter['gender'] ? $current_chapter['gender'] : 1; $query = Book::leftJoin('book_configs', 'books.id', 'book_configs.bid')->whereIn('book_configs.is_on_shelf', [1, 2]) ->select('books.id', 'books.name', 'books.intro', 'books.cover', 'books.author', 'books.category_name', 'books.gender', DB::raw("(select sum(price) from orders where from_bid = books.id and status = 'PAID') as all_price")) ->where('books.gender', $gender); // 会员制站点只展示短篇书籍 // if ($channel_type = 'PERIOD') { // $query->where('book_configs.charge_type', 'BOOK'); // } if ($search_key) { // 书名或分类名模糊搜索 if ($search_key == '其他') { // 其他分类按无分类算 $query->where('books.name', 'like', '%' . $search_key . '%')->orWhere('books.category_name', ''); } else { $query->where('books.name', 'like', '%' . $search_key . '%')->orWhere('books.category_name', 'like', '%' . $search_key . '%'); } } if ($category_id) { $query->where('books.category_id', $category_id); } if ($status !== '') { $query->where('books.status', $status); } if ($page_number) { return $query->orderBy('all_price', 'desc')->paginate($page_number); } else { return $query->orderBy('all_price', 'desc')->paginate(); } } /** * 生成派单链接 * * @param $data * @throws \App\Exceptions\ApiException */ public function setSendOrder($data) { $distribution_channel_id = Site::getCurrentChannelId(); $bid = getProp($data, 'bid'); $cid = getProp($data, 'cid'); $template_id = getProp($data, 'template_id'); $report_percent = getProp($data, 'report_percent', 0); $send_order_name = getProp($data, 'send_order_name'); if (!is_numeric($report_percent) || $report_percent > 100) { Utils::throwError(ErrorConst::DATA_EXCEPTION); } if ($template_id) { $template = DB::table('channel_templates')->where('id', $template_id)->where('distribution_channel_id', $distribution_channel_id)->first(); if (!$template) { Utils::throwError('1002:该充值模板不存在!'); } } $chapter = Chapter::leftJoin('books', 'books.id', 'chapters.bid') ->leftJoin('book_configs', 'book_configs.bid', 'books.id')->whereIn('book_configs.is_on_shelf', [1, 2]) ->where(['chapters.id' => $cid, 'chapters.bid' => $bid, 'chapters.is_check' => 1, 'chapters.is_deleted' => 0]) ->select('chapters.bid as book_id', 'books.name as book_name', 'chapters.id as chapter_id', 'chapters.name as chapter_name') ->orderBy('chapters.sequence')->first(); if (!$chapter) Utils::throwError(ErrorConst::CHAPTER_NOT_EXIST); $channel_info = DB::table('distribution_channels')->where('id', $distribution_channel_id)->select('id', 'name', 'channel_type', 'pay_id')->first(); if (!$channel_info) Utils::throwError(ErrorConst::CHANNEL_NOT_EXIST); if (DB::table('send_orders')->where('name', $send_order_name)->value('id')) { Utils::throwError(ErrorConst::SEND_ORDER_NAME_INVALID); } $insert_data = $chapter->toArray(); $insert_data['name'] = $send_order_name; $insert_data['distribution_channel_id'] = getProp($channel_info, 'id'); $insert_data['channel_type'] = getProp($channel_info, 'channel_type'); $insert_data['cost'] = '0'; $insert_data['pay_id'] = getProp($channel_info, 'pay_id'); $insert_data['report_percent'] = $report_percent; $insert_data['created_at'] = $insert_data['updated_at'] = $insert_data['send_time'] = date('Y-m-d H:i:s'); if ($template_id) $insert_data['pay_id'] = $template_id; $send_order_id = DB::table('send_orders')->insertGetId($insert_data); // 调用抖音三方库生成分享链接(非本地模式调用) if ($send_order_id && env('APP_ENV') != 'local') { $arr = [ 'bid' => $bid ? Hashids::encode($bid) : '', 'cid' => $cid ? Hashids::encode($cid) : '', 'send_order_id' => $send_order_id, 'distribution_channel_id' => $distribution_channel_id, 'jump' => 'no', ]; $json = json_encode($arr, 256); $path = 'pages/reader/index'; // 固定页面(阅读页) $instance = $this->openService->getInstance(['sandbox' => env('DOUYIN_APP_SANDBOX')]); $urlLink = $instance->generateShareLink($path, $json); if (!isset($urlLink['url_link'])) { Utils::throwError(ErrorConst::SYS_EXCEPTION); } return DB::table('send_orders')->where('id', $send_order_id)->update([ 'send_order_url' => $urlLink['url_link'], 'updated_at' => date('Y-m-d H:i:s') ]); } return $send_order_id; } /** * 计算扣减书币 * * @param $bid * @param $charge_length * @param $user_vip_level * @return int */ public function calcDeductCoin($bid, $charge_length, $user_vip_level = 0) { return $this->calNormalCoin($charge_length, $user_vip_level); } /** * 默认情况扣减书币的金额 * * @param $charge_length * @param int $user_vip_level * @return int */ private function calNormalCoin($charge_length, $user_vip_level = 0): int { // 正常价 $coin = ceil(($charge_length / 1000) * CoinConst::DEFAULT_CHAPTER_PRICE * CoinConst::PRICE_COIN_RATIO); switch ($user_vip_level) { // 初级vip 77折 case BaseConst::JUNIOR_VIP_LEVEL: $coin *= CoinConst::JUNIOR_CHAPTER_DISCOUNT; break; // 高级vip 55折 case BaseConst::SENIOR_VIP_LEVEL: $coin *= CoinConst::SENIOR_CHAPTER_DISCOUNT; break; default: break; } return (int)round($coin); } /** * 执行扣减书币 * * @param $user_info //用户信息 * @param $chapter_info //书籍章节信息 * @param $cid //章节id * @param $coin //待扣减的书币 */ private function decreaseBookCoin($user_info, $chapter_info, $cid, $coin) { $origin_coin = $coin; $balance = getProp($user_info, 'balance'); $charge_balance = getProp($user_info, 'charge_balance'); $reward_balance = getProp($user_info, 'reward_balance'); if ($coin > $balance) return false; $update_data = [ 'reward_balance' => $reward_balance, 'charge_balance' => $charge_balance ]; $balance -= $coin; foreach ($update_data as $key => $val) { if ($coin <= 0) break; if ($coin > $val) { $coin -= $val; $val = 0; } else { $val -= $coin; $coin = 0; } $update_data[$key] = $val; } $update_data['balance'] = $balance; $update_data['updated_at'] = date('Y-m-d H:i:s'); $decrease_charge_balance = $charge_balance - $update_data['charge_balance']; $decrease_reward_balance = $reward_balance - $update_data['reward_balance']; $chapter_order_data = [ 'uid' => getProp($user_info, 'id'), 'fee' => $origin_coin, 'cid' => $cid, 'bid' => getProp($chapter_info, 'bid'), 'distribution_channel_id' => Site::getChannelId(), 'book_name' => getProp($chapter_info, 'book_name'), 'chapter_name' => getProp($chapter_info, 'chapter_name'), 'send_order_id' => Site::getSendOrderId(), 'charge_balance' => $decrease_charge_balance, 'reward_balance' => $decrease_reward_balance, 'created_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s') ]; try { DB::beginTransaction(); // 更新用户书币 $boolen = User::where('id', getProp($user_info, 'id'))->update($update_data); if (!$boolen) { $update_data['uid'] = getProp($user_info, 'id'); DB::rollBack(); dLog('chapter_orders')->info('更新用户书币余额失败: ', $update_data); Utils::throwError(ErrorConst::DB_INVALID); } // 订阅记录写入redis $subscribe_data = [ 'bid' => getProp($chapter_info, 'bid'), 'day' => date('Y-m-d'), 'balance' => getProp($chapter_order_data, 'fee'), 'charge_balance' => getProp($chapter_order_data, 'charge_balance'), 'reward_balance' => getProp($chapter_order_data, 'reward_balance'), 'is_chapter' => getProp($chapter_info, 'charge_type') == 'CHAPTER' ? 1 : 0, 'count' => 1, 'uid' => getProp($user_info, 'id'), ]; $boolen1 = StatisticCache::setSubscribe($subscribe_data, date('Ymd')); if (!$boolen1) { DB::rollBack(); dLog('chapter_orders')->info('添加临时订阅记录失败: ', $chapter_order_data); Utils::throwError(ErrorConst::DB_INVALID); } // 增加订阅记录 $boolen2 = $this->chapterOrderService->addChapterOrder($chapter_order_data); if (!$boolen2) { DB::rollBack(); dLog('chapter_orders')->info('添加订阅记录失败: ', $chapter_order_data); Utils::throwError(ErrorConst::DB_INVALID); } } catch (\Exception $e) { DB::rollBack(); return false; } DB::commit(); return true; } // /** // * 扣减书币执行 // * @param $uid // * @param $coin // * @param $changeType // * @param $extra // * @return array|bool // * @throws ApiException // */ // public function decreaseBookCoin($uid, $coin, $changeType, $extra = []) // { // // 1. 获取用户书币信息 // $userCoin = User::getUserCoin($uid, true); // $bookInfo = getProp($extra, 'bookInfo', []); // $chapterInfo = getProp($extra, 'chapterInfo', []); // $bid = getProp($extra, 'bid', 0); // $cid = getProp($extra, 'cid', 0); // // // 2. 扣费提示 // if ($coin > $userCoin['total']) { // // 书币不够,弹框提示返回数据 // // } // // // 3. 根据扣除规则,获取需要扣减的书币列表 // [$updateData, $changeData] = $this->buildDecreaseData($uid, $coin, $userCoin); // [$amount, $awardTotal, $awardForever, $awardValid, $charge] = $this->filterFieldUpdate($changeData); // // // 5. 开启事务,执行扣除(添加操作记录) // try { // DB::beginTransaction(); // // // 扣除书币 // $upRes = true; // if ($updateData) { // foreach ($updateData as $data) { // $res = BookCoinLogs::getInstance($uid)->updateUserCoinLog($data['id'], $uid, $data['remain'], $data['updated_at']); // $upRes = $upRes && $res; // } // } // // $addRes = false; // $multiRes = false; // // // 扣除书币成功 // if ($upRes) { // // 增加扣币记录 // $addRes = $this->addOptToCoinLog([ // 'uid' => $uid, // 'crease_type' => CoinConst::DECREASE, // 'coin_type' => '', // 'change' => $coin, // 'change_type' => $changeType, // 'extra' => json_encode([ // 'updateData' => $changeData // ]) // ]); // // // 批量更新用户相关计数 // if ($amount) { // $multiRes = User::multiUpdateUserStat($uid, [ // 'balance_total' => 'balance_total - ' . $amount, // 总书币 // 'bonus_total' => 'bonus_total - ' . $awardTotal, // 奖金币汇总 // 'bonus_forever' => 'bonus_forever - ' . $awardForever, // 永久奖金币 // 'bonus_valid' => 'bonus_valid - ' . $awardValid, // 有效期奖金币 // 'charge_balance' => 'charge_balance - ' . $charge, // 充值书币 // ]); // } else { // // 兼容限免 // $multiRes = true; // } // } // // // 判断返回结果 // if (!$upRes || !$addRes || !$multiRes) { // myLog('decreaseBookCoin')->info('rollback', compact('uid', 'upRes', 'addRes', 'multiRes')); // DB::rollback(); // return false; // } // // // 提交事务 // DB::commit(); // } catch (\Exception $e) { // myLog('decreaseBookCoin')->info('exception', [$e->getMessage(), $e->getTraceAsString()]); // DB::rollback(); // return false; // } // // return ['originCoinChange' => (int)$charge, 'awardCoinChange' => (int)$amount - (int)$charge]; // } }