curlOpen($url); $res = json_decode($res,true); if(isset($res['errmsg'])) return [false, $res['errmsg']]; if(! isset($res['access_token'])) return [false, 'request error']; $token = $res['access_token']; $expire_time = $res['expires_in']-300; Redis::set($token_key,$token); Redis::expire($token_key, $expire_time); return [true,$token]; } return [true, $token]; } public function getPublicWxArticle($data){ list($status, $msg) = $this->rule($data); if(! $status) { file_put_contents('record_ip.txt',date("Y-m-d H:i:s",time()).json_encode($data) . PHP_EOL."来源IP".$msg.PHP_EOL,8); return [false, 'IP未入白名单']; } list($status, $msg) = $this->getToken(); if(! $status) return [false, $msg]; $config = config('qingyaoWx'); $url = sprintf($config['get_article'], $msg); $offset = empty($data['page_index']) ? 0 : $data['page_index'] - 1; $count = empty($data['page_size']) || $data['page_size'] > 10 ? 10 : $data['page_size']; $post = [ 'offset' => $offset, 'count' => $count, 'no_content' => 0, ]; $result = $this->curlOpen($url, ['post' => json_encode($post)]); $result = json_decode($result,true); if(isset($result['errmsg'])) return [false, $result['errmsg']]; Log::channel('apiLog')->info('wxget', ["message" => $result]); return [true, ['data' => $result['item'] ?? [], 'total' => $result['total_count'], 'data_count' => $result['item_count']]]; } public function getPublicWxArticleDetail($data){ list($status, $msg) = $this->rule($data); if(! $status) { file_put_contents('record_ip.txt',date("Y-m-d H:i:s",time()).json_encode($data) . PHP_EOL."来源IP".$msg.PHP_EOL,8); return [false, 'IP未入白名单']; } if(empty($data['article_id'])) return [false, '文章ID不能为空']; list($status, $msg) = $this->getToken(); if(! $status) return [false, $msg]; $config = config('qingyaoWx'); $url = sprintf($config['get_article_detail'], $msg); $post = [ 'article_id' => $data['article_id'], ]; $result = $this->curlOpen($url, ['post' => json_encode($post)]); $result = json_decode($result,true); if(isset($result['errmsg'])) return [false, $result['errmsg']]; return [true, ['data' => $result['news_item'] ?? [] ]]; } public function getPublicWxMaterial($data){ list($status, $msg) = $this->rule($data); if(! $status) { file_put_contents('record_ip.txt',date("Y-m-d H:i:s",time()).json_encode($data) . PHP_EOL."来源IP".$msg.PHP_EOL,8); return [false, 'IP未入白名单']; } list($status, $msg) = $this->getToken(); if(! $status) return [false, $msg]; $config = config('qingyaoWx'); $url = sprintf($config['get_material'], $msg); $offset = empty($data['page_index']) ? 1 : $data['page_index'] - 1; $count = empty($data['page_size']) || $data['page_size'] > 10 ? 10 : $data['page_size']; $post = [ 'offset' => $offset, 'count' => $count, 'type' => 'news', ]; $result = $this->curlOpen($url, ['post' => json_encode($post)]); $result = json_decode($result,true); if(isset($result['errmsg'])) return [false, $result['errmsg']]; return [true, ['data' => $result['item'] ?? [], 'total' => $result['total_count'], 'data_count' => $result['item_count']]]; } public function getPublicWxDraft($data){ list($status, $msg) = $this->rule($data); if(! $status) { file_put_contents('record_ip.txt',date("Y-m-d H:i:s",time()).json_encode($data) . PHP_EOL."来源IP".$msg.PHP_EOL,8); return [false, 'IP未入白名单']; } list($status, $msg) = $this->getToken(); if(! $status) return [false, $msg]; $config = config('qingyaoWx'); $url = sprintf($config['get_draft'], $msg); $offset = empty($data['page_index']) ? 1 : $data['page_index'] - 1; $count = empty($data['page_size']) || $data['page_size'] > 10 ? 10 : $data['page_size']; $post = [ 'offset' => $offset, 'count' => $count, 'no_content' => 0, ]; $result = $this->curlOpen($url, ['post' => json_encode($post)]); $result = json_decode($result,true); if(isset($result['errmsg'])) return [false, $result['errmsg']]; return [true, ['data' => $result['item'] ?? [], 'total' => $result['total_count'], 'data_count' => $result['item_count']]]; } public function getWxFile($data){ list($status, $msg) = $this->rule($data); if(! $status) { file_put_contents('record_ip.txt',date("Y-m-d H:i:s",time()).json_encode($data) . PHP_EOL."来源IP".$msg.PHP_EOL,8); return [false, 'IP未入白名单']; } if(empty($data['wx_url'])) return [false, "URL不存在"]; $header = ['Content-Type:application/json']; list($status,$msg) = $this->get_helper_for_img($data['wx_url'],$header); if(! $status) return [false, $msg]; return [true, $msg]; } public function rule($data){ // 获取用户的IP地址 $userIP = $_SERVER['REMOTE_ADDR']; // 获取设置的IP地址 $allowedIPs = $this->allowedIPs(); if(empty($allowedIPs)) return [false, $userIP]; // 校验用户IP是否在允许的范围内 $isValidIP = false; foreach ($allowedIPs as $allowedIP) { if (strpos($allowedIP, '/') !== false) { // IP段表示法校验 list($subnet, $mask) = explode('/', $allowedIP); if ((ip2long($userIP) & ~((1 << (32 - $mask)) - 1)) == ip2long($subnet)) { $isValidIP = true; break; } } else { // 单个IP地址校验 if ($allowedIP === $userIP) { $isValidIP = true; break; } } } return [$isValidIP, $userIP]; } public function allowedIPs(){ $allowedIPs = Settings::where('setting_name','allowedIPs')->first(); if(empty($allowedIPs) || empty($allowedIPs->setting_value)) return []; return explode(',',$allowedIPs->setting_value); } public function get_helper_for_img($url,$header=[],$timeout = 20){ $ch = curl_init(); curl_setopt_array($ch, array( CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_ENCODING => '', CURLOPT_MAXREDIRS => 10, CURLOPT_TIMEOUT => $timeout, CURLOPT_FOLLOWLOCATION => true, CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, CURLOPT_CUSTOMREQUEST => 'GET', CURLOPT_SSL_VERIFYPEER => false, CURLOPT_HTTPHEADER => $header, )); $r = curl_exec($ch); if ($r === false) { // 获取错误号 $errorNumber = curl_errno($ch); // 获取错误信息 $errorMessage = curl_error($ch); $message = "cURL Error #{$errorNumber}: {$errorMessage}"; Log::channel('apiLog')->info('wx', ["message" => $message]); return [false, $message]; } $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); // 检查是否为图片 if (! $this->isImage($r)) return [false, "资源不是图片"]; if ($httpCode == 200) { // 检查是否为图片 list($status, $msg) = $this->getTemporaryUrl($r, $url); if(! $status) return [false, $msg]; $img = $msg; } else { return [false, '']; } return [true, $img]; } function isImage($data) { $imageInfo = getimagesizefromstring($data); return $imageInfo !== false; } //生成临时文件 public function getTemporaryUrl($body,$url) { // 定义本地文件路径 $name = md5($url) . '.jpg'; $localFilePath = storage_path(self::wx_img . $name); // 写入文件前先检查文件是否存在 if (! file_exists($localFilePath)) { // 检查目录是否存在,如果不存在则创建 $directoryPath = dirname($localFilePath); if (!is_dir($directoryPath)) { // 设置目录权限,可以根据需要更改 $mode = 0777; // 使用递归选项创建目录 if (!mkdir($directoryPath, $mode, true)) { return [false, '目录创建失败']; } } file_put_contents($localFilePath, $body); } return [true, $name]; } public function getArticle(){ list($status, $msg) = $this->getToken(); if(! $status) return [false, $msg]; $config = config('qingyaoWx'); $url = sprintf($config['get_article'], $msg); $offset = 0; $count = 20; $get_total = 0; do { $post = [ 'offset' => $offset, 'count' => $count, 'no_content' => 0, ]; $result = $this->curlOpen($url, ['post' => json_encode($post)]); $result = json_decode($result, true); Log::channel('apiLog')->info('wxget', ["message" => $result]); if (isset($result['errmsg'])) return [false, $result['errmsg']]; //保存文章 list($status,$msg) = $this->saveWxArticle($result); if(! $status) return [false, $msg]; // 更新 offset $offset += 1; // 更新 total_count $get_total += $result['total_count']; // 检查是否还有更多数据 $hasMore = isset($result['total_count']) && ($result['total_count'] > $get_total); } while ($hasMore); return [true, '']; } public function saveWxArticle($result){ if(! empty($result['item'])){ try { DB::beginTransaction(); foreach ($result['item'] as $value){ $model = WxArticle::where('del_time',0) ->where('article_id', $value['article_id']) ->first(); if(empty($model)){ $model = new WxArticle(); $model->article_id = $value['article_id']; $model->upd_time = $value['update_time']; $model->save(); $id = $model->id; if(! empty($value['content']['news_item'])){ foreach ($value['content']['news_item'] as $val){ // 获取当前时间的微秒级时间戳 list($micro, $seconds) = explode(' ', microtime()); // 从微秒部分截取全部6位作为唯一标识符 $microSuffix = substr($micro, 2, 6); $content_name = date('YmdHis', $seconds) . $microSuffix; $content_img = $this->saveContent($val['content'], $content_name); $this->get_helper_for_img($val['thumb_url']); $modelDetail = new WxArticleDetail(); $modelDetail->wx_article_id = $id; $modelDetail->title = $val['title']; $modelDetail->author = $val['author']; $modelDetail->digest = $val['digest']; $modelDetail->content_name = $content_name; $modelDetail->content_source_url = $val['content_source_url']; $modelDetail->thumb_media_id = $val['thumb_media_id']; $modelDetail->show_cover_pic = $val['show_cover_pic']; $modelDetail->url = $val['url']; $modelDetail->thumb_url = $val['thumb_url']; $modelDetail->need_open_comment = $val['need_open_comment']; $modelDetail->only_fans_can_comment = $val['only_fans_can_comment']; $modelDetail->is_deleted = $val['is_deleted']; $modelDetail->upd_time = $value['content']['update_time']; $modelDetail->crt_time = $value['content']['create_time']; $modelDetail->save(); if(! empty($content_img)){ $insert = []; foreach ($content_img as $value){ $insert[] = [ 'wx_article_detail_id' => $modelDetail->id, 'url' => $value ]; } WxArticleDetailContent::insert($insert); } } } }else{ if($model->upd_time != $value['update_time']){ $model->upd_time = $value['update_time']; $model->save(); $id = $model->id; $detail = WxArticleDetail::where('del_time',0) ->where('wx_article_id',$id) ->select('content_name as txt','thumb_url as img','id') ->get()->toArray(); WxArticleDetail::where('del_time',0) ->where('wx_article_id',$id) ->update(['del_time' => time()]); $detail2 = WxArticleDetailContent::where('del_time',0) ->whereIn('id', array_column($detail,'id')) ->select('url as img') ->get()->toArray(); WxArticleDetailContent::where('del_time',0) ->whereIn('id', array_column($detail,'id')) ->update(['del_time' => time()]); if(! empty($value['content']['news_item'])){ foreach ($value['content']['news_item'] as $val){ // 获取当前时间的微秒级时间戳 list($micro, $seconds) = explode(' ', microtime()); // 从微秒部分截取全部6位作为唯一标识符 $microSuffix = substr($micro, 2, 6); $content_name = date('YmdHis', $seconds) . $microSuffix; $content_img = $this->saveContent($val['content'], $content_name); $this->get_helper_for_img($val['thumb_url']); $modelDetail = new WxArticleDetail(); $modelDetail->wx_article_id = $id; $modelDetail->title = $val['title']; $modelDetail->author = $val['author']; $modelDetail->digest = $val['digest']; $modelDetail->content_name = $content_name; $modelDetail->content_source_url = $val['content_source_url']; $modelDetail->thumb_media_id = $val['thumb_media_id']; $modelDetail->show_cover_pic = $val['show_cover_pic']; $modelDetail->url = $val['url']; $modelDetail->thumb_url = $val['thumb_url']; $modelDetail->need_open_comment = $val['need_open_comment']; $modelDetail->only_fans_can_comment = $val['only_fans_can_comment']; $modelDetail->is_deleted = $val['is_deleted']; $modelDetail->upd_time = $value['content']['update_time']; $modelDetail->crt_time = $value['content']['create_time']; $modelDetail->save(); if(! empty($content_img)){ $insert = []; foreach ($content_img as $value){ $insert[] = [ 'wx_article_detail_id' => $modelDetail->id, 'url' => $value ]; } WxArticleDetailContent::insert($insert); } } } $this->delFile($detail); $this->delFile($detail2); } } } DB::commit(); return [true, '']; }catch (\Throwable $exception){ DB::rollBack(); return [false, $exception->getMessage() . '|' . $exception->getLine() . '|' . $exception->getFile()]; } } return [false, '暂无数据']; } //生成临时文件 public function saveContent($content, $name) { // 定义本地文件路径 $name = $name . '.txt'; $localFilePath = storage_path(self::wx_img . $name); // 写入文件前先检查文件是否存在 if (! file_exists($localFilePath)) { // 检查目录是否存在,如果不存在则创建 $directoryPath = dirname($localFilePath); if (!is_dir($directoryPath)) { // 设置目录权限,可以根据需要更改 $mode = 0777; // 使用递归选项创建目录 if (!mkdir($directoryPath, $mode, true)) { return [false, '目录创建失败']; } } list($content, $img) = $this->dealContent($content); file_put_contents($localFilePath, $content); } return $img ?? []; } public function dealContent($content){ // 正则表达式匹配 data-src 属性 $pattern = '/data-src="([^"]+)"/'; // 匹配所有 data-src 属性 preg_match_all($pattern, $content, $matches); // 提取所有匹配到的 URL $urls = $matches[1]; $return = []; $header = ['Content-Type:application/json']; // 遍历每个匹配项并替换 foreach ($urls as $url) { list($status, $msg) = $this->get_helper_for_img($url,$header); if($status){ $newImageUrl = "https://adminapi.qingyaokeji.com/weixin/getWxFileLocal/" . $msg; // 构建完整的 data-src 属性 $oldDataSrc = 'data-src="' . $url . '"'; $newDataSrc = 'data-src="' . $newImageUrl . '"'; // 替换 $content = str_replace($oldDataSrc, $newDataSrc, $content); $return[] = $msg; } } return [$content,$return]; } public function delFile($detail){ if(empty($detail)) return; $dir = 'wx_img/'; foreach ($detail as $value){ if(isset($value['txt'])){ $dir_content = $dir . $value['txt'] . '.txt'; if(Storage::disk('public')->exists($dir_content)) Storage::disk('public')->delete($dir_content); } if(! empty($value['img'])){ $dir_thumb = $dir . md5($value['img']) . '.jpg'; if(Storage::disk('public')->exists($dir_thumb)) Storage::disk('public')->delete($dir_thumb); } } } public function getArticleList($data){ list($status, $msg) = $this->rule($data); if(! $status) { file_put_contents('record_ip.txt',date("Y-m-d H:i:s",time()).json_encode($data) . PHP_EOL."来源IP".$msg.PHP_EOL,8); return [false, 'IP未入白名单']; } $model = WxArticleDetail::where('del_time',0) ->where('is_deleted', 0) ->select('title','author','digest','thumb_url','id','crt_time') ->orderBy('crt_time','desc'); $list = $this->limit($model,'',$data); //组织数据 $list = $this->organizationData($list); return [true, $list]; } public function organizationData($data){ if(empty($data['data'])) return $data; foreach ($data['data'] as $key => $value){ $my_thumb_url = ""; if(! empty($value['thumb_url'])) $my_thumb_url = "getWxFileLocal/" . md5($value['thumb_url']) . '.jpg'; $data['data'][$key]['my_thumb_url'] = $my_thumb_url; $data['data'][$key]['crt_time'] = $value['crt_time'] ? date("Y-m-d H:i", $value['crt_time']) : ""; } return $data; } public function getArticleContent($data){ list($status, $msg) = $this->rule($data); if(! $status) { file_put_contents('record_ip.txt',date("Y-m-d H:i:s",time()).json_encode($data) . PHP_EOL."来源IP".$msg.PHP_EOL,8); return [false, 'IP未入白名单']; } if(empty($data['id'])) return [false, 'ID不能为空']; $detail = WxArticleDetail::where('id',$data['id'])->first(); if(empty($detail)) return [false, '文章不存在或已被删除']; $detail = $detail->toArray(); if($detail['is_deleted']) return [false, '文章不存在或被删除']; if(empty($detail['content_name'])) return [true, ['content' => '']]; // 定义本地文件路径 $name = $detail['content_name'] . '.txt'; $localFilePath = storage_path(self::wx_img . $name); // 读取文件内容 $fileContent = file_get_contents($localFilePath); $detail['content'] = $fileContent; return [true, ['data' => $detail]]; } }