458 lines
18 KiB
PHP
Executable File
458 lines
18 KiB
PHP
Executable File
<?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;
|
||
}
|
||
|
||
}
|