dd/app/Services/Article/Schema.php
2026-01-22 19:08:25 +08:00

458 lines
18 KiB
PHP
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare(strict_types=1);
namespace App\Services\Article;
use App\Models\ArticleSchema as ModelArticleSchema;
use App\Models\SiteMap as ModelSiteMap;
use App\Models\Option as ModelOption;
use App\Services\TomTool\Telegram\Slave as TeleSlave;
use App\Services\TomTool\Flow;
//use App\Services\Article\Cmd as ServiceArticleCmd;
//use App\Models\Option as ModelOption;
//use Intervention\Image\Facades\Image;
use Intervention\Image\ImageManager;
use Intervention\Image\Drivers\Gd\Driver;
use Illuminate\Support\Str;
final class Schema
{
// 必须
public string $sArticleCode;
public string $sSiteCode;
// 自动必有
public object $oArticle;
public string $sSiteGroupCode;
public string $sSiteName;
public string $sSiteUrl;
// public string $aArticleConfig;
// 调试
public bool $isTest = false;
public static function start($sArticleCode, $sSiteCode)
{
$oArticleSchema = ModelArticleSchema::where("code", $sArticleCode)->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;
}
// 废弃,改直接推了
// public function api_buildArticle()
// {
// $oFlow = Flow::start("api_buildArticle");
//
// $sArg = file_get_contents('php://input');
// $aArg = json_decode($sArg, true);
//
// $sArticleCode = $aArg[""] ?? '';
// $sSiteCode = $aArg["sSiteCode"] ?? ''; // ffcp
// $sSiteName = $aArg["sSiteName"] ?? ''; // ttcp
// $sSiteUrl = $aArg["sSiteUrl"] ?? ''; // ttcp.com
// $isTest = $aArg["isTest"] ?? false;
//
// $sSiteGroupCode = ModelSiteMap::where("code", $sSiteCode)->first()?->group_code;
// $oArticleSchema = ModelArticleSchema::where("code", $sArticleCode)->first();
//
// $aArg = [
// "sArticleCode" => $sArticleCode,
// "sSiteGroupCode"=> $sSiteGroupCode,
// "sSiteCode" => $sSiteCode,
// "sSiteName" => $sSiteName,
// "sSiteUrl" => $sSiteUrl,
// "isTest" => $isTest,
// "aSchema" => json_decode($oArticleSchema->schema, true),
// ];
//
// $sMethod = "build_article__".$sSiteGroupCode;
// if (method_exists($this, $sMethod)) {
// $oArticleFlow = $this->$sMethod($aArg);
// } else {
// $aResult = $oFlow->fail("没有".$sMethod."的对应处理")->getResult();
// echo json_encode($aReturn);
// exit;
// }
//
// echo json_encode($oArticleFlow->getResult());
// exit;
// }
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") {
if ($this->oArticle->group_code == "tg_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);
$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);
// 路径入库
$aSavedPath[$sSuffix] = $sCurrentPathUrl;
unset($oTmp); // 及时释放资源,老兵的职业操守
}
$aSchemaProRow["data"] = $aSavedPath;
$aSchemaPro[] = $aSchemaProRow; // 关键
$aDataSend[] = $aSchemaProRow; // 公约
} // is photo
} // 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}}", "### <img style='height:16px;' src='/icon.png'> ", $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("<br />", $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 = "<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('/(?<! ) (?! )/', '', $sContent);
// $sContent = preg_replace('/★/', '⭐️', $sContent);
$sArticle .= "::: important {$sSiteName}打分";
$sArticle .= $sContent;
$sArticle .= $sEnter;
$sArticle .= ":::";
$sArticle .= $sEnter;
} else if ($sTmpName == "str_jieshao") {
} else if ($sTmpName == "table_taocan") {
$sArticle .= $sEnter;
$sArticle .= $sEnter;
$sArticle .= "## 套餐价格";
$sArticle .= $sEnter;
$sArticle .= $sEnter;
$sArticle .= $sContent;
} else if ($sTmpName == "img_cesu") {
if ($j === false) {
$sArticle .= $sEnter;
$sArticle .= $sEnter;
$sArticle .= "## 节点测试";
$sArticle .= $sEnter;
$j = true;
}
$sArticle .= $sEnter;
$sArticle .= $sContent;
} else if ($sTmpName == "tag_tiqu") {
$sArticle .= $sEnter;
$sArticle .= $sEnter;
$sArticle .= $sContent;
} else if ($sTmpName == "schema_name") {
} else {
$sArticle .= $sEnter;
$sArticle .= $sEnter;
$sArticle .= "## 其他";
$sArticle .= $sEnter;
$sArticle .= $sEnter;
$sArticle .= $sContent;
}
}
////// step 5 返回 通知提取
$aResultData = [
"title" => $aArticle["title"] ?? "⚠️ 没有titile",
"content" => $sArticle,
];
return $oFlow->setDataA($aResultData)->done();
}
private function clearBr($sStr)
{
$sEnter = "
";
return str_replace("<br />", $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;
}
}