需要直接拿去用

<?php
/**
* 宝塔备份邮件发送系统 – 多目录支持版
*/

// ============ 配置区域 =============
$CONFIG = [
// SMTP服务器配置
‘SMTP’ => [
‘HOST’ => ‘smtp.qq.com’,
‘PORT’ => 465,
‘USERNAME’ => ‘8111216@qq.com’,
‘PASSWORD’ => ‘你的授权key,不是密码’,
‘FROM_EMAIL’ => ‘8111216@qq.com’,
‘FROM_NAME’ => ‘网站备份系统’
],

// 收件人列表
‘RECIPIENTS’ => [
‘yfd83@163.com’,
],

// 多个备份目录配置,换成你的目录
‘BACKUP_DIRS’ => [
‘crm’ => ‘/www/wwwroot/xx.sjcaiji.com/backup/database/mysql/crontab_backup/crm’,
‘aacrm’ => ‘/www/wwwroot/xx.sjcaiji.com/backup/database/mysql/crontab_backup/aacrm’,
// 可以继续添加更多目录
// ‘site1’ => ‘/path/to/site1/backup’,
// ‘site2’ => ‘/path/to/site2/backup’,
],

// 支持的文件类型
‘BACKUP_TYPES’ => [‘.sql’, ‘.gz’, ‘.zip’, ‘.tar’, ‘.7z’, ‘.rar’],

// 其他设置
‘MAX_FILE_SIZE’ => 50 * 1024 * 1024,
‘MAX_FILES_SEND’ => 5,
‘MAX_FILES_PER_DIR’ => 10,
‘LOG_FILE’ => ‘/www/wwwroot/xx.sjcaiji.com/logs/backup_email.log’
];

// ============ 错误处理 ============
error_reporting(E_ALL & ~E_NOTICE);
ini_set(‘display_errors’, 0);

