ProgressChapterController.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  1. <?php
  2. namespace App\Http\Controllers;
  3. use App\Http\Api\StudioApi;
  4. use App\Models\Channel;
  5. use App\Models\Like;
  6. use App\Models\PaliText;
  7. use App\Models\ProgressChapter;
  8. use App\Models\Tag;
  9. use App\Models\TagMap;
  10. use App\Models\View;
  11. use App\Services\AuthService;
  12. use Illuminate\Http\Request;
  13. use Illuminate\Http\Response;
  14. use Illuminate\Support\Facades\Cache;
  15. use Illuminate\Support\Facades\DB;
  16. use Illuminate\Support\Str;
  17. class ProgressChapterController extends Controller
  18. {
  19. /**
  20. * Display a listing of the resource.
  21. *
  22. * @return Response
  23. */
  24. public function index(Request $request)
  25. {
  26. $minProgress = (float) $request->input('progress', 0.8);
  27. $offset = (int) $request->input('offset', 0);
  28. $limit = (int) $request->input('limit', 20);
  29. $channel_id = $request->input('channel');
  30. //
  31. $chapters = false;
  32. switch ($request->input('view')) {
  33. case 'ids':
  34. $aChannel = explode(',', $request->input('channel'));
  35. $chapters = ProgressChapter::select('channel_id')->selectRaw('uid as id')
  36. ->with(['channel' => function ($query) { // city对应上面province模型中定义的city方法名 闭包内是子查询
  37. return $query->select('*');
  38. }])
  39. ->where('book', $request->input('book'))
  40. ->where('para', $request->input('par'))
  41. ->whereIn('channel_id', $aChannel)->get();
  42. $all_count = count($chapters);
  43. break;
  44. case 'studio':
  45. // 查询该studio的channel
  46. $name = $request->input('name');
  47. $studioId = StudioApi::getIdByName($request->input('name'));
  48. if ($studioId === false) {
  49. return $this->error('no user');
  50. }
  51. $table = Channel::where('owner_uid', $studioId);
  52. if ($request->input('public') === 'true') {
  53. $table = $table->where('status', 30);
  54. }
  55. $channels = $table->select('uid')->get();
  56. $chapters = ProgressChapter::whereIn('progress_chapters.channel_id', $channels)
  57. ->leftJoin('pali_texts', function ($join) {
  58. $join->on('progress_chapters.book', '=', 'pali_texts.book');
  59. $join->on('progress_chapters.para', '=', 'pali_texts.paragraph');
  60. })
  61. ->where('progress', '>', 0.85)
  62. ->orderby('progress_chapters.created_at', 'desc')
  63. ->skip($request->input('offset', 0))
  64. ->take($request->input('limit', 1000))
  65. ->get();
  66. $all_count = ProgressChapter::whereIn('progress_chapters.channel_id', $channels)
  67. ->where('progress', '>', 0.85)->count();
  68. break;
  69. case 'tag':
  70. $tm = (new TagMap)->getTable();
  71. $pc = (new ProgressChapter)->getTable();
  72. $t = (new Tag)->getTable();
  73. $query = "select t.name,count(*) from $tm tm
  74. join tags as t on tm.tag_id = t.id
  75. join progress_chapters as pc on tm.anchor_id = pc.uid
  76. where tm.table_name = 'progress_chapters' and
  77. pc.progress > ?
  78. group by t.name;";
  79. $chapters = DB::select($query, [$minProgress]);
  80. if ($chapters) {
  81. $all_count = count($chapters);
  82. } else {
  83. $all_count = 0;
  84. }
  85. break;
  86. case 'chapter-tag':
  87. if ($request->input('tags') && $request->input('tags') !== '') {
  88. $tags = explode(',', $request->input('tags'));
  89. foreach ($tags as $tag) {
  90. // code...
  91. if (! empty($tag)) {
  92. $tagNames[] = $tag;
  93. }
  94. }
  95. }
  96. $tm = (new TagMap)->getTable();
  97. $pc = (new ProgressChapter)->getTable();
  98. $tg = (new Tag)->getTable();
  99. $pt = (new PaliText)->getTable();
  100. $param[] = $minProgress;
  101. if (isset($tagNames)) {
  102. $where1 = ' where co = '.count($tagNames);
  103. $a = implode(',', array_fill(0, count($tagNames), '?'));
  104. $in1 = "and t.name in ({$a})";
  105. $param = array_merge($param, $tagNames);
  106. } else {
  107. $where1 = ' ';
  108. $in1 = ' ';
  109. }
  110. if (Str::isUuid($channel_id)) {
  111. $channel = "and channel_id = '{$channel_id}' ";
  112. } else {
  113. $channel = '';
  114. }
  115. $query = "
  116. select tags.id,tags.name,co as count
  117. from (
  118. select tm.tag_id,count(*) as co from (
  119. select anchor_id as id from (
  120. select tm.anchor_id , count(*) as co
  121. from $tm as tm
  122. left join $tg as t on tm.tag_id = t.id
  123. left join $pc as pc on tm.anchor_id = pc.uid
  124. where tm.table_name = 'progress_chapters' and
  125. pc.progress > ?
  126. $in1
  127. $channel
  128. group by tm.anchor_id
  129. ) T
  130. $where1
  131. ) CID
  132. left join $tm as tm on tm.anchor_id = CID.id
  133. group by tm.tag_id
  134. ) tid
  135. left join $tg on $tg.id = tid.tag_id
  136. order by count desc
  137. ";
  138. if (isset($param)) {
  139. $chapters = DB::select($query, $param);
  140. } else {
  141. $chapters = DB::select($query);
  142. }
  143. $all_count = count($chapters);
  144. break;
  145. case 'lang':
  146. $chapters = ProgressChapter::select('lang')
  147. ->selectRaw('count(*) as count')
  148. ->where('progress', '>', $minProgress)
  149. ->groupBy('lang')
  150. ->get();
  151. $all_count = count($chapters);
  152. break;
  153. case 'channel-type':
  154. break;
  155. case 'channel':
  156. /**
  157. * 总共有多少channel
  158. */
  159. $chapters = ProgressChapter::select('channel_id')
  160. ->selectRaw('count(*) as count')
  161. ->with(['channel' => function ($query) {
  162. return $query->select('*');
  163. }])
  164. ->leftJoin('channels', 'progress_chapters.channel_id', '=', 'channels.uid')
  165. ->where('progress', '>', $minProgress)
  166. ->where('channels.status', '>=', 30);
  167. if (! empty($request->input('channel_type'))) {
  168. $chapters = $chapters->where('channels.type', $request->input('channel_type'));
  169. }
  170. if (! empty($request->input('lang'))) {
  171. $chapters = $chapters->where('progress_chapters.lang', $request->input('lang'));
  172. }
  173. $chapters = $chapters->groupBy('channel_id')
  174. ->orderBy('count', 'desc')
  175. ->get();
  176. foreach ($chapters as $key => $chapter) {
  177. $chapter->studio = StudioApi::getById($chapter->channel->owner_uid);
  178. }
  179. $all_count = count($chapters);
  180. break;
  181. case 'chapter_channels':
  182. /**
  183. * 某个章节 有多少channel
  184. */
  185. $chapters = ProgressChapter::select(
  186. 'book',
  187. 'para',
  188. 'progress_chapters.uid',
  189. 'progress_chapters.channel_id',
  190. 'progress',
  191. 'channels.name',
  192. 'channels.type',
  193. 'channels.owner_uid',
  194. 'progress_chapters.updated_at'
  195. )
  196. ->leftJoin('channels', 'progress_chapters.channel_id', '=', 'channels.uid')
  197. ->where('book', $request->input('book'))
  198. ->where('para', $request->input('par'))
  199. ->orderBy('progress', 'desc')
  200. ->get();
  201. // 整个循环共用一次登录判定,不必每行都解一次 JWT
  202. $currentUser = AuthService::current($request);
  203. foreach ($chapters as $key => $value) {
  204. // code...
  205. $chapters[$key]->views = View::where('target_id', $value->uid)->count();
  206. $likes = Like::where('target_id', $value->uid)
  207. ->groupBy('type')
  208. ->select('type')
  209. ->selectRaw('count(*)')
  210. ->get();
  211. // 原先凭据取自 $_COOKIE['user_uid'](v1 遗留的认证绕过)。这段只
  212. // 决定 selected 这个可选字段出不出现,改走 AuthService 后带 bearer
  213. // 的登录用户才拿得到——以前只有揣着 v1 cookie 的浏览器有。
  214. // FIXME: 这里是 N+1,每个 chapter × 每个 type 各查一次。
  215. if ($currentUser) {
  216. foreach ($likes as $key1 => $like) {
  217. // 查看这些点赞里有没有我点的
  218. $myLikeId = Like::where([
  219. 'target_id' => $value->uid,
  220. 'type' => $like->type,
  221. 'user_id' => $currentUser['user_uid'],
  222. ])->value('id');
  223. if ($myLikeId) {
  224. $likes[$key1]->selected = $myLikeId;
  225. }
  226. }
  227. }
  228. $chapters[$key]->likes = $likes;
  229. $chapters[$key]->studio = StudioApi::getById($value->owner_uid);
  230. $chapters[$key]->channel = ['uid' => $value->channel_id, 'name' => $value->name, 'type' => $value->type];
  231. $progress_key = "/chapter_dynamic/{$value->book}/{$value->para}/ch_{$value->channel_id}";
  232. $chapters[$key]->progress_line = Cache::get($progress_key);
  233. }
  234. $all_count = count($chapters);
  235. break;
  236. case 'chapter':
  237. $tm = (new TagMap)->getTable();
  238. $pc = (new ProgressChapter)->getTable();
  239. $tg = (new Tag)->getTable();
  240. $pt = (new PaliText)->getTable();
  241. // 标签过滤
  242. if ($request->has('tags') && ! empty($request->input('tags'))) {
  243. $tags = explode(',', $request->input('tags'));
  244. foreach ($tags as $tag) {
  245. // code...
  246. if (! empty($tag)) {
  247. $tagNames[] = $tag;
  248. }
  249. }
  250. }
  251. if (isset($tagNames)) {
  252. $where1 = ' where co = '.count($tagNames);
  253. $a = implode(',', array_fill(0, count($tagNames), '?'));
  254. $in1 = "and t.name in ({$a})";
  255. $param = $tagNames;
  256. } else {
  257. $where1 = ' ';
  258. $in1 = ' ';
  259. }
  260. if ($request->has('studio')) {
  261. $studioId = StudioApi::getIdByName($request->input('studio'));
  262. $table = Channel::where('owner_uid', $studioId);
  263. if ($request->input('public') === 'true') {
  264. $table = $table->where('status', 30);
  265. }
  266. $channels = $table->select('uid')->get();
  267. $arrChannel = [];
  268. foreach ($channels as $oneChannel) {
  269. // code...
  270. if (Str::isUuid($oneChannel->uid)) {
  271. $arrChannel[] = "'{$oneChannel->uid}'";
  272. }
  273. }
  274. $channel = 'and channel_id in ('.implode(',', $arrChannel).') ';
  275. } else {
  276. if (Str::isUuid($channel_id)) {
  277. $channel = "and channel_id = '{$channel_id}' ";
  278. } else {
  279. $channel = '';
  280. }
  281. }
  282. // 完成度过滤
  283. $param[] = $minProgress;
  284. // 语言过滤
  285. if (! empty($request->input('lang'))) {
  286. $whereLang = ' and pc.lang = ? ';
  287. $param[] = $request->input('lang');
  288. } else {
  289. $whereLang = ' ';
  290. }
  291. // channel type过滤
  292. if ($request->has('channel_type') && ! empty($request->input('channel_type'))) {
  293. $channel_type = 'and ch.type = ? ';
  294. $param[] = $request->input('channel_type');
  295. } else {
  296. $channel_type = '';
  297. }
  298. $param_count = $param;
  299. $param[] = $offset;
  300. $query = "
  301. select tpc.pc_uid as uid, tpc.book ,tpc.para,tpc.channel_id,tpc.title,pt.toc,pt.path,tpc.progress,tpc.summary,tpc.created_at,tpc.updated_at
  302. from (
  303. select pcd.uid as pc_uid, ch.uid as ch_uid, book , para, channel_id,progress, title ,pcd.summary , pcd.created_at,pcd.updated_at
  304. from (
  305. select uid, book,para,lang,progress,channel_id,title,summary ,created_at ,updated_at
  306. from (
  307. select anchor_id as cid
  308. from (
  309. select tm.anchor_id , count(*) as co
  310. from $tm as tm
  311. left join $tg as t on tm.tag_id = t.id
  312. where tm.table_name = 'progress_chapters'
  313. $in1
  314. group by tm.anchor_id
  315. ) T
  316. $where1
  317. ) CID
  318. left join $pc as pc on CID.cid = pc.uid
  319. where pc.progress > ?
  320. $channel $whereLang
  321. ) pcd
  322. left join channels as ch on pcd.channel_id = ch.uid
  323. where ch.status >= 30 $channel_type
  324. order by pcd.created_at desc
  325. limit {$limit} offset ?
  326. ) tpc
  327. left join $pt as pt on tpc.book = pt.book and tpc.para = pt.paragraph;";
  328. $chapters = DB::select($query, $param);
  329. foreach ($chapters as $key => $chapter) {
  330. // code...
  331. $chapter->channel = Channel::where('uid', $chapter->channel_id)->select(['name', 'owner_uid'])->first();
  332. $chapter->studio = StudioApi::getById($chapter->channel['owner_uid']);
  333. $chapter->views = View::where('target_id', $chapter->uid)->count();
  334. $chapter->likes = Like::where(['type' => 'like', 'target_id' => $chapter->uid])->count();
  335. $chapter->tags = TagMap::where('anchor_id', $chapter->uid)
  336. ->leftJoin('tags', 'tag_maps.tag_id', '=', 'tags.id')
  337. ->select(['tags.id', 'tags.name', 'tags.description'])
  338. ->get();
  339. }
  340. // 计算按照这个条件搜索到的总数
  341. $query = "
  342. select count(*) as count
  343. from (
  344. select *
  345. from (
  346. select anchor_id as cid
  347. from (
  348. select tm.anchor_id , count(*) as co
  349. from $tm as tm
  350. left join $tg as t on tm.tag_id = t.id
  351. where tm.table_name = 'progress_chapters'
  352. $in1
  353. group by tm.anchor_id
  354. ) T
  355. $where1
  356. ) CID
  357. left join $pc as pc on CID.cid = pc.uid
  358. where pc.progress > ?
  359. $channel $whereLang
  360. ) pcd
  361. left join channels as ch on pcd.channel_id = ch.uid
  362. where ch.status >= 30 $channel_type
  363. ";
  364. $count = DB::select($query, $param_count);
  365. $all_count = $count[0]->count;
  366. break;
  367. case 'top':
  368. break;
  369. case 'search':
  370. $key = $request->input('key');
  371. $table = ProgressChapter::where('title', 'like', "%{$key}%");
  372. // 获取记录总条数
  373. $all_count = $table->count();
  374. // 处理排序
  375. if ($request->has('order') && $request->has('dir')) {
  376. $table = $table->orderBy($request->input('order'), $request->input('dir'));
  377. } else {
  378. // 默认排序
  379. $table = $table->orderBy('updated_at', 'desc');
  380. }
  381. // 处理分页
  382. if ($request->has('limit')) {
  383. if ($request->has('offset')) {
  384. $offset = $request->input('offset');
  385. } else {
  386. $offset = 0;
  387. }
  388. $table = $table->skip($offset)->take($request->input('limit'));
  389. }
  390. // 获取数据
  391. $chapters = $table->get();
  392. // TODO 移到resource
  393. foreach ($chapters as $key => $chapter) {
  394. // code...
  395. $chapter->toc = PaliText::where('book', $chapter->book)->where('paragraph', $chapter->para)->value('toc');
  396. $chapter->path = PaliText::where('book', $chapter->book)->where('paragraph', $chapter->para)->value('path');
  397. $chapter->channel = Channel::where('uid', $chapter->channel_id)->select(['name', 'owner_uid'])->first();
  398. if ($chapter->channel) {
  399. $chapter->studio = StudioApi::getById($chapter->channel['owner_uid']);
  400. } else {
  401. $chapter->channel = [
  402. 'name' => 'unknown',
  403. 'owner_uid' => 'unknown',
  404. ];
  405. $chapter->studio = [
  406. 'id' => '',
  407. 'nickName' => 'unknown',
  408. 'realName' => 'unknown',
  409. 'avatar' => '',
  410. ];
  411. }
  412. $chapter->views = View::where('target_id', $chapter->uid)->count();
  413. $chapter->likes = Like::where(['type' => 'like', 'target_id' => $chapter->uid])->count();
  414. $chapter->tags = TagMap::where('anchor_id', $chapter->uid)
  415. ->leftJoin('tags', 'tag_maps.tag_id', '=', 'tags.id')
  416. ->select(['tags.id', 'tags.name', 'tags.description'])
  417. ->get();
  418. }
  419. break;
  420. case 'public':
  421. break;
  422. }
  423. if ($chapters) {
  424. return $this->ok(['rows' => $chapters, 'count' => $all_count]);
  425. } else {
  426. return $this->error('no data');
  427. }
  428. }
  429. /**
  430. * Show the form for creating a new resource.
  431. *
  432. * @return Response
  433. */
  434. public function create()
  435. {
  436. //
  437. }
  438. /**
  439. * Store a newly created resource in storage.
  440. *
  441. * @return Response
  442. */
  443. public function store(Request $request)
  444. {
  445. //
  446. }
  447. /**
  448. * Display the specified resource.
  449. *
  450. * @return Response
  451. */
  452. public function show(ProgressChapter $progress)
  453. {
  454. //
  455. return $this->ok($progress);
  456. }
  457. /**
  458. * Show the form for editing the specified resource.
  459. *
  460. * @return Response
  461. */
  462. public function edit(ProgressChapter $progressChapter)
  463. {
  464. //
  465. }
  466. /**
  467. * Update the specified resource in storage.
  468. *
  469. * @return Response
  470. */
  471. public function update(Request $request, ProgressChapter $progressChapter)
  472. {
  473. //
  474. }
  475. /**
  476. * Remove the specified resource from storage.
  477. *
  478. * @return Response
  479. */
  480. public function destroy(ProgressChapter $progressChapter)
  481. {
  482. //
  483. }
  484. }