no message
This commit is contained in:
commit
ea84bca5e0
18
.editorconfig
Normal file
18
.editorconfig
Normal file
@ -0,0 +1,18 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.{yml,yaml}]
|
||||
indent_size = 2
|
||||
|
||||
[compose.yaml]
|
||||
indent_size = 4
|
||||
65
.env.example
Normal file
65
.env.example
Normal file
@ -0,0 +1,65 @@
|
||||
APP_NAME=Laravel
|
||||
APP_ENV=local
|
||||
APP_KEY=
|
||||
APP_DEBUG=true
|
||||
APP_URL=http://localhost
|
||||
|
||||
APP_LOCALE=en
|
||||
APP_FALLBACK_LOCALE=en
|
||||
APP_FAKER_LOCALE=en_US
|
||||
|
||||
APP_MAINTENANCE_DRIVER=file
|
||||
# APP_MAINTENANCE_STORE=database
|
||||
|
||||
# PHP_CLI_SERVER_WORKERS=4
|
||||
|
||||
BCRYPT_ROUNDS=12
|
||||
|
||||
LOG_CHANNEL=stack
|
||||
LOG_STACK=single
|
||||
LOG_DEPRECATIONS_CHANNEL=null
|
||||
LOG_LEVEL=debug
|
||||
|
||||
DB_CONNECTION=sqlite
|
||||
# DB_HOST=127.0.0.1
|
||||
# DB_PORT=3306
|
||||
# DB_DATABASE=laravel
|
||||
# DB_USERNAME=root
|
||||
# DB_PASSWORD=
|
||||
|
||||
SESSION_DRIVER=database
|
||||
SESSION_LIFETIME=120
|
||||
SESSION_ENCRYPT=false
|
||||
SESSION_PATH=/
|
||||
SESSION_DOMAIN=null
|
||||
|
||||
BROADCAST_CONNECTION=log
|
||||
FILESYSTEM_DISK=local
|
||||
QUEUE_CONNECTION=database
|
||||
|
||||
CACHE_STORE=database
|
||||
# CACHE_PREFIX=
|
||||
|
||||
MEMCACHED_HOST=127.0.0.1
|
||||
|
||||
REDIS_CLIENT=phpredis
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
|
||||
MAIL_MAILER=log
|
||||
MAIL_SCHEME=null
|
||||
MAIL_HOST=127.0.0.1
|
||||
MAIL_PORT=2525
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_FROM_ADDRESS="hello@example.com"
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
|
||||
AWS_ACCESS_KEY_ID=
|
||||
AWS_SECRET_ACCESS_KEY=
|
||||
AWS_DEFAULT_REGION=us-east-1
|
||||
AWS_BUCKET=
|
||||
AWS_USE_PATH_STYLE_ENDPOINT=false
|
||||
|
||||
VITE_APP_NAME="${APP_NAME}"
|
||||
11
.gitattributes
vendored
Normal file
11
.gitattributes
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
* text=auto eol=lf
|
||||
|
||||
*.blade.php diff=html
|
||||
*.css diff=css
|
||||
*.html diff=html
|
||||
*.md diff=markdown
|
||||
*.php diff=php
|
||||
|
||||
/.github export-ignore
|
||||
CHANGELOG.md export-ignore
|
||||
.styleci.yml export-ignore
|
||||
25
.gitignore
vendored
Normal file
25
.gitignore
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
*.log
|
||||
.DS_Store
|
||||
.env
|
||||
.env.backup
|
||||
.env.production
|
||||
.phpactor.json
|
||||
.phpunit.result.cache
|
||||
/.fleet
|
||||
/.idea
|
||||
/.nova
|
||||
/.phpunit.cache
|
||||
/.vscode
|
||||
/.zed
|
||||
/auth.json
|
||||
/node_modules
|
||||
/public/build
|
||||
/public/hot
|
||||
/public/storage
|
||||
/public/upload
|
||||
/storage/*.key
|
||||
/storage/pail
|
||||
/vendor
|
||||
Homestead.json
|
||||
Homestead.yaml
|
||||
Thumbs.db
|
||||
59
README.md
Executable file
59
README.md
Executable file
@ -0,0 +1,59 @@
|
||||
<p align="center"><a href="https://laravel.com" target="_blank"><img src="https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg" width="400" alt="Laravel Logo"></a></p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/laravel/framework/actions"><img src="https://github.com/laravel/framework/workflows/tests/badge.svg" alt="Build Status"></a>
|
||||
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/dt/laravel/framework" alt="Total Downloads"></a>
|
||||
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/v/laravel/framework" alt="Latest Stable Version"></a>
|
||||
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/l/laravel/framework" alt="License"></a>
|
||||
</p>
|
||||
|
||||
## About Laravel
|
||||
|
||||
Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as:
|
||||
|
||||
- [Simple, fast routing engine](https://laravel.com/docs/routing).
|
||||
- [Powerful dependency injection container](https://laravel.com/docs/container).
|
||||
- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage.
|
||||
- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent).
|
||||
- Database agnostic [schema migrations](https://laravel.com/docs/migrations).
|
||||
- [Robust background job processing](https://laravel.com/docs/queues).
|
||||
- [Real-time event broadcasting](https://laravel.com/docs/broadcasting).
|
||||
|
||||
Laravel is accessible, powerful, and provides tools required for large, robust applications.
|
||||
|
||||
## Learning Laravel
|
||||
|
||||
Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework. You can also check out [Laravel Learn](https://laravel.com/learn), where you will be guided through building a modern Laravel application.
|
||||
|
||||
If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library.
|
||||
|
||||
## Laravel Sponsors
|
||||
|
||||
We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the [Laravel Partners program](https://partners.laravel.com).
|
||||
|
||||
### Premium Partners
|
||||
|
||||
- **[Vehikl](https://vehikl.com)**
|
||||
- **[Tighten Co.](https://tighten.co)**
|
||||
- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)**
|
||||
- **[64 Robots](https://64robots.com)**
|
||||
- **[Curotec](https://www.curotec.com/services/technologies/laravel)**
|
||||
- **[DevSquad](https://devsquad.com/hire-laravel-developers)**
|
||||
- **[Redberry](https://redberry.international/laravel-development)**
|
||||
- **[Active Logic](https://activelogic.com)**
|
||||
|
||||
## Contributing
|
||||
|
||||
Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
|
||||
|
||||
## Security Vulnerabilities
|
||||
|
||||
If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed.
|
||||
|
||||
## License
|
||||
|
||||
The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).
|
||||
351
app/Http/Controllers/Api/ArticleSchemaController.php
Normal file
351
app/Http/Controllers/Api/ArticleSchemaController.php
Normal file
@ -0,0 +1,351 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Services\TomTool\Flow;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use App\Services\TomTool\Telegram\slave as TeleSlave;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use App\Models\Gems as ModelGems;
|
||||
use App\Models\Articles as ModelArticles;
|
||||
use Exception;
|
||||
|
||||
class ArticleSchemaController extends Controller
|
||||
{
|
||||
/**
|
||||
* 【中央弹药库】全局共享数据与状态
|
||||
*/
|
||||
private array $aData = [];
|
||||
private string $sCmd = '';
|
||||
private array $aCmdValue = []; // 修正:类型改为 array,配合 a 前缀
|
||||
private string $sGroupCode = '';
|
||||
private string $sArticleCode = '';
|
||||
private array $aArticleOpt = [];
|
||||
// private ?object $oModelGems = null;
|
||||
private ?object $oArticleLoc = null;
|
||||
private array $aSchemaPro = [];
|
||||
|
||||
/**
|
||||
* 【核心指挥部】接收入口
|
||||
*/
|
||||
public function receive(Request $oRequest)
|
||||
{
|
||||
$oFlow = Flow::start("resource-schema-receive");
|
||||
|
||||
// 1. 数据归仓:强制初始化,防止脏数据
|
||||
$this->aData = $oRequest->all();
|
||||
$this->sCmd = (string) ($this->aData["cmd"] ?? '');
|
||||
$this->aCmdValue = (array) ($this->aData["value"] ?? []);
|
||||
$this->sGroupCode = (string) ($this->aData["group_code"] ?? '');
|
||||
$this->sArticleCode = (string) ($this->aData["article_code"] ?? '');
|
||||
$this->aArticleOpt = (array) ($this->aData["article_opt"] ?? []);
|
||||
$this->aSchemaPro = (array) ($this->aData["article"] ?? []);
|
||||
|
||||
$this->oArticleLoc = ModelArticles::firstOrCreate(
|
||||
['sCode' => $this->sArticleCode]
|
||||
);
|
||||
|
||||
//// test
|
||||
|
||||
//// test end
|
||||
|
||||
$this->oArticleLoc->sAuthorCode = $this->sGroupCode;
|
||||
$this->oArticleLoc->aSchemaPro = $this->aSchemaPro;
|
||||
$this->oArticleLoc->aOpt = $this->aArticleOpt;
|
||||
$this->oArticleLoc->saveOrFail();
|
||||
|
||||
try {
|
||||
// 参数合法性初审
|
||||
if (empty($this->sCmd)) {
|
||||
throw new Exception("缺少作战指令 (cmd)");
|
||||
}
|
||||
|
||||
// 2. 指令匹配:一身轻松
|
||||
$sMsg = match ($this->sCmd) {
|
||||
'new', 'renew' => $this->cmdSaveArticle(),
|
||||
'opt' => $this->cmdOptArticle(),
|
||||
'del' => $this->cmdDeleteArticle(),
|
||||
default => throw new Exception("未知指令: {$this->sCmd}"),
|
||||
};
|
||||
|
||||
return response()->json($oFlow->done($sMsg)->toArray());
|
||||
|
||||
} catch (Exception $e) {
|
||||
$sLog = "【战损报告】" . $e->getMessage();
|
||||
TeleSlave::warn()->enableSlaveQueue(false)->send($sLog);
|
||||
return response()->json(['isDone' => false, 'error' => $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 【指令级】处理保存或更新任务
|
||||
*/
|
||||
private function cmdSaveArticle(): string
|
||||
{
|
||||
$aArticle = $this->aData["article"] ?? [];
|
||||
if (empty($aArticle) || !is_array($aArticle)) {
|
||||
throw new Exception("弹药不足:article 数据为空");
|
||||
}
|
||||
|
||||
// 番号匹配
|
||||
$aResults = match ($this->sGroupCode) {
|
||||
'tg_xiuren_a' => $this->groupXiurenHandler($aArticle),
|
||||
default => throw new Exception("番号 {$this->sGroupCode} 未在编制内"),
|
||||
};
|
||||
|
||||
return "{$this->sGroupCode} 任务处理完成,有效战果数: " . count($aResults);
|
||||
}
|
||||
|
||||
/**
|
||||
* 【番号级】秀人网专项调度
|
||||
*/
|
||||
private function groupXiurenHandler(array $aArticle): array
|
||||
{
|
||||
$aStatus = [];
|
||||
|
||||
foreach ($aArticle as $aRow) {
|
||||
$sRowType = $aRow["type"] ?? '';
|
||||
$aRowData = $aRow["data"] ?? [];
|
||||
|
||||
if (empty($aRowData)) continue;
|
||||
|
||||
// 类型匹配
|
||||
$aStatus[] = match ($sRowType) {
|
||||
'photo' => $this->typePhotoHandler($aRowData),
|
||||
default => "跳过未知类型: {$sRowType}",
|
||||
};
|
||||
}
|
||||
return $aStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* 【类型级】图片处理处置官
|
||||
*/
|
||||
private function typePhotoHandler(array $aData): array
|
||||
{
|
||||
$aSaved = [];
|
||||
$sMasterUrl = "https://" . config("path.url_master_base");
|
||||
|
||||
// 先查出来,拿到实例
|
||||
$aImages = $this->oArticleLoc->oImages()
|
||||
->where('sArticleCode', $this->sArticleCode)
|
||||
->get();
|
||||
|
||||
// 循环删除,这时每一个都会触发 booted 里的 deleting
|
||||
foreach ($aImages as $oImage) {
|
||||
$oImage->delete();
|
||||
}
|
||||
|
||||
foreach ($aData as $sPhotoName => $sPhotoUrl) {
|
||||
$xBinary = $this->utilDownload($sMasterUrl . "/" . $sPhotoUrl);
|
||||
|
||||
if ($xBinary) {
|
||||
$sFileName = basename($sPhotoUrl);
|
||||
// 检查存储结果
|
||||
if (Storage::disk('upload_images')->put($sFileName, $xBinary)) {
|
||||
$aSaved[] = $sFileName;
|
||||
|
||||
$this->oArticleLoc->oImages()->create([
|
||||
'sArticleCode' => $this->sArticleCode,
|
||||
'sFileName' => $sFileName
|
||||
]);
|
||||
|
||||
$this->utilNotify($sFileName, "已保存: " . $sPhotoName);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $aSaved;
|
||||
}
|
||||
|
||||
/**
|
||||
* 【工具级】资源下载硬攻
|
||||
*/
|
||||
private function utilDownload(string $sUrl)
|
||||
{
|
||||
try {
|
||||
$oRes = Http::withoutVerifying()
|
||||
->timeout(15)
|
||||
->retry(3, 200)
|
||||
->get($sUrl);
|
||||
return $oRes->successful() ? $oRes->body() : null;
|
||||
} catch (Exception $e) {
|
||||
return null; // 下载失败不炸毁主流程
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 【工具级】Telegram 通报勤务
|
||||
*/
|
||||
private function utilNotify(string $sFileName, string $sText)
|
||||
{
|
||||
$sUrl = Storage::disk('upload_images')->url($sFileName); // 统一 Disk
|
||||
|
||||
if (config("app.is_loc")) {
|
||||
$sUrl = "https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png";
|
||||
}
|
||||
|
||||
TeleSlave::warn()->enableSlaveQueue(false)->setPhotoUrl($sUrl)->send($sText);
|
||||
}
|
||||
|
||||
private function cmdOptArticle(): string
|
||||
{
|
||||
return "resource的opt更新为".json_code($this->oArticleLoc->opt);
|
||||
}
|
||||
|
||||
private function cmdDeleteArticle(): string
|
||||
{
|
||||
$this->oArticleLoc->delete();
|
||||
|
||||
return "目标 {$this->sArticleCode} 已从阵地蒸发";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//public function receive_old(Request $oRequest)
|
||||
//{
|
||||
// $oFlow = Flow::start("resource-schema-receive");
|
||||
//
|
||||
// //// test
|
||||
// // $oModelSlaveSites = ModelSlaveSites::find(1);
|
||||
// // $oModelSlaveSites->oImages()->create([
|
||||
// // "sPath" => "xxxx",
|
||||
// // ]);
|
||||
// //// test
|
||||
//
|
||||
// $aData = $oRequest->all();
|
||||
//
|
||||
// $oFlow = $oFlow->setDataA($aData)->fail();
|
||||
//
|
||||
// $sGroupCode = $aData["_raw"]["group_code"] ?? '';
|
||||
// $aArticle = $aData["article"] ?? [];
|
||||
// $sCmd = $aData["cmd"] ?? '';
|
||||
//
|
||||
// $sMasterUrl = "https://".config("path.url_master_base");
|
||||
//
|
||||
// foreach ($aArticle as $aArticleRow) {
|
||||
// if ($sGroupCode == "tg_xiuren_a") {
|
||||
// $sArticleRowType = $aArticleRow["type"] ?? "";
|
||||
// $aArticleRowData = $aArticleRow["data"] ?? [];
|
||||
// if ($sArticleRowType == "photo") {
|
||||
// foreach ($aArticleRowData as $sPhotoName => $sPhotoUrl) {
|
||||
// $sImgUrl = $sMasterUrl."/".$sPhotoUrl;
|
||||
// $sFileName = basename($sPhotoUrl);
|
||||
//
|
||||
// $oResponse = Http::withoutVerifying() // 放弃证书防线,直接硬攻
|
||||
// ->timeout(15) // 15秒决胜负
|
||||
// ->retry(3, 200) // 三次冲锋,每次间隔200ms
|
||||
// ->get($sImgUrl);
|
||||
//
|
||||
// if ($oResponse->failed()) {
|
||||
// $sMsg = "【战损报告】图片请求全军覆没。URL: {$sImgUrl},状态码: " . $oResponse->status();
|
||||
// TeleSlave::warn()->enableSlaveQueue(false)->send($sMsg);
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// $xData = $oResponse->body();
|
||||
//
|
||||
// $b = Storage::disk('upload')->put($sFileName, $xData);
|
||||
//
|
||||
// if (!$b) {
|
||||
// $sMsg = "文件保存失败";
|
||||
// TeleSlave::warn()->enableSlaveQueue(false)->send($sMsg);
|
||||
// }
|
||||
//
|
||||
// $sResourceImgUrl = Storage::disk('upload')->url($sFileName);
|
||||
//
|
||||
// if (config("app.is_loc") == true) {
|
||||
// $sResourceImgUrl = "https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png"; //// test
|
||||
// }
|
||||
//
|
||||
// TeleSlave::warn()->enableSlaveQueue(false)->setPhotoUrl($sResourceImgUrl)->send($sPhotoName);
|
||||
//
|
||||
// } // foreach articleRow
|
||||
// } // articleRow type is photo
|
||||
// } // code = xiuren
|
||||
// } // foreach article
|
||||
//
|
||||
// // todo 值入库
|
||||
//
|
||||
//
|
||||
// exit;
|
||||
//
|
||||
// // return response()->json([
|
||||
// // 'status' => '战斗准备就绪',
|
||||
// // 'received_id' => $intId,
|
||||
// // 'all_data' => $arrData
|
||||
// // ]);
|
||||
//}
|
||||
//
|
||||
|
||||
|
||||
// array(9) {
|
||||
// ["site_code"]=>
|
||||
// string(8) "resource"
|
||||
// ["cmd"]=>
|
||||
// string(3) "new"
|
||||
// ["value"]=>
|
||||
// array(0) {
|
||||
// }
|
||||
// ["group_code"] = "tg_xiuren_a"
|
||||
// ["date"]=>
|
||||
// string(19) "2026-01-17 19:54:40"
|
||||
// ["_raw"]=>
|
||||
// array(13) {
|
||||
// ["id"]=>
|
||||
// int(126)
|
||||
// ["code"]=>
|
||||
// string(38) "tg_xiuren_a-01KF5WX0N8VE3D773CFWZQBJVX"
|
||||
// ["name"]=>
|
||||
// NULL
|
||||
// ["schema"]=>
|
||||
// string(728) "{"type":"cache_tg","data":[{"type":"photo","photo_fullSizeKey":3,"photo":[{"file_size":1382,"width":60,"height":90,"file_path":"\/private\/var\/www\/wwwroot\/dd.loc\/public\/upload\/tg_xiuren_a-01KF5WX0N8VE3D773CFWZQBJVX-file_4.jpg"},{"file_size":19420,"width":213,"height":320,"file_path":"\/private\/var\/www\/wwwroot\/dd.loc\/public\/upload\/tg_xiuren_a-01KF5WX0N8VE3D773CFWZQBJVX-file_5.jpg"},{"file_size":87322,"width":533,"height":800,"file_path":"\/private\/var\/www\/wwwroot\/dd.loc\/public\/upload\/tg_xiuren_a-01KF5WX0N8VE3D773CFWZQBJVX-file_6.jpg"},{"file_size":169979,"width":853,"height":1280,"file_path":"\/private\/var\/www\/wwwroot\/dd.loc\/public\/upload\/tg_xiuren_a-01KF5WX0N8VE3D773CFWZQBJVX-file_7.jpg"}]}]}"
|
||||
// ["schema_pro"]=>
|
||||
// string(325) "[{"type":"photo","data":{"full":"upload\/tg_xiuren_a-01KF5WX0N8VE3D773CFWZQBJVX-fn_b-full.jpg","thumb_a":"upload\/tg_xiuren_a-01KF5WX0N8VE3D773CFWZQBJVX-fn_b-thumb_a.jpg","thumb_b":"upload\/tg_xiuren_a-01KF5WX0N8VE3D773CFWZQBJVX-fn_b-thumb_b.jpg","thumb_c":"upload\/tg_xiuren_a-01KF5WX0N8VE3D773CFWZQBJVX-fn_b-thumb_c.jpg"}}]"
|
||||
// ["group_code"]=>
|
||||
// string(11) "tg_xiuren_a"
|
||||
// ["cmd"]=>
|
||||
// string(115) "{"|resource|resource|20260117195440|":{"site_code":"resource","cmd":"new","value":[],"date":"2026-01-17 19:54:40"}}"
|
||||
// ["cmd_log"]=>
|
||||
// NULL
|
||||
// ["opt"]=>
|
||||
// string(23) "{"thumb_use":["b","c"]}"
|
||||
// ["enable_sync"]=>
|
||||
// int(1)
|
||||
// ["status"]=>
|
||||
// int(1)
|
||||
// ["created_at"]=>
|
||||
// string(27) "2026-01-17T11:54:40.000000Z"
|
||||
// ["updated_at"]=>
|
||||
// string(27) "2026-01-17T12:07:03.000000Z"
|
||||
// }
|
||||
// ["key"]=>
|
||||
// string(34) "|resource|resource|20260117195440|"
|
||||
// ["article_code"]=>
|
||||
// string(38) "tg_xiuren_a-01KF5WX0N8VE3D773CFWZQBJVX"
|
||||
// ["config"]=>
|
||||
// NULL
|
||||
// ["article"]=>
|
||||
// array(1) {
|
||||
// [0]=>
|
||||
// array(2) {
|
||||
// ["type"]=>
|
||||
// string(5) "photo"
|
||||
// ["data"]=>
|
||||
// array(4) {
|
||||
// ["full"]=>
|
||||
// string(59) "upload/tg_xiuren_a-01KF5WX0N8VE3D773CFWZQBJVX-fn_b-full.jpg"
|
||||
// ["thumb_a"]=>
|
||||
// string(62) "upload/tg_xiuren_a-01KF5WX0N8VE3D773CFWZQBJVX-fn_b-thumb_a.jpg"
|
||||
// ["thumb_b"]=>
|
||||
// string(62) "upload/tg_xiuren_a-01KF5WX0N8VE3D773CFWZQBJVX-fn_b-thumb_b.jpg"
|
||||
// ["thumb_c"]=>
|
||||
// string(62) "upload/tg_xiuren_a-01KF5WX0N8VE3D773CFWZQBJVX-fn_b-thumb_c.jpg"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
77
app/Http/Controllers/Api/Images/GetController.php
Normal file
77
app/Http/Controllers/Api/Images/GetController.php
Normal file
@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Images;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Articles as ModelArticles;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use App\Services\TomTool\Telegram\Slave as TeleSlave;
|
||||
use App\Services\TomTool\Flow;
|
||||
|
||||
class GetController extends Controller
|
||||
{
|
||||
private $iStartNum = 0; // 改个名字,更有语义
|
||||
private $iMaxTry = 5; // 最大尝试次数
|
||||
|
||||
public function urlbyNum(Request $oRequest)
|
||||
{
|
||||
$oFlow = Flow::start("urlByNum");
|
||||
|
||||
$sAuthorCode = $oRequest->input("sAuthorCode");
|
||||
$iNum = (int)$oRequest->input("iNum", 1);
|
||||
|
||||
$this->iStartNum = $iNum;
|
||||
|
||||
return $this->findValidUrl($sAuthorCode, $iNum, $oFlow);
|
||||
}
|
||||
|
||||
private function findValidUrl($sAuthorCode, $iNum, $oFlow)
|
||||
{
|
||||
try {
|
||||
$oQuery = ModelArticles::where("sAuthorCode", $sAuthorCode)->orderBy("id", "asc");
|
||||
$iTotal = $oQuery->count();
|
||||
|
||||
if ($iTotal === 0) throw new \Exception("作者 [{$sAuthorCode}] 旗下无任何记录");
|
||||
|
||||
// 1. 计算当前查库的偏移量,用 % 取模是最稳的,或者用 if 但不要改 iNum 本身
|
||||
$iOffset = ($iNum - 1) % $iTotal;
|
||||
$iTrueNum = $iOffset + 1; // 真正对应数据库的第几条
|
||||
|
||||
$oArticle = $oQuery->skip($iOffset)->first();
|
||||
|
||||
// 2. 拼文件名
|
||||
$aThumbUse = $oArticle->aOpt["thumb_use"] ?? ["b"];
|
||||
$sThumbUse = $aThumbUse[array_rand($aThumbUse)];
|
||||
$sFileName = $oArticle->sCode . '-1-' . 'thumb_' . $sThumbUse . '.jpg';
|
||||
|
||||
// 3. 检查文件
|
||||
if (!Storage::disk('upload_images')->exists($sFileName)) {
|
||||
// 报警
|
||||
TeleSlave::warn()->enableSlaveQueue(false)->enableAsync(false)->send("findValidUrl:图片文件不存在:{$sFileName}");
|
||||
|
||||
// 💡 这里的出口判定最重要!
|
||||
// 如果尝试次数已经超过了最大限制,或者已经把库里所有的记录都扫了一遍还没找到
|
||||
if ($iNum >= ($this->iStartNum + $this->iMaxTry) || $iNum >= ($this->iStartNum + $iTotal)) {
|
||||
throw new \Exception("该作者连续查找多张图片均不存在,已停止尝试");
|
||||
}
|
||||
|
||||
// 递归:iNum 必须一直往上涨,不能回头!
|
||||
return $this->findValidUrl($sAuthorCode, $iNum + 1, $oFlow);
|
||||
}
|
||||
|
||||
// 4. 成功
|
||||
$aData = [
|
||||
"sFileUrl" => Storage::disk('upload_images')->url($sFileName),
|
||||
"iTrueNum" => $iTrueNum,
|
||||
];
|
||||
|
||||
return response()->json($oFlow->setDataA($aData)->done()->toArray());
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$sMsg = $e->getMessage();
|
||||
TeleSlave::warn()->enableSlaveQueue(false)->enableAsync(false)->send($sMsg);
|
||||
return response()->json($oFlow->fail($sMsg)->toArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
168
app/Http/Controllers/Api/Tg/HookController.php
Executable file
168
app/Http/Controllers/Api/Tg/HookController.php
Executable file
@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api\Tg;
|
||||
|
||||
//use App\Models\Article;
|
||||
use App\Services\TomTool\Http;
|
||||
use App\Services\Tg\Http as TGHttp;
|
||||
use App\Models\Hook as ModelHook;
|
||||
use App\Services\TomTool\ThrowableHandler;
|
||||
//use App\Services\TomTool\TelegramVdb;
|
||||
use App\Services\TomTool\Telegram\Slave as TelegramSlave;
|
||||
|
||||
final class HookController
|
||||
{
|
||||
private $sCustomText = '';
|
||||
|
||||
// public function cmd_xiuren_a()
|
||||
// {
|
||||
// try {
|
||||
//
|
||||
// $sUpdate = file_get_contents("php://input");
|
||||
// $aUpdate = json_decode($sUpdate, true);
|
||||
// $sText = $aUpdate["message"]["text"] ?? "没有text";
|
||||
// if (!str_starts_with($sText, '/')) {
|
||||
// // $this->sCustomCmd = "article";
|
||||
// // $this->sCustomFunc = "cache";
|
||||
// // $this->aCustomOption[1] = "ffq";
|
||||
// $this->sCustomText = "/article#cacheAdd__ffq_tg_a"." ".$sUpdate;
|
||||
// }
|
||||
//
|
||||
// $this->cmd();
|
||||
//
|
||||
// } catch (\Throwable $e) {
|
||||
//
|
||||
// $aDataTg = ThrowableHandler::make($e)->enableTrace()->fetch();
|
||||
// TelegramSlave::fail()->enableSlaveQueue(false)->setMsgA([
|
||||
// "msg" => $aDataTg,
|
||||
// "data" => $sUpdate,
|
||||
// ])->send();
|
||||
//
|
||||
// $aDataCmd = $aDataTg;
|
||||
// $sDataCmd = json_encode($aDataCmd, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||
// $sDataCmd = str_replace("\\n", "<br>", $sDataCmd);
|
||||
// echo $sDataCmd;
|
||||
// exit;
|
||||
// }
|
||||
// }
|
||||
|
||||
public function cmd()
|
||||
{
|
||||
|
||||
try {
|
||||
|
||||
$oTGHttp = new TGHttp();
|
||||
|
||||
$oTGHttp->sApiToken = env('telegram_token');
|
||||
$oTGHttp->sApiChatId = env('telegram_chatid');
|
||||
|
||||
$sUpdate = file_get_contents("php://input");
|
||||
|
||||
$aUpdate = json_decode($sUpdate, true);
|
||||
|
||||
// @file_put_contents('/www/_tg/bot_log.txt', json_encode($aUpdate, JSON_PRETTY_PRINT));
|
||||
|
||||
$aData['chatId'] = (string) $aUpdate["message"]["chat"]["id"];
|
||||
$aData['fromId'] = (string) $aUpdate["message"]["from"]["id"];
|
||||
|
||||
if ($this->sCustomText) {
|
||||
$aUpdate["message"]["text"] = $this->sCustomText;
|
||||
}
|
||||
|
||||
if (!isset($aUpdate["message"]["text"])) {
|
||||
throw new \Exception(json_encode($aUpdate));
|
||||
}
|
||||
|
||||
$sText = $aUpdate["message"]["text"];
|
||||
|
||||
// hook优先
|
||||
// 非is_hook,并且,首字符非/
|
||||
if ((!isset($aUpdate["is_hook"]) || $aUpdate["is_hook"] == false) && substr($sText, 0, 1) !== "/") {
|
||||
|
||||
$sHookKey = ModelHook::where("tg_user_id", $aData["fromId"])->first()?->key;
|
||||
|
||||
if ($sHookKey) {
|
||||
if ($sText == "run") {
|
||||
$sText = $sHookKey;
|
||||
} else {
|
||||
$sText = $sHookKey.$sText;
|
||||
}
|
||||
$aUpdate["is_hook"] = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@$aArg = explode(" ", $sText) ?? [];
|
||||
@$aOption = explode("__", $aArg[0]) ?? [];
|
||||
|
||||
$sCmd = $this->cleanCmd($aOption[0]);
|
||||
|
||||
// 大于3个说明是hook或dove之类
|
||||
$aCmd = explode("#", $sCmd);
|
||||
// if (count($aCmd) >= 3) {
|
||||
// $sHook = $aCmd[0];
|
||||
// $sClass = $sHook;
|
||||
// $sFunc = "go";
|
||||
// } else {
|
||||
$sClass = $aCmd[0] ?? false;
|
||||
$sFunc = $aCmd[1] ?? false;
|
||||
// }
|
||||
|
||||
if (strpos($sClass, "dove$") !== false) {
|
||||
$sClass = "dove";
|
||||
$sFunc = "go";
|
||||
}
|
||||
|
||||
$sUClass = ucfirst($sClass);
|
||||
$sUFunc = ucfirst($sFunc);
|
||||
|
||||
unset($aOption[0]);
|
||||
unset($aArg[0]);
|
||||
|
||||
$aParams = [];
|
||||
$aParams['aOption'] = $aOption;
|
||||
$aParams['aArg'] = $aArg;
|
||||
|
||||
$sClassPath = "App\\Services\\Tg\\Hook\\Mod\\".$sClass;
|
||||
$sUClassPath = "App\\Services\\Tg\\Hook\\Mod\\".$sUClass;
|
||||
|
||||
$oInstans = new $sUClassPath($aUpdate);
|
||||
$r = $oInstans->$sFunc($aParams, $aUpdate);
|
||||
|
||||
$oTGHttp->send($r, $aUpdate);
|
||||
|
||||
echo $r;
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
|
||||
$aDataTg = ThrowableHandler::make($e)->enableTrace()->fetch();
|
||||
TelegramSlave::fail()->enableSlaveQueue(false)->setMsgA($aDataTg)->send();
|
||||
|
||||
$aDataCmd = $aDataTg;
|
||||
$sDataCmd = json_encode($aDataCmd, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||
$sDataCmd = str_replace("\\n", "<br>", $sDataCmd);
|
||||
echo $sDataCmd;
|
||||
exit;
|
||||
|
||||
// old
|
||||
// $sMessage = "错误(文件: " . $e->getFile() . ",行: " . $e->getLine() . "): " . $e->getMessage();
|
||||
// $oTGHttp->send($sMessage, $sUpdate);
|
||||
// //Http::response(200, 'error'); 注释的
|
||||
// old end
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private function cleanCmd($input) {
|
||||
|
||||
$input = str_replace('\\', '', $input);
|
||||
$input = str_replace('/', '', $input);
|
||||
|
||||
return $input;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
8
app/Http/Controllers/Controller.php
Executable file
8
app/Http/Controllers/Controller.php
Executable file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
//
|
||||
}
|
||||
13
app/Http/Controllers/TestController.php
Normal file
13
app/Http/Controllers/TestController.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TestController extends Controller
|
||||
{
|
||||
public function hello()
|
||||
{
|
||||
tt("hello");
|
||||
}
|
||||
}
|
||||
25
app/Http/Middleware/ApiCheckToken.php
Normal file
25
app/Http/Middleware/ApiCheckToken.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class ApiCheckToken
|
||||
{
|
||||
|
||||
public function handle($oRequest, Closure $next)
|
||||
{
|
||||
// 获取 Token
|
||||
$sIncomingToken = $oRequest->bearerToken();
|
||||
$sLocalToken = (string) env('token_base', false);
|
||||
|
||||
// 严丝合缝的校验
|
||||
if (!$sIncomingToken || $sIncomingToken !== $sLocalToken) {
|
||||
return response()->json(['msg' => 'token error'], 401);
|
||||
}
|
||||
|
||||
return $next($oRequest);
|
||||
}
|
||||
}
|
||||
40
app/Models/Articles.php
Executable file
40
app/Models/Articles.php
Executable file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||
|
||||
class Articles extends Model
|
||||
{
|
||||
protected $guarded = [];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'aOpt' => 'array',
|
||||
'aSchemaPro' => 'array',
|
||||
];
|
||||
}
|
||||
|
||||
protected static function booted(): void
|
||||
{
|
||||
static::deleting(function ($oArticle) {
|
||||
$oArticle->oImages()->get()->each(function ($oImage) {
|
||||
$oImage->delete();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public function oSubject(): MorphTo
|
||||
{
|
||||
return $this->morphTo('oSubject');
|
||||
}
|
||||
|
||||
public function oImages(): MorphMany
|
||||
{
|
||||
return $this->morphMany(UploadImages::class, 'oImageable');
|
||||
}
|
||||
|
||||
}
|
||||
43
app/Models/DogOption.php
Executable file
43
app/Models/DogOption.php
Executable file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class DogOption extends Model
|
||||
{
|
||||
protected $table = 'dog_option';
|
||||
public $timestamps = false;
|
||||
|
||||
public static function _hit($sDogName, $k)
|
||||
{
|
||||
$v = self::where("dog_name", $sDogName)->where("k", $k)->first()?->v;
|
||||
return $v;
|
||||
}
|
||||
|
||||
public static function _list($sDogName)
|
||||
{
|
||||
$oSelf = self::where("dog_name", $sDogName)->get();
|
||||
|
||||
return self::_filterList($oSelf);
|
||||
}
|
||||
|
||||
public static function _filter($oOption)
|
||||
{
|
||||
$arr = [];
|
||||
$arr[$oOption->dog_name."#".$oOption->k] = $oOption->v;
|
||||
|
||||
return $arr;
|
||||
}
|
||||
|
||||
public static function _filterList($oOption)
|
||||
{
|
||||
$arr = [];
|
||||
foreach ($oOption as $oOptionRow) {
|
||||
$arr[] = self::_filter($oOptionRow);
|
||||
}
|
||||
|
||||
return $arr;
|
||||
}
|
||||
|
||||
}
|
||||
47
app/Models/Option.php
Executable file
47
app/Models/Option.php
Executable file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Option extends Model
|
||||
{
|
||||
protected $table = 'option';
|
||||
public $timestamps = false;
|
||||
protected $primaryKey = 'k';
|
||||
public $incrementing = false;
|
||||
protected $keyType = 'string';
|
||||
|
||||
public static function _hit($k)
|
||||
{
|
||||
$v = self::where("k", $k)->first()?->v;
|
||||
|
||||
return $v;
|
||||
}
|
||||
|
||||
public static function _list()
|
||||
{
|
||||
$oSelf = self::get();
|
||||
|
||||
return self::_filterList($oSelf);
|
||||
}
|
||||
|
||||
public static function _filter($oOption)
|
||||
{
|
||||
$arr = [];
|
||||
$arr[$oOption->k] = $oOption->v;
|
||||
|
||||
return $arr;
|
||||
}
|
||||
|
||||
public static function _filterList($oOption)
|
||||
{
|
||||
$arr = [];
|
||||
foreach ($oOption as $oOptionRow) {
|
||||
$arr[] = self::_filter($oOptionRow);
|
||||
}
|
||||
|
||||
return $arr;
|
||||
}
|
||||
|
||||
}
|
||||
43
app/Models/UploadImages.php
Executable file
43
app/Models/UploadImages.php
Executable file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute; // 必须加!
|
||||
use Illuminate\Support\Facades\Storage; // 必须加!
|
||||
//use Illuminate\Support\Facades\Log;
|
||||
|
||||
class UploadImages extends Model
|
||||
{
|
||||
protected $guarded = [];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'aArticleCode' => 'array',
|
||||
];
|
||||
}
|
||||
|
||||
protected static function booted(): void
|
||||
{
|
||||
static::deleting(function (UploadImages $oImage) {
|
||||
if ($oImage->sFileName && Storage::disk('upload_images')->exists($oImage->sFileName)) {
|
||||
Storage::disk('upload_images')->delete($oImage->sFileName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function oImageable(): MorphTo
|
||||
{
|
||||
return $this->morphTo('oImageable');
|
||||
}
|
||||
|
||||
protected function sPath(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn (string $value) => Storage::disk('upload_images')->url($value),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
48
app/Models/User.php
Executable file
48
app/Models/User.php
Executable file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
|
||||
class User extends Authenticatable
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\UserFactory> */
|
||||
use HasFactory, Notifiable;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'email',
|
||||
'password',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be hidden for serialization.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $hidden = [
|
||||
'password',
|
||||
'remember_token',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
];
|
||||
}
|
||||
}
|
||||
28
app/Providers/AppServiceProvider.php
Executable file
28
app/Providers/AppServiceProvider.php
Executable file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
Relation::morphMap([
|
||||
'Gems' => 'App\Models\Gems',
|
||||
'Articles' => 'App\Models\Articles',
|
||||
]);
|
||||
}
|
||||
}
|
||||
1052
app/Services/Tg/Hook/Dog.php
Executable file
1052
app/Services/Tg/Hook/Dog.php
Executable file
File diff suppressed because it is too large
Load Diff
57
app/Services/Tg/Hook/Mod/Base.php
Executable file
57
app/Services/Tg/Hook/Mod/Base.php
Executable file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Tg\Hook\Mod;
|
||||
//use App\Services\TomTool\Http;
|
||||
//use App\Models\Article as ModelArticle;
|
||||
use App\Services\TomTool\Http;
|
||||
use App\Services\Tg\Http as TGHttp;
|
||||
|
||||
class Base {
|
||||
|
||||
public function __construct() {
|
||||
$this->oHttp = new Http();
|
||||
$this->oTGHttp = new TGHttp();
|
||||
// $this->oTGHttp->sApiToken = env("telegram_token");
|
||||
// $this->oTGHttp->sApiChatId = env("telegram_chatid");
|
||||
}
|
||||
|
||||
public function toJson($aData)
|
||||
{
|
||||
return json_encode($aData, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
|
||||
public function eachSendTg($aData)
|
||||
{
|
||||
foreach ($aData as $row) {
|
||||
$this->oTGHttp->sendToTG(json_encode($row, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
|
||||
}
|
||||
|
||||
$this->oHttp::response(200, 'ok');
|
||||
}
|
||||
|
||||
// protected function cmdGood($sCmd) {
|
||||
//
|
||||
// $sCmdNew = "";
|
||||
// switch ($sCmd) {
|
||||
// case "user":
|
||||
// $sCmdNew = "user_name";
|
||||
// break;
|
||||
// case "project":
|
||||
// $sCmdNew = "project_name";
|
||||
// break;
|
||||
// case "code":
|
||||
// $sCmdNew = "sale_code";
|
||||
// break;
|
||||
// case "condition":
|
||||
// $sCmdNew = "cron_condition";
|
||||
// break;
|
||||
// default:
|
||||
// $sCmdNew = $sCmd;
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// return $sCmdNew;
|
||||
//
|
||||
// }
|
||||
|
||||
}
|
||||
24
app/Services/Tg/Hook/Mod/Images.php
Executable file
24
app/Services/Tg/Hook/Mod/Images.php
Executable file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Tg\Hook\Mod;
|
||||
use App\Models\Images as ModelImages;
|
||||
use App\Services\Tg\Hook\Dog;
|
||||
|
||||
class Images extends Base {
|
||||
|
||||
private $oDog;
|
||||
|
||||
public function __construct($aUpdate = [])
|
||||
{
|
||||
$this->oDog = new Dog(new ModelImages());
|
||||
// $this->oDog->_setPrimaryKey("k"); // 可选,默认id
|
||||
$this->oDog->_setDogName("images"); // 必须
|
||||
}
|
||||
|
||||
public function __call($sName, $aArg)
|
||||
{
|
||||
return $this->oDog->$sName($aArg[0]);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
49
app/Services/Tg/Hook/Mod/Option.php
Executable file
49
app/Services/Tg/Hook/Mod/Option.php
Executable file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Tg\Hook\Mod;
|
||||
use App\Models\Option as ModelOption;
|
||||
use App\Services\Tg\Hook\Dog;
|
||||
|
||||
class Option extends Base {
|
||||
|
||||
private $oDog;
|
||||
|
||||
public function __construct($aUpdate = [])
|
||||
{
|
||||
$this->oDog = new Dog(new ModelOption());
|
||||
$this->oDog->_setPrimaryKey("k"); // 可选,默认id
|
||||
$this->oDog->_setDogName("option"); // 必须
|
||||
}
|
||||
|
||||
public function __call($sName, $aArg)
|
||||
{
|
||||
return $this->oDog->$sName($aArg[0]);
|
||||
}
|
||||
|
||||
// public function Pause($aParam)
|
||||
// {
|
||||
// $v = $aParam['aArg'][1];
|
||||
//
|
||||
// // 获取第一个匹配的选项
|
||||
// $oOption = ModelOption::where("type", "pause")->first();
|
||||
//
|
||||
// // 检查是否找到了选项
|
||||
// if ($oOption) {
|
||||
// $oOption->value = $v;
|
||||
//
|
||||
// // 保存并返回结果
|
||||
// return json_encode($oOption->save());
|
||||
// }
|
||||
//
|
||||
// // 如果没有找到选项,返回一个错误信息
|
||||
// return json_encode(['error' => 'Option not found']);
|
||||
// }
|
||||
//
|
||||
// public function List()
|
||||
// {
|
||||
// $aOptionList = ModelOption::all()->toArray();
|
||||
//
|
||||
// return json_encode($aOptionList, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
||||
// }
|
||||
|
||||
}
|
||||
108
app/Services/Tg/Http.php
Executable file
108
app/Services/Tg/Http.php
Executable file
@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Tg;
|
||||
use App\Services\TomTool\Http as TomHttp;
|
||||
|
||||
class Http {
|
||||
|
||||
public $sApiToken;
|
||||
public $sApiChatId;
|
||||
|
||||
function __construct()
|
||||
{
|
||||
$this->sApiToken = env('telegram_token');
|
||||
$this->sApiChatId = env('telegram_chatid');
|
||||
}
|
||||
|
||||
public function send($sMsg = "msg空", $aUpdate = [])
|
||||
{
|
||||
|
||||
$bSendTg = true;
|
||||
|
||||
$sShell = $aUpdate["shell"] ?? false;
|
||||
$bIsDove = $aUpdate["is_dove"] ?? false;
|
||||
|
||||
if ($sShell == "cmd") {
|
||||
$bSendTg = false;
|
||||
}
|
||||
|
||||
if ($bIsDove == true) {
|
||||
$bSendTg = false;
|
||||
}
|
||||
|
||||
if (env("is_loc") == true) {
|
||||
$bSendTg = false;
|
||||
}
|
||||
|
||||
if (is_array($sMsg)) {
|
||||
if ($bSendTg) {
|
||||
foreach ($sMsg as $sMsgRow) {
|
||||
$this->sendTg($sMsgRow);
|
||||
}
|
||||
} else {
|
||||
echo json_encode($sMsg, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
||||
}
|
||||
} else {
|
||||
if ($bSendTg) {
|
||||
$this->sendTg($sMsg);
|
||||
} else {
|
||||
echo $sMsg;
|
||||
}
|
||||
}
|
||||
|
||||
if ($bSendTg) {
|
||||
TomHttp::response(200);
|
||||
} else {
|
||||
exit;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function sendTg($sMsg)
|
||||
{
|
||||
$oHttp = new TomHttp();
|
||||
$oHttp->sMethod = "post";
|
||||
$oHttp->sToUrl = "https://api.telegram.org/bot$this->sApiToken/sendMessage";
|
||||
// $oHttp->bIsJson = true;
|
||||
$oHttp->aData = [
|
||||
"chat_id" => $this->sApiChatId,
|
||||
"text" => $sMsg
|
||||
];
|
||||
|
||||
$oHttp->send();
|
||||
}
|
||||
|
||||
public function sendToTG($sMsg, $aUpdate = [])
|
||||
{
|
||||
|
||||
if (isset($aUpdate["shell"]) && $aUpdate["shell"] == "cmd") {
|
||||
|
||||
echo $sMsg;
|
||||
|
||||
} else if (isset($aUpdate["is_dove"]) && $aUpdate["is_dove"] == true) {
|
||||
|
||||
echo $sMsg;
|
||||
|
||||
} else {
|
||||
|
||||
if (env("is_loc") == true) {
|
||||
echo $sMsg;
|
||||
} else {
|
||||
$oHttp = new TomHttp();
|
||||
$oHttp->sMethod = "post";
|
||||
$oHttp->sToUrl = "https://api.telegram.org/bot$this->sApiToken/sendMessage";
|
||||
// $oHttp->bIsJson = true;
|
||||
$oHttp->aData = [
|
||||
"chat_id" => $this->sApiChatId,
|
||||
"text" => $sMsg
|
||||
];
|
||||
|
||||
$oHttp->send();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
425
app/Services/TomTool/Flow.php
Executable file
425
app/Services/TomTool/Flow.php
Executable file
@ -0,0 +1,425 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\TomTool;
|
||||
|
||||
use App\Services\TomTool\TelegramVdb;
|
||||
|
||||
/**
|
||||
|
||||
默认:
|
||||
name:none
|
||||
isDone:false
|
||||
|
||||
new:
|
||||
$oResult = Flow::make("name");
|
||||
|
||||
快速:
|
||||
Flow::make("name")->fail("kkkkkkk");
|
||||
|
||||
进阶:
|
||||
Flow::make("name")->noDone()->msg("哟哟哟哟哟哟")->sendTg()->fetch();
|
||||
|
||||
业务:
|
||||
step()
|
||||
msg()
|
||||
data()
|
||||
aData()
|
||||
jData()
|
||||
sData()
|
||||
code()
|
||||
put()
|
||||
merge()
|
||||
toDone()
|
||||
noDone()
|
||||
step("start")
|
||||
|
||||
action:
|
||||
reset()
|
||||
done(?$msg)
|
||||
fail(?$msg, ?$aParentResult)
|
||||
sendTg() 伪action
|
||||
fetch() or get()
|
||||
fetchAll() or geAll()
|
||||
|
||||
set:
|
||||
enableTg();
|
||||
parent($aParentResult);
|
||||
|
||||
with:
|
||||
withTraceId();
|
||||
withTimestamp();
|
||||
withStep();
|
||||
|
||||
判断:
|
||||
::isDone($aResult);
|
||||
::isFail($aResult);
|
||||
|
||||
*/
|
||||
|
||||
class Flow
|
||||
{
|
||||
protected array $aStep = [];
|
||||
protected array $aParent = [];
|
||||
protected bool $isToTg = false;
|
||||
protected bool $isToTgOne = false;
|
||||
protected array $aResult = [
|
||||
'isDone' => false,
|
||||
];
|
||||
protected string $sName;
|
||||
private bool $isChain = false;
|
||||
|
||||
public function __construct(string $name = 'none')
|
||||
{
|
||||
$this->sName = $name;
|
||||
$this->aStep[] = "{$this->sName}::start";
|
||||
}
|
||||
|
||||
// new
|
||||
public static function make(string $name = 'none'): self
|
||||
{
|
||||
return new self($name);
|
||||
}
|
||||
|
||||
// new
|
||||
public static function start($sName = 'none')
|
||||
{
|
||||
$oInstance = new self($sName);
|
||||
$oInstance->isChain = true;
|
||||
return $oInstance;
|
||||
}
|
||||
|
||||
// option
|
||||
public function enableTg(bool $flag = true): self
|
||||
{
|
||||
$this->isToTg = $flag;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// option
|
||||
public function enableTgOne(bool $flag = true): self
|
||||
{
|
||||
$this->enableTg(true);
|
||||
$this->isToTgOne = $flag;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// option
|
||||
public function parent(array $aParentResult): self
|
||||
{
|
||||
$this->aParent = $aParentResult;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// data
|
||||
public function step(string $step): self
|
||||
{
|
||||
$this->aStep[] = "{$this->sName}::{$step}";
|
||||
return $this;
|
||||
}
|
||||
|
||||
// data
|
||||
public function msg($msg = "msg未设置"): self
|
||||
{
|
||||
if (is_array($msg)) {
|
||||
$msg = json_encode($msg);
|
||||
}
|
||||
$this->put('sMsg', $msg);
|
||||
return $this;
|
||||
}
|
||||
|
||||
// data
|
||||
public function aData(array $data): self
|
||||
{
|
||||
$this->put('aData', $data);
|
||||
return $this;
|
||||
}
|
||||
|
||||
// data
|
||||
public function aDataPut($k, $v)
|
||||
{
|
||||
$this->aResult["aData"][$k] = $v;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// data
|
||||
public function sData(string $data): self
|
||||
{
|
||||
$this->put('sData', $data);
|
||||
return $this;
|
||||
}
|
||||
|
||||
// data
|
||||
public function code(string $code): self
|
||||
{
|
||||
$this->put('sCode', $code);
|
||||
return $this;
|
||||
}
|
||||
|
||||
// data
|
||||
public function put(string $key, mixed $value, $sDataKey = ''): self
|
||||
{
|
||||
if ($sDataKey) {
|
||||
$this->aResult[$sDataKey][$key] = $value;
|
||||
} else {
|
||||
$this->aResult[$key] = $value;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// data
|
||||
public function merge(array $data): self
|
||||
{
|
||||
foreach ($data as $k => $v) {
|
||||
$this->put($k, $v);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
// data
|
||||
public function setDone(bool $b = true): self
|
||||
{
|
||||
$this->aResult['isDone'] = $b;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// data
|
||||
public function setFail(string $step = ''): self
|
||||
{
|
||||
if ($step) {
|
||||
$this->step($step);
|
||||
}
|
||||
$this->aResult['isDone'] = false;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// with
|
||||
public function withTraceId(string $id = null): self
|
||||
{
|
||||
$this->aResult['sTraceId'] = $id ?? uniqid('trace_', true);
|
||||
return $this;
|
||||
}
|
||||
|
||||
// with
|
||||
public function withTimestamp(): self
|
||||
{
|
||||
$this->aResult['sTimestamp'] = (new \DateTime())->format(\DateTime::ATOM);
|
||||
return $this;
|
||||
}
|
||||
|
||||
// with
|
||||
public function withStep(bool $flag = true): self
|
||||
{
|
||||
if ($flag) {
|
||||
$this->withParentStep();
|
||||
$this->aResult['aStep'] = $this->aStep;
|
||||
} else {
|
||||
unset($this->aResult['aStep']);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
// action
|
||||
public function reset(): self
|
||||
{
|
||||
$this->aStep = [$this->sName . '::start'];
|
||||
$this->aParent = [];
|
||||
$this->isToTg = false;
|
||||
$this->isToTgOne = false;
|
||||
$this->aResult = ['isDone' => false];
|
||||
return $this;
|
||||
}
|
||||
|
||||
// action
|
||||
public function done(mixed $msg = null)
|
||||
{
|
||||
$this->setDone();
|
||||
if ($msg) {
|
||||
$this->msg($msg);
|
||||
}
|
||||
return $this->get();
|
||||
}
|
||||
|
||||
// action
|
||||
public function fail(mixed $msg = null, array $aParentResult = [])
|
||||
{
|
||||
$this->setFail('fail');
|
||||
if ($aParentResult) {
|
||||
$this->aParent = $aParentResult;
|
||||
}
|
||||
if ($msg) {
|
||||
$this->msg($msg);
|
||||
}
|
||||
return $this->getFull();
|
||||
}
|
||||
|
||||
// action
|
||||
// public function sendTg() // 伪装成非set
|
||||
// {
|
||||
// $this->enableTgOne();
|
||||
// return $this;
|
||||
// }
|
||||
|
||||
// action
|
||||
public function sendTg($sBotCode = 'base', $sChatCode = 'base')
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function getDataA()
|
||||
{
|
||||
return $this->aResult["aData"] ?? [];
|
||||
}
|
||||
|
||||
public function getDataS()
|
||||
{
|
||||
return (string) $this->aResult["sData"] ?? '';
|
||||
}
|
||||
|
||||
public function getDataI()
|
||||
{
|
||||
return (int) $this->aResult["iData"] ?? 0;
|
||||
}
|
||||
|
||||
public function getDataB()
|
||||
{
|
||||
return (bool) $this->aResult["bData"] ?? false;
|
||||
}
|
||||
|
||||
public function getDataX()
|
||||
{
|
||||
return $this->aResult["xData"] ?? '';
|
||||
}
|
||||
|
||||
public function setDataA(array $aData): self
|
||||
{
|
||||
$this->aData($aData);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setDataB(bool $bData): self
|
||||
{
|
||||
$this->aResult["bData"] = $bData;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setDataS(string $sData): self
|
||||
{
|
||||
$this->aResult["sData"] = $sData;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setDataI(int $iData): self
|
||||
{
|
||||
$this->aResult["iData"] = $iData;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setDataX($xData): self
|
||||
{
|
||||
$this->aResult["xData"] = $xData;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getResult(): array
|
||||
{
|
||||
return $this->aResult;
|
||||
}
|
||||
|
||||
// action
|
||||
public function get()
|
||||
{
|
||||
if ($this->isToTg) {
|
||||
$this->_sendTg();
|
||||
}
|
||||
|
||||
if ($this->isToTgOne) {
|
||||
$this->isToTg = false;
|
||||
$this->isToTgOne = false;
|
||||
}
|
||||
|
||||
if ($this->isChain) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
return $this->aResult;
|
||||
}
|
||||
|
||||
// action
|
||||
public function getFull()
|
||||
{
|
||||
$this->withStep();
|
||||
return $this->get();
|
||||
}
|
||||
|
||||
// action
|
||||
public function toArray()
|
||||
{
|
||||
return $this->aResult;
|
||||
}
|
||||
|
||||
// action
|
||||
public function toJson($bFormat = true)
|
||||
{
|
||||
if ($bFormat === true) {
|
||||
return json_encode($this->aResult, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
||||
} else {
|
||||
return json_encode($this->aResult);
|
||||
}
|
||||
}
|
||||
|
||||
// helper
|
||||
public function isDone()
|
||||
{
|
||||
return $this->aResult["isDone"] ?? false;
|
||||
}
|
||||
|
||||
// helper
|
||||
public function isFail()
|
||||
{
|
||||
return !$this->isDone();
|
||||
}
|
||||
|
||||
// helper
|
||||
public static function isDoneResult(array $aResult): bool
|
||||
{
|
||||
return $aResult['isDone'] ?? false;
|
||||
}
|
||||
|
||||
// helper
|
||||
public static function isFailResult(array $aResult): bool
|
||||
{
|
||||
return !self::isDoneResult($aResult);
|
||||
}
|
||||
|
||||
// shell
|
||||
public function fetch()
|
||||
{
|
||||
return $this->get();
|
||||
}
|
||||
|
||||
// shell
|
||||
public function fetchAll()
|
||||
{
|
||||
return $this->getFull();
|
||||
}
|
||||
|
||||
private function _sendTg()
|
||||
{
|
||||
TelegramVdb::make()->dataArr($this->aResult)->save();
|
||||
}
|
||||
|
||||
protected function addStepParent(): void
|
||||
{
|
||||
$this->aStep[] = $this->aParent;
|
||||
}
|
||||
|
||||
protected function withParentStep(): void
|
||||
{
|
||||
if ($this->aParent) {
|
||||
$this->addStepParent();
|
||||
}
|
||||
}
|
||||
}
|
||||
93
app/Services/TomTool/Http.php
Executable file
93
app/Services/TomTool/Http.php
Executable file
@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\TomTool;
|
||||
|
||||
class Http
|
||||
{
|
||||
public $sMethod = 'get';
|
||||
public $aData = [];
|
||||
public $sToUrl; // 必须
|
||||
public $bIsJson = false;
|
||||
|
||||
public function send($sUrl = false): string
|
||||
{
|
||||
// 两种传递方式
|
||||
if ($sUrl) {
|
||||
$this->sToUrl = $sUrl;
|
||||
}
|
||||
|
||||
// 初始化 cURL 会话
|
||||
$ch = curl_init();
|
||||
|
||||
// 设置请求 URL
|
||||
curl_setopt($ch, CURLOPT_URL, $this->sToUrl);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 返回响应而不是输出
|
||||
|
||||
// 根据请求方法设置 cURL 选项
|
||||
switch (strtolower($this->sMethod)) {
|
||||
case 'post':
|
||||
curl_setopt($ch, CURLOPT_POST, true); // 设置为 POST 请求
|
||||
if ($this->bIsJson) {
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($this->aData)); // 设置 POST 数据为 JSON
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Content-Type: application/json', // 设置内容类型为 JSON
|
||||
'Content-Length: ' . strlen(json_encode($this->aData)) // 设置内容长度
|
||||
]);
|
||||
} else {
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($this->aData)); // 设置 POST 数据为普通表单格式
|
||||
}
|
||||
break;
|
||||
|
||||
case 'put':
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT'); // 设置为 PUT 请求
|
||||
if ($this->bIsJson) {
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($this->aData)); // 设置 PUT 数据为 JSON
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Content-Type: application/json', // 设置内容类型为 JSON
|
||||
'Content-Length: ' . strlen(json_encode($this->aData)) // 设置内容长度
|
||||
]);
|
||||
} else {
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($this->aData)); // 设置 PUT 数据为普通表单格式
|
||||
}
|
||||
break;
|
||||
|
||||
case 'get':
|
||||
// 默认使用 GET 请求
|
||||
curl_setopt($ch, CURLOPT_HTTPGET, true); // 设置为 GET 请求
|
||||
curl_setopt($ch, CURLOPT_URL, $this->sToUrl . '?' . http_build_query($this->aData)); // 设置请求 URL
|
||||
if ($this->bIsJson) {
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Accept: application/json' // 设置接受的内容类型为 JSON
|
||||
]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// 禁用 SSL 验证
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 不验证对等证书
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // 不验证主机名
|
||||
|
||||
// 执行 cURL 请求
|
||||
$response = curl_exec($ch);
|
||||
|
||||
// 检查是否有错误
|
||||
if (curl_errno($ch)) {
|
||||
return 'Error: ' . curl_error($ch);
|
||||
}
|
||||
|
||||
// 关闭 cURL 会话
|
||||
curl_close($ch);
|
||||
|
||||
// 返回响应
|
||||
return $response;
|
||||
}
|
||||
|
||||
public static function response($iCode, $sMsg = '')
|
||||
{
|
||||
http_response_code($iCode);
|
||||
echo $sMsg;
|
||||
exit;
|
||||
}
|
||||
}
|
||||
261
app/Services/TomTool/HttpV2.php
Executable file
261
app/Services/TomTool/HttpV2.php
Executable file
@ -0,0 +1,261 @@
|
||||
<?php
|
||||
|
||||
// 版本:25-12-12
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\TomTool;
|
||||
|
||||
use App\Services\TomTool\Flow;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
|
||||
默认:
|
||||
method:post
|
||||
|
||||
new:
|
||||
HttpV2::make($url)
|
||||
|
||||
基本:
|
||||
HttpV2::make($url)->send()
|
||||
|
||||
进阶:
|
||||
HttpV2::make($url, "get")->setDataA($aData)->enableJsonAs()->enableAsync()->optMethod("get")->send();
|
||||
|
||||
action:
|
||||
send()
|
||||
|
||||
data:
|
||||
xData()
|
||||
sData()
|
||||
jData()
|
||||
sData()
|
||||
|
||||
set:
|
||||
optMethod()
|
||||
enableJsonAs()
|
||||
enableJsonSend()
|
||||
enableJsonReturn()
|
||||
|
||||
@note:
|
||||
默认关闭 SSL 验证(适用于开发环境),生产环境建议开启
|
||||
异步模式下不会返回响应内容,仅快速触发请求
|
||||
|
||||
*/
|
||||
|
||||
|
||||
class HttpV2
|
||||
{
|
||||
protected string $method;
|
||||
protected $data = [];
|
||||
protected string $url;
|
||||
protected bool $sendJson = false;
|
||||
protected bool $returnJson = false;
|
||||
protected bool $async = false;
|
||||
// private bool $isChain = false;
|
||||
// private array $aResult = [
|
||||
// "isDone" = false
|
||||
// ];
|
||||
|
||||
public static function make(string $url, string $method = 'post'): self
|
||||
{
|
||||
$instance = new self();
|
||||
$instance->url = $url;
|
||||
$instance->optMethod($method);
|
||||
return $instance;
|
||||
}
|
||||
|
||||
// public static function start(string $sUrl, string $sMethod = 'post'): self
|
||||
// {
|
||||
// $oInstance = new self();
|
||||
// $oInstance->url = $sUrl;
|
||||
// $oInstance->method = $sMethod;
|
||||
// $oInstance->isChain = true;
|
||||
// return $oInstance;
|
||||
// }
|
||||
//
|
||||
// public function isDone()
|
||||
// {
|
||||
// return $this->aResult["isDone"] ?? false;
|
||||
// }
|
||||
//
|
||||
// public function isFail()
|
||||
// {
|
||||
// return !$this->isDone();
|
||||
// }
|
||||
|
||||
// public function getResult()
|
||||
// {
|
||||
// return $this->aResult;
|
||||
// }
|
||||
|
||||
public function optMethod(string $method): self
|
||||
{
|
||||
$this->method = strtolower($method);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setDataX($xData)
|
||||
{
|
||||
$this->setData($xData);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setDataA($aData)
|
||||
{
|
||||
$this->setData($aData);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setDataS($sData)
|
||||
{
|
||||
$this->setData($sData);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setData($xData)
|
||||
{
|
||||
// if (is_array($xData)) {
|
||||
// json_encode($xData)
|
||||
// }
|
||||
$this->data = $xData;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// public function aData($aData = [])
|
||||
// {
|
||||
// $this->data($aData);
|
||||
// return $this;
|
||||
// }
|
||||
//
|
||||
// public function sData($sData = '')
|
||||
// {
|
||||
// $this->data($sData);
|
||||
// return $this;
|
||||
// }
|
||||
//
|
||||
// public function jData($jData = '')
|
||||
// {
|
||||
// $this->data($jData);
|
||||
// return $this;
|
||||
// }
|
||||
//
|
||||
// public function xData($xData = '')
|
||||
// {
|
||||
// $this->data($xData);
|
||||
// return $this;
|
||||
// }
|
||||
//
|
||||
// public function data($data = ''): self
|
||||
// {
|
||||
// $this->data = $data;
|
||||
// return $this;
|
||||
// }
|
||||
|
||||
public function enableJsonSend(bool $flag = true): self
|
||||
{
|
||||
$this->sendJson = $flag;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function enableJsonReturn(bool $flag = true): self
|
||||
{
|
||||
$this->returnJson = $flag;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function enableJsonAs(): self
|
||||
{
|
||||
$this->sendJson = true;
|
||||
$this->returnJson = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function enableAsync(bool $flag = true): self
|
||||
{
|
||||
$this->async = $flag;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function send(): string
|
||||
{
|
||||
$oFlow = Flow::make("HttpV2::send");
|
||||
|
||||
$ch = curl_init();
|
||||
$headers = [];
|
||||
|
||||
// 构建 URL 和请求体
|
||||
if ($this->method === 'get') {
|
||||
$url = $this->url . '?' . http_build_query($this->data);
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_HTTPGET, true);
|
||||
} else {
|
||||
curl_setopt($ch, CURLOPT_URL, $this->url);
|
||||
$payload = $this->sendJson ? json_encode($this->data) : http_build_query($this->data);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
|
||||
|
||||
if ($this->method === 'post') {
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
} else {
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, strtoupper($this->method));
|
||||
}
|
||||
|
||||
if ($this->sendJson) {
|
||||
$headers[] = 'Content-Type: application/json';
|
||||
$headers[] = 'Content-Length: ' . strlen($payload);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->returnJson) {
|
||||
$headers[] = 'Accept: application/json';
|
||||
}
|
||||
|
||||
if (!empty($headers)) {
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
}
|
||||
|
||||
// SSL 设置
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||||
|
||||
// 异步发送
|
||||
if ($this->async) {
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT_MS, 200);
|
||||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, 200);
|
||||
curl_setopt($ch, CURLOPT_NOSIGNAL, true);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
|
||||
curl_exec($ch);
|
||||
curl_close($ch);
|
||||
return 'sent';
|
||||
}
|
||||
|
||||
// 阻塞执行
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
$response = curl_exec($ch);
|
||||
|
||||
if (curl_errno($ch)) {
|
||||
$error = curl_error($ch);
|
||||
curl_close($ch);
|
||||
throw new Exception("cURL Error: {$error}");
|
||||
}
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
return $response;
|
||||
// $this->aResult = $oFlow->sData($response)->done();
|
||||
//
|
||||
// if ($this->isChain) {
|
||||
// return $this;
|
||||
// }
|
||||
//
|
||||
// return $this->aResult;
|
||||
}
|
||||
|
||||
// public static function response(int $code, string $message = ''): void
|
||||
// {
|
||||
// http_response_code($code);
|
||||
// echo $message;
|
||||
// exit;
|
||||
// }
|
||||
}
|
||||
26
app/Services/TomTool/Map.php
Executable file
26
app/Services/TomTool/Map.php
Executable file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\TomTool;
|
||||
|
||||
class Map
|
||||
{
|
||||
public static function fnClientToSfx($sClient)
|
||||
{
|
||||
$sfx = "";
|
||||
|
||||
switch ($sClient) {
|
||||
case "clash":
|
||||
$sfx = "yaml";
|
||||
break;
|
||||
case "v2ray":
|
||||
$sfx = "txt";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return $sfx;
|
||||
}
|
||||
}
|
||||
177
app/Services/TomTool/Telegram/Master.php
Executable file
177
app/Services/TomTool/Telegram/Master.php
Executable file
@ -0,0 +1,177 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\TomTool\Telegram;
|
||||
|
||||
use App\Services\HttpQueue as ServiceHttpQueue;
|
||||
use App\Services\TomTool\Flow;
|
||||
use App\Services\TomTool\Telegram\Sdk as ServiceTelegramSdk;
|
||||
use App\Models\TelegramKey as ModelTelegramKey;
|
||||
|
||||
/**
|
||||
|
||||
注意是master服务器端专用的,这里会对code找map,slave不会。
|
||||
|
||||
new:
|
||||
start(?$sBodeCode, ?$sChatCode)
|
||||
|
||||
set:
|
||||
setBotCode()
|
||||
setChatCode()
|
||||
setBotToken()
|
||||
setChatId()
|
||||
setMsgS()
|
||||
|
||||
action:
|
||||
send()
|
||||
|
||||
*/
|
||||
|
||||
final class Master
|
||||
{
|
||||
public string $sBotCode = "base";
|
||||
public string $sChatCode = "base";
|
||||
public string $sBotToken = '';
|
||||
public int $iChatId = 0;
|
||||
|
||||
public string $sMsg = '';
|
||||
public string $sPhotoUrl = '';
|
||||
public int $iPhotoWidth = 0;
|
||||
public int $iPhotoHeight = 0;
|
||||
|
||||
public string $sFileUrl = '';
|
||||
public string $sFileName = '';
|
||||
|
||||
public string $sType = '';
|
||||
|
||||
public bool $enableQueue = false; // 应该用不到了
|
||||
|
||||
public bool $bKeyBegin = false;
|
||||
|
||||
public static function start($sBotCode = 'base', $sChatCode = 'base'): self
|
||||
{
|
||||
$oInstance = new self();
|
||||
$oInstance->sBotCode = $sBotCode;
|
||||
$oInstance->sChatCode = $sChatCode;
|
||||
return $oInstance;
|
||||
}
|
||||
|
||||
public function setBotCode(string $sBotCode): self
|
||||
{
|
||||
$this->sBotCode = $sBotCode;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setChatCode(string $sChatCode): self
|
||||
{
|
||||
$this->sChatCode = $sChatCode;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setBotToken(string $sBotToken): self
|
||||
{
|
||||
$this->sBotToken = $sBotToken;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setChatId(int $iChatId): self
|
||||
{
|
||||
$this->iChatId = $iChatId;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setMsgS(string $sMsg): self
|
||||
{
|
||||
$this->sMsg = $sMsg;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setPhotoUrl(string $sPhotoUrl, int $iPhotoWidth = 0, int $iPhotoHeight = 0): self
|
||||
{
|
||||
$this->sPhotoUrl = $sPhotoUrl;
|
||||
$this->iPhotoWidth = $iPhotoWidth;
|
||||
$this->iPhotoHeight = $iPhotoHeight;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setType(string $sType): self
|
||||
{
|
||||
$this->sType = $sType;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setFileUrl(string $sFileUrl = '', string $sFileName = ''): self
|
||||
{
|
||||
$this->sFileUrl = $sFileUrl;
|
||||
$this->sFileName = $sFileName;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function send($sMsg = ''): Flow
|
||||
{
|
||||
if ($sMsg) {
|
||||
$this->sMsg = $sMsg;
|
||||
}
|
||||
|
||||
if (!$this->sMsg) {
|
||||
// throw new \Exception("未设置msg");
|
||||
}
|
||||
|
||||
$this->keyBegin();
|
||||
|
||||
if ($this->enableQueue) {
|
||||
$aData = $this->buildDataTom();
|
||||
$oFlow = ServiceHttpQueue::start("telegram_local")->arg($aData)->save();
|
||||
} else {
|
||||
$oFlow = ServiceTelegramSdk::start()
|
||||
->setBotToken($this->sBotToken)
|
||||
->setChatId($this->iChatId)
|
||||
->setMsgS($this->sMsg)
|
||||
->setPhotoUrl($this->sPhotoUrl, $this->iPhotoWidth, $this->iPhotoHeight)
|
||||
->setFileUrl($this->sFileUrl, $this->sFileName)
|
||||
->setType($this->sType)
|
||||
->send();
|
||||
}
|
||||
|
||||
return $oFlow;
|
||||
}
|
||||
|
||||
private function keyBegin()
|
||||
{
|
||||
if (!$this->bKeyBegin) {
|
||||
if (!$this->sBotToken) {
|
||||
$this->sBotToken = ModelTelegramKey::botTokenByCode($this->sBotCode);
|
||||
}
|
||||
|
||||
if (!$this->iChatId) {
|
||||
$this->iChatId = ModelTelegramKey::chatIdByCode($this->sChatCode);
|
||||
}
|
||||
|
||||
if (!$this->sBotToken || !$this->iChatId) {
|
||||
throw new \RuntimeException("botToken或chatId没找到,botCode={$this->sBotCode},chatCode={$this->sChatCode}");
|
||||
}
|
||||
|
||||
$this->bKeyBegin = true;
|
||||
}
|
||||
}
|
||||
|
||||
private function buildDataTom(): array
|
||||
{
|
||||
return [
|
||||
"sBotCode" => $this->sBotCode ?? '',
|
||||
"sChatCode" => $this->sChatCode ?? '',
|
||||
"sBotToken" => $this->sBotToken ?? '',
|
||||
"iChatId" => $this->iChatId ?? '',
|
||||
"sPhotoUrl" => $this->sPhotoUrl ?? '',
|
||||
"iPhotoWidth" => $this->iPhotoWidth,
|
||||
"iPhotoHeight" => $this->iPhotoHeight,
|
||||
"sFileUrl" => $this->sFileUrl,
|
||||
"sFileName" => $this->sFileName,
|
||||
"sMsg" => $this->sMsg ?? '',
|
||||
"sType" => $this->sType ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
215
app/Services/TomTool/Telegram/Sdk.php
Executable file
215
app/Services/TomTool/Telegram/Sdk.php
Executable file
@ -0,0 +1,215 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\TomTool\Telegram;
|
||||
use Telegram\Bot\Api;
|
||||
use Telegram\Bot\FileUpload\InputFile;
|
||||
use Telegram\Bot\Exceptions\TelegramSDKException;
|
||||
use App\Services\TomTool\Flow;
|
||||
//use Intervention\Image\Laravel\Facades\Image;
|
||||
use Intervention\Image\ImageManager;
|
||||
use Intervention\Image\Drivers\Gd\Driver;
|
||||
use App\Services\TomTool\HttpV2;
|
||||
|
||||
class Sdk
|
||||
{
|
||||
public $sMsgFormat = [];
|
||||
public $sMsgFormatType = 'none';
|
||||
public $sBotToken = '';
|
||||
public $iChatId = 0;
|
||||
public $sPhotoUrl = '';
|
||||
public $iPhotoWidth = 0;
|
||||
public $iPhotoHeight = 0;
|
||||
public $sFileUrl = '';
|
||||
public $sFileName = '';
|
||||
public $sMsg;
|
||||
public $oApi;
|
||||
|
||||
private const ALLOWED_FORMATS = ['none', 'html', 'markdown', 'markdownV2'];
|
||||
|
||||
public static function start()
|
||||
{
|
||||
$oInstance = new self();
|
||||
return $oInstance;
|
||||
}
|
||||
|
||||
public function setBotToken($sBotToken)
|
||||
{
|
||||
$this->oApi = new Api($sBotToken);
|
||||
$this->sBotToken = $sBotToken;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setChatId($iChatId)
|
||||
{
|
||||
$this->iChatId = $iChatId;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setMsgS($sMsg = '')
|
||||
{
|
||||
$this->sMsg = $sMsg;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setPhotoUrl($sUrl = '', $iPhotoWidth = 0, $iPhotoHeight = 0)
|
||||
{
|
||||
$this->sPhotoUrl = $sUrl;
|
||||
$this->iPhotoWidth = $iPhotoWidth;
|
||||
$this->iPhotoHeight = $iPhotoHeight;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setFileUrl($sFileUrl = '', $sFileName = '')
|
||||
{
|
||||
$this->sFileUrl = $sFileUrl;
|
||||
$this->sFileName = $sFileName;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setType($sType)
|
||||
{
|
||||
if ($sType) {
|
||||
$this->sMsgFormatType = $sType;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function send()
|
||||
{
|
||||
$oFlow = Flow::start("tg_sdk_send");
|
||||
|
||||
$this->msgFormat();
|
||||
|
||||
// try {
|
||||
|
||||
if ($this->sPhotoUrl) {
|
||||
$r = $this->oApi->sendPhoto($this->sMsgFormat);
|
||||
} else if ($this->sFileUrl) {
|
||||
$r = $this->oApi->sendDocument($this->sMsgFormat);
|
||||
} else {
|
||||
$r = $this->oApi->sendMessage($this->sMsgFormat);
|
||||
}
|
||||
|
||||
if (!($r && $r->getMessageId())) {
|
||||
return $oFlow->fail($r);
|
||||
}
|
||||
// } catch (TelegramSDKException $e) {
|
||||
// return $oFlow->fail(['error' => $e->getMessage()]);
|
||||
// }
|
||||
|
||||
return $oFlow->done();
|
||||
}
|
||||
|
||||
public function getMsgFormat()
|
||||
{
|
||||
$this->msgFormat();
|
||||
|
||||
return $this->sMsgFormat;
|
||||
}
|
||||
|
||||
public function msgFormat()
|
||||
{
|
||||
if (!in_array($this->sMsgFormatType, self::ALLOWED_FORMATS, true)) {
|
||||
throw new \RuntimeException("不支持的格式类型: {$this->sMsgFormatType}");
|
||||
}
|
||||
|
||||
$method = 'msgFormat_' . $this->sMsgFormatType;
|
||||
$this->sMsgFormat = $this->$method();
|
||||
|
||||
if ($this->sPhotoUrl) {
|
||||
|
||||
$oManager = new ImageManager(new Driver());
|
||||
|
||||
$sTempPath = $_ENV['dir_base'].'public/upload';
|
||||
$sTempPath .= "/tg_output.png";
|
||||
|
||||
$sImg = file_get_contents($this->sPhotoUrl);
|
||||
$oImage = $oManager->read($sImg);
|
||||
|
||||
if ($this->iPhotoWidth && $this->iPhotoHeight) {
|
||||
$oImage = $oImage->cover($this->iPhotoWidth, $this->iPhotoHeight);
|
||||
}
|
||||
|
||||
$oImage->toJpg(80)->save($sTempPath);
|
||||
|
||||
$photo = InputFile::create($sTempPath, date("Y-m-d").'.jpg');
|
||||
|
||||
$this->sMsgFormat["photo"] = $photo;
|
||||
|
||||
$this->sMsgFormat["caption"] = $this->sMsgFormat["text"];
|
||||
unset($this->sMsgFormat["text"]);
|
||||
|
||||
} else if ($this->sFileUrl) {
|
||||
|
||||
if (!$this->sFileName) {
|
||||
$sFileName = date("Y-m-d H:i:s").'.txt';
|
||||
} else {
|
||||
$sFileName = $this->sFileName;
|
||||
}
|
||||
|
||||
$sFile = InputFile::create($this->sFileUrl, $sFileName);
|
||||
|
||||
$this->sMsgFormat["document"] = $sFile;
|
||||
$this->sMsgFormat["caption"] = $this->sMsgFormat["text"];
|
||||
unset($this->sMsgFormat["text"]);
|
||||
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function msgFormat_none()
|
||||
{
|
||||
return [
|
||||
'chat_id' => $this->iChatId,
|
||||
'text' => $this->sMsg,
|
||||
'parse_mode' => '',
|
||||
'disable_web_page_preview' => false,
|
||||
'reply_to_message_id' => null,
|
||||
'reply_markup' => null,
|
||||
];
|
||||
}
|
||||
|
||||
private function msgFormat_html()
|
||||
{
|
||||
return [
|
||||
'chat_id' => $this->iChatId,
|
||||
'text' => strip_tags($this->sMsg, [
|
||||
'b', 'strong', 'i', 'em', 'u', 'ins', 's', 'strike', 'del', 'span', 'tg-spoiler', 'a', 'tg-emoji',
|
||||
'code', 'pre',
|
||||
]),
|
||||
'parse_mode' => 'HTML',
|
||||
'disable_web_page_preview' => false,
|
||||
'reply_to_message_id' => null,
|
||||
'reply_markup' => null,
|
||||
];
|
||||
}
|
||||
|
||||
private function msgFormat_markdown()
|
||||
{
|
||||
return [
|
||||
'chat_id' => $this->iChatId,
|
||||
'text' => $this->sMsg,
|
||||
'parse_mode' => 'Markdown',
|
||||
'disable_web_page_preview' => false,
|
||||
'reply_to_message_id' => null,
|
||||
'reply_markup' => null,
|
||||
];
|
||||
}
|
||||
|
||||
private function msgFormat_markdownV2()
|
||||
{
|
||||
return [
|
||||
'chat_id' => $this->iChatId,
|
||||
'text' => $this->sMsg,
|
||||
'parse_mode' => 'MarkdownV2',
|
||||
'disable_web_page_preview' => false,
|
||||
'reply_to_message_id' => null,
|
||||
'reply_markup' => null,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
361
app/Services/TomTool/Telegram/Slave.php
Executable file
361
app/Services/TomTool/Telegram/Slave.php
Executable file
@ -0,0 +1,361 @@
|
||||
<?php
|
||||
|
||||
// 版本 25/12/12
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\TomTool\Telegram;
|
||||
|
||||
use App\Services\TomTool\HttpV2;
|
||||
use App\Services\TomTool\Telegram\Sdk as ServiceTelegramSdk;
|
||||
use App\Services\TomTool\Flow;
|
||||
use App\Services\HttpQueue as ServiceHttpQueue;
|
||||
|
||||
/**
|
||||
|
||||
提醒:
|
||||
master自身已经没有队列了,收到slave消息直接转发
|
||||
|
||||
默认:
|
||||
走本地队列,发送到master
|
||||
走异步
|
||||
|
||||
new:
|
||||
start()
|
||||
log()
|
||||
warn()
|
||||
fail()
|
||||
notify()
|
||||
|
||||
opt:
|
||||
enableMaster(:true)
|
||||
enableMasterQueue(:true)
|
||||
enableSlaveQueue(:true)
|
||||
enableDetail(:true)
|
||||
|
||||
set:
|
||||
setMsgS()
|
||||
setMsgA()
|
||||
setBotCode()
|
||||
setChatCode()
|
||||
setBotToken()
|
||||
setChatId()
|
||||
|
||||
action:
|
||||
send()
|
||||
sendMaster()
|
||||
sendLocal()
|
||||
|
||||
*/
|
||||
|
||||
class Slave
|
||||
{
|
||||
public string $sBotCode;
|
||||
public string $sChatCode;
|
||||
|
||||
public string $sBotToken = '';
|
||||
public int $iChatId = 0;
|
||||
|
||||
public string $sMsg = '';
|
||||
public string $sPhotoUrl = '';
|
||||
public int $iPhotoWidth = 0;
|
||||
public int $iPhotoHeight = 0;
|
||||
|
||||
public string $sFileUrl = '';
|
||||
public string $sFileName = '';
|
||||
|
||||
public string $sType = '';
|
||||
|
||||
public bool $enableMaster = true;
|
||||
public bool $enableMasterQueue = false; // 这里之前想错了,不会有master队列,只在slave有,master转发就行了
|
||||
public bool $enableSlaveQueue = false;
|
||||
|
||||
public bool $enableDetail = true;
|
||||
|
||||
public bool $enableAsync = true;
|
||||
|
||||
private bool $enableSend = true;
|
||||
|
||||
// new
|
||||
public static function start(string $sBotCode = 'base', string $sChatCode = 'base'): self
|
||||
{
|
||||
$oInstance = new self;
|
||||
$oInstance->sBotCode = $sBotCode;
|
||||
$oInstance->sChatCode = $sChatCode;
|
||||
$oInstance->enableSend = filter_var($_ENV["tg_send_enable"] ?? true, FILTER_VALIDATE_BOOLEAN);
|
||||
return $oInstance;
|
||||
}
|
||||
|
||||
// new
|
||||
public static function log(string $sChatCode = 'admin'): self
|
||||
{
|
||||
return self::start("log", $sChatCode);
|
||||
}
|
||||
|
||||
// new
|
||||
public static function warn(string $sChatCode = 'admin'): self
|
||||
{
|
||||
return self::start("warn", $sChatCode);
|
||||
}
|
||||
|
||||
// new
|
||||
public static function fail(string $sChatCode = 'admin'): self
|
||||
{
|
||||
return self::start("fail", $sChatCode);
|
||||
}
|
||||
|
||||
// new
|
||||
public static function notify(string $sChatCode = 'admin'): self
|
||||
{
|
||||
return self::start("fail", $sChatCode);
|
||||
}
|
||||
|
||||
|
||||
// opt
|
||||
public function enableAsync(bool $b = true): self
|
||||
{
|
||||
$this->enableAsync = $b;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// opt
|
||||
public function enableDetail(bool $b = true): self
|
||||
{
|
||||
$this->enableDetail = $b;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// opt
|
||||
public function enableSlaveQueue(bool $b = true): self
|
||||
{
|
||||
$this->enableSlaveQueue = $b;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// opt
|
||||
public function enableMaster(bool $b = true): self
|
||||
{
|
||||
$this->enableMaster = $b;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// opt
|
||||
public function enableMasterQueue(bool $b = true): self
|
||||
{
|
||||
$this->enableMasterQueue = $b;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// set
|
||||
public function setPhotoUrl(string $sPhotoUrl, int $iWidth = 0, int $iHeight = 0): self
|
||||
{
|
||||
$this->sPhotoUrl = $sPhotoUrl;
|
||||
$this->iPhotoWidth = $iWidth;
|
||||
$this->iPhotoHeight = $iHeight;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// set
|
||||
public function setFileUrl(string $sFileUrl, string $sFileName = ''): self
|
||||
{
|
||||
$this->sFileUrl = $sFileUrl;
|
||||
$this->sFileName = $sFileName;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// set
|
||||
public function setType(string $sType): self
|
||||
{
|
||||
$this->sType = $sType;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// set
|
||||
public function setMsgS(string $sMsg = ''): self
|
||||
{
|
||||
if ($this->enableDetail) {
|
||||
$sMsg = "[ ".$this->getEnvSiteCode()." | ".date("d:H:i:s")." ] ".$sMsg;
|
||||
}
|
||||
|
||||
$this->sMsg = $sMsg;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// set
|
||||
public function setMsgA(array $aMsg = []): self
|
||||
{
|
||||
$aMsgNew = $aMsg;
|
||||
|
||||
if ($this->enableDetail) {
|
||||
$aMsgNew = [];
|
||||
$aMsgNew["site_code"] = $this->getEnvSiteCode();
|
||||
$aMsgNew["date"] = date("Y-m-d H:i:s");
|
||||
$aMsgNew["data"] = $aMsg;
|
||||
}
|
||||
|
||||
$this->sMsg = json_encode($aMsgNew, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// set
|
||||
public function setBotCode(string $sBotCode): self
|
||||
{
|
||||
$this->sBotCode = $sBotCode;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// set
|
||||
public function setChatCode(string $sChatCode): self
|
||||
{
|
||||
$this->sChatCode = $sChatCode;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// set
|
||||
public function setBotToken(string $sBotToken): self
|
||||
{
|
||||
$this->sBotToken = $sBotToken;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// set
|
||||
public function setChatId(int $iChatId): self
|
||||
{
|
||||
$this->iChatId = $iChatId;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// action
|
||||
public function send($xMsg = ''): Flow
|
||||
{
|
||||
if ($this->enableSend == false) {
|
||||
$oFlow = Flow::start();
|
||||
return $oFlow->done("不发送");
|
||||
}
|
||||
|
||||
if ($xMsg) {
|
||||
if (is_array($xMsg)) {
|
||||
$this->setMsgA($xMsg);
|
||||
} else {
|
||||
$this->setMsgS($xMsg);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->sMsg) {
|
||||
// throw new \Exception("未设置msg");
|
||||
}
|
||||
|
||||
if ($this->enableMaster) {
|
||||
$oFlow = $this->_send_master();
|
||||
} else {
|
||||
$oFlow = $this->_send_local();
|
||||
}
|
||||
|
||||
return $oFlow;
|
||||
}
|
||||
|
||||
// action
|
||||
public function sendMaster($xMsg = ''): Flow
|
||||
{
|
||||
$this->enableMaster(true);
|
||||
return $this->send($xMsg);
|
||||
}
|
||||
|
||||
// action
|
||||
public function sendLocal($xMsg = ''): Flow
|
||||
{
|
||||
$this->enableMaster(false);
|
||||
return $this->send($xMsg);
|
||||
}
|
||||
|
||||
private function _send_master(): Flow
|
||||
{
|
||||
$aData = $this->buildDataTom();
|
||||
|
||||
if ($this->enableSlaveQueue) {
|
||||
$oFlow = ServiceHttpQueue::start("telegram_master")->arg($aData)->save();
|
||||
} else {
|
||||
|
||||
$oFlow = Flow::start("_send_master");
|
||||
$sUrlMaSterBase = $_ENV["url_master_base"] ?? "";
|
||||
|
||||
if (!$sUrlMaSterBase) {
|
||||
throw new \Exception("未设置env的url_master_base");
|
||||
}
|
||||
|
||||
$sUrlMasterPost = "https://".$sUrlMaSterBase."/api/telegram/post";
|
||||
|
||||
try {
|
||||
|
||||
$oHttpV2 = HttpV2::make($sUrlMasterPost);
|
||||
|
||||
if ($this->enableAsync) {
|
||||
$oHttpV2 = $oHttpV2->enableAsync();
|
||||
}
|
||||
|
||||
$r = $oHttpV2->enableJsonAs()->setData($aData)->send();
|
||||
|
||||
if ($this->enableAsync) {
|
||||
return $oFlow->sData("async")->done("async");
|
||||
} else {
|
||||
$a = json_decode($r, true);
|
||||
$isDone = $a["isDone"] ?? false;
|
||||
|
||||
if ($isDone) {
|
||||
return $oFlow->done();
|
||||
} else {
|
||||
return $oFlow->fail($r);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
$oFlow = $oFlow->fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return $oFlow;
|
||||
}
|
||||
|
||||
// 直接从本地发到tg,几乎用不上,都走master了
|
||||
private function _send_local(): Flow
|
||||
{
|
||||
if ($this->enableSlaveQueue) {
|
||||
$aData = $this->buildDataTom();
|
||||
$oFlow = ServiceHttpQueue::start("telegram_local")->arg($aData)->save();
|
||||
} else {
|
||||
$oFlow = ServiceTelegramSdk::start($this->sBotToken, $this->iChatId)->setMsgS($this->sMsg)->send();
|
||||
}
|
||||
|
||||
return $oFlow;
|
||||
}
|
||||
|
||||
private function buildDataTom(): array
|
||||
{
|
||||
return [
|
||||
"sBotCode" => $this->sBotCode ?? '',
|
||||
"sChatCode" => $this->sChatCode ?? '',
|
||||
"sBotToken" => $this->sBotToken ?? '',
|
||||
"iChatId" => $this->iChatId ?? '',
|
||||
"enableMasterQueue" => $this->enableMasterQueue ?? '',
|
||||
"enableMaster" => $this->enableMaster,
|
||||
"sMsg" => $this->sMsg ?? '',
|
||||
"sPhotoUrl" => $this->sPhotoUrl ?? '',
|
||||
"iPhotoWidth" => $this->iPhotoWidth ?? 0,
|
||||
"iPhotoHeight" => $this->iPhotoHeight ?? 0,
|
||||
"sType" => $this->sType ?? '',
|
||||
"sFileUrl" => $this->sFileUrl ?? '',
|
||||
"sFileName" => $this->sFileName ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
private function getEnvSiteCode(): string
|
||||
{
|
||||
$sEnvSiteCode = $_ENV["site_code"] ?? "未设置env的site_code";
|
||||
|
||||
return $sEnvSiteCode;
|
||||
}
|
||||
|
||||
}
|
||||
121
app/Services/TomTool/TelegramV2.php
Executable file
121
app/Services/TomTool/TelegramV2.php
Executable file
@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\IM;
|
||||
|
||||
use App\Models\Config;
|
||||
use Telegram\Bot\Api;
|
||||
use Telegram\Bot\Exceptions\TelegramSDKException;
|
||||
use function strip_tags;
|
||||
|
||||
// 没有必须,默认使用base配置
|
||||
final class TelegramV2
|
||||
{
|
||||
private Api $bot;
|
||||
public $iTo;
|
||||
public $sToken;
|
||||
public $sMsgType = false;
|
||||
|
||||
public function __construct($sToken = "base", $sTo = "base")
|
||||
{
|
||||
$this->setToken($sToken);
|
||||
|
||||
$this->setTo($sTo);
|
||||
|
||||
// $this->bot = new Api($this->sToken);
|
||||
}
|
||||
|
||||
public function setMsgType($sMsgType)
|
||||
{
|
||||
$this->sMsgType = $sMsgType;
|
||||
}
|
||||
|
||||
public function setTo($xTo, $sType = "code")
|
||||
{
|
||||
$iChatId = $xTo;
|
||||
|
||||
if ($sType == "code") {
|
||||
// $sEnvKey = "tgChat_".$xTo;
|
||||
$iChatId = $_ENV['tgChat'][$iChatId];
|
||||
}
|
||||
|
||||
$this->iTo = $iChatId;
|
||||
}
|
||||
|
||||
public function setToken($xToken, $sType = "code")
|
||||
{
|
||||
$sToken = $xToken;
|
||||
|
||||
if ($sType == "code") {
|
||||
// $sEnvKey = "tgToken_".$xToken;
|
||||
$sToken = $_ENV['tgToken'][$sToken];
|
||||
}
|
||||
|
||||
$this->sToken = $sToken;
|
||||
|
||||
$this->bot = new Api($this->sToken);
|
||||
}
|
||||
|
||||
public function send($sMsg, $sMsgType = 'str')
|
||||
{
|
||||
if ($sMsgType == "arr") {
|
||||
$sMsg = json_encode($sMsg, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
if ($this->sMsgType === false) {
|
||||
$sendMessage = [
|
||||
'chat_id' => $this->iTo,
|
||||
'text' => $sMsg,
|
||||
'parse_mode' => '',
|
||||
'disable_web_page_preview' => false,
|
||||
'reply_to_message_id' => null,
|
||||
'reply_markup' => null,
|
||||
];
|
||||
} else if ($this->sMsgType === "html") {
|
||||
$sendMessage = [
|
||||
'chat_id' => $this->iTo,
|
||||
'text' => strip_tags(
|
||||
$sMsg,
|
||||
['b', 'strong', 'i', 'em', 'u', 'ins', 's', 'strike','del', 'span','tg-spoiler', 'a', 'tg-emoji',
|
||||
'code', 'pre',
|
||||
]
|
||||
),
|
||||
'parse_mode' => 'HTML',
|
||||
'disable_web_page_preview' => false,
|
||||
'reply_to_message_id' => null,
|
||||
'reply_markup' => null,
|
||||
];
|
||||
} else if ($this->sMsgType === "markdown") {
|
||||
$sendMessage = [
|
||||
'chat_id' => $this->iTo,
|
||||
'text' => $sMsg,
|
||||
'parse_mode' => 'Markdown',
|
||||
'disable_web_page_preview' => false,
|
||||
'reply_to_message_id' => null,
|
||||
'reply_markup' => null,
|
||||
];
|
||||
} else if ($this->sMsgType === "markdownV2") {
|
||||
$sendMessage = [
|
||||
'chat_id' => $this->iTo,
|
||||
'text' => $sMsg,
|
||||
'parse_mode' => 'MarkdownV2',
|
||||
'disable_web_page_preview' => false,
|
||||
'reply_to_message_id' => null,
|
||||
'reply_markup' => null,
|
||||
];
|
||||
} else {
|
||||
$sendMessage = [
|
||||
'chat_id' => $this->iTo,
|
||||
'text' => $sMsg,
|
||||
'parse_mode' => '',
|
||||
'disable_web_page_preview' => false,
|
||||
'reply_to_message_id' => null,
|
||||
'reply_markup' => null,
|
||||
];
|
||||
}
|
||||
|
||||
return $this->bot->sendMessage($sendMessage);
|
||||
}
|
||||
|
||||
}
|
||||
362
app/Services/TomTool/TelegramVdb_rengle.php
Executable file
362
app/Services/TomTool/TelegramVdb_rengle.php
Executable file
@ -0,0 +1,362 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\TomTool;
|
||||
|
||||
use App\Services\HttpQueue as ServiceHttpQueue;
|
||||
use App\Services\TomTool\Flow;
|
||||
use App\Models\TelegramKey as ModelTelegramKey;
|
||||
use Telegram\Bot\Api;
|
||||
use Telegram\Bot\Exceptions\TelegramSDKException;
|
||||
use function strip_tags;
|
||||
|
||||
/**
|
||||
|
||||
更新:
|
||||
data会save清空,不再属于set
|
||||
|
||||
new:
|
||||
TelegramVdb::make(?$botCode, ?$chatCode)
|
||||
|
||||
快速:
|
||||
TelegramVdb::make()->save($sData)
|
||||
|
||||
进阶:
|
||||
TelegramVdb::make()->dataArr($aData)->setFormat("html")->save();
|
||||
TelegramVdb::make()->setFormat()->data($xData)->toJson()->save();
|
||||
|
||||
data:
|
||||
data[Arr/Str/Json]($data)
|
||||
data($aData)->toJson()
|
||||
|
||||
action:
|
||||
save()
|
||||
|
||||
set:
|
||||
setChatCode($code)
|
||||
setBotCode($code)
|
||||
setChatId($id) 覆盖code
|
||||
setBotToken($token) 覆盖code
|
||||
setFormat($type)
|
||||
enableDate()
|
||||
|
||||
*/
|
||||
|
||||
final class TelegramVdb
|
||||
{
|
||||
public $sBotCode;
|
||||
public $sChatCode;
|
||||
public $sBotToken = null;
|
||||
public $iChatId = null;
|
||||
public $sMsg = '';
|
||||
public $sMsgFormat = '';
|
||||
public $sMsgFormatType = 'none';
|
||||
public $bEnableDate = false;
|
||||
private $bKeyBegin = false;
|
||||
public $sClient = "master";
|
||||
|
||||
private const ALLOWED_FORMATS = ['none', 'html', 'markdown', 'markdownV2'];
|
||||
|
||||
public static function log()
|
||||
{
|
||||
return self::make("log", "admin");
|
||||
}
|
||||
|
||||
public static function warn()
|
||||
{
|
||||
return self::make("warn", "admin");
|
||||
}
|
||||
|
||||
public static function fail()
|
||||
{
|
||||
return self::make("fail", "admin");
|
||||
}
|
||||
|
||||
public static function notify()
|
||||
{
|
||||
return self::make("notify", "admin");
|
||||
}
|
||||
|
||||
public static function make($sBotCode = "base", $sChatCode = "base")
|
||||
{
|
||||
$oInstance = new self();
|
||||
$oInstance->sBotCode = $sBotCode;
|
||||
$oInstance->sChatCode = $sChatCode;
|
||||
return $oInstance;
|
||||
}
|
||||
|
||||
public function send($sMsg = '')
|
||||
{
|
||||
return $this->_ok($sMsg, true);
|
||||
}
|
||||
|
||||
public function save($sMsg = '')
|
||||
{
|
||||
return $this->_ok($sMsg);
|
||||
}
|
||||
|
||||
private function sendNow()
|
||||
{
|
||||
$oResult = Flow::make("send");
|
||||
$oBot = new Api($this->sBotToken);
|
||||
|
||||
$r = $oBot->sendMessage($this->sMsgFormat);
|
||||
|
||||
if ($r->isOk()){
|
||||
return $oResult->done();
|
||||
} else {
|
||||
return $oResult->fail("send失败");
|
||||
}
|
||||
}
|
||||
|
||||
private function HttpQueueSave()
|
||||
{
|
||||
$aTelegramArg = [
|
||||
"bot_token" => $this->sBotToken,
|
||||
"chat_id" => $this->iChatId,
|
||||
"msg" => $this->sMsgFormat,
|
||||
];
|
||||
|
||||
$aReturn = ServiceHttpQueue::start("telegram")->arg($aTelegramArg)->save()->getResult();
|
||||
|
||||
return $aReturn;
|
||||
}
|
||||
|
||||
public function _ok($sMsg = '', $bSendNow = false)
|
||||
{
|
||||
$bTgSendEnable = $_ENV["tg_send_enable"] ?? false;
|
||||
if (!$bTgSendEnable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$oResult = Flow::make("save");
|
||||
|
||||
$this->keyBegin();
|
||||
|
||||
if ($sMsg) {
|
||||
if (is_array($sMsg)) {
|
||||
$this->aData($sMsg);
|
||||
} else {
|
||||
$this->sData($sMsg);
|
||||
}
|
||||
}
|
||||
|
||||
$this->msgFormat();
|
||||
|
||||
if ($bSendNow) {
|
||||
$aReturn = $this->sendNow();
|
||||
} else {
|
||||
$aReturn = $this->HttpQueueSave();
|
||||
}
|
||||
|
||||
$this->sMsg = '';
|
||||
|
||||
return $aReturn;
|
||||
}
|
||||
|
||||
public function enableDate($b = true)
|
||||
{
|
||||
$this->bEnableDate = $b;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function data($xData)
|
||||
{
|
||||
$this->sMsg = $xData;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function jData($sMsg = '')
|
||||
{
|
||||
$this->dataJson($sMsg);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function aData($sMsg = [])
|
||||
{
|
||||
$this->dataArr($sMsg);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function sData($sMsg = '')
|
||||
{
|
||||
$this->dataStr($sMsg);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function toJson()
|
||||
{
|
||||
$this->withDateArr();
|
||||
$this->sMsg = json_encode($this->sMsg, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function dataStr($sMsg = '')
|
||||
{
|
||||
$this->withDateStr();
|
||||
$this->sMsg = $sMsg;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function dataArr($sMsg = [])
|
||||
{
|
||||
$this->withDateArr();
|
||||
$this->sMsg = json_encode($sMsg, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function dataJson($sMsg = '')
|
||||
{
|
||||
$this->dataStr($sMsg);
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function withDateArr()
|
||||
{
|
||||
if ($this->bEnableDate) {
|
||||
$this->sMsg["date"] = date("Y-m-d H:i:s");
|
||||
}
|
||||
}
|
||||
|
||||
private function withDateStr()
|
||||
{
|
||||
if ($this->bEnableDate) {
|
||||
$this->sMsg = '['.date("Y-m-d H:i:s").'] '.$this->sMsg;
|
||||
}
|
||||
}
|
||||
|
||||
public function setFormat($sMsgFormatType = "none")
|
||||
{
|
||||
$this->sMsgFormatType = $sMsgFormatType;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setBotCode($sBotCode)
|
||||
{
|
||||
$this->sBotCode = $sBotCode;
|
||||
$this->bKeyBegin = false;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setChatCode($sChatCode)
|
||||
{
|
||||
$this->sChatCode = $sChatCode;
|
||||
$this->bKeyBegin = false;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setBotToken($sBotToken)
|
||||
{
|
||||
$this->sBotToken = $sBotToken;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setChatId($iChatId)
|
||||
{
|
||||
$this->iChatId = $iChatId;
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function keyBegin()
|
||||
{
|
||||
if (!$this->bKeyBegin) {
|
||||
if (!$this->sBotToken) {
|
||||
$this->sBotToken = $this->botTokenByCode();
|
||||
}
|
||||
|
||||
if (!$this->iChatId) {
|
||||
$this->iChatId = $this->chatIdByCode();
|
||||
}
|
||||
|
||||
if (!$this->sBotToken || !$this->iChatId) {
|
||||
throw new \RuntimeException("botToken或chatId没找到,botCode={$this->sBotCode},chatCode={$this->sChatCode}");
|
||||
}
|
||||
|
||||
$this->bKeyBegin = true;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function msgFormat()
|
||||
{
|
||||
if (!in_array($this->sMsgFormatType, self::ALLOWED_FORMATS, true)) {
|
||||
throw new \RuntimeException("不支持的格式类型: {$this->sMsgFormatType}");
|
||||
}
|
||||
|
||||
$method = 'msgFormat_' . $this->sMsgFormatType;
|
||||
$this->$method();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function botTokenByCode()
|
||||
{
|
||||
return ModelTelegramKey::botTokenByCode($this->sBotCode);
|
||||
}
|
||||
|
||||
private function chatIdByCode()
|
||||
{
|
||||
return ModelTelegramKey::chatIdByCode($this->sChatCode);
|
||||
}
|
||||
|
||||
private function msgFormat_none()
|
||||
{
|
||||
$this->sMsgFormat = [
|
||||
'chat_id' => $this->iChatId,
|
||||
'text' => $this->sMsg,
|
||||
'parse_mode' => '',
|
||||
'disable_web_page_preview' => false,
|
||||
'reply_to_message_id' => null,
|
||||
'reply_markup' => null,
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function msgFormat_html()
|
||||
{
|
||||
$this->sMsgFormat = [
|
||||
'chat_id' => $this->iChatId,
|
||||
'text' => strip_tags($this->sMsg, [
|
||||
'b', 'strong', 'i', 'em', 'u', 'ins', 's', 'strike', 'del', 'span', 'tg-spoiler', 'a', 'tg-emoji',
|
||||
'code', 'pre',
|
||||
]),
|
||||
'parse_mode' => 'HTML',
|
||||
'disable_web_page_preview' => false,
|
||||
'reply_to_message_id' => null,
|
||||
'reply_markup' => null,
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function msgFormat_markdown()
|
||||
{
|
||||
$this->sMsgFormat = [
|
||||
'chat_id' => $this->iChatId,
|
||||
'text' => $this->sMsg,
|
||||
'parse_mode' => 'Markdown',
|
||||
'disable_web_page_preview' => false,
|
||||
'reply_to_message_id' => null,
|
||||
'reply_markup' => null,
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function msgFormat_markdownV2()
|
||||
{
|
||||
$this->sMsgFormat = [
|
||||
'chat_id' => $this->iChatId,
|
||||
'text' => $this->sMsg,
|
||||
'parse_mode' => 'MarkdownV2',
|
||||
'disable_web_page_preview' => false,
|
||||
'reply_to_message_id' => null,
|
||||
'reply_markup' => null,
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
130
app/Services/TomTool/ThrowableHandler.php
Executable file
130
app/Services/TomTool/ThrowableHandler.php
Executable file
@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\TomTool;
|
||||
|
||||
use Throwable as ThrowableBase;
|
||||
use Illuminate\Database\QueryException;
|
||||
use App\Services\TomTool\TelegramVdb;
|
||||
|
||||
/**
|
||||
|
||||
new:
|
||||
$oThrowableHandler = ThrowableHandler::make($e);
|
||||
|
||||
快速:
|
||||
$aResult = ThrowableHandler::make($e)->toArr();
|
||||
|
||||
action:
|
||||
fetch() or get()
|
||||
|
||||
set:
|
||||
enableTg()
|
||||
enableTrace()
|
||||
setMaxDepth()
|
||||
|
||||
*/
|
||||
|
||||
class ThrowableHandler
|
||||
{
|
||||
protected bool $tgEnable = false;
|
||||
protected bool $showTrace = false;
|
||||
protected int $maxDepth = 3;
|
||||
protected ThrowableBase $e;
|
||||
|
||||
public static function make(ThrowableBase $e): self
|
||||
{
|
||||
$oInstance = new self();
|
||||
$oInstance->e = $e;
|
||||
return $oInstance;
|
||||
}
|
||||
|
||||
public function fetch()
|
||||
{
|
||||
return $this->get();
|
||||
}
|
||||
|
||||
public function get(): array
|
||||
{
|
||||
$data = $this->buildExceptionData($this->e, 0);
|
||||
|
||||
if ($this->tgEnable) {
|
||||
$this->sendTelegram($data);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function toArr(): array
|
||||
{
|
||||
return $this->get();
|
||||
}
|
||||
|
||||
public function toJson(): string
|
||||
{
|
||||
return json_encode($this->get(), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
public function enableTg(bool $enable = true): self
|
||||
{
|
||||
$this->tgEnable = $enable;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function enableTrace(bool $enable = true): self
|
||||
{
|
||||
$this->showTrace = $enable;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setMaxDepth(int $depth): self
|
||||
{
|
||||
$this->maxDepth = max(1, $depth);
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function buildExceptionData(ThrowableBase $e, int $depth): array
|
||||
{
|
||||
$data = [
|
||||
'message' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
];
|
||||
|
||||
$code = $e->getCode();
|
||||
if ($code !== null && $code !== 0) {
|
||||
$data['code'] = $code;
|
||||
}
|
||||
|
||||
if ($this->showTrace) {
|
||||
$filteredTrace = array_filter($e->getTrace(), function ($frame) {
|
||||
return empty($frame['file']) || !str_contains($frame['file'], '/vendor/');
|
||||
});
|
||||
|
||||
$traceLines = [];
|
||||
foreach ($filteredTrace as $frame) {
|
||||
$file = $frame['file'] ?? '[internal]';
|
||||
$line = $frame['line'] ?? '';
|
||||
$func = isset($frame['class'], $frame['function'])
|
||||
? "{$frame['class']}{$frame['type']}{$frame['function']}"
|
||||
: ($frame['function'] ?? '[unknown]');
|
||||
$traceLines[] = "{$file}:{$line} {$func}()";
|
||||
}
|
||||
|
||||
$data['trace'] = implode("\n", $traceLines);
|
||||
}
|
||||
|
||||
if ($depth < $this->maxDepth && $e->getPrevious() instanceof ThrowableBase) {
|
||||
$data['previous'] = $this->buildExceptionData($e->getPrevious(), $depth + 1);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function sendTelegram(array $data): void
|
||||
{
|
||||
TelegramVdb::make()->aData($data)->save();
|
||||
}
|
||||
}
|
||||
|
||||
21
app/Services/TomTool/Tom.php
Executable file
21
app/Services/TomTool/Tom.php
Executable file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\TomTool;
|
||||
|
||||
class Tom
|
||||
{
|
||||
public static function toJson($aData)
|
||||
{
|
||||
return json_encode($aData, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
|
||||
public static function toJsonBr($aData)
|
||||
{
|
||||
$sData = self::toJson($aData);
|
||||
$sData = str_replace("\\n", "<br>", $sData);
|
||||
|
||||
return $sData;
|
||||
}
|
||||
}
|
||||
18
artisan
Executable file
18
artisan
Executable file
@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
use Illuminate\Foundation\Application;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
|
||||
define('LARAVEL_START', microtime(true));
|
||||
|
||||
// Register the Composer autoloader...
|
||||
require __DIR__.'/vendor/autoload.php';
|
||||
|
||||
// Bootstrap Laravel and handle the command...
|
||||
/** @var Application $app */
|
||||
$app = require_once __DIR__.'/bootstrap/app.php';
|
||||
|
||||
$status = $app->handleCommand(new ArgvInput);
|
||||
|
||||
exit($status);
|
||||
21
bootstrap/app.php
Executable file
21
bootstrap/app.php
Executable file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Foundation\Configuration\Exceptions;
|
||||
use Illuminate\Foundation\Configuration\Middleware;
|
||||
|
||||
return Application::configure(basePath: dirname(__DIR__))
|
||||
->withRouting(
|
||||
web: __DIR__.'/../routes/web.php',
|
||||
commands: __DIR__.'/../routes/console.php',
|
||||
health: '/up',
|
||||
)
|
||||
->withMiddleware(function (Middleware $middleware) {
|
||||
$middleware->alias([
|
||||
]);
|
||||
$middleware->validateCsrfTokens(except: [
|
||||
'api/*',
|
||||
]);
|
||||
})
|
||||
->withExceptions(function (Exceptions $exceptions) {
|
||||
})->create();
|
||||
2
bootstrap/cache/.gitignore
vendored
Executable file
2
bootstrap/cache/.gitignore
vendored
Executable file
@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
5
bootstrap/providers.php
Executable file
5
bootstrap/providers.php
Executable file
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
App\Providers\AppServiceProvider::class,
|
||||
];
|
||||
86
composer.json
Executable file
86
composer.json
Executable file
@ -0,0 +1,86 @@
|
||||
{
|
||||
"$schema": "https://getcomposer.org/schema.json",
|
||||
"name": "laravel/laravel",
|
||||
"type": "project",
|
||||
"description": "The skeleton application for the Laravel framework.",
|
||||
"keywords": ["laravel", "framework"],
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^8.2",
|
||||
"laravel/framework": "^12.0",
|
||||
"laravel/tinker": "^2.10.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"fakerphp/faker": "^1.23",
|
||||
"laravel/pail": "^1.2.2",
|
||||
"laravel/pint": "^1.24",
|
||||
"laravel/sail": "^1.41",
|
||||
"mockery/mockery": "^1.6",
|
||||
"nunomaduro/collision": "^8.6",
|
||||
"phpunit/phpunit": "^11.5.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"App\\": "app/",
|
||||
"Database\\Factories\\": "database/factories/",
|
||||
"Database\\Seeders\\": "database/seeders/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"setup": [
|
||||
"composer install",
|
||||
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\"",
|
||||
"@php artisan key:generate",
|
||||
"@php artisan migrate --force",
|
||||
"npm install",
|
||||
"npm run build"
|
||||
],
|
||||
"dev": [
|
||||
"Composer\\Config::disableProcessTimeout",
|
||||
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite --kill-others"
|
||||
],
|
||||
"test": [
|
||||
"@php artisan config:clear --ansi",
|
||||
"@php artisan test"
|
||||
],
|
||||
"post-autoload-dump": [
|
||||
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
|
||||
"@php artisan package:discover --ansi"
|
||||
],
|
||||
"post-update-cmd": [
|
||||
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
|
||||
],
|
||||
"post-root-package-install": [
|
||||
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
|
||||
],
|
||||
"post-create-project-cmd": [
|
||||
"@php artisan key:generate --ansi",
|
||||
"@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"",
|
||||
"@php artisan migrate --graceful --ansi"
|
||||
],
|
||||
"pre-package-uninstall": [
|
||||
"Illuminate\\Foundation\\ComposerScripts::prePackageUninstall"
|
||||
]
|
||||
},
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"dont-discover": []
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"optimize-autoloader": true,
|
||||
"preferred-install": "dist",
|
||||
"sort-packages": true,
|
||||
"allow-plugins": {
|
||||
"pestphp/pest-plugin": true,
|
||||
"php-http/discovery": true
|
||||
}
|
||||
},
|
||||
"minimum-stability": "stable",
|
||||
"prefer-stable": true
|
||||
}
|
||||
8373
composer.lock
generated
Executable file
8373
composer.lock
generated
Executable file
File diff suppressed because it is too large
Load Diff
117
config/app.php
Executable file
117
config/app.php
Executable file
@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'is_loc' => env("is_loc", false),
|
||||
|
||||
'name' => env('APP_NAME', 'Laravel'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Environment
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value determines the "environment" your application is currently
|
||||
| running in. This may determine how you prefer to configure various
|
||||
| services the application utilizes. Set this in your ".env" file.
|
||||
|
|
||||
*/
|
||||
|
||||
'env' => env('APP_ENV', 'production'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Debug Mode
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When your application is in debug mode, detailed error messages with
|
||||
| stack traces will be shown on every error that occurs within your
|
||||
| application. If disabled, a simple generic error page is shown.
|
||||
|
|
||||
*/
|
||||
|
||||
'debug' => (bool) env('APP_DEBUG', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application URL
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This URL is used by the console to properly generate URLs when using
|
||||
| the Artisan command line tool. You should set this to the root of
|
||||
| the application so that it's available within Artisan commands.
|
||||
|
|
||||
*/
|
||||
|
||||
'url' => env('APP_URL', 'http://localhost'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Timezone
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify the default timezone for your application, which
|
||||
| will be used by the PHP date and date-time functions. The timezone
|
||||
| is set to "UTC" by default as it is suitable for most use cases.
|
||||
|
|
||||
*/
|
||||
|
||||
'timezone' => 'UTC',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Locale Configuration
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The application locale determines the default locale that will be used
|
||||
| by Laravel's translation / localization methods. This option can be
|
||||
| set to any locale for which you plan to have translation strings.
|
||||
|
|
||||
*/
|
||||
|
||||
'locale' => env('APP_LOCALE', 'en'),
|
||||
|
||||
'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'),
|
||||
|
||||
'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Encryption Key
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This key is utilized by Laravel's encryption services and should be set
|
||||
| to a random, 32 character string to ensure that all encrypted values
|
||||
| are secure. You should do this prior to deploying the application.
|
||||
|
|
||||
*/
|
||||
|
||||
'cipher' => 'AES-256-CBC',
|
||||
|
||||
'key' => env('APP_KEY'),
|
||||
|
||||
'previous_keys' => [
|
||||
...array_filter(
|
||||
explode(',', (string) env('APP_PREVIOUS_KEYS', ''))
|
||||
),
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Maintenance Mode Driver
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| These configuration options determine the driver used to determine and
|
||||
| manage Laravel's "maintenance mode" status. The "cache" driver will
|
||||
| allow maintenance mode to be controlled across multiple machines.
|
||||
|
|
||||
| Supported drivers: "file", "cache"
|
||||
|
|
||||
*/
|
||||
|
||||
'maintenance' => [
|
||||
'driver' => env('APP_MAINTENANCE_DRIVER', 'file'),
|
||||
'store' => env('APP_MAINTENANCE_STORE', 'database'),
|
||||
],
|
||||
|
||||
];
|
||||
115
config/auth.php
Executable file
115
config/auth.php
Executable file
@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Authentication Defaults
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option defines the default authentication "guard" and password
|
||||
| reset "broker" for your application. You may change these values
|
||||
| as required, but they're a perfect start for most applications.
|
||||
|
|
||||
*/
|
||||
|
||||
'defaults' => [
|
||||
'guard' => env('AUTH_GUARD', 'web'),
|
||||
'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Authentication Guards
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Next, you may define every authentication guard for your application.
|
||||
| Of course, a great default configuration has been defined for you
|
||||
| which utilizes session storage plus the Eloquent user provider.
|
||||
|
|
||||
| All authentication guards have a user provider, which defines how the
|
||||
| users are actually retrieved out of your database or other storage
|
||||
| system used by the application. Typically, Eloquent is utilized.
|
||||
|
|
||||
| Supported: "session"
|
||||
|
|
||||
*/
|
||||
|
||||
'guards' => [
|
||||
'web' => [
|
||||
'driver' => 'session',
|
||||
'provider' => 'users',
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| User Providers
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| All authentication guards have a user provider, which defines how the
|
||||
| users are actually retrieved out of your database or other storage
|
||||
| system used by the application. Typically, Eloquent is utilized.
|
||||
|
|
||||
| If you have multiple user tables or models you may configure multiple
|
||||
| providers to represent the model / table. These providers may then
|
||||
| be assigned to any extra authentication guards you have defined.
|
||||
|
|
||||
| Supported: "database", "eloquent"
|
||||
|
|
||||
*/
|
||||
|
||||
'providers' => [
|
||||
'users' => [
|
||||
'driver' => 'eloquent',
|
||||
'model' => env('AUTH_MODEL', App\Models\User::class),
|
||||
],
|
||||
|
||||
// 'users' => [
|
||||
// 'driver' => 'database',
|
||||
// 'table' => 'users',
|
||||
// ],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Resetting Passwords
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| These configuration options specify the behavior of Laravel's password
|
||||
| reset functionality, including the table utilized for token storage
|
||||
| and the user provider that is invoked to actually retrieve users.
|
||||
|
|
||||
| The expiry time is the number of minutes that each reset token will be
|
||||
| considered valid. This security feature keeps tokens short-lived so
|
||||
| they have less time to be guessed. You may change this as needed.
|
||||
|
|
||||
| The throttle setting is the number of seconds a user must wait before
|
||||
| generating more password reset tokens. This prevents the user from
|
||||
| quickly generating a very large amount of password reset tokens.
|
||||
|
|
||||
*/
|
||||
|
||||
'passwords' => [
|
||||
'users' => [
|
||||
'provider' => 'users',
|
||||
'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
|
||||
'expire' => 60,
|
||||
'throttle' => 60,
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Password Confirmation Timeout
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may define the number of seconds before a password confirmation
|
||||
| window expires and users are asked to re-enter their password via the
|
||||
| confirmation screen. By default, the timeout lasts for three hours.
|
||||
|
|
||||
*/
|
||||
|
||||
'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),
|
||||
|
||||
];
|
||||
117
config/cache.php
Executable file
117
config/cache.php
Executable file
@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Cache Store
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the default cache store that will be used by the
|
||||
| framework. This connection is utilized if another isn't explicitly
|
||||
| specified when running a cache operation inside the application.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('CACHE_STORE', 'database'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Cache Stores
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may define all of the cache "stores" for your application as
|
||||
| well as their drivers. You may even define multiple stores for the
|
||||
| same cache driver to group types of items stored in your caches.
|
||||
|
|
||||
| Supported drivers: "array", "database", "file", "memcached",
|
||||
| "redis", "dynamodb", "octane",
|
||||
| "failover", "null"
|
||||
|
|
||||
*/
|
||||
|
||||
'stores' => [
|
||||
|
||||
'array' => [
|
||||
'driver' => 'array',
|
||||
'serialize' => false,
|
||||
],
|
||||
|
||||
'database' => [
|
||||
'driver' => 'database',
|
||||
'connection' => env('DB_CACHE_CONNECTION'),
|
||||
'table' => env('DB_CACHE_TABLE', 'cache'),
|
||||
'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'),
|
||||
'lock_table' => env('DB_CACHE_LOCK_TABLE'),
|
||||
],
|
||||
|
||||
'file' => [
|
||||
'driver' => 'file',
|
||||
'path' => storage_path('framework/cache/data'),
|
||||
'lock_path' => storage_path('framework/cache/data'),
|
||||
],
|
||||
|
||||
'memcached' => [
|
||||
'driver' => 'memcached',
|
||||
'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
|
||||
'sasl' => [
|
||||
env('MEMCACHED_USERNAME'),
|
||||
env('MEMCACHED_PASSWORD'),
|
||||
],
|
||||
'options' => [
|
||||
// Memcached::OPT_CONNECT_TIMEOUT => 2000,
|
||||
],
|
||||
'servers' => [
|
||||
[
|
||||
'host' => env('MEMCACHED_HOST', '127.0.0.1'),
|
||||
'port' => env('MEMCACHED_PORT', 11211),
|
||||
'weight' => 100,
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
'redis' => [
|
||||
'driver' => 'redis',
|
||||
'connection' => env('REDIS_CACHE_CONNECTION', 'cache'),
|
||||
'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'),
|
||||
],
|
||||
|
||||
'dynamodb' => [
|
||||
'driver' => 'dynamodb',
|
||||
'key' => env('AWS_ACCESS_KEY_ID'),
|
||||
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
|
||||
'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
|
||||
'endpoint' => env('DYNAMODB_ENDPOINT'),
|
||||
],
|
||||
|
||||
'octane' => [
|
||||
'driver' => 'octane',
|
||||
],
|
||||
|
||||
'failover' => [
|
||||
'driver' => 'failover',
|
||||
'stores' => [
|
||||
'database',
|
||||
'array',
|
||||
],
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Cache Key Prefix
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When utilizing the APC, database, memcached, Redis, and DynamoDB cache
|
||||
| stores, there might be other applications using the same cache. For
|
||||
| that reason, you may prefix every cache key to avoid collisions.
|
||||
|
|
||||
*/
|
||||
|
||||
'prefix' => env('CACHE_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-cache-'),
|
||||
|
||||
];
|
||||
183
config/database.php
Executable file
183
config/database.php
Executable file
@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Database Connection Name
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify which of the database connections below you wish
|
||||
| to use as your default connection for database operations. This is
|
||||
| the connection which will be utilized unless another connection
|
||||
| is explicitly specified when you execute a query / statement.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('DB_CONNECTION', 'sqlite'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Database Connections
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Below are all of the database connections defined for your application.
|
||||
| An example configuration is provided for each database system which
|
||||
| is supported by Laravel. You're free to add / remove connections.
|
||||
|
|
||||
*/
|
||||
|
||||
'connections' => [
|
||||
|
||||
'sqlite' => [
|
||||
'driver' => 'sqlite',
|
||||
'url' => env('DB_URL'),
|
||||
'database' => env('DB_DATABASE', database_path('database.sqlite')),
|
||||
'prefix' => '',
|
||||
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
|
||||
'busy_timeout' => null,
|
||||
'journal_mode' => null,
|
||||
'synchronous' => null,
|
||||
'transaction_mode' => 'DEFERRED',
|
||||
],
|
||||
|
||||
'mysql' => [
|
||||
'driver' => 'mysql',
|
||||
'url' => env('DB_URL'),
|
||||
'host' => env('DB_HOST', '127.0.0.1'),
|
||||
'port' => env('DB_PORT', '3306'),
|
||||
'database' => env('DB_DATABASE', 'laravel'),
|
||||
'username' => env('DB_USERNAME', 'root'),
|
||||
'password' => env('DB_PASSWORD', ''),
|
||||
'unix_socket' => env('DB_SOCKET', ''),
|
||||
'charset' => env('DB_CHARSET', 'utf8mb4'),
|
||||
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
|
||||
'prefix' => '',
|
||||
'prefix_indexes' => true,
|
||||
'strict' => true,
|
||||
'engine' => null,
|
||||
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
||||
(PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_CA : \PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'),
|
||||
]) : [],
|
||||
],
|
||||
|
||||
'mariadb' => [
|
||||
'driver' => 'mariadb',
|
||||
'url' => env('DB_URL'),
|
||||
'host' => env('DB_HOST', '127.0.0.1'),
|
||||
'port' => env('DB_PORT', '3306'),
|
||||
'database' => env('DB_DATABASE', 'laravel'),
|
||||
'username' => env('DB_USERNAME', 'root'),
|
||||
'password' => env('DB_PASSWORD', ''),
|
||||
'unix_socket' => env('DB_SOCKET', ''),
|
||||
'charset' => env('DB_CHARSET', 'utf8mb4'),
|
||||
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
|
||||
'prefix' => '',
|
||||
'prefix_indexes' => true,
|
||||
'strict' => true,
|
||||
'engine' => null,
|
||||
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
||||
(PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_CA : \PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'),
|
||||
]) : [],
|
||||
],
|
||||
|
||||
'pgsql' => [
|
||||
'driver' => 'pgsql',
|
||||
'url' => env('DB_URL'),
|
||||
'host' => env('DB_HOST', '127.0.0.1'),
|
||||
'port' => env('DB_PORT', '5432'),
|
||||
'database' => env('DB_DATABASE', 'laravel'),
|
||||
'username' => env('DB_USERNAME', 'root'),
|
||||
'password' => env('DB_PASSWORD', ''),
|
||||
'charset' => env('DB_CHARSET', 'utf8'),
|
||||
'prefix' => '',
|
||||
'prefix_indexes' => true,
|
||||
'search_path' => 'public',
|
||||
'sslmode' => env('DB_SSLMODE', 'prefer'),
|
||||
],
|
||||
|
||||
'sqlsrv' => [
|
||||
'driver' => 'sqlsrv',
|
||||
'url' => env('DB_URL'),
|
||||
'host' => env('DB_HOST', 'localhost'),
|
||||
'port' => env('DB_PORT', '1433'),
|
||||
'database' => env('DB_DATABASE', 'laravel'),
|
||||
'username' => env('DB_USERNAME', 'root'),
|
||||
'password' => env('DB_PASSWORD', ''),
|
||||
'charset' => env('DB_CHARSET', 'utf8'),
|
||||
'prefix' => '',
|
||||
'prefix_indexes' => true,
|
||||
// 'encrypt' => env('DB_ENCRYPT', 'yes'),
|
||||
// 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'),
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Migration Repository Table
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This table keeps track of all the migrations that have already run for
|
||||
| your application. Using this information, we can determine which of
|
||||
| the migrations on disk haven't actually been run on the database.
|
||||
|
|
||||
*/
|
||||
|
||||
'migrations' => [
|
||||
'table' => 'migrations',
|
||||
'update_date_on_publish' => true,
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Redis Databases
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Redis is an open source, fast, and advanced key-value store that also
|
||||
| provides a richer body of commands than a typical key-value system
|
||||
| such as Memcached. You may define your connection settings here.
|
||||
|
|
||||
*/
|
||||
|
||||
'redis' => [
|
||||
|
||||
'client' => env('REDIS_CLIENT', 'phpredis'),
|
||||
|
||||
'options' => [
|
||||
'cluster' => env('REDIS_CLUSTER', 'redis'),
|
||||
'prefix' => env('REDIS_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-database-'),
|
||||
'persistent' => env('REDIS_PERSISTENT', false),
|
||||
],
|
||||
|
||||
'default' => [
|
||||
'url' => env('REDIS_URL'),
|
||||
'host' => env('REDIS_HOST', '127.0.0.1'),
|
||||
'username' => env('REDIS_USERNAME'),
|
||||
'password' => env('REDIS_PASSWORD'),
|
||||
'port' => env('REDIS_PORT', '6379'),
|
||||
'database' => env('REDIS_DB', '0'),
|
||||
'max_retries' => env('REDIS_MAX_RETRIES', 3),
|
||||
'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'),
|
||||
'backoff_base' => env('REDIS_BACKOFF_BASE', 100),
|
||||
'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000),
|
||||
],
|
||||
|
||||
'cache' => [
|
||||
'url' => env('REDIS_URL'),
|
||||
'host' => env('REDIS_HOST', '127.0.0.1'),
|
||||
'username' => env('REDIS_USERNAME'),
|
||||
'password' => env('REDIS_PASSWORD'),
|
||||
'port' => env('REDIS_PORT', '6379'),
|
||||
'database' => env('REDIS_CACHE_DB', '1'),
|
||||
'max_retries' => env('REDIS_MAX_RETRIES', 3),
|
||||
'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'),
|
||||
'backoff_base' => env('REDIS_BACKOFF_BASE', 100),
|
||||
'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000),
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
96
config/filesystems.php
Executable file
96
config/filesystems.php
Executable file
@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Filesystem Disk
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify the default filesystem disk that should be used
|
||||
| by the framework. The "local" disk, as well as a variety of cloud
|
||||
| based disks are available to your application for file storage.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('FILESYSTEM_DISK', 'local'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Filesystem Disks
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Below you may configure as many filesystem disks as necessary, and you
|
||||
| may even configure multiple disks for the same driver. Examples for
|
||||
| most supported storage drivers are configured here for reference.
|
||||
|
|
||||
| Supported drivers: "local", "ftp", "sftp", "s3"
|
||||
|
|
||||
*/
|
||||
|
||||
'disks' => [
|
||||
|
||||
'local' => [
|
||||
'driver' => 'local',
|
||||
'root' => storage_path('app/private'),
|
||||
'serve' => true,
|
||||
'throw' => false,
|
||||
'report' => false,
|
||||
],
|
||||
|
||||
'public' => [
|
||||
'driver' => 'local',
|
||||
'root' => storage_path('app/public'),
|
||||
'url' => rtrim(env('APP_URL'), '/').'/storage',
|
||||
'visibility' => 'public',
|
||||
'throw' => false,
|
||||
'report' => false,
|
||||
],
|
||||
|
||||
'upload' => [
|
||||
'driver' => 'local',
|
||||
'root' => public_path('upload'),
|
||||
'url' => env('APP_URL').'/upload',
|
||||
'visibility' => 'public',
|
||||
'throw' => false,
|
||||
],
|
||||
|
||||
'upload_images' => [
|
||||
'driver' => 'local',
|
||||
'root' => public_path('upload/images'),
|
||||
'url' => env('APP_URL').'/upload/images',
|
||||
'visibility' => 'public',
|
||||
'throw' => false,
|
||||
],
|
||||
|
||||
's3' => [
|
||||
'driver' => 's3',
|
||||
'key' => env('AWS_ACCESS_KEY_ID'),
|
||||
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||
'region' => env('AWS_DEFAULT_REGION'),
|
||||
'bucket' => env('AWS_BUCKET'),
|
||||
'url' => env('AWS_URL'),
|
||||
'endpoint' => env('AWS_ENDPOINT'),
|
||||
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
|
||||
'throw' => false,
|
||||
'report' => false,
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Symbolic Links
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure the symbolic links that will be created when the
|
||||
| `storage:link` Artisan command is executed. The array keys should be
|
||||
| the locations of the links and the values should be their targets.
|
||||
|
|
||||
*/
|
||||
|
||||
'links' => [
|
||||
public_path('storage') => storage_path('app/public'),
|
||||
],
|
||||
|
||||
];
|
||||
132
config/logging.php
Executable file
132
config/logging.php
Executable file
@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
use Monolog\Handler\NullHandler;
|
||||
use Monolog\Handler\StreamHandler;
|
||||
use Monolog\Handler\SyslogUdpHandler;
|
||||
use Monolog\Processor\PsrLogMessageProcessor;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Log Channel
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option defines the default log channel that is utilized to write
|
||||
| messages to your logs. The value provided here should match one of
|
||||
| the channels present in the list of "channels" configured below.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('LOG_CHANNEL', 'stack'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Deprecations Log Channel
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the log channel that should be used to log warnings
|
||||
| regarding deprecated PHP and library features. This allows you to get
|
||||
| your application ready for upcoming major versions of dependencies.
|
||||
|
|
||||
*/
|
||||
|
||||
'deprecations' => [
|
||||
'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
|
||||
'trace' => env('LOG_DEPRECATIONS_TRACE', false),
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Log Channels
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure the log channels for your application. Laravel
|
||||
| utilizes the Monolog PHP logging library, which includes a variety
|
||||
| of powerful log handlers and formatters that you're free to use.
|
||||
|
|
||||
| Available drivers: "single", "daily", "slack", "syslog",
|
||||
| "errorlog", "monolog", "custom", "stack"
|
||||
|
|
||||
*/
|
||||
|
||||
'channels' => [
|
||||
|
||||
'stack' => [
|
||||
'driver' => 'stack',
|
||||
'channels' => explode(',', (string) env('LOG_STACK', 'single')),
|
||||
'ignore_exceptions' => false,
|
||||
],
|
||||
|
||||
'single' => [
|
||||
'driver' => 'single',
|
||||
'path' => storage_path('logs/laravel.log'),
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
'replace_placeholders' => true,
|
||||
],
|
||||
|
||||
'daily' => [
|
||||
'driver' => 'daily',
|
||||
'path' => storage_path('logs/laravel.log'),
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
'days' => env('LOG_DAILY_DAYS', 14),
|
||||
'replace_placeholders' => true,
|
||||
],
|
||||
|
||||
'slack' => [
|
||||
'driver' => 'slack',
|
||||
'url' => env('LOG_SLACK_WEBHOOK_URL'),
|
||||
'username' => env('LOG_SLACK_USERNAME', 'Laravel Log'),
|
||||
'emoji' => env('LOG_SLACK_EMOJI', ':boom:'),
|
||||
'level' => env('LOG_LEVEL', 'critical'),
|
||||
'replace_placeholders' => true,
|
||||
],
|
||||
|
||||
'papertrail' => [
|
||||
'driver' => 'monolog',
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class),
|
||||
'handler_with' => [
|
||||
'host' => env('PAPERTRAIL_URL'),
|
||||
'port' => env('PAPERTRAIL_PORT'),
|
||||
'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'),
|
||||
],
|
||||
'processors' => [PsrLogMessageProcessor::class],
|
||||
],
|
||||
|
||||
'stderr' => [
|
||||
'driver' => 'monolog',
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
'handler' => StreamHandler::class,
|
||||
'handler_with' => [
|
||||
'stream' => 'php://stderr',
|
||||
],
|
||||
'formatter' => env('LOG_STDERR_FORMATTER'),
|
||||
'processors' => [PsrLogMessageProcessor::class],
|
||||
],
|
||||
|
||||
'syslog' => [
|
||||
'driver' => 'syslog',
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER),
|
||||
'replace_placeholders' => true,
|
||||
],
|
||||
|
||||
'errorlog' => [
|
||||
'driver' => 'errorlog',
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
'replace_placeholders' => true,
|
||||
],
|
||||
|
||||
'null' => [
|
||||
'driver' => 'monolog',
|
||||
'handler' => NullHandler::class,
|
||||
],
|
||||
|
||||
'emergency' => [
|
||||
'path' => storage_path('logs/laravel.log'),
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
118
config/mail.php
Executable file
118
config/mail.php
Executable file
@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Mailer
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the default mailer that is used to send all email
|
||||
| messages unless another mailer is explicitly specified when sending
|
||||
| the message. All additional mailers can be configured within the
|
||||
| "mailers" array. Examples of each type of mailer are provided.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('MAIL_MAILER', 'log'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Mailer Configurations
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure all of the mailers used by your application plus
|
||||
| their respective settings. Several examples have been configured for
|
||||
| you and you are free to add your own as your application requires.
|
||||
|
|
||||
| Laravel supports a variety of mail "transport" drivers that can be used
|
||||
| when delivering an email. You may specify which one you're using for
|
||||
| your mailers below. You may also add additional mailers if needed.
|
||||
|
|
||||
| Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2",
|
||||
| "postmark", "resend", "log", "array",
|
||||
| "failover", "roundrobin"
|
||||
|
|
||||
*/
|
||||
|
||||
'mailers' => [
|
||||
|
||||
'smtp' => [
|
||||
'transport' => 'smtp',
|
||||
'scheme' => env('MAIL_SCHEME'),
|
||||
'url' => env('MAIL_URL'),
|
||||
'host' => env('MAIL_HOST', '127.0.0.1'),
|
||||
'port' => env('MAIL_PORT', 2525),
|
||||
'username' => env('MAIL_USERNAME'),
|
||||
'password' => env('MAIL_PASSWORD'),
|
||||
'timeout' => null,
|
||||
'local_domain' => env('MAIL_EHLO_DOMAIN', parse_url((string) env('APP_URL', 'http://localhost'), PHP_URL_HOST)),
|
||||
],
|
||||
|
||||
'ses' => [
|
||||
'transport' => 'ses',
|
||||
],
|
||||
|
||||
'postmark' => [
|
||||
'transport' => 'postmark',
|
||||
// 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'),
|
||||
// 'client' => [
|
||||
// 'timeout' => 5,
|
||||
// ],
|
||||
],
|
||||
|
||||
'resend' => [
|
||||
'transport' => 'resend',
|
||||
],
|
||||
|
||||
'sendmail' => [
|
||||
'transport' => 'sendmail',
|
||||
'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'),
|
||||
],
|
||||
|
||||
'log' => [
|
||||
'transport' => 'log',
|
||||
'channel' => env('MAIL_LOG_CHANNEL'),
|
||||
],
|
||||
|
||||
'array' => [
|
||||
'transport' => 'array',
|
||||
],
|
||||
|
||||
'failover' => [
|
||||
'transport' => 'failover',
|
||||
'mailers' => [
|
||||
'smtp',
|
||||
'log',
|
||||
],
|
||||
'retry_after' => 60,
|
||||
],
|
||||
|
||||
'roundrobin' => [
|
||||
'transport' => 'roundrobin',
|
||||
'mailers' => [
|
||||
'ses',
|
||||
'postmark',
|
||||
],
|
||||
'retry_after' => 60,
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Global "From" Address
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| You may wish for all emails sent by your application to be sent from
|
||||
| the same address. Here you may specify a name and address that is
|
||||
| used globally for all emails that are sent by your application.
|
||||
|
|
||||
*/
|
||||
|
||||
'from' => [
|
||||
'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
|
||||
'name' => env('MAIL_FROM_NAME', 'Example'),
|
||||
],
|
||||
|
||||
];
|
||||
7
config/path.php
Executable file
7
config/path.php
Executable file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'url_master_base' => env('url_master_base'),
|
||||
|
||||
];
|
||||
129
config/queue.php
Executable file
129
config/queue.php
Executable file
@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Queue Connection Name
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Laravel's queue supports a variety of backends via a single, unified
|
||||
| API, giving you convenient access to each backend using identical
|
||||
| syntax for each. The default queue connection is defined below.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('QUEUE_CONNECTION', 'database'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Queue Connections
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure the connection options for every queue backend
|
||||
| used by your application. An example configuration is provided for
|
||||
| each backend supported by Laravel. You're also free to add more.
|
||||
|
|
||||
| Drivers: "sync", "database", "beanstalkd", "sqs", "redis",
|
||||
| "deferred", "background", "failover", "null"
|
||||
|
|
||||
*/
|
||||
|
||||
'connections' => [
|
||||
|
||||
'sync' => [
|
||||
'driver' => 'sync',
|
||||
],
|
||||
|
||||
'database' => [
|
||||
'driver' => 'database',
|
||||
'connection' => env('DB_QUEUE_CONNECTION'),
|
||||
'table' => env('DB_QUEUE_TABLE', 'jobs'),
|
||||
'queue' => env('DB_QUEUE', 'default'),
|
||||
'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90),
|
||||
'after_commit' => false,
|
||||
],
|
||||
|
||||
'beanstalkd' => [
|
||||
'driver' => 'beanstalkd',
|
||||
'host' => env('BEANSTALKD_QUEUE_HOST', 'localhost'),
|
||||
'queue' => env('BEANSTALKD_QUEUE', 'default'),
|
||||
'retry_after' => (int) env('BEANSTALKD_QUEUE_RETRY_AFTER', 90),
|
||||
'block_for' => 0,
|
||||
'after_commit' => false,
|
||||
],
|
||||
|
||||
'sqs' => [
|
||||
'driver' => 'sqs',
|
||||
'key' => env('AWS_ACCESS_KEY_ID'),
|
||||
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||
'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
|
||||
'queue' => env('SQS_QUEUE', 'default'),
|
||||
'suffix' => env('SQS_SUFFIX'),
|
||||
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
|
||||
'after_commit' => false,
|
||||
],
|
||||
|
||||
'redis' => [
|
||||
'driver' => 'redis',
|
||||
'connection' => env('REDIS_QUEUE_CONNECTION', 'default'),
|
||||
'queue' => env('REDIS_QUEUE', 'default'),
|
||||
'retry_after' => (int) env('REDIS_QUEUE_RETRY_AFTER', 90),
|
||||
'block_for' => null,
|
||||
'after_commit' => false,
|
||||
],
|
||||
|
||||
'deferred' => [
|
||||
'driver' => 'deferred',
|
||||
],
|
||||
|
||||
'background' => [
|
||||
'driver' => 'background',
|
||||
],
|
||||
|
||||
'failover' => [
|
||||
'driver' => 'failover',
|
||||
'connections' => [
|
||||
'database',
|
||||
'deferred',
|
||||
],
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Job Batching
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following options configure the database and table that store job
|
||||
| batching information. These options can be updated to any database
|
||||
| connection and table which has been defined by your application.
|
||||
|
|
||||
*/
|
||||
|
||||
'batching' => [
|
||||
'database' => env('DB_CONNECTION', 'sqlite'),
|
||||
'table' => 'job_batches',
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Failed Queue Jobs
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| These options configure the behavior of failed queue job logging so you
|
||||
| can control how and where failed jobs are stored. Laravel ships with
|
||||
| support for storing failed jobs in a simple file or in a database.
|
||||
|
|
||||
| Supported drivers: "database-uuids", "dynamodb", "file", "null"
|
||||
|
|
||||
*/
|
||||
|
||||
'failed' => [
|
||||
'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'),
|
||||
'database' => env('DB_CONNECTION', 'sqlite'),
|
||||
'table' => 'failed_jobs',
|
||||
],
|
||||
|
||||
];
|
||||
38
config/services.php
Executable file
38
config/services.php
Executable file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Third Party Services
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This file is for storing the credentials for third party services such
|
||||
| as Mailgun, Postmark, AWS and more. This file provides the de facto
|
||||
| location for this type of information, allowing packages to have
|
||||
| a conventional file to locate the various service credentials.
|
||||
|
|
||||
*/
|
||||
|
||||
'postmark' => [
|
||||
'key' => env('POSTMARK_API_KEY'),
|
||||
],
|
||||
|
||||
'resend' => [
|
||||
'key' => env('RESEND_API_KEY'),
|
||||
],
|
||||
|
||||
'ses' => [
|
||||
'key' => env('AWS_ACCESS_KEY_ID'),
|
||||
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
|
||||
],
|
||||
|
||||
'slack' => [
|
||||
'notifications' => [
|
||||
'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'),
|
||||
'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'),
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
217
config/session.php
Executable file
217
config/session.php
Executable file
@ -0,0 +1,217 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Session Driver
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option determines the default session driver that is utilized for
|
||||
| incoming requests. Laravel supports a variety of storage options to
|
||||
| persist session data. Database storage is a great default choice.
|
||||
|
|
||||
| Supported: "file", "cookie", "database", "memcached",
|
||||
| "redis", "dynamodb", "array"
|
||||
|
|
||||
*/
|
||||
|
||||
'driver' => env('SESSION_DRIVER', 'database'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Lifetime
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify the number of minutes that you wish the session
|
||||
| to be allowed to remain idle before it expires. If you want them
|
||||
| to expire immediately when the browser is closed then you may
|
||||
| indicate that via the expire_on_close configuration option.
|
||||
|
|
||||
*/
|
||||
|
||||
'lifetime' => (int) env('SESSION_LIFETIME', 120),
|
||||
|
||||
'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Encryption
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option allows you to easily specify that all of your session data
|
||||
| should be encrypted before it's stored. All encryption is performed
|
||||
| automatically by Laravel and you may use the session like normal.
|
||||
|
|
||||
*/
|
||||
|
||||
'encrypt' => env('SESSION_ENCRYPT', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session File Location
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When utilizing the "file" session driver, the session files are placed
|
||||
| on disk. The default storage location is defined here; however, you
|
||||
| are free to provide another location where they should be stored.
|
||||
|
|
||||
*/
|
||||
|
||||
'files' => storage_path('framework/sessions'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Database Connection
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When using the "database" or "redis" session drivers, you may specify a
|
||||
| connection that should be used to manage these sessions. This should
|
||||
| correspond to a connection in your database configuration options.
|
||||
|
|
||||
*/
|
||||
|
||||
'connection' => env('SESSION_CONNECTION'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Database Table
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When using the "database" session driver, you may specify the table to
|
||||
| be used to store sessions. Of course, a sensible default is defined
|
||||
| for you; however, you're welcome to change this to another table.
|
||||
|
|
||||
*/
|
||||
|
||||
'table' => env('SESSION_TABLE', 'sessions'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Cache Store
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When using one of the framework's cache driven session backends, you may
|
||||
| define the cache store which should be used to store the session data
|
||||
| between requests. This must match one of your defined cache stores.
|
||||
|
|
||||
| Affects: "dynamodb", "memcached", "redis"
|
||||
|
|
||||
*/
|
||||
|
||||
'store' => env('SESSION_STORE'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Sweeping Lottery
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Some session drivers must manually sweep their storage location to get
|
||||
| rid of old sessions from storage. Here are the chances that it will
|
||||
| happen on a given request. By default, the odds are 2 out of 100.
|
||||
|
|
||||
*/
|
||||
|
||||
'lottery' => [2, 100],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Cookie Name
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may change the name of the session cookie that is created by
|
||||
| the framework. Typically, you should not need to change this value
|
||||
| since doing so does not grant a meaningful security improvement.
|
||||
|
|
||||
*/
|
||||
|
||||
'cookie' => env(
|
||||
'SESSION_COOKIE',
|
||||
Str::slug((string) env('APP_NAME', 'laravel')).'-session'
|
||||
),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Cookie Path
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The session cookie path determines the path for which the cookie will
|
||||
| be regarded as available. Typically, this will be the root path of
|
||||
| your application, but you're free to change this when necessary.
|
||||
|
|
||||
*/
|
||||
|
||||
'path' => env('SESSION_PATH', '/'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Cookie Domain
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value determines the domain and subdomains the session cookie is
|
||||
| available to. By default, the cookie will be available to the root
|
||||
| domain without subdomains. Typically, this shouldn't be changed.
|
||||
|
|
||||
*/
|
||||
|
||||
'domain' => env('SESSION_DOMAIN'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| HTTPS Only Cookies
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| By setting this option to true, session cookies will only be sent back
|
||||
| to the server if the browser has a HTTPS connection. This will keep
|
||||
| the cookie from being sent to you when it can't be done securely.
|
||||
|
|
||||
*/
|
||||
|
||||
'secure' => env('SESSION_SECURE_COOKIE'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| HTTP Access Only
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Setting this value to true will prevent JavaScript from accessing the
|
||||
| value of the cookie and the cookie will only be accessible through
|
||||
| the HTTP protocol. It's unlikely you should disable this option.
|
||||
|
|
||||
*/
|
||||
|
||||
'http_only' => env('SESSION_HTTP_ONLY', true),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Same-Site Cookies
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option determines how your cookies behave when cross-site requests
|
||||
| take place, and can be used to mitigate CSRF attacks. By default, we
|
||||
| will set this value to "lax" to permit secure cross-site requests.
|
||||
|
|
||||
| See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value
|
||||
|
|
||||
| Supported: "lax", "strict", "none", null
|
||||
|
|
||||
*/
|
||||
|
||||
'same_site' => env('SESSION_SAME_SITE', 'lax'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Partitioned Cookies
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Setting this value to true will tie the cookie to the top-level site for
|
||||
| a cross-site context. Partitioned cookies are accepted by the browser
|
||||
| when flagged "secure" and the Same-Site attribute is set to "none".
|
||||
|
|
||||
*/
|
||||
|
||||
'partitioned' => env('SESSION_PARTITIONED_COOKIE', false),
|
||||
|
||||
];
|
||||
1
database/.gitignore
vendored
Executable file
1
database/.gitignore
vendored
Executable file
@ -0,0 +1 @@
|
||||
*.sqlite*
|
||||
44
database/factories/UserFactory.php
Executable file
44
database/factories/UserFactory.php
Executable file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
|
||||
*/
|
||||
class UserFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* The current password being used by the factory.
|
||||
*/
|
||||
protected static ?string $password;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'name' => fake()->name(),
|
||||
'email' => fake()->unique()->safeEmail(),
|
||||
'email_verified_at' => now(),
|
||||
'password' => static::$password ??= Hash::make('password'),
|
||||
'remember_token' => Str::random(10),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that the model's email address should be unverified.
|
||||
*/
|
||||
public function unverified(): static
|
||||
{
|
||||
return $this->state(fn (array $attributes) => [
|
||||
'email_verified_at' => null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
49
database/migrations/0001_01_01_000000_create_users_table.php
Executable file
49
database/migrations/0001_01_01_000000_create_users_table.php
Executable file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('users', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('email')->unique();
|
||||
$table->timestamp('email_verified_at')->nullable();
|
||||
$table->string('password');
|
||||
$table->rememberToken();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('password_reset_tokens', function (Blueprint $table) {
|
||||
$table->string('email')->primary();
|
||||
$table->string('token');
|
||||
$table->timestamp('created_at')->nullable();
|
||||
});
|
||||
|
||||
Schema::create('sessions', function (Blueprint $table) {
|
||||
$table->string('id')->primary();
|
||||
$table->foreignId('user_id')->nullable()->index();
|
||||
$table->string('ip_address', 45)->nullable();
|
||||
$table->text('user_agent')->nullable();
|
||||
$table->longText('payload');
|
||||
$table->integer('last_activity')->index();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('users');
|
||||
Schema::dropIfExists('password_reset_tokens');
|
||||
Schema::dropIfExists('sessions');
|
||||
}
|
||||
};
|
||||
35
database/migrations/0001_01_01_000001_create_cache_table.php
Executable file
35
database/migrations/0001_01_01_000001_create_cache_table.php
Executable file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('cache', function (Blueprint $table) {
|
||||
$table->string('key')->primary();
|
||||
$table->mediumText('value');
|
||||
$table->integer('expiration');
|
||||
});
|
||||
|
||||
Schema::create('cache_locks', function (Blueprint $table) {
|
||||
$table->string('key')->primary();
|
||||
$table->string('owner');
|
||||
$table->integer('expiration');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('cache');
|
||||
Schema::dropIfExists('cache_locks');
|
||||
}
|
||||
};
|
||||
57
database/migrations/0001_01_01_000002_create_jobs_table.php
Executable file
57
database/migrations/0001_01_01_000002_create_jobs_table.php
Executable file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('jobs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('queue')->index();
|
||||
$table->longText('payload');
|
||||
$table->unsignedTinyInteger('attempts');
|
||||
$table->unsignedInteger('reserved_at')->nullable();
|
||||
$table->unsignedInteger('available_at');
|
||||
$table->unsignedInteger('created_at');
|
||||
});
|
||||
|
||||
Schema::create('job_batches', function (Blueprint $table) {
|
||||
$table->string('id')->primary();
|
||||
$table->string('name');
|
||||
$table->integer('total_jobs');
|
||||
$table->integer('pending_jobs');
|
||||
$table->integer('failed_jobs');
|
||||
$table->longText('failed_job_ids');
|
||||
$table->mediumText('options')->nullable();
|
||||
$table->integer('cancelled_at')->nullable();
|
||||
$table->integer('created_at');
|
||||
$table->integer('finished_at')->nullable();
|
||||
});
|
||||
|
||||
Schema::create('failed_jobs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('uuid')->unique();
|
||||
$table->text('connection');
|
||||
$table->text('queue');
|
||||
$table->longText('payload');
|
||||
$table->longText('exception');
|
||||
$table->timestamp('failed_at')->useCurrent();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('jobs');
|
||||
Schema::dropIfExists('job_batches');
|
||||
Schema::dropIfExists('failed_jobs');
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('upload_images', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('sPath');
|
||||
$table->string('sFileType');
|
||||
$table->morphs('model');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('upload_images');
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('slave_sites', function (Blueprint $table) {
|
||||
$table->id();
|
||||
// 核心代码,绝对不能空!
|
||||
// $table->string('sCode')->unique(); // 建议加上唯一索引,代码重了还怎么混?
|
||||
|
||||
// 以下全部放行,允许为空
|
||||
$table->string('sName')->nullable();
|
||||
$table->json('aConfigs')->nullable();
|
||||
$table->json('aPaths')->nullable();
|
||||
$table->string('sMemo')->nullable();
|
||||
|
||||
$table->timestamps(); // 硬汉必备,没时间戳你怎么知道谁是新兵谁是老兵?
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('slave_site');
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::rename('slave_sites', 'servers');
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::rename('servers', 'slave_sites');
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
// 1. 正式改名:把 servers 改成 members
|
||||
Schema::rename('servers', 'members');
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::rename('members', 'servers');
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
// 1. 阵地转移:members -> gems
|
||||
Schema::rename('members', 'gems');
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::rename('gems', 'members');
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('images', function (Blueprint $table) {
|
||||
// 简单暴力,直接删除
|
||||
$table->dropColumn('group');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('images', function (Blueprint $table) {
|
||||
// 撤回时记得加回来(假设原来是字符串)
|
||||
$table->string('group')->nullable();
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('upload_images', function (Blueprint $table) {
|
||||
// 在 id 后面增加 sArticleCode,并建立索引提高匹配效率
|
||||
$table->string('sArticleCode')->nullable()->after('id')->index();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('upload_images', function (Blueprint $table) {
|
||||
$table->dropColumn('sArticleCode');
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('articles', function (Blueprint $table) {
|
||||
// 阿诺提醒:morphs('oSubject') 会生成 oSubject_id 和 oSubject_type
|
||||
// 并且会自动建立复合索引,非常扎实!
|
||||
$table->morphs('oSubject');
|
||||
|
||||
// 如果你之前已经有数据了,建议给这两个字段允许 null,防止迁移报错
|
||||
// $table->morphs('oSubject')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('articles', function (Blueprint $table) {
|
||||
// 降级操作:把这两个字段和索引一并撤销,保持数据库清爽
|
||||
$table->dropMorphs('oSubject');
|
||||
});
|
||||
}
|
||||
};
|
||||
29
database/migrations/slave_sites-2 2.php
Normal file
29
database/migrations/slave_sites-2 2.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('slave_sites', function (Blueprint $table) {
|
||||
// 1. 先把那个重名的唯一索引给我卸了!
|
||||
$table->dropUnique(['sCode']);
|
||||
});
|
||||
|
||||
Schema::table('slave_sites', function (Blueprint $table) {
|
||||
// 2. 重新冲锋:修改长度并重新建立唯一约束
|
||||
$table->string('sCode', 100)->unique()->change();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('slave_sites', function (Blueprint $table) {
|
||||
// 撤退逻辑:按需写
|
||||
$table->string('sCode', 255)->change();
|
||||
});
|
||||
}
|
||||
};
|
||||
24
database/migrations/slave_sites-2.php
Normal file
24
database/migrations/slave_sites-2.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('slave_sites', function (Blueprint $table) {
|
||||
// 1. 不允许为空 2. 必须唯一 3. 放在 id 后面
|
||||
$table->string('sCode')->unique()->after('id');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('slave_sites', function (Blueprint $table) {
|
||||
// 撤退时,如果是 SQLite 以外的数据库,直接删字段就行
|
||||
$table->dropColumn('sCode');
|
||||
});
|
||||
}
|
||||
};
|
||||
23
database/migrations/upload_images-2.php
Normal file
23
database/migrations/upload_images-2.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('upload_images', function (Blueprint $table) {
|
||||
// 在 sFileType 后面追加字段,匈牙利命名法安排上
|
||||
$table->string('sMasterOpt')->nullable()->after('sFileType');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('upload_images', function (Blueprint $table) {
|
||||
$table->dropColumn('sMasterOpt');
|
||||
});
|
||||
}
|
||||
};
|
||||
25
database/seeders/DatabaseSeeder.php
Executable file
25
database/seeders/DatabaseSeeder.php
Executable file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class DatabaseSeeder extends Seeder
|
||||
{
|
||||
use WithoutModelEvents;
|
||||
|
||||
/**
|
||||
* Seed the application's database.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
// User::factory(10)->create();
|
||||
|
||||
User::factory()->create([
|
||||
'name' => 'Test User',
|
||||
'email' => 'test@example.com',
|
||||
]);
|
||||
}
|
||||
}
|
||||
17
package.json
Executable file
17
package.json
Executable file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"$schema": "https://www.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"dev": "vite"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
"axios": "^1.11.0",
|
||||
"concurrently": "^9.0.1",
|
||||
"laravel-vite-plugin": "^2.0.0",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"vite": "^7.0.7"
|
||||
}
|
||||
}
|
||||
35
phpunit.xml
Executable file
35
phpunit.xml
Executable file
@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
|
||||
bootstrap="vendor/autoload.php"
|
||||
colors="true"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="Unit">
|
||||
<directory>tests/Unit</directory>
|
||||
</testsuite>
|
||||
<testsuite name="Feature">
|
||||
<directory>tests/Feature</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<source>
|
||||
<include>
|
||||
<directory>app</directory>
|
||||
</include>
|
||||
</source>
|
||||
<php>
|
||||
<env name="APP_ENV" value="testing"/>
|
||||
<env name="APP_MAINTENANCE_DRIVER" value="file"/>
|
||||
<env name="BCRYPT_ROUNDS" value="4"/>
|
||||
<env name="BROADCAST_CONNECTION" value="null"/>
|
||||
<env name="CACHE_STORE" value="array"/>
|
||||
<env name="DB_CONNECTION" value="sqlite"/>
|
||||
<env name="DB_DATABASE" value=":memory:"/>
|
||||
<env name="MAIL_MAILER" value="array"/>
|
||||
<env name="QUEUE_CONNECTION" value="sync"/>
|
||||
<env name="SESSION_DRIVER" value="array"/>
|
||||
<env name="PULSE_ENABLED" value="false"/>
|
||||
<env name="TELESCOPE_ENABLED" value="false"/>
|
||||
<env name="NIGHTWATCH_ENABLED" value="false"/>
|
||||
</php>
|
||||
</phpunit>
|
||||
25
public/.htaccess
Executable file
25
public/.htaccess
Executable file
@ -0,0 +1,25 @@
|
||||
<IfModule mod_rewrite.c>
|
||||
<IfModule mod_negotiation.c>
|
||||
Options -MultiViews -Indexes
|
||||
</IfModule>
|
||||
|
||||
RewriteEngine On
|
||||
|
||||
# Handle Authorization Header
|
||||
RewriteCond %{HTTP:Authorization} .
|
||||
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
|
||||
|
||||
# Handle X-XSRF-Token Header
|
||||
RewriteCond %{HTTP:x-xsrf-token} .
|
||||
RewriteRule .* - [E=HTTP_X_XSRF_TOKEN:%{HTTP:X-XSRF-Token}]
|
||||
|
||||
# Redirect Trailing Slashes If Not A Folder...
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteCond %{REQUEST_URI} (.+)/$
|
||||
RewriteRule ^ %1 [L,R=301]
|
||||
|
||||
# Send Requests To Front Controller...
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteRule ^ index.php [L]
|
||||
</IfModule>
|
||||
0
public/favicon.ico
Executable file
0
public/favicon.ico
Executable file
42
public/index.php
Executable file
42
public/index.php
Executable file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
define('LARAVEL_START', microtime(true));
|
||||
|
||||
function t($xData)
|
||||
{
|
||||
_t($xData);
|
||||
}
|
||||
|
||||
function tt($xData)
|
||||
{
|
||||
_t($xData, 1);
|
||||
}
|
||||
|
||||
function _t($aData, $iType = 0)
|
||||
{
|
||||
if (env("is_loc") == true) {
|
||||
echo "<pre>";
|
||||
var_dump($aData);
|
||||
|
||||
if ($iType == 1) {
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Determine if the application is in maintenance mode...
|
||||
if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) {
|
||||
require $maintenance;
|
||||
}
|
||||
|
||||
// Register the Composer autoloader...
|
||||
require __DIR__.'/../vendor/autoload.php';
|
||||
|
||||
// Bootstrap Laravel and handle the request...
|
||||
/** @var Application $app */
|
||||
$app = require_once __DIR__.'/../bootstrap/app.php';
|
||||
|
||||
$app->handleRequest(Request::capture());
|
||||
2
public/robots.txt
Executable file
2
public/robots.txt
Executable file
@ -0,0 +1,2 @@
|
||||
User-agent: *
|
||||
Disallow:
|
||||
11
resources/css/app.css
Executable file
11
resources/css/app.css
Executable file
@ -0,0 +1,11 @@
|
||||
@import 'tailwindcss';
|
||||
|
||||
@source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php';
|
||||
@source '../../storage/framework/views/*.php';
|
||||
@source '../**/*.blade.php';
|
||||
@source '../**/*.js';
|
||||
|
||||
@theme {
|
||||
--font-sans: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
|
||||
'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
}
|
||||
1
resources/js/app.js
Executable file
1
resources/js/app.js
Executable file
@ -0,0 +1 @@
|
||||
import './bootstrap';
|
||||
4
resources/js/bootstrap.js
vendored
Executable file
4
resources/js/bootstrap.js
vendored
Executable file
@ -0,0 +1,4 @@
|
||||
import axios from 'axios';
|
||||
window.axios = axios;
|
||||
|
||||
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||
277
resources/views/welcome.blade.php
Executable file
277
resources/views/welcome.blade.php
Executable file
File diff suppressed because one or more lines are too long
43
routes/console.php
Executable file
43
routes/console.php
Executable file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Foundation\Inspiring;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
Artisan::command('inspire', function () {
|
||||
$this->comment(Inspiring::quote());
|
||||
})->purpose('Display an inspiring quote');
|
||||
|
||||
Artisan::command('db:reCreate {sTableName?}', function ($sTableName = null) {
|
||||
// 1. 没传参数?直接亮红牌!
|
||||
if (empty($sTableName)) {
|
||||
$this->error('错误:必须指定要重建的表名!我可不想乱开火。');
|
||||
$this->info('用法示例:php artisan db:reCreate slave_sites');
|
||||
return; // 直接撤退,不往下走
|
||||
}
|
||||
|
||||
// 2. 确定目标
|
||||
$sTable = $sTableName;
|
||||
|
||||
// 3. 检查表是否存在(不存在炸个寂寞?)
|
||||
if (!Schema::hasTable($sTable)) {
|
||||
$this->warn("目标表 [{$sTable}] 本就不存在,但我还是会清理迁移记录并尝试重建。");
|
||||
}
|
||||
|
||||
// 4. 物理抹除表
|
||||
Schema::dropIfExists($sTable);
|
||||
|
||||
// 5. 撕掉迁移记录
|
||||
// 针对你的匈牙利命名或标准命名,用 snake 匹配最稳
|
||||
$sMatch = Str::snake($sTable);
|
||||
DB::table('migrations')->where('migration', 'like', "%{$sMatch}%")->delete();
|
||||
|
||||
$this->warn("已爆破表及记录: {$sTable}");
|
||||
|
||||
// 6. 重新集结
|
||||
$this->comment('正在根据迁移文件重新创建表...');
|
||||
Artisan::call('migrate');
|
||||
|
||||
$this->info("表 [{$sTable}] 重建成功!任务完成!");
|
||||
})->purpose('定点爆破并重建指定的表,不带参数不动手');
|
||||
21
routes/web.php
Executable file
21
routes/web.php
Executable file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use App\Http\Controllers\TestController;
|
||||
use App\Http\Controllers\Api\Tg\HookController;
|
||||
use App\Http\Controllers\Api\ArticleSchemaController;
|
||||
use App\Http\Middleware\ApiCheckToken;
|
||||
use App\Http\Controllers\Api\Images\GetController as ApiImagesGetController;
|
||||
|
||||
Route::get('/', function () {
|
||||
return view('welcome');
|
||||
});
|
||||
|
||||
Route::get('/hello', [TestController::class, 'hello']);
|
||||
|
||||
Route::any('/api/tg/hook', [HookController::class, 'cmd']);
|
||||
|
||||
Route::middleware([ApiCheckToken::class])->group(function () {
|
||||
Route::post("/api/article/schema/receive", [ArticleSchemaController::class, "receive"]);
|
||||
Route::post("/api/images/get/url-by-num", [ApiImagesGetController::class, "urlByNum"]);
|
||||
});
|
||||
4
storage/app/.gitignore
vendored
Executable file
4
storage/app/.gitignore
vendored
Executable file
@ -0,0 +1,4 @@
|
||||
*
|
||||
!private/
|
||||
!public/
|
||||
!.gitignore
|
||||
2
storage/app/private/.gitignore
vendored
Executable file
2
storage/app/private/.gitignore
vendored
Executable file
@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
2
storage/app/public/.gitignore
vendored
Executable file
2
storage/app/public/.gitignore
vendored
Executable file
@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
9
storage/framework/.gitignore
vendored
Executable file
9
storage/framework/.gitignore
vendored
Executable file
@ -0,0 +1,9 @@
|
||||
compiled.php
|
||||
config.php
|
||||
down
|
||||
events.scanned.php
|
||||
maintenance.php
|
||||
routes.php
|
||||
routes.scanned.php
|
||||
schedule-*
|
||||
services.json
|
||||
3
storage/framework/cache/.gitignore
vendored
Executable file
3
storage/framework/cache/.gitignore
vendored
Executable file
@ -0,0 +1,3 @@
|
||||
*
|
||||
!data/
|
||||
!.gitignore
|
||||
2
storage/framework/cache/data/.gitignore
vendored
Executable file
2
storage/framework/cache/data/.gitignore
vendored
Executable file
@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
2
storage/framework/sessions/.gitignore
vendored
Executable file
2
storage/framework/sessions/.gitignore
vendored
Executable file
@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
2
storage/framework/testing/.gitignore
vendored
Executable file
2
storage/framework/testing/.gitignore
vendored
Executable file
@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
2
storage/framework/views/.gitignore
vendored
Executable file
2
storage/framework/views/.gitignore
vendored
Executable file
@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
2
storage/logs/.gitignore
vendored
Executable file
2
storage/logs/.gitignore
vendored
Executable file
@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
19
tests/Feature/ExampleTest.php
Executable file
19
tests/Feature/ExampleTest.php
Executable file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
// use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ExampleTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* A basic test example.
|
||||
*/
|
||||
public function test_the_application_returns_a_successful_response(): void
|
||||
{
|
||||
$response = $this->get('/');
|
||||
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
}
|
||||
10
tests/TestCase.php
Executable file
10
tests/TestCase.php
Executable file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Tests;
|
||||
|
||||
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
|
||||
|
||||
abstract class TestCase extends BaseTestCase
|
||||
{
|
||||
//
|
||||
}
|
||||
16
tests/Unit/ExampleTest.php
Executable file
16
tests/Unit/ExampleTest.php
Executable file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ExampleTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* A basic test example.
|
||||
*/
|
||||
public function test_that_true_is_true(): void
|
||||
{
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
}
|
||||
18
vite.config.js
Executable file
18
vite.config.js
Executable file
@ -0,0 +1,18 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import laravel from 'laravel-vite-plugin';
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
laravel({
|
||||
input: ['resources/css/app.css', 'resources/js/app.js'],
|
||||
refresh: true,
|
||||
}),
|
||||
tailwindcss(),
|
||||
],
|
||||
server: {
|
||||
watch: {
|
||||
ignored: ['**/storage/framework/views/**'],
|
||||
},
|
||||
},
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user