// ============ 核心功能类 ============
class BackupMailSender {
private $config;
private $logFile;

public function __construct($config) {
$this->config = $config;
$this->logFile = $config[‘LOG_FILE’];

$logDir = dirname($this->logFile);
if (!is_dir($logDir)) {
@mkdir($logDir, 0755, true);
}
}

/**
* 写入日志
*/
private function log($message) {
$time = date(‘Y-m-d H:i:s’);
$logMessage = “[{$time}] {$message}\n”;
@file_put_contents($this->logFile, $logMessage, FILE_APPEND);
}

/**
* 格式化文件大小
*/
private function formatSize($bytes) {
if ($bytes >= 1073741824) {
return number_format($bytes / 1073741824, 2) . ‘ GB’;
} elseif ($bytes >= 1048576) {
return number_format($bytes / 1048576, 2) . ‘ MB’;
} elseif ($bytes >= 1024) {
return number_format($bytes / 1024, 2) . ‘ KB’;
} elseif ($bytes > 1) {
return $bytes . ‘ 字节’;
} elseif ($bytes == 1) {
return ‘1 字节’;
} else {
return ‘0 字节’;
}
}

/**
* 获取单个目录的备份文件列表
*/
private function getDirBackupFiles($dirName, $dirPath, $limit = 10) {
$this->log(“正在搜索备份目录[{$dirName}]: {$dirPath}”);

// 检查目录是否存在
if (!is_dir($dirPath)) {
$this->log(“备份目录不存在: {$dirPath}”);
return [
‘success’ => false,
‘message’ => “备份目录不存在”,
‘dir_name’ => $dirName,
‘dir_path’ => $dirPath
];
}

// 检查目录权限
if (!is_readable($dirPath)) {
$perms = substr(sprintf(‘%o’, fileperms($dirPath)), -4);
$this->log(“备份目录无读取权限: {$dirPath},权限:{$perms}”);
return [
‘success’ => false,
‘message’ => “备份目录无读取权限”,
‘dir_name’ => $dirName,
‘dir_path’ => $dirPath,
‘permissions’ => $perms
];
}

// 扫描目录
$items = @scandir($dirPath);

if ($items === false) {
$this->log(“无法读取备份目录: {$dirPath}”);
return [
‘success’ => false,
‘message’ => “无法读取备份目录”,
‘dir_name’ => $dirName,
‘dir_path’ => $dirPath
];
}

$files = [];
foreach ($items as $item) {
if ($item == ‘.’ || $item == ‘..’) continue;

$filePath = $dirPath . ‘/’ . $item;

// 检查是否为文件
if (!is_file($filePath)) {
continue;
}

// 检查文件类型
$ext = strtolower(strrchr($item, ‘.’));
if (!empty($this->config[‘BACKUP_TYPES’]) &&
!in_array($ext, $this->config[‘BACKUP_TYPES’])) {
continue;
}

// 获取文件信息
$fileSize = @filesize($filePath);
$fileTime = @filemtime($filePath);

$files[] = [
‘dir_name’ => $dirName,
‘name’ => $item,
‘path’ => $filePath,
‘size’ => $fileSize,
‘size_formatted’ => $this->formatSize($fileSize),
‘time’ => $fileTime,
‘date’ => date(‘Y-m-d H:i:s’, $fileTime),
‘ext’ => $ext
];

$this->log(“找到备份文件[{$dirName}]: {$item}, 大小: {$this->formatSize($fileSize)}”);
}

// 按修改时间排序(最新的在前)
usort($files, function($a, $b) {
return $b[‘time’] – $a[‘time’];
});

return [
‘success’ => true,
‘dir_name’ => $dirName,
‘dir_path’ => $dirPath,
‘files’ => array_slice($files, 0, $limit),
‘total_count’ => count($files),
‘message’ => “找到 ” . count($files) . ” 个文件”
];
}

/**
* 获取所有备份目录的文件列表
*/
public function getAllBackupFiles($limitPerDir = null) {
if (empty($this->config[‘BACKUP_DIRS’])) {
return [
‘success’ => false,
‘message’ => ‘未配置备份目录’
];
}

$limit = $limitPerDir ?: $this->config[‘MAX_FILES_PER_DIR’];
$allResults = [];
$allFiles = [];
$totalFiles = 0;

foreach ($this->config[‘BACKUP_DIRS’] as $dirName => $dirPath) {
$result = $this->getDirBackupFiles($dirName, $dirPath, $limit);
$allResults[$dirName] = $result;

if ($result[‘success’]) {
foreach ($result[‘files’] as $file) {
$allFiles[] = $file;
$totalFiles++;
}
}
}

// 按时间排序所有文件
usort($allFiles, function($a, $b) {
return $b[‘time’] – $a[‘time’];
});

return [
‘success’ => true,
‘all_results’ => $allResults,
‘all_files’ => $allFiles,
‘total_files’ => $totalFiles,
‘total_dirs’ => count($this->config[‘BACKUP_DIRS’]),
‘message’ => “从 ” . count($this->config[‘BACKUP_DIRS’]) . ” 个目录中找到 ” . $totalFiles . ” 个文件”
];
}

/**
* 发送备份文件
*/
public function sendBackup($fileInfos) {
$attachments = [];
$sentFiles = [];

$this->log(“开始发送备份文件,数量: ” . count($fileInfos));

// 收集附件
foreach ($fileInfos as $fileInfo) {
$filePath = $fileInfo[‘path’] ?? $fileInfo;
$fileName = basename($filePath);

if (!file_exists($filePath)) {
$this->log(“文件不存在: {$filePath}”);
return [
‘success’ => false,
‘message’ => “备份文件不存在: {$fileName}”
];
}

if (!is_readable($filePath)) {
$this->log(“文件不可读: {$filePath}”);
return [
‘success’ => false,
‘message’ => “备份文件不可读: {$fileName}”
];
}

// 检查文件大小
$fileSize = filesize($filePath);
if ($fileSize > $this->config[‘MAX_FILE_SIZE’]) {
$this->log(“文件过大: {$fileName}, 大小: {$fileSize} 字节”);
return [
‘success’ => false,
‘message’ => “备份文件过大: {$fileName} ({$this->formatSize($fileSize)}),最大允许 {$this->formatSize($this->config[‘MAX_FILE_SIZE’])}”
];
}

$attachments[] = $filePath;
$sentFiles[] = $fileName;
$this->log(“添加附件: {$fileName}, 大小: {$this->formatSize($fileSize)}”);
}

// 限制发送的文件数量
if (count($attachments) > $this->config[‘MAX_FILES_SEND’]) {
$attachments = array_slice($attachments, 0, $this->config[‘MAX_FILES_SEND’]);
$sentFiles = array_slice($sentFiles, 0, $this->config[‘MAX_FILES_SEND’]);
$this->log(“限制附件数量为: {$this->config[‘MAX_FILES_SEND’]}”);
}

// 构建邮件内容
$subject = ‘网站备份文件 – ‘ . date(‘Y-m-d H:i:s’);
$message = “备份文件发送时间: ” . date(‘Y-m-d H:i:s’) . “\n\n”;
$message .= “备份目录:\n”;

foreach ($this->config[‘BACKUP_DIRS’] as $dirName => $dirPath) {
$message .= “- {$dirName}: {$dirPath}\n”;
}

$message .= “\n包含以下文件:\n”;

foreach ($attachments as $attachment) {
$fileName = basename($attachment);
$fileSize = filesize($attachment);
$message .= “- {$fileName} ({$this->formatSize($fileSize)})\n”;
}

$message .= “\n此邮件为自动发送,请勿回复。”;

// 发送邮件
$result = $this->sendEmail($this->config[‘RECIPIENTS’], $subject, $message, $attachments);
$result[‘sent_files’] = $sentFiles;
$this->log(“邮件发送结果: ” . ($result[‘success’] ? ‘成功’ : ‘失败 – ‘ . $result[‘message’]));

return $result;
}

/**
* 发送最新备份(每个目录最新的一个文件)
*/
public function sendLatestBackup() {
$allFiles = $this->getAllBackupFiles(1);

if (!$allFiles[‘success’] || empty($allFiles[‘all_files’])) {
return [
‘success’ => false,
‘message’ => ‘没有找到可发送的备份文件’
];
}

// 每个目录取最新的文件
$latestFiles = [];
$processedDirs = [];

foreach ($allFiles[‘all_files’] as $file) {
$dirName = $file[‘dir_name’];
if (!in_array($dirName, $processedDirs)) {
$latestFiles[] = $file;
$processedDirs[] = $dirName;
}
}

return $this->sendBackup($latestFiles);
}

/**
* 读取SMTP响应
*/
private function readSMTPResponse($socket) {
$response = ”;
while ($line = fgets($socket, 515)) {
$response .= $line;
if (substr($line, 3, 1) == ‘ ‘) {
break;
}
}
return $response;
}

/**
* 发送SMTP命令
*/
private function sendSMTPCommand($socket, $command) {
fwrite($socket, $command . “\r\n”);
return $this->readSMTPResponse($socket);
}

/**
* 发送邮件(支持SSL)
*/
private function sendEmail($to, $subject, $message, $attachments = []) {
$smtp = $this->config[‘SMTP’];

// 生成边界
$boundary = md5(time());

// 构建邮件头
$headers = [];
$headers[] = “From: =?UTF-8?B?” . base64_encode($smtp[‘FROM_NAME’]) . “?= <{$smtp[‘FROM_EMAIL’]}>”;
$headers[] = “Reply-To: {$smtp[‘FROM_EMAIL’]}”;
$headers[] = “MIME-Version: 1.0″;

// 构建邮件体
$body = ”;

if (empty($attachments)) {
// 无附件
$headers[] = “Content-Type: text/plain; charset=\”utf-8\””;
$headers[] = “Content-Transfer-Encoding: base64”;
$body = chunk_split(base64_encode($message));
} else {
// 有附件
$headers[] = “Content-Type: multipart/mixed; boundary=\”{$boundary}\””;

$body = “–{$boundary}\r\n”;
$body .= “Content-Type: text/plain; charset=\”utf-8\”\r\n”;
$body .= “Content-Transfer-Encoding: base64\r\n\r\n”;
$body .= chunk_split(base64_encode($message)) . “\r\n”;

// 添加附件
foreach ($attachments as $attachment) {
if (file_exists($attachment) && is_readable($attachment)) {
$fileName = basename($attachment);
$fileContent = @file_get_contents($attachment);

if ($fileContent !== false) {
$body .= “–{$boundary}\r\n”;
$body .= “Content-Type: application/octet-stream; name=\”{$fileName}\”\r\n”;
$body .= “Content-Transfer-Encoding: base64\r\n”;
$body .= “Content-Disposition: attachment; filename=\”{$fileName}\”\r\n\r\n”;
$body .= chunk_split(base64_encode($fileContent)) . “\r\n”;
}
}
}

$body .= “–{$boundary}–“;
}

// 连接SMTP服务器
$socket = @fsockopen(“ssl://{$smtp[‘HOST’]}”, $smtp[‘PORT’], $errno, $errstr, 30);

if (!$socket) {
$this->log(“SMTP连接失败: {$errstr}”);
return [
‘success’ => false,
‘message’ => “SMTP连接失败: {$errstr}”
];
}

try {
// 设置超时
stream_set_timeout($socket, 30);

// 读取欢迎信息
$response = $this->readSMTPResponse($socket);
if (substr($response, 0, 3) != ‘220’) {
throw new Exception(“SMTP服务器响应错误: {$response}”);
}

// EHLO
$this->sendSMTPCommand($socket, “EHLO {$smtp[‘HOST’]}”);

// 登录认证
$this->sendSMTPCommand($socket, “AUTH LOGIN”);
$this->sendSMTPCommand($socket, base64_encode($smtp[‘USERNAME’]));
$this->sendSMTPCommand($socket, base64_encode($smtp[‘PASSWORD’]));

// 发件人
$this->sendSMTPCommand($socket, “MAIL FROM: <{$smtp[‘FROM_EMAIL’]}>”);

// 收件人
if (is_array($to)) {
foreach ($to as $recipient) {
$this->sendSMTPCommand($socket, “RCPT TO: <{$recipient}>”);
}
} else {
$this->sendSMTPCommand($socket, “RCPT TO: <{$to}>”);
}

// 发送数据
$this->sendSMTPCommand($socket, “DATA”);

// 发送邮件内容
fwrite($socket, “Subject: =?UTF-8?B?” . base64_encode($subject) . “?=\r\n”);
fwrite($socket, implode(“\r\n”, $headers) . “\r\n\r\n”);
fwrite($socket, $body . “\r\n.\r\n”);

// 检查发送结果
$response = $this->readSMTPResponse($socket);

// 退出
$this->sendSMTPCommand($socket, “QUIT”);

fclose($socket);

if (substr($response, 0, 3) == ‘250’) {
return [‘success’ => true, ‘message’ => ‘邮件发送成功’];
} else {
$this->log(“邮件发送失败响应: {$response}”);
return [‘success’ => false, ‘message’ => “邮件发送失败: {$response}”];
}

} catch (Exception $e) {
if ($socket) @fclose($socket);
$this->log(“邮件发送异常: ” . $e->getMessage());
return [‘success’ => false, ‘message’ => $e->getMessage()];
}
}
}

// ============ 初始化 ============
$mailSender = new BackupMailSender($CONFIG);

// ============ API处理 =============
if (isset($_GET[‘api’])) {
// 清空输出缓冲区
while (ob_get_level()) {
ob_end_clean();
}

// 设置JSON头
header(‘Content-Type: application/json; charset=utf-8’);

$action = $_GET[‘api’] ?? ”;

try {
$result = [];

switch ($action) {
case ‘list’:
$result = $mailSender->getAllBackupFiles();

// 如果没找到文件,提供调试信息
if (!$result[‘success’] || empty($result[‘all_files’])) {
$debugInfo = [];
foreach ($CONFIG[‘BACKUP_DIRS’] as $dirName => $dirPath) {
$debugInfo[$dirName] = [
‘path’ => $dirPath,
‘exists’ => is_dir($dirPath) ? ‘是’ : ‘否’,
‘readable’ => is_readable($dirPath) ? ‘是’ : ‘否’,
‘contents’ => is_dir($dirPath) ? scandir($dirPath) : []
];
}
$result[‘debug_info’] = $debugInfo;
}
break;

case ‘send’:
if (isset($_GET[‘files’]) && !empty($_GET[‘files’])) {
// 发送指定文件
$fileNames = explode(‘,’, $_GET[‘files’]);
$fileInfos = [];

// 先获取所有文件,然后匹配
$allFiles = $mailSender->getAllBackupFiles();
if ($allFiles[‘success’]) {
foreach ($fileNames as $fileName) {
foreach ($allFiles[‘all_files’] as $file) {
if ($file[‘name’] === $fileName) {
$fileInfos[] = $file;
break;
}
}
}
}

if (!empty($fileInfos)) {
$result = $mailSender->sendBackup($fileInfos);
} else {
$result = [‘success’ => false, ‘message’ => ‘未找到指定的文件’];
}
} else {
// 发送最新备份(每个目录最新的一个文件)
$result = $mailSender->sendLatestBackup();
}
break;

case ‘send_all_latest’:
// 发送每个目录的最新文件
$result = $mailSender->sendLatestBackup();
break;

case ‘send_all’:
// 发送所有文件(注意文件大小限制)
$allFiles = $mailSender->getAllBackupFiles();
if ($allFiles[‘success’] && !empty($allFiles[‘all_files’])) {
$result = $mailSender->sendBackup($allFiles[‘all_files’]);
} else {
$result = [‘success’ => false, ‘message’ => ‘没有找到可发送的备份文件’];
}
break;

case ‘test’:
$result = [
‘success’ => true,
‘message’ => ‘API连接正常’,
‘timestamp’ => date(‘Y-m-d H:i:s’),
‘config’ => [
‘backup_dirs’ => $CONFIG[‘BACKUP_DIRS’],
‘backup_dirs_count’ => count($CONFIG[‘BACKUP_DIRS’]),
],
‘server’ => [
‘php_version’ => phpversion(),
‘open_basedir’ => ini_get(‘open_basedir’),
‘current_dir’ => __DIR__,
‘script_file’ => __FILE__
]
];

// 检查所有备份目录
$dirsInfo = [];
foreach ($CONFIG[‘BACKUP_DIRS’] as $dirName => $dirPath) {
$exists = is_dir($dirPath);
$readable = $exists && is_readable($dirPath);

$dirInfo = [
‘dir_name’ => $dirName,
‘path’ => $dirPath,
‘exists’ => $exists,
‘readable’ => $readable,
];

if ($exists && $readable) {
$items = scandir($dirPath);
$actualFiles = array_filter($items, function($item) use ($dirPath) {
return $item != ‘.’ && $item != ‘..’ && is_file($dirPath . ‘/’ . $item);
});

$dirInfo[‘all_items_count’] = count($items) – 2;
$dirInfo[‘all_items’] = $items;
$dirInfo[‘actual_files_count’] = count($actualFiles);
$dirInfo[‘actual_files’] = array_values($actualFiles);
}

$dirsInfo[$dirName] = $dirInfo;
}

$result[‘backup_dirs_info’] = $dirsInfo;
break;

default:
$result = [
‘success’ => false,
‘message’ => ‘未知的API操作’,
‘supported_actions’ => [‘list’, ‘send’, ‘send_all_latest’, ‘send_all’, ‘test’]
];
}

echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);

} catch (Exception $e) {
echo json_encode([
‘success’ => false,
‘message’ => ‘服务器内部错误’,
‘error’ => $e->getMessage(),
‘trace’ => $e->getTraceAsString()
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
}

exit;
}

// ============ Web界面 ============
?>
<!DOCTYPE html>
<html lang=”zh-CN”>
<head>
<meta charset=”UTF-8″>
<meta name=”viewport” content=”width=device-width, initial-scale=1.0″>
<title>多目录备份邮件发送系统</title>
<style>
body { font-family: Arial, sans-serif; padding: 20px; background: #f5f5f5; }
.container { max-width: 1200px; margin: 0 auto; }
.card { background: #fff; border: 1px solid #ddd; border-radius: 5px; padding: 20px; margin-bottom: 20px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.btn { padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; margin-right: 10px; margin-bottom: 10px; }
.btn-primary { background: #007bff; color: white; }
.btn-success { background: #28a745; color: white; }
.btn-danger { background: #dc3545; color: white; }
.btn-info { background: #17a2b8; color: white; }
.btn-warning { background: #ffc107; color: black; }
.status { padding: 15px; border-radius: 5px; margin: 10px 0; }
.status-success { background: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
.status-error { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
.status-warning { background: #fff3cd; color: #856404; border: 1px solid #ffeaa7; }
.dir-section { margin: 20px 0; padding: 15px; border: 1px solid #eee; border-radius: 5px; }
.dir-header { background: #e9ecef; padding: 10px; margin: -15px -15px 15px -15px; border-radius: 5px 5px 0 0; }
.file-list { margin: 10px 0; }
.file-item { padding: 15px; border-bottom: 1px solid #eee; }
.file-item:hover { background: #f8f9fa; }
pre { background: #f8f9fa; padding: 15px; border-radius: 5px; overflow: auto; font-size: 14px; }
.debug-info { background: #e9ecef; padding: 10px; border-radius: 5px; margin-top: 10px; font-size: 12px; }
h1 { color: #333; border-bottom: 2px solid #007bff; padding-bottom: 10px; }
h2 { color: #555; }
h3 { color: #666; margin-top: 0; }
.file-size { color: #666; font-size: 0.9em; }
.file-date { color: #888; font-size: 0.85em; }
.dir-name { color: #007bff; font-weight: bold; }
.action-buttons { margin-top: 10px; }
.tab-buttons { margin-bottom: 20px; }
.tab-button { padding: 10px 20px; background: #eee; border: none; cursor: pointer; }
.tab-button.active { background: #007bff; color: white; }
.tab-content { display: none; }
.tab-content.active { display: block; }
</style>
</head>
<body>
<div class=”container”>
<h1>📧 多目录备份邮件发送系统</h1>

<div class=”tab-buttons”>
<button class=”tab-button active” onclick=”showTab(‘overview’)”>系统概览</button>
<button class=”tab-button” onclick=”showTab(‘files’)”>文件管理</button>
<button class=”tab-button” onclick=”showTab(’email’)”>邮件发送</button>
<button class=”tab-button” onclick=”showTab(‘debug’)”>API调试</button>
</div>

<!– 系统概览 –>
<div id=”overview” class=”tab-content active”>
<div class=”card”>
<h2>系统信息</h2>
<div id=”systemInfo”>加载中…</div>
<button class=”btn btn-primary” onclick=”loadSystemInfo()”>刷新系统信息</button>
</div>

<div class=”card”>
<h2>备份目录概览</h2>
<div id=”backupDirsInfo”>
<p>正在检查备份目录…</p>
</div>
<button class=”btn btn-primary” onclick=”loadBackupDirs()”>刷新目录状态</button>
</div>
</div>

<!– 文件管理 –>
<div id=”files” class=”tab-content”>
<div class=”card”>
<h2>备份文件管理</h2>
<div id=”fileInfo”>
<p>正在加载文件列表…</p>
</div>
<button class=”btn btn-primary” onclick=”loadAllBackupFiles()”>刷新所有文件</button>
<button class=”btn btn-info” onclick=”loadLatestFiles()”>查看最新文件</button>
</div>
</div>

<!– 邮件发送 –>
<div id=”email” class=”tab-content”>
<div class=”card”>
<h2>邮件发送操作</h2>
<div id=”mailStatus”></div>
<div class=”action-buttons”>
<button class=”btn btn-success” onclick=”sendLatestFromEachDir()”>发送各目录最新文件</button>
<button class=”btn btn-warning” onclick=”sendAllFiles()”>发送所有文件</button>
<button class=”btn btn-danger” onclick=”testEmail()”>测试邮件发送</button>
<button class=”btn btn-info” onclick=”viewLog()”>查看日志</button>
</div>

<h3 style=”margin-top: 30px;”>批量发送</h3>
<p>选择要发送的文件:</p>
<div id=”fileCheckboxes”></div>
<button class=”btn btn-primary” onclick=”sendSelectedFiles()” style=”margin-top: 15px;”>发送选中的文件</button>
</div>
</div>

<!– API调试 –>
<div id=”debug” class=”tab-content”>
<div class=”card”>
<h2>API调试</h2>
<div>
<button class=”btn btn-primary” onclick=”callApi(‘test’)”>测试API</button>
<button class=”btn btn-primary” onclick=”callApi(‘list’)”>获取文件列表</button>
<button class=”btn btn-success” onclick=”callApi(‘send_all_latest’)”>发送各目录最新</button>
<button class=”btn btn-warning” onclick=”callApi(‘send_all’)”>发送所有文件</button>
</div>
<div id=”apiResult”>
<pre>点击按钮查看API响应</pre>
</div>
</div>
</div>
</div>

<script>
// 显示选项卡
function showTab(tabName) {
// 隐藏所有选项卡
document.querySelectorAll(‘.tab-content’).forEach(tab => {
tab.classList.remove(‘active’);
});

// 移除所有按钮的活动状态
document.querySelectorAll(‘.tab-button’).forEach(button => {
button.classList.remove(‘active’);
});

// 显示选中的选项卡
document.getElementById(tabName).classList.add(‘active’);

// 设置按钮活动状态
event.target.classList.add(‘active’);
}

// 加载系统信息
async function loadSystemInfo() {
const result = await callApi(‘test’, false);
const systemDiv = document.getElementById(‘systemInfo’);

if (result.success) {
let html = ‘<div class=”status status-success”>’;
html += ‘<p><strong>✅ 系统正常</strong></p>’;
html += ‘<ul>’;
html += `<li>PHP版本: ${result.server.php_version}</li>`;
html += `<li>当前目录: ${result.server.current_dir}</li>`;
html += `<li>备份目录数量: ${result.config.backup_dirs_count}</li>`;

// 显示目录信息
if (result.backup_dirs_info) {
html += ‘<li><strong>备份目录状态:</strong></li>’;
for (const [dirName, dirInfo] of Object.entries(result.backup_dirs_info)) {
html += `<li>${dirName}: ${dirInfo.path} – `;
html += `存在: ${dirInfo.exists ? ‘✅’ : ‘❌’} `;
html += `可读: ${dirInfo.readable ? ‘✅’ : ‘❌’}`;
if (dirInfo.exists && dirInfo.readable) {
html += ` (${dirInfo.actual_files_count}个文件)`;
}
html += ‘</li>’;
}
}

html += ‘</ul></div>’;
systemDiv.innerHTML = html;
} else {
systemDiv.innerHTML = `<div class=”status status-error”>❌ ${result.message}</div>`;
}
}

// 加载备份目录信息
async function loadBackupDirs() {
const result = await callApi(‘test’, false);
const dirsDiv = document.getElementById(‘backupDirsInfo’);

if (result.success && result.backup_dirs_info) {
let html = ”;

for (const [dirName, dirInfo] of Object.entries(result.backup_dirs_info)) {
html += `<div class=”dir-section”>`;
html += `<div class=”dir-header”>`;
html += `<span class=”dir-name”>${dirName}</span>`;
html += `<span style=”float: right;”>`;
html += `存在: ${dirInfo.exists ? ‘✅’ : ‘❌’}`;
html += ` 可读: ${dirInfo.readable ? ‘✅’ : ‘❌’}`;
html += `</span>`;
html += `</div>`;

html += `<p><strong>路径:</strong> ${dirInfo.path}</p>`;

if (dirInfo.exists && dirInfo.readable) {
html += `<p><strong>文件数量:</strong> ${dirInfo.actual_files_count}</p>`;

if (dirInfo.actual_files && dirInfo.actual_files.length > 0) {
html += `<p><strong>文件列表:</strong> ${dirInfo.actual_files.join(‘, ‘)}</p>`;
} else {
html += `<p class=”status status-warning”>目录中没有备份文件</p>`;
}
} else {
html += `<p class=”status status-error”>目录不可访问</p>`;
}

html += `</div>`;
}

dirsDiv.innerHTML = html;
} else {
dirsDiv.innerHTML = `<div class=”status status-error”>无法加载目录信息</div>`;
}
}

// 加载所有备份文件
async function loadAllBackupFiles() {
const fileDiv = document.getElementById(‘fileInfo’);
fileDiv.innerHTML = ‘<p>正在加载所有文件列表…</p>’;

const result = await callApi(‘list’, false);

if (result.success && result.all_files && result.all_files.length > 0) {
let html = `<div class=”status status-success”>`;
html += `<p><strong>✅ 找到 ${result.total_files} 个备份文件(来自 ${result.total_dirs} 个目录)</strong></p>`;

// 按目录分组显示
const filesByDir = {};
result.all_files.forEach(file => {
if (!filesByDir[file.dir_name]) {
filesByDir[file.dir_name] = [];
}
filesByDir[file.dir_name].push(file);
});

for (const [dirName, files] of Object.entries(filesByDir)) {
html += `<div class=”dir-section”>`;
html += `<div class=”dir-header”>`;
html += `<span class=”dir-name”>${dirName}</span>`;
html += `<span style=”float: right;”>${files.length} 个文件</span>`;
html += `</div>`;

html += `<div class=”file-list”>`;
files.forEach(file => {
html += `<div class=”file-item”>`;
html += `<div><strong>${file.name}</strong></div>`;
html += `<div class=”file-size”>${file.size_formatted}</div>`;
html += `<div class=”file-date”>${file.date}</div>`;
html += `<div style=”margin-top: 10px;”>`;
html += `<button onclick=”sendFile(‘${file.name}’)” class=”btn btn-success” style=”padding: 5px 10px; font-size: 12px;”>发送此文件</button>`;
html += `</div>`;
html += `</div>`;
});
html += `</div>`;
html += `</div>`;
}

html += `</div>`;

// 更新文件选择框
updateFileCheckboxes(result.all_files);

fileDiv.innerHTML = html;
} else {
let html = ‘<div class=”status status-error”>’;
html += `<p><strong>❌ 未找到备份文件</strong></p>`;
if (result.message) html += `<p>${result.message}</p>`;

if (result.debug_info) {
html += ‘<div class=”debug-info”>’;
html += ‘<p><strong>调试信息:</strong></p>’;
for (const [dirName, dirInfo] of Object.entries(result.debug_info)) {
html += `<p><strong>${dirName}:</strong> ${dirInfo.path}</p>`;
html += `<p>存在: ${dirInfo.exists},可读: ${dirInfo.readable}</p>`;
html += `<p>目录内容: ${dirInfo.contents.join(‘, ‘)}</p>`;
}
html += ‘</div>’;
}

html += ‘</div>’;
fileDiv.innerHTML = html;
}
}

// 加载最新文件
async function loadLatestFiles() {
const result = await callApi(‘list’, false);
const fileDiv = document.getElementById(‘fileInfo’);

if (result.success && result.all_results) {
let html = `<div class=”status status-success”>`;
html += `<p><strong>✅ 各目录最新文件</strong></p>`;

for (const [dirName, dirResult] of Object.entries(result.all_results)) {
html += `<div class=”dir-section”>`;
html += `<div class=”dir-header”>`;
html += `<span class=”dir-name”>${dirName}</span>`;
html += `<span style=”float: right;”>${dirResult.total_count} 个文件</span>`;
html += `</div>`;

if (dirResult.success && dirResult.files && dirResult.files.length > 0) {
html += `<div class=”file-list”>`;
dirResult.files.slice(0, 3).forEach(file => {
html += `<div class=”file-item”>`;
html += `<div><strong>${file.name}</strong></div>`;
html += `<div class=”file-size”>${file.size_formatted}</div>`;
html += `<div class=”file-date”>${file.date}</div>`;
html += `<div style=”margin-top: 10px;”>`;
html += `<button onclick=”sendFile(‘${file.name}’)” class=”btn btn-success” style=”padding: 5px 10px; font-size: 12px;”>发送此文件</button>`;
html += `</div>`;
html += `</div>`;
});
html += `</div>`;
} else {
html += `<p class=”status status-warning”>${dirResult.message || ‘没有找到文件’}</p>`;
}
html += `</div>`;
}

html += `</div>`;
fileDiv.innerHTML = html;
}
}

// 更新文件选择框
function updateFileCheckboxes(files) {
const checkboxesDiv = document.getElementById(‘fileCheckboxes’);
let html = ‘<div style=”max-height: 300px; overflow-y: auto; border: 1px solid #ddd; padding: 10px; margin: 10px 0;”>’;

files.forEach((file, index) => {
const uniqueId = `file_${index}`;
html += `<div style=”margin: 5px 0;”>`;
html += `<input type=”checkbox” id=”${uniqueId}” value=”${file.name}” data-fullpath=”${file.path}”>`;
html += `<label for=”${uniqueId}” style=”margin-left: 5px;”>`;
html += `<strong>[${file.dir_name}]</strong> ${file.name} (${file.size_formatted})`;
html += `</label>`;
html += `</div>`;
});

html += ‘</div>’;
checkboxesDiv.innerHTML = html;
}

// 发送指定文件
async function sendFile(fileName) {
if (!confirm(`确定要发送文件 “${fileName}” 吗?`)) return;

showTab(’email’);
const statusDiv = document.getElementById(‘mailStatus’);
statusDiv.innerHTML = ‘<div class=”status status-warning”>正在发送…</div>’;

const result = await callApi(`send&files=${encodeURIComponent(fileName)}`, false);

if (result.success) {
statusDiv.innerHTML = `<div class=”status status-success”>✅ ${result.message}<br>已发送文件: ${fileName}</div>`;
} else {
statusDiv.innerHTML = `<div class=”status status-error”>❌ ${result.message}</div>`;
}
}

// 发送各目录最新文件
async function sendLatestFromEachDir() {
if (!confirm(‘确定要发送每个目录的最新备份文件吗?’)) return;

const statusDiv = document.getElementById(‘mailStatus’);
statusDiv.innerHTML = ‘<div class=”status status-warning”>正在发送各目录最新文件…</div>’;

const result = await callApi(‘send_all_latest’, false);

if (result.success) {
let fileList = ”;
if (result.sent_files && result.sent_files.length > 0) {
fileList = ‘<br>已发送文件:’ + result.sent_files.join(‘, ‘);
}
statusDiv.innerHTML = `<div class=”status status-success”>✅ ${result.message}${fileList}</div>`;
} else {
statusDiv.innerHTML = `<div class=”status status-error”>❌ ${result.message}</div>`;
}
}

// 发送所有文件
async function sendAllFiles() {
if (!confirm(‘警告:这将发送所有备份文件,可能会超过邮件大小限制。确定要继续吗?’)) return;

const statusDiv = document.getElementById(‘mailStatus’);
statusDiv.innerHTML = ‘<div class=”status status-warning”>正在发送所有文件,请稍候…</div>’;

const result = await callApi(‘send_all’, false);

if (result.success) {
let fileList = ”;
if (result.sent_files && result.sent_files.length > 0) {
fileList = ‘<br>已发送文件:’ + result.sent_files.length + ‘ 个文件’;
}
statusDiv.innerHTML = `<div class=”status status-success”>✅ ${result.message}${fileList}</div>`;
} else {
statusDiv.innerHTML = `<div class=”status status-error”>❌ ${result.message}</div>`;
}
}

// 发送选中的文件
async function sendSelectedFiles() {
const checkboxes = document.querySelectorAll(‘#fileCheckboxes input[type=”checkbox”]:checked’);

if (checkboxes.length === 0) {
alert(‘请先选择要发送的文件’);
return;
}

const fileNames = Array.from(checkboxes).map(cb => cb.value);

if (!confirm(`确定要发送选中的 ${fileNames.length} 个文件吗?`)) return;

const statusDiv = document.getElementById(‘mailStatus’);
statusDiv.innerHTML = ‘<div class=”status status-warning”>正在发送选中的文件…</div>’;

const result = await callApi(`send&files=${encodeURIComponent(fileNames.join(‘,’))}`, false);

if (result.success) {
let fileList = ”;
if (result.sent_files && result.sent_files.length > 0) {
fileList = ‘<br>已发送文件:’ + result.sent_files.join(‘, ‘);
}
statusDiv.innerHTML = `<div class=”status status-success”>✅ ${result.message}${fileList}</div>`;
} else {
statusDiv.innerHTML = `<div class=”status status-error”>❌ ${result.message}</div>`;
}
}

// 测试邮件发送
async function testEmail() {
showTab(’email’);
const statusDiv = document.getElementById(‘mailStatus’);
statusDiv.innerHTML = ‘<div class=”status status-warning”>测试邮件发送…</div>’;

// 先获取文件列表
const listResult = await callApi(‘list’, false);

if (listResult.success && listResult.all_files && listResult.all_files.length > 0) {
// 发送第一个文件
const result = await callApi(`send&files=${encodeURIComponent(listResult.all_files[0].name)}`, false);

if (result.success) {
statusDiv.innerHTML = `<div class=”status status-success”>✅ 测试邮件发送成功!<br>已发送文件:${listResult.all_files[0].name}</div>`;
} else {
statusDiv.innerHTML = `<div class=”status status-error”>❌ 测试失败:${result.message}</div>`;
}
} else {
statusDiv.innerHTML = `<div class=”status status-error”>❌ 没有找到可发送的备份文件</div>`;
}
}

// 查看日志
async function viewLog() {
alert(‘日志文件位置: <?php echo $CONFIG[‘LOG_FILE’]; ?>’);
}

// 调用API
async function callApi(action, showInDebug = true) {
if (showInDebug) {
const resultDiv = document.getElementById(‘apiResult’);
resultDiv.innerHTML = ‘<pre>请求中…</pre>’;
}

try {
const response = await fetch(`?api=${action}`);
const text = await response.text();

try {
const result = JSON.parse(text);

if (showInDebug) {
resultDiv.innerHTML = `<pre>${JSON.stringify(result, null, 2)}</pre>`;
}

return result;
} catch (e) {
if (showInDebug) {
resultDiv.innerHTML = `<pre>JSON解析错误:${e.message}\n原始响应:${text}</pre>`;
}
console.error(‘JSON解析错误:’, e);
console.error(‘原始响应:’, text);
return { success: false, message: ‘JSON解析错误’, raw: text };
}
} catch (error) {
if (showInDebug) {
resultDiv.innerHTML = `<pre>请求失败:${error.message}</pre>`;
}
console.error(‘请求失败:’, error);
return { success: false, message: error.message };
}
}

// 页面加载时初始化
document.addEventListener(‘DOMContentLoaded’, function() {
loadSystemInfo();
loadBackupDirs();
loadAllBackupFiles();
});

// 全局配置(用于页面脚本)
const $CONFIG = <?php echo json_encode($CONFIG); ?>;
</script>
</body>
</html>

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。