first(); $oSiteMap = ModelSiteMap::where("code", $sSiteCode)->first(); $sSiteGroupCode = $oSiteMap->group_code; $sSiteName = $oSiteMap->name; // $sArticleConfig = ModelArticleConfig::where("article_group_code", $oArticleSchema->group_code)->where("site_group_code", $sSiteGroupCode)->first()?->config; // $this->aArticleConfig = json_decode($sArticleConfig, true); $oInstance = new self(); $oInstance->sArticleCode = $sArticleCode; $oInstance->sSiteCode = $sSiteCode; $oInstance->sSiteGroupCode = $oSiteMap->group_code; $oInstance->sSiteName = $oSiteMap->name; $oInstance->sSiteUrl = $oSiteMap->url; // $oInstance->sArticleConfig = $sArticleConfig; $oInstance->oArticle = $oArticleSchema; return $oInstance; } public function build() { $sMethod = "build_article__".$this->sSiteGroupCode; if (method_exists($this, $sMethod)) { $oArticleFlow = $this->$sMethod(); } else { return Flow::start("build")->fail("没有".$sMethod."的对应处理"); } return $oArticleFlow; } private function build_article__resource() { $oFlow = Flow::start("build_article__resource"); $aSchema = json_decode($this->oArticle->schema, true) ?? []; $aSchemaData = $aSchema["data"] ?? []; $sSchemaType = $aSchema["type"] ?? ''; $aDataSend = []; // 它是各站各异的,不保存 $aSchemaPro = []; // 他是上者的通用版本,保存一份 $iRowId = 0; foreach ($aSchemaData as $aSchemaDataRow) { $iRowId++; $aSchemaProRow = []; $aSchemaProRow["type"] = $aSchemaDataRow["type"] ?? ''; $aSchemaProRow["data"] = []; // 默认空,等于判断之外的给空 if ($sSchemaType == "cache_tg" || $sSchemaType == "cache_tg_new") { $sImgHandle = ModelOption::where("k", "cache_tg_new::image_handle")->first()?->v; if ($this->oArticle->group_code == "tg_xiuren_a" || $sImgHandle == "xiuren_a") { if ($aSchemaProRow["type"] == "photo") { // $sRowUuid = $sUuid = Str::uuid()->toString(); $iPhotoFullSizeKey = $aSchemaDataRow["photo_fullSizeKey"]; $aPhotoFullSize = $aSchemaDataRow["photo"][$iPhotoFullSizeKey]; // --- 裁剪参数配置区(指挥部) --- $iWatermarkTop = 50; // 顶部裁掉的水印高度 $iWatermarkBottom = 38; // 底部裁掉的水印高度 (合计88) $iThumbClipHeight = 500; // 裁剪取样的原始高度 $iTargetHeight = 400; // 缩略图最终输出高度 // 各部位起始偏移(基于裁完水印后的底部向上计算) $iOffsetLeg = 0; // 腿:贴底 $iOffsetBody = 500; // 身:从底往上500位置开始 $iOffsetHead = 600; // 头:从底往上600位置开始 // --- 1. 初始化阵地 --- $oImageManager = new ImageManager(new Driver()); $oImgMain = $oImageManager->read($aPhotoFullSize["file_path"]); $sExtension = pathinfo($aPhotoFullSize["file_path"], PATHINFO_EXTENSION); $sUploadPath = 'upload/'; $sUploadDir = config('system.dir_base') . "public/" . $sUploadPath; $aSavedPath = []; // 弹药库:存放所有生成的路径 // --- 2. 预处理:裁掉水印 --- $iMainHeight = $oImgMain->height() - ($iWatermarkTop + $iWatermarkBottom); // 保险:防止原图高度不足以支撑裁切 $iMainHeight = max(1, $iMainHeight); $oImgMain->crop($oImgMain->width(), $iMainHeight, 0, $iWatermarkTop); // 保存全尺寸去水印图 $sTmp = $this->oArticle->code . '-'.$iRowId.'-full.' . $sExtension; $sFullSavePath = $sUploadDir . $sTmp; $sFullSavePathUrl = $sUploadPath . $sTmp; $oImgMain->save($sFullSavePath); // chmod($sFullSavePath, 0777); $aSavedPath['full'] = $sFullSavePathUrl; // --- 3. 局部裁剪与归档:[后缀 => 距离底部的偏移] --- $aClips = [ 'thumb_a' => $iOffsetHead, 'thumb_b' => $iOffsetBody, 'thumb_c' => $iOffsetLeg, ]; // 遍历截取图片 foreach ($aClips as $sSuffix => $iBottomOffset) { // 必须克隆,保护主阵地不被连环裁剪搞残 $oTmp = clone $oImgMain; $iCurrentH = $oTmp->height(); // 计算 Y 轴起点:总高 - 底部偏移 - 准备截取的高度 $iY = max(0, $iCurrentH - $iBottomOffset - $iThumbClipHeight); // 计算实际可截取高度:防止上方空间不足 $iH = ($iCurrentH > $iBottomOffset) ? min($iThumbClipHeight, $iCurrentH - $iBottomOffset) : 1; // 拼装存储路径 $sTmp = $this->oArticle->code.'-'.$iRowId.'-'.$sSuffix.'.'.$sExtension; $sCurrentPath = $sUploadDir . $sTmp; $sCurrentPathUrl = $sUploadPath . $sTmp; // 执行:裁切 -> 等比例缩放 -> 保存 $oTmp->crop($oTmp->width(), $iH, 0, $iY) ->scale(height: $iTargetHeight) ->save($sCurrentPath); // chmod($sCurrentPath, 0777); // 路径入库 $aSavedPath[$sSuffix] = $sCurrentPathUrl; unset($oTmp); // 及时释放资源,老兵的职业操守 } $aSchemaProRow["data"] = $aSavedPath; $aSchemaPro[] = $aSchemaProRow; // 关键 $aDataSend[] = $aSchemaProRow; // 公约 } // is photo } else { // 通用, 只有full if ($aSchemaProRow["type"] == "photo") { $iPhotoFullSizeKey = $aSchemaDataRow["photo_fullSizeKey"]; $aPhotoFullSize = $aSchemaDataRow["photo"][$iPhotoFullSizeKey]; $oImageManager = new ImageManager(new Driver()); $oImgMain = $oImageManager->read($aPhotoFullSize["file_path"]); $sExtension = pathinfo($aPhotoFullSize["file_path"], PATHINFO_EXTENSION); $sUploadPath = 'upload/'; $sUploadDir = config('system.dir_base') . "public/" . $sUploadPath; $aSavedPath = []; // 弹药库:存放所有生成的路径 $sTmp = $this->oArticle->code . '-'.$iRowId.'-full.' . $sExtension; $sFullSavePath = $sUploadDir . $sTmp; $sFullSavePathUrl = $sUploadPath . $sTmp; $oImgMain->save($sFullSavePath); $aSavedPath['full'] = $sFullSavePathUrl; $aSchemaProRow["data"] = $aSavedPath; $aSchemaPro[] = $aSchemaProRow; // 关键 $aDataSend[] = $aSchemaProRow; // 公约 } } // xiuren end } // cache_tg end } // schema foreach end $this->oArticle->schema_pro = $aSchemaPro; $this->oArticle->save(); return $oFlow->setDataA($aDataSend)->done(); } private function build_article__jccp_a() { $oFlow = Flow::start("build_article__cp_a"); $sArticleCode = $this->sArticleCode; $sSiteGroupCode = $this->sSiteGroupCode; $sSiteCode = $this->sSiteCode; $sSiteName = $this->sSiteName; $sSiteUrl = $this->sSiteUrl; $aSchema = json_decode($this->oArticle->schema, true); $isTest = $this->isTest; $aArticle["data"] = []; foreach ($aSchema as $k => $aArticleSchemaRow) { $sContent = $aArticleSchemaRow["content"]["base"] ?? "⚠️ 没有content?"; if ($aArticleSchemaRow['type'] == 'str') { if ($aArticleSchemaRow['name'] == "schema_name") { $sSchemaName = $aArticleSchemaRow["content"]["base"] ?? "⚠️ 提取name失败?"; $sSchemaName = trim($sSchemaName); $aArticle["name"] = $sSchemaName; $aArticle["title"] = $this->nameToTitleCS($sSchemaName); } $sContent = str_replace("{{sAuthorName}}", $sSiteName, $sContent); $sContent = str_replace("{{sIconText}}", "### ", $sContent); $aArticleSchemaRow["content"]["base"] = $sContent; $aArticle["data"][] = $aArticleSchemaRow; } else if ($aArticleSchemaRow['type'] == 'img') { $sContent = "![".$aArticleSchemaRow["content"]["base"]."](".$aArticleSchemaRow["content"]["img"]["base"].")"; $aArticleSchemaRow["content"]["base"] = $sContent; $aArticle["data"][] = $aArticleSchemaRow; } else { $aArticle["data"][] = $aArticleSchemaRow; } } ////// step 2 排序 //// 找排序option,没有就走默认 $sOrderKey = "article::schema::order::{$sSiteCode}::name"; $sOrderName = ModelOption::_hit($sOrderKey) ?? ''; $aOrderName = explode(',', $sOrderName); if (!$sOrderName) { TeleSlave::notify()->send("未设置option:".$sOrderKey."使用默认。"); $sDefaultOrderKey = "article::schema::order::default::type"; $sDefaultOrderType = ModelOption::_hit($sDefaultOrderKey) ?? ''; $aDefaultOrderType = explode(',', $sDefaultOrderType); if (!$sDefaultOrderType) { TeleSlave::fail()->send("未设置option:".$sOrderKey."使用默认。"); return $oFlow->fail("未设置option的default排序"); } } if ($sOrderName) { $aArticle["dataOrder"] = $this->articleSort($aArticle["data"], "name", $aOrderName); } else { $aArticle["dataOrder"] = $this->articleSort($aArticle["data"], "type", $aDefaultOrderType); } ////// step 3 文章化 $aArticleOrder = $aArticle["dataOrder"]; $sEnter = " "; $sArticle = ''; $sArticle .= $sEnter; $j = false; foreach ($aArticleOrder as $aArticleOrderRow) { $sContent = $aArticleOrderRow["content"]["base"] ?? "⚠️ content里的base没有?"; $sContent = trim($sContent); $sContent = str_replace("
", $sEnter, $sContent); $sTmpName = $aArticleOrderRow["name"] ?? ''; if ($sTmpName == "tixingnin") { $sArticle .= "::: tip {$sSiteName}提醒您,"; $sArticle .= $sContent; $sArticle .= $sEnter; $sArticle .= ":::"; $sArticle .= $sEnter; } else if ($sTmpName == "jichangzijie") { // $sArticle .= "::: note 机场自介"; // $sArticle .= $sContent; // $sArticle .= $sEnter; // $sArticle .= ":::"; // $sArticle .= $sEnter; $sArticle .= $sEnter; $sArticle .= $sEnter; $sArticle .= "## 机场自介"; $sArticle .= $sEnter; $sArticle .= $sEnter; $sArticle .= $sContent; } else if ($sTmpName == "lianjiedaohang") { $sContent = nl2br($sContent); $br = "
"; $sContent = preg_replace('/套餐.*'.$br.'/', '', $sContent); $sContent = preg_replace('/论坛.*\.html/', '', $sContent); // $sArticle .= "::: info 链接导航"; // $sArticle .= $this->clearBr($sContent); // $sArticle .= $sEnter; // $sArticle .= ":::"; // $sArticle .= $sEnter; $sArticle .= $sEnter; $sArticle .= $sEnter; $sArticle .= "## 链接导航"; $sArticle .= $sEnter; $sArticle .= $sEnter; $sArticle .= $this->clearBr($sContent); } else if ($sTmpName == "zhuguandianping") { $sArticle .= "::: important {$sSiteName}点评"; $sArticle .= $sContent; $sArticle .= $sEnter; $sArticle .= ":::"; $sArticle .= $sEnter; } else if ($sTmpName == "zhuguanpingfen") { $sContent = preg_replace('/(以上评分仅供参考)/', '', $sContent); $sContent = preg_replace('/(? $aArticle["title"] ?? "⚠️ 没有titile?", "content" => $sArticle, ]; return $oFlow->setDataA($aResultData)->done(); } private function clearBr($sStr) { $sEnter = " "; return str_replace("
", $sEnter, $sStr); } private function articleSort(array $data, string $field, array $priorityOrder): array { // 保留原始键名 uasort($data, function ($a, $b) use ($field, $priorityOrder) { $aValue = $a[$field] ?? null; $bValue = $b[$field] ?? null; $aIndex = array_search($aValue, $priorityOrder); $bIndex = array_search($bValue, $priorityOrder); // 如果找不到,排在最后 $aIndex = $aIndex === false ? PHP_INT_MAX : $aIndex; $bIndex = $bIndex === false ? PHP_INT_MAX : $bIndex; return $aIndex <=> $bIndex; }); return $data; } private function nameToTitleCS($sName) { $sTitle = trim($sName); $iCesuCount = ModelArticleSchema::where("name", $sName)->count(); if ($iCesuCount > 1) { $sWrap = ModelOption::_hit("ffq_tg_a-title_wrap"); if (!$sWrap) { return "未设置dog_option::ffq_tg-title_wrap"; } $sTitle = str_replace('{{name}}', $sName, $sWrap); $sTitle = str_replace('{{cesu_count}}', (string) $iCesuCount, $sTitle); } else { $sWrap = ModelOption::_hit("ffq_tg_a-title_wrap-first"); if (!$sWrap) { return "未设置dog_option::ffq_tg-title_wrap-first"; } $sTitle = str_replace('{{name}}', $sName, $sWrap); } return $sTitle; } }