dataLists(new CommunityPostLists()); } // 帖子详情 public function postDetail() { $id = $this->request->get('id/d'); $post = CommunityPost::where('id', $id)->where('status', 1)->findOrEmpty(); if ($post->isEmpty()) { return $this->fail('帖子不存在'); } // 增加浏览数 CommunityPost::where('id', $id)->inc('view_count')->update(); $data = $post->toArray(); $ext = $data['ext'] ? (is_string($data['ext']) ? json_decode($data['ext'], true) : $data['ext']) : []; $data['source_url'] = $ext['url'] ?? ''; unset($data['ext']); // 用户信息 $user = User::field('id,sn,nickname,avatar,sex')->where('id', $data['user_id'])->findOrEmpty(); $data['user'] = $user->isEmpty() ? null : $user->toArray(); // 标签 $data['tags'] = $this->getPostTags($id); $isTrumpPost = $this->isTrumpPost($data['tags']); if ($isTrumpPost && empty($ext['translated_content']) && !empty($data['content'])) { $result = AiService::translateCommunityPost($data['content']); if (!empty($result['success']) && !empty($result['content'])) { $ext['translated_content'] = trim($result['content']); CommunityPost::where('id', $id)->update([ 'ext' => json_encode($ext, JSON_UNESCAPED_UNICODE), 'update_time' => time(), ]); } } $data['translated_content'] = $ext['translated_content'] ?? ''; // 付费帖权限控制 $data['is_locked'] = false; $data['is_purchased'] = false; if ($data['is_paid']) { $isAuthor = $this->userId && $this->userId == $data['user_id']; $isPurchased = $this->userId && Db::name('user_account_log') ->where('user_id', $this->userId) ->where('change_type', AccountLogEnum::UP_DEC_PURCHASE_POST) ->where('relation_id', $id) ->count() > 0; $data['is_purchased'] = $isPurchased; if (!$isAuthor && !$isPurchased) { $data['is_locked'] = true; // 付费帖子只显示前20字符 if (mb_strlen($data['content']) > 20) { $data['content'] = mb_substr($data['content'], 0, 20) . '...'; } // 图片只保留第1张 $images = $data['images']; if (is_array($images) && count($images) > 1) { $data['images'] = [$images[0]]; } } } // 当前用户是否点赞 $data['is_liked'] = false; $data['is_self'] = $this->userId && $this->userId == $data['user_id']; $data['is_followed'] = false; $data['is_friend'] = false; $data['relationship'] = PrivateChatService::relationship($this->userId, (int) $data['user_id']); if ($this->userId) { $data['is_liked'] = CommunityLike::where([ 'user_id' => $this->userId, 'target_id' => $id, 'target_type' => 1 ])->count() > 0; // 是否关注作者 $data['is_followed'] = CommunityFollow::where([ 'user_id' => $this->userId, 'follow_user_id' => $data['user_id'] ])->count() > 0; $data['is_friend'] = $data['is_followed']; $data['relationship'] = PrivateChatService::relationship($this->userId, (int) $data['user_id']); } return $this->data($data); } // 删除帖子 public function deletePost() { if (!$this->userId) { return $this->fail('请先登录'); } $postId = $this->request->post('post_id/d'); $post = CommunityPost::where('id', $postId)->findOrEmpty(); if ($post->isEmpty()) { return $this->fail('帖子不存在'); } if ((int) $post->user_id !== (int) $this->userId) { return $this->fail('无权删除该帖子'); } Db::startTrans(); try { $post->delete(); // 同时删除帖子下的评论 CommunityComment::where('post_id', $postId)->delete(); Db::commit(); KbSyncService::enqueue('post', 'post', $postId, 'delete', 20); return $this->success('删除成功'); } catch (\Throwable $e) { Db::rollback(); return $this->fail('删除失败:' . $e->getMessage()); } } // 发帖 public function publish() { $content = $this->request->post('content/s', ''); $images = $this->request->post('images/a', []); $postType = $this->request->post('post_type/d', 0); $matchId = $this->request->post('match_id/d', 0); $tagIds = $this->request->post('tag_ids/a', []); $isPaid = $this->request->post('is_paid/d', 0); $pricePoints = $this->request->post('price_points/d', 0); $freeContentLen = $this->request->post('free_content_len/d', 100); if (empty($content) && empty($images)) { return $this->fail('请输入内容或上传图片'); } if ($isPaid && $pricePoints <= 0) { return $this->fail('付费帖子必须设置积分价格'); } if ($isPaid && ($pricePoints < 1 || $pricePoints > 9999)) { return $this->fail('积分价格范围为1~9999'); } Db::startTrans(); try { $post = CommunityPost::create([ 'user_id' => $this->userId, 'content' => $content, 'images' => $images, 'post_type' => $postType, 'match_id' => $matchId, 'is_paid' => $isPaid ? 1 : 0, 'price_points' => $isPaid ? $pricePoints : 0, 'free_content_len' => $isPaid ? $freeContentLen : 100, 'status' => 1, 'create_time' => time(), 'update_time' => time(), ]); // 关联标签 if (!empty($tagIds)) { $tagData = []; foreach ($tagIds as $tagId) { $tagData[] = ['post_id' => $post->id, 'tag_id' => $tagId]; } Db::name('community_post_tag')->insertAll($tagData); // 更新标签帖子数 CommunityTag::whereIn('id', $tagIds)->inc('post_count')->update(); } Db::commit(); KbSyncService::enqueue('post', 'post', (int) $post->id, 'upsert', 30); return $this->data(['id' => $post->id]); } catch (\Exception $e) { Db::rollback(); return $this->fail('发布失败:' . $e->getMessage()); } } // 评论列表 public function commentLists() { $postId = $this->request->get('post_id/d'); $page = $this->request->get('page_no/d', 1); $size = $this->request->get('page_size/d', 20); $list = CommunityComment::where('post_id', $postId) ->where('status', 1) ->where('parent_id', 0) ->order('create_time desc') ->page($page, $size) ->select() ->toArray(); $total = CommunityComment::where('post_id', $postId) ->where('status', 1) ->where('parent_id', 0) ->count(); // 填充用户信息和子评论 foreach ($list as &$item) { $user = User::field('id,sn,nickname,avatar')->where('id', $item['user_id'])->findOrEmpty(); $item['user'] = $user->isEmpty() ? null : $user->toArray(); $item['is_self'] = $this->userId > 0 && (int) $item['user_id'] === (int) $this->userId; // 子评论(最多3条) $item['replies'] = CommunityComment::where('parent_id', $item['id']) ->where('status', 1) ->order('create_time asc') ->limit(3) ->select() ->each(function ($reply) { $u = User::field('id,sn,nickname,avatar')->where('id', $reply['user_id'])->findOrEmpty(); $reply['user'] = $u->isEmpty() ? null : $u->toArray(); $reply['is_self'] = $this->userId > 0 && (int) $reply['user_id'] === (int) $this->userId; if ($reply['reply_user_id']) { $ru = User::field('id,nickname')->where('id', $reply['reply_user_id'])->findOrEmpty(); $reply['reply_user'] = $ru->isEmpty() ? null : $ru->toArray(); } return $reply; }) ->toArray(); $item['reply_count'] = CommunityComment::where('parent_id', $item['id'])->where('status', 1)->count(); // 当前用户是否点赞 $item['is_liked'] = false; if ($this->userId) { $item['is_liked'] = CommunityLike::where([ 'user_id' => $this->userId, 'target_id' => $item['id'], 'target_type' => 2 ])->count() > 0; } } return $this->data([ 'lists' => $list, 'count' => $total, 'page_no' => $page, 'page_size' => $size, ]); } // 发评论 public function addComment() { $postId = $this->request->post('post_id/d'); $content = $this->request->post('content/s', ''); $parentId = $this->request->post('parent_id/d', 0); $replyUserId = $this->request->post('reply_user_id/d', 0); if (empty($content)) { return $this->fail('请输入评论内容'); } $post = CommunityPost::where('id', $postId)->where('status', 1)->findOrEmpty(); if ($post->isEmpty()) { return $this->fail('帖子不存在'); } $comment = CommunityComment::create([ 'post_id' => $postId, 'user_id' => $this->userId, 'parent_id' => $parentId, 'reply_user_id' => $replyUserId, 'content' => $content, 'status' => 1, 'create_time' => time(), ]); // 更新帖子评论数 CommunityPost::where('id', $postId)->inc('comment_count')->update(); return $this->data(['id' => $comment->id]); } // 删除评论 public function deleteComment() { $commentId = $this->request->post('comment_id/d'); $comment = CommunityComment::where('id', $commentId)->where('status', 1)->findOrEmpty(); if ($comment->isEmpty()) { return $this->fail('评论不存在'); } if ((int) $comment->user_id !== (int) $this->userId) { return $this->fail('无权删除该评论'); } Db::startTrans(); try { $deleteIds = [$commentId]; $replyIds = CommunityComment::where('parent_id', $commentId)->where('status', 1)->column('id'); if (!empty($replyIds)) { $deleteIds = array_merge($deleteIds, $replyIds); } CommunityComment::whereIn('id', $deleteIds)->delete(); CommunityPost::where('id', $comment->post_id)->dec('comment_count', count($deleteIds))->update(); Db::commit(); return $this->data(['deleted_ids' => $deleteIds]); } catch (\Throwable $e) { Db::rollback(); return $this->fail('删除失败:' . $e->getMessage()); } } // 点赞/取消点赞 public function like() { $targetId = $this->request->post('target_id/d'); $targetType = $this->request->post('target_type/d', 1); $exists = CommunityLike::where([ 'user_id' => $this->userId, 'target_id' => $targetId, 'target_type' => $targetType ])->findOrEmpty(); if ($exists->isEmpty()) { CommunityLike::create([ 'user_id' => $this->userId, 'target_id' => $targetId, 'target_type' => $targetType, 'create_time' => time(), ]); if ($targetType == 1) { CommunityPost::where('id', $targetId)->inc('like_count')->update(); } else { CommunityComment::where('id', $targetId)->inc('like_count')->update(); } return $this->data(['is_liked' => true]); } else { $exists->delete(); if ($targetType == 1) { CommunityPost::where('id', $targetId)->dec('like_count')->update(); } else { CommunityComment::where('id', $targetId)->dec('like_count')->update(); } return $this->data(['is_liked' => false]); } } // 关注/取消关注 public function follow() { $followUserId = $this->request->post('follow_user_id/d'); if ($followUserId == $this->userId) { return $this->fail('不能关注自己'); } $exists = CommunityFollow::where([ 'user_id' => $this->userId, 'follow_user_id' => $followUserId ])->findOrEmpty(); if ($exists->isEmpty()) { CommunityFollow::create([ 'user_id' => $this->userId, 'follow_user_id' => $followUserId, 'create_time' => time(), ]); return $this->data(['is_followed' => true]); } else { $exists->delete(); return $this->data(['is_followed' => false]); } } // 标签列表 public function tagLists() { $list = CommunityTag::where('status', 1) ->order('sort asc') ->field('id,name,icon,post_count,is_hot') ->select() ->toArray(); return $this->data($list); } // 购买帖子 // confirm=0 探测是否已购买;confirm=1 确认扣分购买 public function purchasePost() { $postId = $this->request->post('post_id/d'); $confirm = $this->request->post('confirm/d', 0); $post = CommunityPost::where('id', $postId)->where('status', 1)->findOrEmpty(); if ($post->isEmpty()) { return $this->fail('帖子不存在'); } if (!$post->is_paid) { return $this->fail('该帖子为免费帖'); } if ($post->user_id == $this->userId) { return $this->data([ 'content' => $post->content, 'from_cache' => true, ]); } // VIP解锁免费权益判断 $vipUnlockFree = VipService::hasUnlockFree($this->userId); // 检查是否已购买 $hasPurchased = Db::name('user_account_log') ->where('user_id', $this->userId) ->where('change_type', AccountLogEnum::UP_DEC_PURCHASE_POST) ->where('relation_id', $postId) ->count() > 0; if ($hasPurchased || $vipUnlockFree) { return $this->data([ 'content' => $post->content, 'from_cache' => true, 'vip_free' => $vipUnlockFree, ]); } // 未购买且未确认 → 返回待确认标记 if (!$confirm) { return $this->data([ 'needs_payment' => true, 'cost' => (int) $post->price_points, ]); } // 防重复扣分:再次校验 $hasPurchased2 = Db::name('user_account_log') ->where('user_id', $this->userId) ->where('change_type', AccountLogEnum::UP_DEC_PURCHASE_POST) ->where('relation_id', $postId) ->count() > 0; if ($hasPurchased2) { return $this->data([ 'content' => $post->content, 'from_cache' => true, ]); } $user = User::findOrEmpty($this->userId); $price = (int) $post->price_points; if ($user->user_points < $price) { return $this->fail('积分不足,当前积分' . $user->user_points . ',需要' . $price . '积分'); } Db::startTrans(); try { // 扣除买家积分 User::where('id', $this->userId)->dec('user_points', $price)->update(); AccountLogLogic::add( $this->userId, AccountLogEnum::UP_DEC_PURCHASE_POST, AccountLogEnum::DEC, $price, '', '购买帖子#' . $postId, [], $postId ); // 作者获得积分 User::where('id', $post->user_id)->inc('user_points', $price)->update(); AccountLogLogic::add( $post->user_id, AccountLogEnum::UP_INC_POST_SOLD, AccountLogEnum::INC, $price, '', '帖子#' . $postId . '被购买', [], $postId ); // 更新帖子已购买人数 CommunityPost::where('id', $postId)->inc('paid_count')->update(); Db::commit(); return $this->data([ 'content' => $post->content, 'from_cache' => false, ]); } catch (\Exception $e) { Db::rollback(); return $this->fail('购买失败:' . $e->getMessage()); } } // AI翻译帖子(需登录,暂不扣积分) public function translatePost() { $postId = $this->request->post('post_id/d'); $post = CommunityPost::where('id', $postId)->where('status', 1)->findOrEmpty(); if ($post->isEmpty()) { return $this->fail('帖子不存在'); } $ext = $post->ext ? (is_string($post->ext) ? json_decode($post->ext, true) : $post->ext) : []; $tags = $this->getPostTags($postId); $isLiuhePost = $this->isLiuhePost($tags, $ext); if ($isLiuhePost) { $analysis = $this->ensureLiuheAnalysisContent($post, $ext); if (!$analysis['success']) { return $this->fail($analysis['error'] ?? '图片分析失败'); } $ext = $analysis['ext']; return $this->data([ 'translated_content' => $ext['lottery_analysis_content'] ?? '', 'from_cache' => $analysis['from_cache'] ?? false, ]); } $cacheField = 'translated_content'; if (!empty($ext[$cacheField])) { return $this->data([ 'translated_content' => $ext[$cacheField], 'from_cache' => true, ]); } if (empty($ext[$cacheField])) { $result = AiService::translateCommunityPost($post->content ?: ''); if (!$result['success']) { $ext[$cacheField] = self::buildVirtualAnalysisContent($post, false); CommunityPost::where('id', $postId)->update([ 'ext' => json_encode($ext, JSON_UNESCAPED_UNICODE), 'update_time' => time(), ]); KbSyncService::enqueue('post', 'post', $postId, 'upsert', 45); return $this->data([ 'translated_content' => $ext[$cacheField], 'from_cache' => false, ]); } $ext[$cacheField] = trim($result['content']); CommunityPost::where('id', $postId)->update([ 'ext' => json_encode($ext, JSON_UNESCAPED_UNICODE), 'update_time' => time(), ]); KbSyncService::enqueue('post', 'post', $postId, 'upsert', 45); } return $this->data([ 'translated_content' => $ext[$cacheField], 'from_cache' => false, ]); } public function aiAnalysis() { $result = AiAnalysisRequestService::analyzePost( $this->request->get('post_id/d', 0), $this->userId, $this->request->get('force/d', 0) === 1 ); if (empty($result['success'])) { return $this->fail($result['error'] ?? 'AI分析失败', [ 'remain_count' => $result['remain_count'] ?? null, ]); } unset($result['success'], $result['error'], $result['code']); return $this->data($result); } public function aiAnalysisResult() { try { $postId = $this->request->get('post_id/d', 0); if ($postId <= 0) { return $this->fail('参数缺失'); } $post = CommunityPost::where('id', $postId)->where('status', 1)->findOrEmpty(); if ($post->isEmpty()) { return $this->fail('帖子不存在'); } $data = $post->toArray(); $ext = is_array($data['ext'] ?? null) ? $data['ext'] : (json_decode((string) ($data['ext'] ?? ''), true) ?: []); $isLiuhePost = $this->isLiuhePost($this->getPostTags($postId), $ext); $cache = AiAnalysis::getCache(AiAnalysis::TYPE_POST_ANALYSIS, $postId); if ($cache) { $cacheData = json_decode((string) ($cache['result'] ?? ''), true); $isValid = is_array($cacheData) && !empty($cacheData); $isValidLiuheCache = !empty($cacheData['next_prediction']) && ($cacheData['analysis_source'] ?? '') === 'image_free_v1'; if ($isValid && (!$isLiuhePost || $isValidLiuheCache)) { return $this->data([ 'status' => 'success', 'analysis' => $cacheData, 'from_cache' => true, ]); } } return $this->data([ 'status' => 'processing', 'analysis' => null, 'from_cache' => false, ]); } catch (\Throwable $e) { return $this->fail('AI分析结果查询异常: ' . $e->getMessage()); } } private function getPostTags(int $postId): array { $tagIds = Db::name('community_post_tag')->where('post_id', $postId)->column('tag_id'); if (empty($tagIds)) { return []; } return array_values(CommunityTag::whereIn('id', $tagIds)->column('name')); } private function isTrumpPost(array $tags): bool { return in_array('特朗普', $tags, true); } private function isLiuhePost(array $tags, array $ext = []): bool { foreach (self::LIUHE_TAGS as $tag) { if (in_array($tag, $tags, true)) { return true; } } $lotteryKey = (string) ($ext['lottery_key'] ?? ''); return in_array($lotteryKey, ['a6', 'xa6'], true); } private function localizeLiuhePostImagesForAi(CommunityPost $post, array $images): array { $localizedImages = []; $changed = false; foreach ($images as $image) { $image = trim((string) $image); if ($image === '') { continue; } if ($this->isLocalUploadImage($image)) { $localUrl = $this->normalizeLocalUploadImageUrl($image); $localizedImages[] = $localUrl; $changed = $changed || $localUrl !== $image; continue; } if (!preg_match('/^https?:\/\//i', $image)) { $localUrl = FileService::getFileUrl(ltrim($image, '/')); $localizedImages[] = $localUrl; $changed = $changed || $localUrl !== $image; continue; } $localUrl = $this->saveRemoteImageForAiAnalysis($image); if ($localUrl === '') { return [ 'success' => false, 'error' => '图片下载到本地失败,请稍后重试', 'images' => [], ]; } $localizedImages[] = $localUrl; $changed = true; } if (empty($localizedImages)) { return ['success' => false, 'error' => '帖子图片为空,无法分析', 'images' => []]; } if ($changed || $localizedImages !== array_values($images)) { $now = time(); Db::name('community_post')->where('id', (int) $post->id)->update([ 'images' => json_encode($localizedImages, JSON_UNESCAPED_UNICODE), 'update_time' => $now, ]); Db::name('community_post_image')->where('post_id', (int) $post->id)->delete(); foreach ($localizedImages as $index => $imageUrl) { Db::name('community_post_image')->insert([ 'post_id' => (int) $post->id, 'image_url' => $imageUrl, 'sort' => $index, 'create_time' => $now, ]); } } return ['success' => true, 'images' => $localizedImages, 'changed' => $changed]; } private function isLocalUploadImage(string $image): bool { if (!preg_match('/^https?:\/\//i', $image)) { return str_starts_with(ltrim($image, '/'), 'uploads/'); } $path = (string) (parse_url($image, PHP_URL_PATH) ?: ''); if (!str_starts_with($path, '/uploads/')) { return false; } $imageHost = strtolower((string) (parse_url($image, PHP_URL_HOST) ?: '')); $currentHost = strtolower((string) (parse_url(request()->domain(), PHP_URL_HOST) ?: '')); return $imageHost !== '' && $currentHost !== '' && $imageHost === $currentHost; } private function normalizeLocalUploadImageUrl(string $image): string { if (preg_match('/^https?:\/\//i', $image)) { return $image; } return FileService::getFileUrl(ltrim($image, '/')); } private function saveRemoteImageForAiAnalysis(string $imageUrl): string { $ch = curl_init($imageUrl); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_TIMEOUT => 25, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_USERAGENT => 'Mozilla/5.0 SportEraBot/1.0', ]); $body = curl_exec($ch); $httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); $contentType = (string) curl_getinfo($ch, CURLINFO_CONTENT_TYPE); curl_close($ch); if ($httpCode !== 200 || !is_string($body) || $body === '' || strlen($body) > 8 * 1024 * 1024) { return ''; } $mime = strtolower(trim(explode(';', $contentType)[0] ?? '')); $imageInfo = @getimagesizefromstring($body); if (is_array($imageInfo) && !empty($imageInfo['mime'])) { $mime = strtolower((string) $imageInfo['mime']); } $extension = match ($mime) { 'image/jpeg', 'image/jpg' => 'jpg', 'image/png' => 'png', 'image/webp' => 'webp', 'image/gif' => 'gif', default => '', }; if ($extension === '') { $path = parse_url($imageUrl, PHP_URL_PATH) ?: ''; $extension = strtolower(pathinfo($path, PATHINFO_EXTENSION)); if (!in_array($extension, ['jpg', 'jpeg', 'png', 'webp', 'gif'], true)) { return ''; } $extension = $extension === 'jpeg' ? 'jpg' : $extension; } $relativeDir = 'uploads/ai_lottery_posts/' . date('Ymd'); $publicRoot = rtrim(public_path(), DIRECTORY_SEPARATOR . '/'); $targetDir = $publicRoot . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $relativeDir); if (!is_dir($targetDir) && !@mkdir($targetDir, 0755, true) && !is_dir($targetDir)) { return ''; } if (!is_writable($targetDir)) { return ''; } $filename = sha1($imageUrl . "\n" . $body) . '.' . $extension; $relativeUri = $relativeDir . '/' . $filename; $targetPath = $targetDir . DIRECTORY_SEPARATOR . $filename; if (!is_file($targetPath) && @file_put_contents($targetPath, $body) === false) { return ''; } return FileService::getFileUrl($relativeUri); } private function ensureLiuheAnalysisContent(CommunityPost $post, array $ext): array { $images = is_array($post->images) ? array_values($post->images) : []; $originalImageHash = md5(json_encode($images, JSON_UNESCAPED_UNICODE)); $localizeResult = $this->localizeLiuhePostImagesForAi($post, $images); if (empty($localizeResult['success'])) { return [ 'success' => false, 'error' => (string) ($localizeResult['error'] ?? '图片下载到本地失败'), ]; } $images = array_values($localizeResult['images']); $imageHash = md5(json_encode($images, JSON_UNESCAPED_UNICODE)); $cacheSource = (string) ($ext['lottery_analysis_source'] ?? ''); $cacheHash = (string) ($ext['lottery_analysis_images_hash'] ?? ''); if ( !empty($ext['lottery_analysis_content']) && $cacheSource === 'image_only' && in_array($cacheHash, [$imageHash, $originalImageHash], true) ) { if ($cacheHash !== $imageHash) { $ext['lottery_analysis_images_hash'] = $imageHash; CommunityPost::where('id', (int) $post->id)->update([ 'ext' => json_encode($ext, JSON_UNESCAPED_UNICODE), 'update_time' => time(), ]); KbSyncService::enqueue('post', 'post', (int) $post->id, 'upsert', 45); } return ['success' => true, 'ext' => $ext, 'from_cache' => true]; } $result = AiService::analyzeLotteryPostImages($post->content ?: '', $images); if (empty($result['success']) || empty($result['content'])) { $fallbackContent = $this->buildLiuheImageAnalysisFallback($images, (string) ($result['error'] ?? '')); if ($fallbackContent === '') { return [ 'success' => false, 'error' => (string) ($result['error'] ?? '帖子图片分析失败'), ]; } $result = [ 'success' => true, 'content' => $fallbackContent, 'tokens' => 0, 'cost_ms' => (int) ($result['cost_ms'] ?? 0), ]; } $ext['lottery_analysis_content'] = trim((string) $result['content']); $ext['lottery_analysis_source'] = 'image_only'; $ext['lottery_analysis_images_hash'] = $imageHash; $ext['lottery_analysis_generated_at'] = time(); CommunityPost::where('id', (int) $post->id)->update([ 'ext' => json_encode($ext, JSON_UNESCAPED_UNICODE), 'update_time' => time(), ]); KbSyncService::enqueue('post', 'post', (int) $post->id, 'upsert', 45); return ['success' => true, 'ext' => $ext, 'from_cache' => false]; } private function buildLiuheImageAnalysisFallback(array $images, string $error): string { $error = trim($error); $isTimeout = $error !== '' && (stripos($error, 'timed out') !== false || stripos($error, 'timeout') !== false || stripos($error, 'cURL错误') !== false); if (!$isTimeout) { return ''; } $lines = [ '图片已下载并替换为站内链接,但视觉模型在限定时间内未返回完整识别结果。', '当前可确认信息:帖子包含 ' . count($images) . ' 张本地化图片,可在原帖图片区域查看。', '识别状态:图片文字、号码、生肖、波色等细项暂未可靠识别。', '处理建议:以下分析只能结合帖子文案和图片展示场景进行保守解读,不能视为开奖结果或中奖承诺。', ]; return implode("\n", $lines); } private static function buildVirtualAnalysisContent($post, bool $isLottery): string { if ($isLottery) { $longText = "【49宝典开奖号码深度分析】\n\n" . "一、本期开奖概况\n" . "本期开奖号码为:03 07 12 18 25 31 + 特码 08。\n" . "其中红波号码2个(07、25),蓝波号码3个(03、12、31),绿波号码1个(18)。\n\n" . "二、号码属性分析\n" . str_repeat("1. 号码 03:生肖鼠,五行木,蓝波,小单。该号码近10期出现2次,属于温号。\n" . "2. 号码 07:生肖虎,五行火,红波,小单。该号码近10期出现3次,属于热号。\n" . "3. 号码 12:生肖猪,五行水,蓝波,大双。该号码近10期出现1次,属于冷号。\n" . "4. 号码 18:生肖蛇,五行土,绿波,大双。该号码近10期出现2次,属于温号。\n" . "5. 号码 25:生肖鼠,五行金,红波,大单。该号码近10期出现4次,属于热号。\n" . "6. 号码 31:生肖马,五行木,蓝波,大单。该号码近10期出现1次,属于冷号。\n" . "7. 特码 08:生肖兔,五行火,蓝波,小双。该号码近10期出现2次,属于温号。\n\n", 5) . "三、走势趋势\n" . "从近期走势来看,大号(25-49)区间号码持续走热,小号(01-24)区间号码相对偏冷。\n" . "建议关注下期大号区间号码的回补机会。\n\n" . "四、冷热统计\n" . "热号(近10期出现3次以上):07、25\n" . "温号(近10期出现1-2次):03、18、08\n" . "冷号(近10期出现0次):01、02、04、05、06、09、10、11、13、14、15、16、17、19、20、21、22、23、24、26、27、28、29、30、32、33、34、35、36、37、38、39、40、41、42、43、44、45、46、47、48、49\n\n" . "五、综合建议\n" . "彩票开奖具有随机性,以上分析仅基于历史数据统计,不构成购买建议。请理性购彩,量力而行。\n\n" . "—— AI智能分析仅供参考 ——"; return $longText; } $content = $post->content ?? ''; $short = mb_strlen($content) > 100 ? mb_substr($content, 0, 100) . '...' : $content; $longTestText = "这是一段模拟的长文本翻译内容,用于测试弹窗滚动效果。\n\n" . "在H5页面中,如果弹窗内容过长,用户需要能够滚动查看完整内容。\n\n" . str_repeat("这是第%d段测试文本。弹窗滚动功能测试中,请确保内容可以正常滚动显示。" . "如果滚动功能正常,用户将能够顺畅地阅读所有内容,而不会因为内容过长导致体验下降。" . "同时,弹窗背后的页面应该保持固定,不能随着弹窗内容的滚动而滚动。\n\n", 30) . "【测试结束】感谢您的耐心阅读!"; return $longTestText; } // 用户统计 public function userStats() { $userId = $this->userId; $postCount = CommunityPost::where('user_id', $userId)->where('status', 1)->count(); $followCount = CommunityFollow::where('user_id', $userId)->count(); $fansCount = CommunityFollow::where('follow_user_id', $userId)->count(); $collectCount = Db::name('article_collect')->where('user_id', $userId)->where('status', 1)->whereNull('delete_time')->count(); $userPoints = User::where('id', $userId)->value('user_points') ?: 0; $chatUnreadCount = PrivateChatService::unreadCount($userId); return $this->data([ 'post_count' => $postCount, 'follow_count' => $followCount, 'fans_count' => $fansCount, 'collect_count' => $collectCount, 'user_points' => $userPoints, 'chat_unread_count' => $chatUnreadCount, ]); } // 关注列表 public function followList() { $page = $this->request->get('page_no/d', 1); $size = $this->request->get('page_size/d', 20); $targetUserId = $this->request->get('user_id/d', 0) ?: $this->userId; if (!$targetUserId) { return $this->data(['lists' => [], 'page_no' => $page, 'page_size' => $size]); } $followIds = CommunityFollow::where('user_id', $targetUserId) ->order('create_time desc') ->page($page, $size) ->column('follow_user_id'); $list = []; if ($followIds) { $users = User::field('id,sn,nickname,avatar') ->whereIn('id', $followIds) ->select() ->toArray(); // 检查对方是否也关注了我(互相关注) $fansOfMe = CommunityFollow::whereIn('user_id', $followIds) ->where('follow_user_id', $targetUserId) ->column('user_id'); foreach ($users as &$u) { $u['is_followed'] = true; // 关注列表里都是已关注的 $u['is_mutual'] = in_array($u['id'], $fansOfMe); } $list = $users; } return $this->data([ 'lists' => $list, 'page_no' => $page, 'page_size' => $size, ]); } // 粉丝列表 public function fansList() { $page = $this->request->get('page_no/d', 1); $size = $this->request->get('page_size/d', 20); $targetUserId = $this->request->get('user_id/d', 0) ?: $this->userId; if (!$targetUserId) { return $this->data(['lists' => [], 'page_no' => $page, 'page_size' => $size]); } $fansIds = CommunityFollow::where('follow_user_id', $targetUserId) ->order('create_time desc') ->page($page, $size) ->column('user_id'); $list = []; if ($fansIds) { $users = User::field('id,sn,nickname,avatar') ->whereIn('id', $fansIds) ->select() ->toArray(); // 检查我是否关注了该粉丝(is_followed)+ 互相关注 $myFollows = CommunityFollow::where('user_id', $targetUserId) ->whereIn('follow_user_id', $fansIds) ->column('follow_user_id'); foreach ($users as &$u) { $u['is_followed'] = in_array($u['id'], $myFollows); $u['is_mutual'] = $u['is_followed']; // 粉丝关注了我,我也关注了他 = 互关 } $list = $users; } return $this->data([ 'lists' => $list, 'page_no' => $page, 'page_size' => $size, ]); } // 用户主页(公开) public function userProfile() { $userId = $this->request->get('user_id/d'); if (!$userId) { return $this->fail('参数错误'); } $user = User::field('id,sn,nickname,avatar,sex,create_time')->where('id', $userId)->findOrEmpty(); if ($user->isEmpty()) { return $this->fail('用户不存在'); } $data = $user->toArray(); $data['post_count'] = CommunityPost::where('user_id', $userId)->where('status', 1)->count(); $data['comment_count'] = CommunityComment::where('user_id', $userId)->count(); $data['follow_count'] = CommunityFollow::where('user_id', $userId)->count(); $data['fans_count'] = CommunityFollow::where('follow_user_id', $userId)->count(); $data['like_count'] = CommunityPost::where('user_id', $userId)->where('status', 1)->sum('like_count'); // 加入天数 $data['join_days'] = max(1, (int) ceil((time() - strtotime($data['create_time'])) / 86400)); // 当前登录用户是否关注 $data['is_followed'] = false; $data['is_friend'] = false; $data['relationship'] = PrivateChatService::relationship($this->userId, $userId); if ($this->userId && $this->userId != $userId) { $data['is_followed'] = CommunityFollow::where([ 'user_id' => $this->userId, 'follow_user_id' => $userId ])->count() > 0; $data['is_friend'] = $data['is_followed']; $data['relationship'] = PrivateChatService::relationship($this->userId, $userId); } $data['is_self'] = $this->userId == $userId; return $this->data($data); } }