// +---------------------------------------------------------------------- declare (strict_types = 1); namespace addon\wxchat\controller\api; use addon\wxchat\model\WxchatMoment as MomentModel; use think\facade\Log; use think\Request; use ywxapp\controller\FrontendBase; header('Access-Control-Allow-Origin: *'); // 允许所有域 header('Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE, PUT'); header('Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Authorization'); header('Access-Control-Allow-Credentials: true'); /** * Moment 类 * * @author ywxapp */ class Moment extends FrontendBase { private $uid = 0; /** * Summary of noNeedLogin * @var array */ protected $noNeedLogin = []; /** * Summary of noNeedVerify * @var array */ protected $noNeedVerify = ['*']; /** * 控制器初始化 initialize * @return void */ protected function initialize() { if ($this->auth->isLogin) { $this->uid = $this->auth->model->uid; } } /** * 获取动态详情 * @param int $id * @return Json */ public function red(int $id) { try { // 查询动态详情 $info = MomentModel::with([ 'member' => function ($query) { $query->field('id,username,nickname,avatar,introduction,follower_count,following_count,dynamic_count'); }, 'media' => function ($query) { $query->field('id,mid,type,url,thumbnail_url,width,height,duration,sort_order')->order('sort_order', 'asc'); }, 'hashtags' => function ($query) { $query->field('hashtags.id,hashtags.name,hashtags.description'); }, ])->where('id', $id) ->where('status', 1) ->find(); if (! $info) { $this->error(); } // 增加浏览量 $this->increaseViewCount($id, $this->uid); // 获取当前用户与动态的交互状态 $interaction = $this->getUserInteraction($id, $this->uid); // 获取评论列表(分页) $page = Request::param('page', 1); $limit = Request::param('limit', 20); $comments = $this->getComments($id, $page, $limit, $this->uid); // 构建响应数据 $response = [ 'moment' => $this->formatDynamic($info, $interaction), 'comments' => $comments['list'], 'comment_total' => $comments['total'], 'comment_pages' => $comments['pages'], ]; $this->success($response, 'success'); } catch (\Exception $e) { $this->error('服务器错误:' . $e->getMessage()); } } /** * 动态广场列表(推荐/关注/附近/热门) */ public function index() { $uid = $this->auth->model->uid; $scope = Request::param('scope', 'recommend'); $page = max(1, (int) Request::param('page', 1)); $pageSize = max(1, min(50, (int) Request::param('page_size', 10))); $query = MomentModel::with([ 'member' => function ($q) { $q->field('id,username,nickname,avatar,introduction'); }, 'media' => function ($q) { $q->field('id,mid,type,url,thumbnail_url,sort_order')->order('sort_order', 'asc'); }, 'hashtags' => function ($q) { $q->field('hashtags.id,hashtags.name'); }, ])->where('status', 1); if ($scope == 'follow') { $ids = UserFollowModel::where('follower_id', $uid)->column('following_id'); if (empty($ids)) { $this->success(['list' => [], 'total' => 0, 'page' => $page]); return; } $query->where('uid', 'in', $ids)->order('create_at', 'desc'); } elseif ($scope == 'hot') { $query->order('like_count', 'desc')->order('comment_count', 'desc'); } else { if ($scope == 'nearby') { $query->where('latitude', '>', 0)->where('longitude', '>', 0); } $query->order('create_at', 'desc'); } $total = $query->count(); $list = $query->page($page, $pageSize)->select(); $result = []; foreach ($list as $info) { $result[] = $this->formatDynamic($info, $this->getUserInteraction($info->id, $uid)); } $this->success(['list' => $result, 'total' => $total, 'page' => $page]); } /** * 我的动态列表(获赞页复用) */ public function mine() { $uid = $this->uid; $page = max(1, (int) Request::param('page', 1)); $pageSize = max(1, min(50, (int) Request::param('page_size', 10))); $query = MomentModel::with([ 'member' => function ($q) { $q->field('id,username,nickname,avatar,introduction'); }, 'media' => function ($q) { $q->field('id,mid,type,url,thumbnail_url,sort_order')->order('sort_order', 'asc'); }, 'hashtags' => function ($q) { $q->field('hashtags.id,hashtags.name'); }, ])->where('uid', $uid)->where('status', 1)->order('create_at', 'desc'); $total = $query->count(); $list = $query->page($page, $pageSize)->select(); $result = []; foreach ($list as $info) { $result[] = $this->formatDynamic($info, $this->getUserInteraction($info->id, $uid)); } $this->success(['list' => $result, 'total' => $total, 'page' => $page]); } /** * 我的收藏列表 */ public function collectedList() { $uid = $this->uid; $page = max(1, (int) Request::param('page', 1)); $pageSize = max(1, min(50, (int) Request::param('page_size', 10))); $mids = Db::name('wxchat_moment_collects') ->where('uid', $uid) ->order('create_at', 'desc') ->column('mid'); if (empty($mids)) { $this->success(['list' => [], 'total' => 0, 'page' => $page]); return; } $total = count($mids); $slice = array_slice($mids, ($page - 1) * $pageSize, $pageSize); $list = MomentModel::with([ 'member' => function ($q) { $q->field('id,username,nickname,avatar,introduction'); }, 'media' => function ($q) { $q->field('id,mid,type,url,thumbnail_url,sort_order')->order('sort_order', 'asc'); }, 'hashtags' => function ($q) { $q->field('hashtags.id,hashtags.name'); }, ])->where('id', 'in', $slice)->where('status', 1)->select(); $map = []; foreach ($list as $info) { $map[$info->id] = $info; } $result = []; foreach ($slice as $mid) { if (isset($map[$mid])) { $result[] = $this->formatDynamic($map[$mid], $this->getUserInteraction($mid, $uid)); } } $this->success(['list' => $result, 'total' => $total, 'page' => $page]); } /** * 我的浏览记录 */ public function historyList() { $uid = $this->uid; $page = max(1, (int) Request::param('page', 1)); $pageSize = max(1, min(50, (int) Request::param('page_size', 10))); $mids = Db::name('wxchat_view_history') ->where('uid', $uid) ->order('create_at', 'desc') ->column('mid'); if (empty($mids)) { $this->success(['list' => [], 'total' => 0, 'page' => $page]); return; } $mids = array_values(array_unique($mids)); $total = count($mids); $slice = array_slice($mids, ($page - 1) * $pageSize, $pageSize); $list = MomentModel::with([ 'member' => function ($q) { $q->field('id,username,nickname,avatar,introduction'); }, 'media' => function ($q) { $q->field('id,mid,type,url,thumbnail_url,sort_order')->order('sort_order', 'asc'); }, 'hashtags' => function ($q) { $q->field('hashtags.id,hashtags.name'); }, ])->where('id', 'in', $slice)->where('status', 1)->select(); $map = []; foreach ($list as $info) { $map[$info->id] = $info; } $result = []; foreach ($slice as $mid) { if (isset($map[$mid])) { $result[] = $this->formatDynamic($map[$mid], $this->getUserInteraction($mid, $uid)); } } $this->success(['list' => $result, 'total' => $total, 'page' => $page]); } /** * 发布动态 * @return Json */ public function create() { try { $data = Request::post(); $validate = Validate::rule([ 'content' => 'max:2000', 'type' => 'require|in:1,2,3', 'is_public' => 'in:0,1', 'media' => 'array', 'hashtags' => 'array', ]); if (! $validate->check($data)) { $this->error($validate->getError()); } // 内容与媒体至少存在其一 if (empty($data['content']) && empty($data['media'])) { $this->error('请填写内容或添加图片'); } Db::startTrans(); try { // 创建动态 $info = new MomentModel(); $info->uid = $this->uid; $info->content = $data['content'] ?? ''; $info->type = $data['type']; $info->location = $data['location'] ?? null; $info->latitude = $data['latitude'] ?? null; $info->longitude = $data['longitude'] ?? null; $info->is_public = $data['is_public'] ?? 1; $info->save(); $infoId = $info->id; // 保存媒体文件 if (! empty($data['media'])) { $mediaData = []; foreach ($data['media'] as $index => $media) { $mediaData[] = [ 'dynamic_id' => $infoId, 'type' => $media['type'], 'url' => $media['url'], 'thumbnail_url' => $media['thumbnail_url'] ?? null, 'width' => $media['width'] ?? null, 'height' => $media['height'] ?? null, 'duration' => $media['duration'] ?? null, 'sort_order' => $index, ]; } DynamicMediaModel::insertAll($mediaData); } // 处理话题标签 if (! empty($data['hashtags'])) { $hashtagData = []; foreach ($data['hashtags'] as $hashtagName) { // 查找或创建标签 $hashtag = HashtagModel::where('name', $hashtagName)->find(); if (! $hashtag) { $hashtag = new HashtagModel(); $hashtag->name = $hashtagName; $hashtag->save(); } // 关联动态和标签 $hashtagData[] = [ 'dynamic_id' => $infoId, 'hashtag_id' => $hashtag->id, ]; // 更新标签计数 $hashtag->dynamic_count = Db::raw('dynamic_count + 1'); $hashtag->save(); } if (! empty($hashtagData)) { Db::name('dynamic_hashtags')->insertAll($hashtagData); } } // 更新用户动态计数 UserModel::where('id', $this->uid)->inc('dynamic_count')->update(); Db::commit(); return json([ 'code' => 200, 'message' => '动态发布成功', 'data' => [ 'id' => $infoId, ], ]); } catch (\Exception $e) { Db::rollback(); throw $e; } } catch (\Exception $e) { return json([ 'code' => 500, 'message' => '发布失败:' . $e->getMessage(), 'data' => null, ]); } } /** * 发布评论 * @return Json */ public function comment(): Json { try { $data = Request::post(); // 验证数据 $validate = Validate::rule([ 'dynamic_id' => 'require|integer|gt:0', 'content' => 'require|max:1000', 'parent_id' => 'integer|gt:0', 'reply_to_user_id' => 'integer|gt:0', ]); if (! $validate->check($data)) { return json([ 'code' => 400, 'message' => $validate->getError(), 'data' => null, ]); } // 检查动态是否存在 $info = MomentModel::where('id', $data['dynamic_id']) ->where('status', 1) ->find(); if (! $info) { return json([ 'code' => 404, 'message' => '动态不存在', 'data' => null, ]); } // 开始事务 Db::startTrans(); try { // 创建评论 $comment = new CommentModel(); $comment->dynamic_id = $data['dynamic_id']; $comment->user_id = $this->uid; $comment->parent_id = $data['parent_id'] ?? 0; $comment->reply_to_user_id = $data['reply_to_user_id'] ?? null; $comment->content = $data['content']; $comment->save(); $commentId = $comment->id; // 更新动态评论数 $info->comment_count = Db::raw('comment_count + 1'); $info->save(); // 如果是回复评论,更新父评论的回复数 if (! empty($data['parent_id'])) { CommentModel::where('id', $data['parent_id']) ->inc('reply_count') ->update(); } Db::commit(); // 获取完整的评论信息 $comment = CommentModel::with(['member' => function ($query) { $query->field('id,username,nickname,avatar'); }])->where('id', $commentId)->find(); return json([ 'code' => 200, 'message' => '评论成功', 'data' => $this->formatComment($comment), ]); } catch (\Exception $e) { Db::rollback(); throw $e; } } catch (\Exception $e) { return json([ 'code' => 500, 'message' => '评论失败:' . $e->getMessage(), 'data' => null, ]); } } /** * 点赞动态 * @return Json */ public function like(): Json { try { $infoId = Request::post('dynamic_id', 0); if (! $infoId) { return json([ 'code' => 400, 'message' => '动态ID不能为空', 'data' => null, ]); } // 检查动态是否存在 $info = MomentModel::where('id', $infoId) ->where('status', 1) ->find(); if (! $info) { return json([ 'code' => 404, 'message' => '动态不存在', 'data' => null, ]); } // 检查是否已点赞 $like = DynamicLikeModel::where('dynamic_id', $infoId) ->where('user_id', $this->uid) ->find(); if ($like) { // 取消点赞 $like->delete(); $info->like_count = Db::raw('like_count - 1'); $info->save(); $liked = false; $message = '取消点赞成功'; } else { // 点赞 $like = new DynamicLikeModel(); $like->dynamic_id = $infoId; $like->user_id = $this->uid; $like->save(); $info->like_count = Db::raw('like_count + 1'); $info->save(); $liked = true; $message = '点赞成功'; } return json([ 'code' => 200, 'message' => $message, 'data' => [ 'liked' => $liked, 'like_count' => $info->like_count, ], ]); } catch (\Exception $e) { return json([ 'code' => 500, 'message' => '操作失败:' . $e->getMessage(), 'data' => null, ]); } } /** * 收藏动态 * @return Json */ public function collect(): Json { try { $infoId = Request::post('dynamic_id', 0); $folderId = Request::post('folder_id', 0); if (! $infoId) { return json([ 'code' => 400, 'message' => '动态ID不能为空', 'data' => null, ]); } // 检查动态是否存在 $info = MomentModel::where('id', $infoId) ->where('status', 1) ->find(); if (! $info) { return json([ 'code' => 404, 'message' => '动态不存在', 'data' => null, ]); } // 检查是否已收藏 $collect = DynamicCollectModel::where('dynamic_id', $infoId) ->where('user_id', $this->uid) ->find(); if ($collect) { // 取消收藏 $collect->delete(); $info->collect_count = Db::raw('collect_count - 1'); $info->save(); $collected = false; $message = '取消收藏成功'; } else { // 收藏 $collect = new DynamicCollectModel(); $collect->dynamic_id = $infoId; $collect->user_id = $this->uid; $collect->folder_id = $folderId; $collect->save(); $info->collect_count = Db::raw('collect_count + 1'); $info->save(); $collected = true; $message = '收藏成功'; } return json([ 'code' => 200, 'message' => $message, 'data' => [ 'collected' => $collected, 'collect_count' => $info->collect_count, ], ]); } catch (\Exception $e) { return json([ 'code' => 500, 'message' => '操作失败:' . $e->getMessage(), 'data' => null, ]); } } /** * 关注用户 * @return Json */ public function follow(): Json { try { $followingId = Request::post('following_id', 0); if (! $followingId) { return json([ 'code' => 400, 'message' => '用户ID不能为空', 'data' => null, ]); } if ($this->uid == $followingId) { return json([ 'code' => 400, 'message' => '不能关注自己', 'data' => null, ]); } // 检查用户是否存在 $user = UserModel::where('id', $followingId) ->where('status', 1) ->find(); if (! $user) { return json([ 'code' => 404, 'message' => '用户不存在', 'data' => null, ]); } // 检查是否已关注 $follow = UserFollowModel::where('follower_id', $this->uid) ->where('following_id', $followingId) ->find(); if ($follow) { // 取消关注 $follow->delete(); // 更新用户关注数 UserModel::where('id', $this->uid) ->dec('following_count') ->update(); UserModel::where('id', $followingId) ->dec('follower_count') ->update(); $followed = false; $message = '取消关注成功'; } else { // 关注 $follow = new UserFollowModel(); $follow->follower_id = $this->uid; $follow->following_id = $followingId; $follow->save(); // 更新用户关注数 UserModel::where('id', $this->uid) ->inc('following_count') ->update(); UserModel::where('id', $followingId) ->inc('follower_count') ->update(); $followed = true; $message = '关注成功'; } return json([ 'code' => 200, 'message' => $message, 'data' => [ 'followed' => $followed, ], ]); } catch (\Exception $e) { return json([ 'code' => 500, 'message' => '操作失败:' . $e->getMessage(), 'data' => null, ]); } } /** * 获取评论列表 * @param int $infoId * @param int $page * @param int $limit * @param int $this->uid * @return array */ private function getComments(int $infoId, int $page, int $limit, int $uid = 0) { // 查询顶级评论 $query = CommentModel::with([ 'member' => function ($query) { $query->field('id,username,nickname,avatar'); }, 'replyToUser' => function ($query) { $query->field('id,username,nickname'); }, ])->where('dynamic_id', $infoId) ->where('parent_id', 0) ->where('status', 1) ->order('is_top', 'desc') ->order('create_at', 'desc'); $total = $query->count(); $pages = ceil($total / $limit); $comments = $query->page($page, $limit)->select(); $formattedComments = []; foreach ($comments as $comment) { $formattedComment = $this->formatComment($comment); // 获取评论的回复 $replies = CommentModel::with([ 'member' => function ($query) { $query->field('id,username,nickname,avatar'); }, 'replyToUser' => function ($query) { $query->field('id,username,nickname'); }, ])->where('parent_id', $comment->id) ->where('status', 1) ->order('create_at', 'asc') ->limit(5) // 只获取前5条回复 ->select(); $formattedComment['replies'] = []; foreach ($replies as $reply) { $formattedComment['replies'][] = $this->formatComment($reply); } $formattedComments[] = $formattedComment; } return [ 'list' => $formattedComments, 'total' => $total, 'pages' => $pages, ]; } /** * 增加浏览量 * @param int $infoId * @param int $userId */ private function increaseViewCount(int $infoId, int $userId = 0): void { try { // 更新动态浏览量 MomentModel::where('id', $infoId) ->inc('view_count') ->update(); // 记录浏览历史 $viewHistory = new ViewHistoryModel(); $viewHistory->dynamic_id = $infoId; $viewHistory->user_id = $userId ?: null; $viewHistory->ip_address = Request::ip(); $viewHistory->user_agent = Request::header('user-agent'); $viewHistory->save(); } catch (\Exception $e) { // 浏览记录失败不影响主流程 } } /** * 获取用户与动态的交互状态 * @param int $infoId * @param int $userId * @return array */ private function getUserInteraction(int $infoId, int $userId): array { if (! $userId) { return [ 'liked' => false, 'collected' => false, 'followed' => false, ]; } $liked = DynamicLikeModel::where('dynamic_id', $infoId) ->where('user_id', $userId) ->find() ? true : false; $collected = DynamicCollectModel::where('dynamic_id', $infoId) ->where('user_id', $userId) ->find() ? true : false; // 获取动态作者ID $info = MomentModel::where('id', $infoId)->find(); $followed = false; if ($info && $info->user_id != $userId) { $followed = UserFollowModel::where('follower_id', $userId) ->where('following_id', $info->user_id) ->find() ? true : false; } return [ 'liked' => $liked, 'collected' => $collected, 'followed' => $followed, ]; } /** * 格式化动态数据 * @param MomentModel $info * @param array $interaction * @return array */ private function formatDynamic(MomentModel $info, array $interaction): array { $user = $info->user; $media = []; $images = []; $videoUrl = ''; foreach ($info->media as $m) { $mType = ($m->type == 2 || $m->type == 'video') ? 'video' : 'image'; $mItem = [ 'type' => $mType, 'url' => $m->url, 'thumbnail' => $m->thumbnail_url, ]; $media[] = $mItem; if ($mType == 'image') { $images[] = $m->url; } elseif ($videoUrl === '') { $videoUrl = $m->url; } } $type = 'text'; if ($videoUrl !== '') { $type = 'video'; } elseif (count($images) > 0) { $type = 'image'; } $hashtags = []; foreach ($info->hashtags as $h) { $hashtags[] = ['id' => $h->id, 'name' => $h->name]; } $isLiked = ! empty($interaction['liked']); $isCollect = ! empty($interaction['collected']); $isFollow = ! empty($interaction['followed']); return [ 'id' => $info->id, 'uid' => $info->uid, 'userId' => $user ? $user->id : 0, 'userName' => $user ? $user->nickname : '', 'userAvatar' => $user ? $user->avatar : '', 'userVip' => false, 'content' => $info->content, 'images' => $images, 'videoUrl' => $videoUrl, 'type' => $type, 'location' => $info->location ?: '', 'time' => (int) $info->create_at * 1000, 'likes' => (int) $info->like_count, 'comments' => (int) $info->comment_count, 'shares' => (int) $info->share_count, 'isLiked' => $isLiked, 'isPinned' => false, 'tags' => array_map(function ($h) { return $h['name']; }, $hashtags), 'hot' => (int) $info->like_count >= 50, 'distance' => 0, // 详情页扩展字段 'media' => $media, 'hashtags' => $hashtags, 'stats' => [ 'like_count' => (int) $info->like_count, 'comment_count' => (int) $info->comment_count, 'share_count' => (int) $info->share_count, 'collect_count' => (int) $info->collect_count, 'view_count' => (int) $info->view_count, ], 'interaction' => [ 'liked' => $isLiked, 'collected' => $isCollect, 'followed' => $isFollow, ], 'view' => (int) $info->view_count, ]; } /** * 格式化评论数据 * @param CommentModel $comment * @return array */ private function formatComment(CommentModel $comment): array { $replyTo = null; if ($comment->reply_to_user_id && $comment->replyToUser) { $replyTo = [ 'id' => $comment->replyToUser->id, 'username' => $comment->replyToUser->username, 'nickname' => $comment->replyToUser->nickname, ]; } return [ 'id' => $comment->id, 'member' => [ 'id' => $comment->user->id, 'username' => $comment->user->username, 'nickname' => $comment->user->nickname, 'avatar' => $comment->user->avatar, ], 'reply_to' => $replyTo, 'content' => $comment->content, 'like_count' => $comment->like_count, 'reply_count' => $comment->reply_count, 'is_top' => $comment->is_top, 'create_time' => (int) $comment->create_at, 'create_time_formatted' => date('Y-m-d H:i:s', (int) $comment->create_at), ]; } public function upload() { $request = $this->request; // 记录请求的详细信息 Log::record('请求方法: ' . $request->method()); Log::record('请求URL: ' . $request->url()); Log::record('请求参数: ' . json_encode($request->param())); Log::record('请求头信息: ' . json_encode($request->header())); Log::record('请求头信息: ' . json_encode($request->file())); Log::record('请求体信息: ' . json_encode($request->post())); // 对于POST请求 Log::record('请求IP: ' . $request->ip()); Log::record('请求时间: ' . date('Y-m-d H:i:s')); // 获取表单上传文件 $files = request()->file('file'); // $savename = \think\facade\Filesystem::disk('local')->putFile( 'topic', $files); $savename = \think\facade\Filesystem::putFile('topic', $files); $disk =\think\facade\Filesystem::getDefaultDriver(); $disk = \think\facade\Filesystem::getDiskConfig($disk) ; $this->success([ 'fid' => 0, 'name' => $files->getOriginalName(), 'url' =>$disk['url'] . DIRECTORY_SEPARATOR . $savename, ], '上传成功'); } }