260 行
9.1 KiB
PHP
260 行
9.1 KiB
PHP
<?php
|
||
|
||
namespace app\api\controller;
|
||
|
||
use think\Db;
|
||
|
||
/**
|
||
* 用户端 · 收藏与关注(Module-FAVORITE)
|
||
* 路由:
|
||
* GET /api/v1/favorite/list 收藏的信息分页列表
|
||
* POST /api/v1/favorite/toggle 收藏/取消收藏信息
|
||
* POST /api/v1/follow/toggle 关注/取消关注用户
|
||
* POST /api/v1/favorite/status 批量查询收藏/关注状态
|
||
*
|
||
* 数据表:fa_fc_favorite(user_id,post_id,createtime) / fa_fc_follow(user_id,target_user_id,createtime)
|
||
* 两表均有唯一索引,所有写入「先查后写 + catch 兜底」,保证并发下**幂等**。
|
||
*/
|
||
class Favorite extends V1
|
||
{
|
||
/**
|
||
* GET /api/v1/favorite/list {page,size}
|
||
* → 分页 {list,page,size,total,hasMore}(list 项为 Post 结构,与信息流同构)
|
||
* 规则:仅本人收藏;只返回 audit_status=1 且 status=1 且未软删的信息;按收藏时间倒序。
|
||
*/
|
||
public function list()
|
||
{
|
||
$user = $this->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);
|
||
}
|
||
}
|