123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047 |
- <?php
- namespace App\Services\Book;
- use App\Cache\StatisticCache;
- use App\Cache\UserCache;
- use App\Consts\BaseConst;
- use App\Consts\CoinConst;
- use App\Consts\ErrorConst;
- use App\Facade\Site;
- use App\Libs\Utils;
- use App\Models\Book\Book;
- use App\Models\Book\BookConfig;
- use App\Models\Book\Category;
- use App\Models\Book\Chapter;
- use App\Models\User\User;
- use App\Services\OpenApi\OpenService;
- use App\Services\Subscribe\ChapterOrderService;
- use App\Services\User\UserService;
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Log;
- use Illuminate\Support\Facades\Redis;
- use Vinkla\Hashids\Facades\Hashids;
- class BookService
- {
- protected $userService;
- protected $openService;
- protected $chapterOrderService;
- public function __construct(UserService $userService, OpenService $openService, ChapterOrderService $chapterOrderService)
- {
- $this->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];
- // }
- }
|