auth(); // 闭包重建查询:TP5.0 的 count() 会清空 Query::options,不能复用同一对象 list($rows, $total, $page, $size) = $this->pageQuery(function ($forCount) use ($user) { $q = Db::name('fc_favorite')->alias('f') ->join('fc_post p', 'p.id = f.post_id') ->where('f.user_id', $user['id']) ->where('p.audit_status', 1) ->where('p.status', 1) ->whereNull('p.deletetime'); if ($forCount) { return $q; } return $q->field('p.*')->order('f.createtime desc, f.id desc'); }); $builder = new Post(); $list = []; foreach ($rows as $row) { // 复用 Post 的信息结构,保证「我的收藏」与首页信息流字段完全一致 $list[] = $builder->buildPost($row); } $this->ok($this->paginate($list, $total, $page, $size)); } /** * POST /api/v1/favorite/toggle {postId} * → {faved,count} * faved=true 表示本次操作后为「已收藏」;count 为该信息当前收藏总数。 */ public function toggle() { $user = $this->auth(); $postId = (int)$this->input('postId', 0); $post = $postId > 0 ? Db::name('fc_post')->where('id', $postId)->whereNull('deletetime')->find() : null; if (!$post) { $this->fail('信息不存在', 404); } $exist = Db::name('fc_favorite')->where('user_id', $user['id'])->where('post_id', $postId)->find(); $faved = false; if ($exist) { Db::name('fc_favorite')->where('id', $exist['id'])->delete(); } else { try { Db::name('fc_favorite')->insert([ 'user_id' => $user['id'], 'post_id' => $postId, 'createtime' => time(), ]); $faved = true; } catch (\Exception $e) { // 并发下唯一索引冲突:视为已收藏(幂等) $faved = true; } } $count = (int)Db::name('fc_favorite')->where('post_id', $postId)->count(); $this->ok(['faved' => $faved, 'count' => $count]); } /** * GET /api/v1/follow/list {page,size} * → 分页 {list,page,size,total,hasMore} * 返回当前登录用户关注的用户/商家列表 * * @return void */ public function followList() { $user = $this->auth(); list($rows, $total, $page, $size) = $this->pageQuery(function ($forCount) use ($user) { $q = Db::name('fc_follow')->where('user_id', $user['id']); if ($forCount) { return $q; } return $q->order('id desc'); }); $list = []; foreach ($rows as $row) { $targetId = (int)$row['target_user_id']; $targetUser = Db::name('user')->where('id', $targetId)->find(); if (!$targetUser) { continue; } $ext = Db::name('fc_user_ext')->where('user_id', $targetId)->find(); $merchant = Db::name('fc_merchant')->where('user_id', $targetId)->find(); $isMerchant = $merchant && (int)$merchant['audit_status'] === 1; // 统计该用户发布的有效信息条数 $postCount = (int)Db::name('fc_post') ->where('user_id', $targetId) ->where('status', 1) ->where('audit_status', 1) ->whereNull('deletetime') ->count(); $list[] = [ 'id' => (int)$row['id'], 'userId' => $targetId, 'nickname' => (string)($merchant && $merchant['name'] ? $merchant['name'] : $targetUser['nickname']), 'avatar' => (string)($merchant && $merchant['logo'] ? $merchant['logo'] : $targetUser['avatar']), 'isMerchant' => $isMerchant, 'merchantId' => $merchant ? (int)$merchant['id'] : 0, 'category' => (string)($merchant && $merchant['category'] ? $merchant['category'] : ''), 'postCount' => $postCount, 'followed' => true, 'followTime' => $this->iso($row['createtime']), ]; } $this->ok($this->paginate($list, $total, $page, $size)); } /** * POST /api/v1/follow/toggle {userId} * → {followed} * 规则:不能关注自己 → fail('不能关注自己');目标用户必须存在。 */ public function follow() { $user = $this->auth(); $targetUserId = (int)$this->input('userId', 0); if ($targetUserId <= 0) { $this->fail('用户不存在', 404); } if ($targetUserId === (int)$user['id']) { $this->fail('不能关注自己'); } $target = Db::name('user')->where('id', $targetUserId)->find(); if (!$target) { $this->fail('用户不存在', 404); } $exist = Db::name('fc_follow') ->where('user_id', $user['id']) ->where('target_user_id', $targetUserId) ->find(); $followed = false; if ($exist) { Db::name('fc_follow')->where('id', $exist['id'])->delete(); } else { try { Db::name('fc_follow')->insert([ 'user_id' => $user['id'], 'target_user_id' => $targetUserId, 'createtime' => time(), ]); $followed = true; } catch (\Exception $e) { // 并发下唯一索引冲突:视为已关注(幂等) $followed = true; } } $this->ok(['followed' => $followed]); } /** * POST /api/v1/favorite/status {postIds:[],userIds:[]} * → {faved:{postId:true|false},followed:{userId:true|false}} * 说明:批量查询(参照「详情页进入时一次性拿状态」); * 入参为空数组时返回**空对象** {}((object) 强转,避免 PHP 空数组被序列化成 []); * 查询结果按请求的 id 全量回填(未收藏/未关注为 false)。 */ public function status() { $user = $this->auth(); $faved = []; foreach ($this->idArray('postIds') as $postId) { $faved[(string)$postId] = false; } if ($faved) { $rows = Db::name('fc_favorite') ->where('user_id', $user['id']) ->where('post_id', 'in', array_keys($faved)) ->column('post_id'); foreach ($rows as $postId) { $faved[(string)$postId] = true; } } $followed = []; foreach ($this->idArray('userIds') as $userId) { $followed[(string)$userId] = false; } if ($followed) { $rows = Db::name('fc_follow') ->where('user_id', $user['id']) ->where('target_user_id', 'in', array_keys($followed)) ->column('target_user_id'); foreach ($rows as $userId) { $followed[(string)$userId] = true; } } $this->ok([ 'faved' => (object)$faved, 'followed' => (object)$followed, ]); } /** * 入参 ID 数组归一化(支持数组 / JSON 字符串 / 逗号串) * @param string $key * @return array 去重后的正整数 ID 列表(上限 200,避免超长 IN) */ protected function idArray($key) { $val = $this->input($key, []); if (is_string($val)) { $decoded = json_decode($val, true); $val = is_array($decoded) ? $decoded : explode(',', $val); } if (!is_array($val)) { return []; } $ids = []; foreach ($val as $item) { $item = (int)$item; if ($item > 0) { $ids[] = $item; } } return array_slice(array_values(array_unique($ids)), 0, 200); } }