Files
nexusphp/app/Repositories/ToolRepository.php

531 lines
22 KiB
PHP
Raw Normal View History

2021-05-02 17:24:05 +08:00
<?php
namespace App\Repositories;
2024-10-20 23:47:40 +08:00
use App\Http\Middleware\Locale;
2022-12-13 13:51:39 +08:00
use App\Models\Invite;
2022-03-31 16:28:08 +08:00
use App\Models\Message;
use App\Models\News;
use App\Models\Poll;
use App\Models\PollAnswer;
2021-05-15 01:24:44 +08:00
use App\Models\Setting;
2022-03-31 16:28:08 +08:00
use App\Models\User;
2022-03-31 22:22:04 +08:00
use Carbon\Carbon;
2022-08-22 21:07:06 +08:00
use Illuminate\Support\Arr;
2022-08-24 00:19:19 +08:00
use Illuminate\Support\Collection;
2022-05-12 19:03:30 +08:00
use Illuminate\Support\Facades\Storage;
2022-12-13 13:51:39 +08:00
use Illuminate\Support\Str;
2022-08-22 21:07:06 +08:00
use Nexus\Database\NexusDB;
use Nexus\Plugin\Plugin;
use NexusPlugin\Permission\PermissionRepository;
2022-03-31 22:22:04 +08:00
use Symfony\Component\Mailer\Transport\Dsn;
use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransportFactory;
use Symfony\Component\Mailer\Mailer;
use Symfony\Component\Mime\Address;
use Symfony\Component\Mime\Email;
2021-05-02 17:24:05 +08:00
class ToolRepository extends BaseRepository
{
2022-08-14 20:58:25 +08:00
const BACKUP_EXCLUDES = ['vendor', 'node_modules', '.git', '.idea', '.settings', '.DS_Store', '.github'];
2022-09-18 17:20:51 +08:00
public function backupWeb($method = null, $transfer = false): array
2021-05-02 17:24:05 +08:00
{
$webRoot = base_path();
$dirName = basename($webRoot);
2022-08-14 20:58:25 +08:00
$excludes = self::BACKUP_EXCLUDES;
2022-05-14 15:19:10 +08:00
$baseFilename = sprintf('%s/%s.web.%s', sys_get_temp_dir(), $dirName, date('Ymd.His'));
if (command_exists('tar') && ($method === 'tar' || $method === null)) {
$filename = $baseFilename . ".tar.gz";
$command = "tar";
foreach ($excludes as $item) {
2022-08-13 17:03:12 +08:00
$command .= " --exclude=$dirName/$item";
2022-05-14 15:19:10 +08:00
}
$command .= sprintf(
2024-08-01 10:56:55 +08:00
' -czf %s -C %s %s 2>&1',
2022-05-14 15:19:10 +08:00
$filename, dirname($webRoot), $dirName
);
$result = exec($command, $output, $result_code);
do_log(sprintf(
"command: %s, output: %s, result_code: %s, result: %s, filename: %s",
$command, json_encode($output), $result_code, $result, $filename
));
} else {
//use php zip
$filename = $baseFilename . ".zip";
$zip = new \ZipArchive();
$zipOpen = $zip->open($filename, \ZipArchive::CREATE);
if ($zipOpen !== true) {
throw new \RuntimeException("Can not open $filename, error: $zipOpen");
}
// create recursive directory iterator
$files = new \RecursiveIteratorIterator (new \RecursiveDirectoryIterator($webRoot, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY);
// let's iterate
foreach ($files as $name => $file) {
$localeName = substr($name, strlen($webRoot) + 1);
$start = strstr($localeName, DIRECTORY_SEPARATOR, true) ?: $localeName;
//add a directory
$localeName = $dirName . DIRECTORY_SEPARATOR . $localeName;
if (!in_array($start, $excludes)) {
if (is_file($name)) {
$zip->addFile($name, $localeName);
} elseif (is_dir($name)) {
do_log("Is dir: $name.");
$zip->addEmptyDir($localeName);
} else {
do_log("Not file or dir $name.", 'error');
}
}
}
$zip->close();
$result_code = 0;
do_log("No tar command, use zip.");
}
2022-09-18 17:20:51 +08:00
if (!$transfer) {
return compact('result_code', 'filename');
}
return $this->transfer($filename, $result_code);
2021-05-02 17:24:05 +08:00
}
2022-09-18 17:20:51 +08:00
public function backupDatabase($transfer = false): array
2021-05-02 17:24:05 +08:00
{
$connectionName = config('database.default');
$config = config("database.connections.$connectionName");
$filename = sprintf('%s/%s.database.%s.sql', sys_get_temp_dir(), basename(base_path()), date('Ymd.His'));
$command = sprintf(
2024-08-01 10:56:55 +08:00
'mysqldump --user=%s --password=%s --host=%s --port=%s --single-transaction --no-create-db %s >> %s 2>&1',
$config['username'], $config['password'], $config['host'], $config['port'], $config['database'], $filename,
2021-05-02 17:24:05 +08:00
);
2021-05-05 22:28:19 +08:00
$result = exec($command, $output, $result_code);
2021-05-02 17:24:05 +08:00
do_log(sprintf(
2021-05-05 22:28:19 +08:00
"command: %s, output: %s, result_code: %s, result: %s, filename: %s",
$command, json_encode($output), $result_code, $result, $filename
2021-05-02 17:24:05 +08:00
));
2022-09-18 17:20:51 +08:00
if (!$transfer) {
return compact('result_code', 'filename');
}
return $this->transfer($filename, $result_code);
2021-05-02 17:24:05 +08:00
}
2022-09-18 17:20:51 +08:00
public function backupAll($method = null, $transfer = false): array
2021-05-02 17:24:05 +08:00
{
2022-05-14 15:19:10 +08:00
$backupWeb = $this->backupWeb($method);
2021-05-05 22:28:19 +08:00
if ($backupWeb['result_code'] != 0) {
2021-05-02 17:24:05 +08:00
throw new \RuntimeException("backup web fail: " . json_encode($backupWeb));
}
$backupDatabase = $this->backupDatabase();
2021-05-05 22:28:19 +08:00
if ($backupDatabase['result_code'] != 0) {
2021-05-02 17:24:05 +08:00
throw new \RuntimeException("backup database fail: " . json_encode($backupDatabase));
}
2022-05-14 15:19:10 +08:00
$baseFilename = sprintf('%s/%s.%s', sys_get_temp_dir(), basename(base_path()), date('Ymd.His'));
if (command_exists('tar') && ($method === 'tar' || $method === null)) {
$filename = $baseFilename . ".tar.gz";
$command = sprintf(
2024-08-01 10:56:55 +08:00
'tar -czf %s -C %s %s -C %s %s 2>&1',
2022-05-14 15:19:10 +08:00
$filename,
dirname($backupWeb['filename']), basename($backupWeb['filename']),
dirname($backupDatabase['filename']), basename($backupDatabase['filename'])
);
$result = exec($command, $output, $result_code);
do_log(sprintf(
"command: %s, output: %s, result_code: %s, result: %s, filename: %s",
$command, json_encode($output), $result_code, $result, $filename
));
} else {
//use php zip
$filename = $baseFilename . ".zip";
$zip = new \ZipArchive();
$zipOpen = $zip->open($filename, \ZipArchive::CREATE);
if ($zipOpen !== true) {
throw new \RuntimeException("Can not open $filename, error: $zipOpen");
}
$zip->addFile($backupWeb['filename'], basename($backupWeb['filename']));
$zip->addFile($backupDatabase['filename'], basename($backupDatabase['filename']));
$zip->close();
$result_code = 0;
do_log("No tar command, use zip.");
}
2022-09-18 17:20:51 +08:00
if (!$transfer) {
return compact('result_code', 'filename');
}
return $this->transfer($filename, $result_code);
2021-05-15 01:24:44 +08:00
}
/**
* do backup cronjob
*
* @return array|false
*/
2022-04-04 17:26:26 +08:00
public function cronjobBackup($force = false): bool|array
2021-05-15 01:24:44 +08:00
{
$setting = Setting::get('backup');
2022-04-04 17:26:26 +08:00
if ($setting['enabled'] != 'yes' && !$force) {
2021-05-15 01:24:44 +08:00
do_log("Backup not enabled.");
return false;
}
$now = now();
$frequency = $setting['frequency'];
$settingHour = (int)$setting['hour'];
$settingMinute = (int)$setting['minute'];
$nowHour = (int)$now->format('H');
2021-05-15 01:45:15 +08:00
$nowMinute = (int)$now->format('i');
2022-04-04 17:26:26 +08:00
do_log("Backup frequency: $frequency, force: " . strval($force));
if (!$force) {
if ($frequency == 'daily') {
if ($settingHour != $nowHour) {
do_log(sprintf('Backup setting hour: %s != now hour: %s', $settingHour, $nowHour));
return false;
}
if ($settingMinute != $nowMinute) {
do_log(sprintf('Backup setting minute: %s != now minute: %s', $settingMinute, $nowMinute));
return false;
}
} elseif ($frequency == 'hourly') {
if ($settingMinute != $nowMinute) {
do_log(sprintf('Backup setting minute: %s != now minute: %s', $settingMinute, $nowMinute));
return false;
}
} else {
throw new \RuntimeException("Unknown backup frequency: $frequency");
2021-05-15 01:24:44 +08:00
}
}
$backupResult = $this->backupAll();
do_log("Backup all result: " . json_encode($backupResult));
2022-09-18 17:20:51 +08:00
$transferResult = $this->transfer($backupResult['filename'], $backupResult['result_code'], $setting);
$backupResult['transfer_result'] = $transferResult;
do_log("[BACKUP_ALL_DONE]: " . json_encode($backupResult));
return $backupResult;
}
2022-05-12 19:03:30 +08:00
2022-09-18 17:20:51 +08:00
public function transfer($filename, $result_code, $setting = null): array
{
if ($result_code != 0) {
throw new \RuntimeException("file: $filename backup fail!");
}
$result = compact('filename', 'result_code');
if (empty($setting)) {
$setting = Setting::get('backup');
}
2022-05-12 19:03:30 +08:00
$saveResult = $this->saveToGoogleDrive($setting, $filename);
do_log("[BACKUP_GOOGLE_DRIVE]: $saveResult");
2022-09-18 17:20:51 +08:00
$result['google_drive'] = $saveResult;
2022-05-12 19:03:30 +08:00
$saveResult = $this->saveToFtp($setting, $filename);
do_log("[BACKUP_FTP]: $saveResult");
2022-09-18 17:20:51 +08:00
$result['ftp'] = $saveResult;
2022-05-12 19:03:30 +08:00
$saveResult = $this->saveToSftp($setting, $filename);
do_log("[BACKUP_SFTP]: $saveResult");
2022-09-18 17:20:51 +08:00
$result['sftp'] = $saveResult;
return $result;
2022-05-12 19:03:30 +08:00
}
private function saveToGoogleDrive(array $setting, $filename): bool|string
{
2021-05-15 01:24:44 +08:00
$clientId = $setting['google_drive_client_id'] ?? '';
$clientSecret = $setting['google_drive_client_secret'] ?? '';
$refreshToken = $setting['google_drive_refresh_token'] ?? '';
$folderId = $setting['google_drive_folder_id'] ?? '';
if (empty($clientId)) {
do_log("No google_drive_client_id, won't do upload.");
return false;
}
if (empty($clientSecret)) {
do_log("No google_drive_client_secret, won't do upload.");
return false;
}
if (empty($refreshToken)) {
do_log("No google_drive_refresh_token, won't do upload.");
return false;
}
do_log("Google drive info: clientId: $clientId, clientSecret: $clientSecret, refreshToken: $refreshToken, folderId: $folderId");
2022-03-31 22:22:04 +08:00
$client = new \Google\Client();
2021-05-15 01:24:44 +08:00
$client->setClientId($clientId);
$client->setClientSecret($clientSecret);
$client->refreshToken($refreshToken);
2022-03-31 22:22:04 +08:00
$service = new \Google\Service\Drive($client);
$adapter = new \Masbug\Flysystem\GoogleDriveAdapter($service, $folderId);
2022-04-04 17:26:26 +08:00
$filesystem = new \League\Flysystem\Filesystem($adapter);
2022-05-12 19:03:30 +08:00
$disk = new \Illuminate\Filesystem\FilesystemAdapter($filesystem, $adapter);
return $this->doTransfer($disk, $filename);
}
2022-03-31 22:22:04 +08:00
2022-05-12 19:03:30 +08:00
private function saveToFtp(array $setting, $filename): bool|string
{
if ($setting['via_ftp'] !== 'yes') {
do_log("via_ftp !== 'yes', via_ftp: " . $setting['via_ftp'] ?? '');
return false;
}
$config = config('filesystems.disks.ftp');
if (empty($config)) {
do_log("No ftp config.");
return false;
}
foreach (['host', 'username', 'password', 'root'] as $item) {
if (empty($config[$item])) {
do_log("No ftp $item.");
return false;
}
}
$disk = Storage::disk('ftp');
return $this->doTransfer($disk, $filename);
}
public function saveToSftp(array $setting, $filename): bool|string
{
if ($setting['via_sftp'] !== 'yes') {
do_log("via_sftp !== 'yes', via_sftp: " . $setting['via_sftp'] ?? '');
return false;
}
$config = config('filesystems.disks.sftp');
if (empty($config)) {
do_log("No sftp config.");
return false;
}
foreach (['host', 'username', 'password', 'root'] as $item) {
if (empty($config[$item])) {
do_log("No sftp $item.");
return false;
}
}
$disk = Storage::disk('sftp');
return $this->doTransfer($disk, $filename);
}
private function doTransfer(\Illuminate\Filesystem\FilesystemAdapter $remoteFilesystem, $filename): bool|string
{
2022-03-31 22:22:04 +08:00
$localAdapter = new \League\Flysystem\Local\LocalFilesystemAdapter('/');
2022-04-04 17:26:26 +08:00
$localFilesystem = new \League\Flysystem\Filesystem($localAdapter);
$start = Carbon::now();
2022-03-31 22:22:04 +08:00
try {
2022-05-12 19:03:30 +08:00
$remoteFilesystem->writeStream(basename($filename), $localFilesystem->readStream($filename));
2025-01-19 23:41:50 +08:00
$speed = !(float)abs($start->diffInSeconds()) ? 0 :filesize($filename) / (float)abs($start->diffInSeconds());
2022-04-04 17:26:26 +08:00
$log = 'Elapsed time: '.$start->diffForHumans(null, true);
2022-03-31 22:22:04 +08:00
$log .= ', Speed: '. number_format($speed/1024,2) . ' KB/s';
do_log($log);
2022-05-12 19:03:30 +08:00
return true;
2022-03-31 22:22:04 +08:00
} catch (\Throwable $exception) {
2022-05-12 19:03:30 +08:00
do_log("Transfer error: " . $exception->getMessage(), 'error');
return $exception->getMessage();
2022-03-31 22:22:04 +08:00
}
2021-05-02 17:24:05 +08:00
}
2021-06-01 23:33:28 +08:00
2022-03-26 04:27:04 +08:00
/**
* @param $to
* @param $subject
* @param $body
* @return bool
*/
2024-06-22 16:30:55 +08:00
public function sendMail($to, $subject, $body, $exception = false): bool
2022-03-26 04:27:04 +08:00
{
2022-03-31 22:22:04 +08:00
$log = "[SEND_MAIL]";
$factory = new EsmtpTransportFactory();
2022-04-04 17:26:26 +08:00
$smtp = Setting::getFromDb('smtp');
2022-08-16 00:09:34 +08:00
do_log("$log, to: $to, subject: $subject, body: $body, smtp: " . json_encode($smtp));
2022-03-26 04:27:04 +08:00
$encryption = null;
if (isset($smtp['encryption']) && in_array($smtp['encryption'], ['ssl', 'tls'])) {
$encryption = $smtp['encryption'];
}
2022-03-31 22:22:04 +08:00
// Create the Transport
$transport = $factory->create(new Dsn(
2025-01-20 00:47:31 +08:00
// $encryption === 'tls' ? (($smtp['smtpport'] == 465) ? 'smtps' : 'smtp') : '',
$smtp['smtpport'] == 465 && in_array($encryption, ['ssl', 'tls']) ? 'smtps' : 'smtp',
2022-03-31 22:22:04 +08:00
$smtp['smtpaddress'],
$smtp['accountname'] ?? null,
$smtp['accountpassword'] ?? null,
2022-05-02 15:26:47 +08:00
$smtp['smtpport'] ?? null,
['verify_peer' => false]
2022-03-31 22:22:04 +08:00
));
2022-03-26 04:27:04 +08:00
// Create the Mailer using your created Transport
2022-03-31 22:22:04 +08:00
$mailer = new Mailer($transport);
2022-03-26 04:27:04 +08:00
// Create a message
2022-03-31 22:22:04 +08:00
$message = (new Email())
2023-06-09 10:41:05 +08:00
->from(new Address(Setting::get('main.SITEEMAIL'), Setting::get('basic.SITENAME')))
2022-03-31 22:22:04 +08:00
->to($to)
->subject($subject)
2025-04-17 01:39:40 +07:00
->text($body)
->html(nl2br($body))
2022-03-26 04:27:04 +08:00
;
// Send the message
try {
2022-03-31 22:22:04 +08:00
$mailer->send($message);
2022-03-26 04:27:04 +08:00
return true;
2022-03-31 22:22:04 +08:00
} catch (\Throwable $e) {
do_log("$log, fail: " . $e->getMessage() . "\n" . $e->getTraceAsString(), 'error');
2024-06-22 16:30:55 +08:00
if ($exception) {
throw $e;
} else {
return false;
}
2022-03-26 04:27:04 +08:00
}
}
2022-03-31 16:28:08 +08:00
public function getNotificationCount(User $user): array
{
$result = [];
//attend or not
$attendRep = new AttendanceRepository();
$attendance = $attendRep->getAttendance($user->id, date('Ymd'));
$result['attendance'] = $attendance ? 0 : 1;
//unread news
$count = News::query()->where('added', '>', $user->last_home)->count();
$result['news'] = $count;
//unread messages
$count = Message::query()->where('receiver', $user->id)->where('unread', 'yes')->count();
$result['message'] = $count;
//un-vote poll
$total = Poll::query()->count();
$userVoteCount = PollAnswer::query()->where('userid', $user->id)->selectRaw('count(distinct(pollid)) as counts')->first()->counts;
$result['poll'] = $total - $userVoteCount;
return $result;
}
2022-05-14 15:19:10 +08:00
2022-08-24 13:36:14 +08:00
public static function listUserClassPermissions($class): array
2022-08-22 21:07:06 +08:00
{
2022-08-24 00:19:19 +08:00
$settings = Setting::get('authority');
$result = [];
foreach ($settings as $permission => $minClass) {
2022-08-24 13:36:14 +08:00
if ($minClass >= User::CLASS_PEASANT && $minClass <= $class) {
2022-08-24 00:19:19 +08:00
$result[] = $permission;
}
}
return $result;
2022-08-22 21:07:06 +08:00
}
2022-08-24 13:36:14 +08:00
public static function listUserAllPermissions($uid): array
2022-08-22 21:07:06 +08:00
{
2022-08-24 00:19:19 +08:00
static $uidPermissionsCached = [];
if (isset($uidPermissionsCached[$uid])) {
return $uidPermissionsCached[$uid];
}
$log = "uid: $uid";
2022-08-24 13:36:14 +08:00
$userInfo = get_user_row($uid);
$class = $userInfo['class'];
//Class permission
$classPermissions = self::listUserClassPermissions($class);
//Role permission
$rolePermissions = apply_filter("user_role_permissions", [], $uid);
//Direct permission
$directPermissions = apply_filter("user_direct_permissions", [], $uid);
$allPermissions = array_merge($classPermissions, $rolePermissions, $directPermissions);
do_log("$log, allPermissions: " . json_encode($allPermissions));
2022-08-24 00:19:19 +08:00
$result = array_combine($allPermissions, $allPermissions);
$uidPermissionsCached[$uid] = $result;
return $result;
2022-08-22 21:07:06 +08:00
}
2022-12-13 13:51:39 +08:00
public function generateUniqueInviteHash(array $hashArr, int $total, int $left, int $deep = 0): array
{
do_log("total: $total, left: $left, deep: $deep");
if ($deep > 10) {
throw new \RuntimeException("deep: $deep > 10");
}
if (count($hashArr) >= $total) {
return array_slice(array_values($hashArr), 0, $total);
}
for ($i = 0; $i < $left; $i++) {
$hash = Str::random(32);
$hashArr[$hash] = $hash;
}
$exists = Invite::query()->whereIn('hash', array_values($hashArr))->get(['id', 'hash']);
foreach($exists as $value) {
unset($hashArr[$value->hash]);
}
return $this->generateUniqueInviteHash($hashArr, $total, $total - count($hashArr), ++$deep);
}
public function removeDuplicateSnatch()
{
$size = 2000;
$stickyPromotionParticipatorsTable = 'sticky_promotion_participators';
2023-06-14 02:08:54 +08:00
$claimTable = "claims";
$hitAndRunTable = "hit_and_runs";
$stickyPromotionExists = NexusDB::hasTable($stickyPromotionParticipatorsTable);
2023-06-14 02:08:54 +08:00
$claimTableExists = NexusDB::hasTable($claimTable);
$hitAndRunTableExists = NexusDB::hasTable($hitAndRunTable);
while (true) {
$snatchRes = NexusDB::select("select userid, torrentid, group_concat(id) as ids from snatched group by userid, torrentid having(count(*)) > 1 limit $size");
if (empty($snatchRes)) {
break;
}
do_log("[DELETE_DUPLICATED_SNATCH], count: " . count($snatchRes));
foreach ($snatchRes as $snatchRow) {
$torrentId = $snatchRow['torrentid'];
$userId = $snatchRow['userid'];
$idArr = explode(',', $snatchRow['ids']);
sort($idArr, SORT_NUMERIC);
$remainId = array_pop($idArr);
$delIdStr = implode(',', $idArr);
do_log("[DELETE_DUPLICATED_SNATCH], torrent: $torrentId, user: $userId, snatchIdStr: $delIdStr");
NexusDB::statement("delete from snatched where id in ($delIdStr)");
2023-06-14 02:08:54 +08:00
if ($claimTableExists) {
NexusDB::statement("update $claimTable set snatched_id = $remainId where torrent_id = $torrentId and uid = $userId");
}
if ($hitAndRunTableExists) {
NexusDB::statement("update $hitAndRunTable set snatched_id = $remainId where torrent_id = $torrentId and uid = $userId");
}
if ($stickyPromotionExists) {
NexusDB::statement("update $stickyPromotionParticipatorsTable set snatched_id = $remainId where torrent_id = $torrentId and uid = $userId");
}
}
}
}
public function removeDuplicatePeer()
{
$size = 2000;
while (true) {
2023-06-17 23:46:29 +08:00
$results = NexusDB::select("select torrent, userid, group_concat(id) as ids from peers group by torrent, peer_id, userid having(count(*)) > 1 limit $size");
if (empty($results)) {
2023-06-17 23:46:29 +08:00
do_log("[DELETE_DUPLICATED_PEERS], no data: ". last_query());
break;
}
do_log("[DELETE_DUPLICATED_PEERS], count: " . count($results));
foreach ($results as $row) {
$torrentId = $row['torrent'];
$userId = $row['userid'];
$idArr = explode(',', $row['ids']);
sort($idArr, SORT_NUMERIC);
$remainId = array_pop($idArr);
$delIdStr = implode(',', $idArr);
do_log("[DELETE_DUPLICATED_PEERS], torrent: $torrentId, user: $userId, snatchIdStr: $delIdStr");
NexusDB::statement("delete from peers where id in ($delIdStr)");
}
}
}
2024-10-20 23:47:40 +08:00
public function sendAlarmEmail(string $subjectTransKey, array $subjectTransContext, string $msgTransKey, array $msgTransContext): void
{
$receiverUid = get_setting("system.alarm_email_receiver");
if (empty($receiverUid)) {
$locale = Locale::getDefault();
$subject = nexus_trans($subjectTransKey, $subjectTransContext, $locale);
$msg = nexus_trans($msgTransKey, $msgTransContext, $locale);
do_log(sprintf("%s - %s", $subject, $msg), "error");
} else {
$receiverUidArr = preg_split("/[\r\n\s,]+/", $receiverUid);
$users = User::query()->whereIn("id", $receiverUidArr)->get(User::$commonFields);
foreach ($users as $user) {
$locale = $user->locale;
$subject = nexus_trans($subjectTransKey, $subjectTransContext, $locale);
$msg = nexus_trans($msgTransKey, $msgTransContext, $locale);
$result = $this->sendMail($user->email, $subject, $msg);
do_log(sprintf("send msg: %s result: %s", $msg, var_export($result, true)), $result ? "info" : "error");
}
}
}
2021-05-02 17:24:05 +08:00
}