chore: 首次提交 - 关联 GitHub fnvtk/MBTI_wang
Made-with: Cursor
This commit is contained in:
750
api/app/controller/superadmin/Database.php
Normal file
750
api/app/controller/superadmin/Database.php
Normal file
@@ -0,0 +1,750 @@
|
||||
<?php
|
||||
namespace app\controller\superadmin;
|
||||
|
||||
use app\BaseController;
|
||||
use app\model\BackupRecord as BackupRecordModel;
|
||||
use think\facade\Request;
|
||||
use think\facade\Db;
|
||||
use think\facade\Config;
|
||||
use think\facade\Log;
|
||||
|
||||
/**
|
||||
* 数据库管理控制器(超管专用)
|
||||
*/
|
||||
class Database extends BaseController
|
||||
{
|
||||
/**
|
||||
* 获取数据库信息
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function info()
|
||||
{
|
||||
// 验证是否为超级管理员
|
||||
$user = $this->request->user ?? null;
|
||||
if (!$user || $user['role'] !== 'superadmin') {
|
||||
return error('无权限访问', 403);
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取数据库配置
|
||||
$config = Config::get('database.connections.mysql');
|
||||
$database = $config['database'] ?? '';
|
||||
|
||||
// 获取数据库大小
|
||||
$dbSize = $this->getDatabaseSize($database);
|
||||
|
||||
// 获取表数量
|
||||
$tableCount = $this->getTableCount($database);
|
||||
|
||||
// 获取连接状态
|
||||
try {
|
||||
Db::query('SELECT 1');
|
||||
$connected = true;
|
||||
} catch (\Exception $e) {
|
||||
$connected = false;
|
||||
}
|
||||
|
||||
return success([
|
||||
'databaseType' => 'MySQL',
|
||||
'databaseName' => $database,
|
||||
'connected' => $connected,
|
||||
'databaseSize' => $dbSize,
|
||||
'tableCount' => $tableCount
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return error('获取数据库信息失败:' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表列表
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function tables()
|
||||
{
|
||||
// 验证是否为超级管理员
|
||||
$user = $this->request->user ?? null;
|
||||
if (!$user || $user['role'] !== 'superadmin') {
|
||||
return error('无权限访问', 403);
|
||||
}
|
||||
|
||||
try {
|
||||
$config = Config::get('database.connections.mysql');
|
||||
$database = $config['database'] ?? '';
|
||||
$prefix = $config['prefix'] ?? 'mbti_';
|
||||
|
||||
// 获取所有表
|
||||
$tables = Db::query("SHOW TABLE STATUS FROM `{$database}`");
|
||||
|
||||
$result = [];
|
||||
foreach ($tables as $table) {
|
||||
$tableName = $table['Name'];
|
||||
|
||||
// 只显示带前缀的表(或者所有表)
|
||||
if (empty($prefix) || strpos($tableName, $prefix) === 0) {
|
||||
// 获取记录数
|
||||
$rowCount = Db::query("SELECT COUNT(*) as count FROM `{$tableName}`")[0]['count'] ?? 0;
|
||||
|
||||
// 获取索引数
|
||||
$indexes = Db::query("SHOW INDEX FROM `{$tableName}`");
|
||||
$indexCount = count(array_unique(array_column($indexes, 'Key_name')));
|
||||
|
||||
$result[] = [
|
||||
'name' => $tableName,
|
||||
'docCount' => intval($rowCount),
|
||||
'size' => intval($table['Data_length'] + $table['Index_length']),
|
||||
'indexCount' => $indexCount,
|
||||
'engine' => $table['Engine'] ?? '',
|
||||
'collation' => $table['Collation'] ?? ''
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return success($result);
|
||||
} catch (\Exception $e) {
|
||||
return error('获取表列表失败:' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看表数据
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function viewTable()
|
||||
{
|
||||
// 验证是否为超级管理员
|
||||
$user = $this->request->user ?? null;
|
||||
if (!$user || $user['role'] !== 'superadmin') {
|
||||
return error('无权限访问', 403);
|
||||
}
|
||||
|
||||
$tableName = Request::param('table', '');
|
||||
$page = Request::param('page', 1);
|
||||
$pageSize = Request::param('pageSize', 20);
|
||||
|
||||
if (empty($tableName)) {
|
||||
return error('表名不能为空', 400);
|
||||
}
|
||||
|
||||
try {
|
||||
// 验证表是否存在
|
||||
$config = Config::get('database.connections.mysql');
|
||||
$database = $config['database'] ?? '';
|
||||
$tables = Db::query("SHOW TABLES FROM `{$database}` LIKE '{$tableName}'");
|
||||
|
||||
if (empty($tables)) {
|
||||
return error('表不存在', 404);
|
||||
}
|
||||
|
||||
// 获取表结构
|
||||
$columns = Db::query("SHOW COLUMNS FROM `{$tableName}`");
|
||||
|
||||
// 获取数据
|
||||
$total = Db::name(str_replace($config['prefix'] ?? 'mbti_', '', $tableName))->count();
|
||||
$list = Db::name(str_replace($config['prefix'] ?? 'mbti_', '', $tableName))
|
||||
->page($page, $pageSize)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
return success([
|
||||
'columns' => $columns,
|
||||
'list' => $list,
|
||||
'total' => $total,
|
||||
'page' => $page,
|
||||
'pageSize' => $pageSize
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return error('查看表数据失败:' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出表数据
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function exportTable()
|
||||
{
|
||||
// 验证是否为超级管理员
|
||||
$user = $this->request->user ?? null;
|
||||
if (!$user || $user['role'] !== 'superadmin') {
|
||||
return error('无权限访问', 403);
|
||||
}
|
||||
|
||||
$tableName = Request::param('table', '');
|
||||
|
||||
if (empty($tableName)) {
|
||||
return error('表名不能为空', 400);
|
||||
}
|
||||
|
||||
try {
|
||||
// 生成SQL导出文件
|
||||
$backupDir = root_path() . 'runtime/backup/';
|
||||
if (!is_dir($backupDir)) {
|
||||
mkdir($backupDir, 0755, true);
|
||||
}
|
||||
|
||||
$filename = $tableName . '_' . date('YmdHis') . '.sql';
|
||||
$filepath = $backupDir . $filename;
|
||||
|
||||
$this->exportTableToSql($tableName, $filepath);
|
||||
|
||||
return success([
|
||||
'filename' => $filename,
|
||||
'filepath' => $filepath,
|
||||
'downloadUrl' => '/api/v1/superadmin/database/download?file=' . urlencode($filename)
|
||||
], '导出成功');
|
||||
} catch (\Exception $e) {
|
||||
return error('导出表数据失败:' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空表数据
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function clearTable()
|
||||
{
|
||||
// 验证是否为超级管理员
|
||||
$user = $this->request->user ?? null;
|
||||
if (!$user || $user['role'] !== 'superadmin') {
|
||||
return error('无权限访问', 403);
|
||||
}
|
||||
|
||||
$tableName = Request::param('table', '');
|
||||
|
||||
if (empty($tableName)) {
|
||||
return error('表名不能为空', 400);
|
||||
}
|
||||
|
||||
try {
|
||||
// 验证表是否存在
|
||||
$config = Config::get('database.connections.mysql');
|
||||
$database = $config['database'] ?? '';
|
||||
$tables = Db::query("SHOW TABLES FROM `{$database}` LIKE '{$tableName}'");
|
||||
|
||||
if (empty($tables)) {
|
||||
return error('表不存在', 404);
|
||||
}
|
||||
|
||||
// 清空表
|
||||
Db::execute("TRUNCATE TABLE `{$tableName}`");
|
||||
|
||||
return success(null, '表数据已清空');
|
||||
} catch (\Exception $e) {
|
||||
return error('清空表数据失败:' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 备份数据库
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function backup()
|
||||
{
|
||||
// 验证是否为超级管理员
|
||||
$user = $this->request->user ?? null;
|
||||
if (!$user || $user['role'] !== 'superadmin') {
|
||||
return error('无权限访问', 403);
|
||||
}
|
||||
|
||||
try {
|
||||
$config = Config::get('database.connections.mysql');
|
||||
$host = $config['hostname'] ?? 'localhost';
|
||||
$port = $config['hostport'] ?? 3306;
|
||||
$database = $config['database'] ?? '';
|
||||
$username = $config['username'] ?? '';
|
||||
$password = $config['password'] ?? '';
|
||||
|
||||
// 创建备份目录
|
||||
$backupDir = root_path() . 'runtime/backup/';
|
||||
if (!is_dir($backupDir)) {
|
||||
mkdir($backupDir, 0755, true);
|
||||
}
|
||||
|
||||
$filename = 'backup_' . $database . '_' . date('YmdHis') . '.sql';
|
||||
$filepath = $backupDir . $filename;
|
||||
|
||||
// 优先使用PHP方式备份(更可靠)
|
||||
$this->backupDatabase($database, $filepath);
|
||||
|
||||
// 如果文件不存在或为空,尝试使用mysqldump
|
||||
if (!file_exists($filepath) || filesize($filepath) == 0) {
|
||||
$mysqldumpPath = $this->findMysqldump();
|
||||
|
||||
if ($mysqldumpPath) {
|
||||
// 使用mysqldump命令
|
||||
$command = sprintf(
|
||||
'"%s" -h%s -P%s -u%s -p%s %s > "%s" 2>&1',
|
||||
$mysqldumpPath,
|
||||
escapeshellarg($host),
|
||||
escapeshellarg($port),
|
||||
escapeshellarg($username),
|
||||
escapeshellarg($password),
|
||||
escapeshellarg($database),
|
||||
escapeshellarg($filepath)
|
||||
);
|
||||
|
||||
exec($command, $output, $returnVar);
|
||||
|
||||
if ($returnVar !== 0) {
|
||||
throw new \Exception('mysqldump执行失败: ' . implode("\n", $output));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取文件大小
|
||||
$fileSize = filesize($filepath);
|
||||
|
||||
// 上传到OSS
|
||||
$ossUrl = null;
|
||||
$ossPath = null;
|
||||
try {
|
||||
$ossResult = $this->uploadBackupToOss($filepath, $filename);
|
||||
if ($ossResult) {
|
||||
$ossUrl = $ossResult['url'];
|
||||
$ossPath = $ossResult['path'];
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// OSS上传失败不影响备份成功,只记录错误
|
||||
Log::error('备份文件上传OSS失败:' . $e->getMessage());
|
||||
}
|
||||
|
||||
// 记录备份信息
|
||||
$this->saveBackupRecord($filename, $filepath, $fileSize, $ossUrl, $ossPath);
|
||||
|
||||
return success([
|
||||
'filename' => $filename,
|
||||
'filepath' => $filepath,
|
||||
'size' => $fileSize,
|
||||
'time' => date('Y-m-d H:i:s'),
|
||||
'ossUrl' => $ossUrl,
|
||||
'ossPath' => $ossPath,
|
||||
'downloadUrl' => '/api/v1/superadmin/database/download?file=' . urlencode($filename)
|
||||
], '备份成功' . ($ossUrl ? ',已上传到OSS' : ''));
|
||||
} catch (\Exception $e) {
|
||||
return error('备份失败:' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取备份记录列表
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function backups()
|
||||
{
|
||||
// 验证是否为超级管理员
|
||||
$user = $this->request->user ?? null;
|
||||
if (!$user || $user['role'] !== 'superadmin') {
|
||||
return error('无权限访问', 403);
|
||||
}
|
||||
|
||||
try {
|
||||
// 从数据库读取备份记录
|
||||
$records = BackupRecordModel::order('createdAt', 'desc')->select()->toArray();
|
||||
|
||||
$backups = [];
|
||||
foreach ($records as $record) {
|
||||
$backups[] = [
|
||||
'id' => $record['id'],
|
||||
'filename' => $record['filename'],
|
||||
'time' => date('Y-m-d\TH:i:s', $record['createdAt']),
|
||||
'size' => intval($record['fileSize']),
|
||||
'status' => $record['status'] ?? 'success',
|
||||
'ossUrl' => $record['ossUrl'] ?? null,
|
||||
'ossPath' => $record['ossPath'] ?? null,
|
||||
'filepath' => $record['filepath'] ?? null
|
||||
];
|
||||
}
|
||||
|
||||
return success($backups);
|
||||
} catch (\Exception $e) {
|
||||
return error('获取备份记录失败:' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除备份记录(软删除)
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
// 验证是否为超级管理员
|
||||
$user = $this->request->user ?? null;
|
||||
if (!$user || $user['role'] !== 'superadmin') {
|
||||
return error('无权限访问', 403);
|
||||
}
|
||||
|
||||
// 支持从路由参数或请求参数获取ID
|
||||
$id = Request::param('id', 0) ?: Request::route('id', 0);
|
||||
|
||||
if (empty($id)) {
|
||||
return error('记录ID不能为空', 400);
|
||||
}
|
||||
|
||||
try {
|
||||
$record = BackupRecordModel::find($id);
|
||||
|
||||
if (!$record) {
|
||||
return error('备份记录不存在', 404);
|
||||
}
|
||||
|
||||
// 软删除(ThinkPHP的SoftDelete会自动设置deletedAt)
|
||||
$record->delete();
|
||||
|
||||
return success(null, '备份记录已删除');
|
||||
} catch (\Exception $e) {
|
||||
return error('删除失败:' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载备份文件
|
||||
*/
|
||||
public function download()
|
||||
{
|
||||
// 验证是否为超级管理员
|
||||
$user = $this->request->user ?? null;
|
||||
if (!$user || $user['role'] !== 'superadmin') {
|
||||
return error('无权限访问', 403);
|
||||
}
|
||||
|
||||
$filename = Request::param('file', '');
|
||||
|
||||
if (empty($filename)) {
|
||||
return error('文件名不能为空', 400);
|
||||
}
|
||||
|
||||
// 安全检查:只允许下载备份目录下的文件
|
||||
$backupDir = root_path() . 'runtime/backup/';
|
||||
$filepath = realpath($backupDir . $filename);
|
||||
|
||||
if (!$filepath || strpos($filepath, realpath($backupDir)) !== 0) {
|
||||
return error('文件不存在', 404);
|
||||
}
|
||||
|
||||
if (!file_exists($filepath)) {
|
||||
return error('文件不存在', 404);
|
||||
}
|
||||
|
||||
// 下载文件
|
||||
header('Content-Type: application/octet-stream');
|
||||
header('Content-Disposition: attachment; filename="' . $filename . '"');
|
||||
header('Content-Length: ' . filesize($filepath));
|
||||
readfile($filepath);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复数据库
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function restore()
|
||||
{
|
||||
// 验证是否为超级管理员
|
||||
$user = $this->request->user ?? null;
|
||||
if (!$user || $user['role'] !== 'superadmin') {
|
||||
return error('无权限访问', 403);
|
||||
}
|
||||
|
||||
$filename = Request::param('file', '');
|
||||
|
||||
if (empty($filename)) {
|
||||
return error('文件名不能为空', 400);
|
||||
}
|
||||
|
||||
try {
|
||||
$backupDir = root_path() . 'runtime/backup/';
|
||||
$filepath = realpath($backupDir . $filename);
|
||||
|
||||
if (!$filepath || strpos($filepath, realpath($backupDir)) !== 0) {
|
||||
return error('文件不存在', 404);
|
||||
}
|
||||
|
||||
if (!file_exists($filepath)) {
|
||||
return error('文件不存在', 404);
|
||||
}
|
||||
|
||||
$config = Config::get('database.connections.mysql');
|
||||
$host = $config['hostname'] ?? 'localhost';
|
||||
$port = $config['hostport'] ?? 3306;
|
||||
$database = $config['database'] ?? '';
|
||||
$username = $config['username'] ?? '';
|
||||
$password = $config['password'] ?? '';
|
||||
|
||||
// 使用PHP方式恢复
|
||||
$this->restoreDatabase($filepath);
|
||||
|
||||
return success(null, '数据库恢复成功');
|
||||
} catch (\Exception $e) {
|
||||
return error('恢复失败:' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据库大小
|
||||
*/
|
||||
private function getDatabaseSize($database)
|
||||
{
|
||||
try {
|
||||
$result = Db::query("SELECT
|
||||
ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS size_mb
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = '{$database}'");
|
||||
|
||||
return floatval($result[0]['size_mb'] ?? 0);
|
||||
} catch (\Exception $e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表数量
|
||||
*/
|
||||
private function getTableCount($database)
|
||||
{
|
||||
try {
|
||||
$result = Db::query("SELECT COUNT(*) as count FROM information_schema.tables WHERE table_schema = '{$database}'");
|
||||
return intval($result[0]['count'] ?? 0);
|
||||
} catch (\Exception $e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出表到SQL文件
|
||||
*/
|
||||
private function exportTableToSql($tableName, $filepath)
|
||||
{
|
||||
$fp = fopen($filepath, 'w');
|
||||
|
||||
// 写入表结构
|
||||
$createTable = Db::query("SHOW CREATE TABLE `{$tableName}`");
|
||||
fwrite($fp, "-- 表结构: {$tableName}\n");
|
||||
fwrite($fp, "DROP TABLE IF EXISTS `{$tableName}`;\n");
|
||||
fwrite($fp, $createTable[0]['Create Table'] . ";\n\n");
|
||||
|
||||
// 写入数据
|
||||
$data = Db::query("SELECT * FROM `{$tableName}`");
|
||||
if (!empty($data)) {
|
||||
fwrite($fp, "-- 表数据: {$tableName}\n");
|
||||
foreach ($data as $row) {
|
||||
$values = [];
|
||||
foreach ($row as $value) {
|
||||
$values[] = is_null($value) ? 'NULL' : "'" . addslashes($value) . "'";
|
||||
}
|
||||
fwrite($fp, "INSERT INTO `{$tableName}` VALUES (" . implode(', ', $values) . ");\n");
|
||||
}
|
||||
}
|
||||
|
||||
fclose($fp);
|
||||
}
|
||||
|
||||
/**
|
||||
* 备份数据库(PHP方式)
|
||||
*/
|
||||
private function backupDatabase($database, $filepath)
|
||||
{
|
||||
$fp = fopen($filepath, 'w');
|
||||
|
||||
// 写入文件头
|
||||
fwrite($fp, "-- MySQL数据库备份\n");
|
||||
fwrite($fp, "-- 数据库: {$database}\n");
|
||||
fwrite($fp, "-- 备份时间: " . date('Y-m-d H:i:s') . "\n");
|
||||
fwrite($fp, "SET NAMES utf8mb4;\n");
|
||||
fwrite($fp, "SET FOREIGN_KEY_CHECKS = 0;\n\n");
|
||||
|
||||
// 获取所有表
|
||||
$tables = Db::query("SHOW TABLES FROM `{$database}`");
|
||||
$tableKey = 'Tables_in_' . $database;
|
||||
|
||||
foreach ($tables as $table) {
|
||||
$tableName = $table[$tableKey];
|
||||
|
||||
// 写入表结构
|
||||
$createTable = Db::query("SHOW CREATE TABLE `{$tableName}`");
|
||||
if (!empty($createTable)) {
|
||||
fwrite($fp, "-- ----------------------------\n");
|
||||
fwrite($fp, "-- Table structure for {$tableName}\n");
|
||||
fwrite($fp, "-- ----------------------------\n");
|
||||
fwrite($fp, "DROP TABLE IF EXISTS `{$tableName}`;\n");
|
||||
fwrite($fp, $createTable[0]['Create Table'] . ";\n\n");
|
||||
|
||||
// 写入数据
|
||||
$data = Db::query("SELECT * FROM `{$tableName}`");
|
||||
if (!empty($data)) {
|
||||
fwrite($fp, "-- ----------------------------\n");
|
||||
fwrite($fp, "-- Records of {$tableName}\n");
|
||||
fwrite($fp, "-- ----------------------------\n");
|
||||
|
||||
foreach ($data as $row) {
|
||||
$columns = [];
|
||||
$values = [];
|
||||
foreach ($row as $col => $val) {
|
||||
$columns[] = "`{$col}`";
|
||||
$values[] = is_null($val) ? 'NULL' : "'" . addslashes($val) . "'";
|
||||
}
|
||||
fwrite($fp, "INSERT INTO `{$tableName}` (" . implode(', ', $columns) . ") VALUES (" . implode(', ', $values) . ");\n");
|
||||
}
|
||||
fwrite($fp, "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fwrite($fp, "SET FOREIGN_KEY_CHECKS = 1;\n");
|
||||
fclose($fp);
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复数据库(PHP方式)
|
||||
*/
|
||||
private function restoreDatabase($filepath)
|
||||
{
|
||||
$sql = file_get_contents($filepath);
|
||||
|
||||
// 分割SQL语句
|
||||
$statements = array_filter(array_map('trim', explode(';', $sql)));
|
||||
|
||||
foreach ($statements as $statement) {
|
||||
if (!empty($statement)) {
|
||||
Db::execute($statement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找mysqldump路径
|
||||
*/
|
||||
private function findMysqldump()
|
||||
{
|
||||
$paths = [
|
||||
'/usr/bin/mysqldump',
|
||||
'/usr/local/bin/mysqldump',
|
||||
'C:\\mysql\\bin\\mysqldump.exe',
|
||||
'C:\\xampp\\mysql\\bin\\mysqldump.exe',
|
||||
'mysqldump'
|
||||
];
|
||||
|
||||
foreach ($paths as $path) {
|
||||
if (is_executable($path) || shell_exec("which {$path}")) {
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找mysql路径
|
||||
*/
|
||||
private function findMysql()
|
||||
{
|
||||
$paths = [
|
||||
'/usr/bin/mysql',
|
||||
'/usr/local/bin/mysql',
|
||||
'C:\\mysql\\bin\\mysql.exe',
|
||||
'C:\\xampp\\mysql\\bin\\mysql.exe',
|
||||
'mysql'
|
||||
];
|
||||
|
||||
foreach ($paths as $path) {
|
||||
if (is_executable($path) || shell_exec("which {$path}")) {
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传备份文件到OSS
|
||||
* @param string $filepath 本地文件路径
|
||||
* @param string $filename 文件名
|
||||
* @return array|null 返回OSS URL和路径,失败返回null
|
||||
*/
|
||||
private function uploadBackupToOss($filepath, $filename)
|
||||
{
|
||||
if (!class_exists('\OSS\OssClient')) {
|
||||
throw new \RuntimeException('未安装 Aliyun OSS SDK,请先执行:composer require aliyuncs/oss-sdk-php');
|
||||
}
|
||||
|
||||
// 读取OSS配置
|
||||
$uploadConfig = Config::get('upload.oss');
|
||||
|
||||
$accessKeyId = $uploadConfig['access_key_id'] ?? '';
|
||||
$accessKeySecret = $uploadConfig['access_key_secret'] ?? '';
|
||||
$endpoint = $uploadConfig['endpoint'] ?? '';
|
||||
$bucket = $uploadConfig['bucket'] ?? '';
|
||||
$baseUrl = rtrim($uploadConfig['url'] ?? '', '/');
|
||||
|
||||
// 如果配置为空,尝试从环境变量读取
|
||||
if (empty($accessKeyId)) {
|
||||
$accessKeyId = getenv('OSS_ACCESS_KEY_ID') ?: getenv('ALIYUN_ACCESS_KEY_ID') ?: env('OSS_ACCESS_KEY_ID', env('ALIYUN_ACCESS_KEY_ID', ''));
|
||||
}
|
||||
if (empty($accessKeySecret)) {
|
||||
$accessKeySecret = getenv('OSS_ACCESS_KEY_SECRET') ?: getenv('ALIYUN_OSS_ACCESS_KEY_SECRET') ?: env('OSS_ACCESS_KEY_SECRET', env('ALIYUN_OSS_ACCESS_KEY_SECRET', ''));
|
||||
}
|
||||
if (empty($endpoint)) {
|
||||
$endpoint = getenv('OSS_ENDPOINT') ?: getenv('ALIYUN_OSS_ENDPOINT') ?: env('OSS_ENDPOINT', env('ALIYUN_OSS_ENDPOINT', ''));
|
||||
}
|
||||
if (empty($bucket)) {
|
||||
$bucket = getenv('OSS_BUCKET') ?: getenv('ALIYUN_OSS_BUCKET') ?: env('OSS_BUCKET', env('ALIYUN_OSS_BUCKET', ''));
|
||||
}
|
||||
if (empty($baseUrl)) {
|
||||
$baseUrl = rtrim(getenv('OSS_URL') ?: getenv('ALIYUN_OSS_URL') ?: env('OSS_URL', env('ALIYUN_OSS_URL', '')), '/');
|
||||
}
|
||||
|
||||
// 检查配置是否完整
|
||||
if (empty($accessKeyId) || empty($accessKeySecret) || empty($endpoint) || empty($bucket) || empty($baseUrl)) {
|
||||
throw new \RuntimeException('OSS配置不完整,无法上传备份文件');
|
||||
}
|
||||
|
||||
// 构建OSS对象路径(不使用OSS_PREFIX,直接使用backup目录)
|
||||
// 格式:backup/2026/02/12/backup_mbti_20260212160100.sql
|
||||
$datePath = date('Y/m/d');
|
||||
$object = 'backup/' . $datePath . '/' . $filename;
|
||||
|
||||
try {
|
||||
// 创建OSS客户端
|
||||
$client = new \OSS\OssClient($accessKeyId, $accessKeySecret, $endpoint);
|
||||
|
||||
// 验证Bucket是否存在
|
||||
if (!$client->doesBucketExist($bucket)) {
|
||||
throw new \RuntimeException("OSS Bucket '{$bucket}' 不存在或无法访问");
|
||||
}
|
||||
|
||||
// 上传文件
|
||||
$client->uploadFile($bucket, $object, $filepath);
|
||||
|
||||
// 生成访问URL
|
||||
$url = $baseUrl . '/' . ltrim($object, '/');
|
||||
|
||||
return [
|
||||
'url' => $url,
|
||||
'path' => $object
|
||||
];
|
||||
} catch (\OSS\Core\OssException $e) {
|
||||
throw new \RuntimeException('OSS上传失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存备份记录
|
||||
*/
|
||||
private function saveBackupRecord($filename, $filepath, $fileSize, $ossUrl = null, $ossPath = null)
|
||||
{
|
||||
try {
|
||||
BackupRecordModel::create([
|
||||
'filename' => $filename,
|
||||
'filepath' => $filepath,
|
||||
'fileSize' => $fileSize,
|
||||
'ossUrl' => $ossUrl,
|
||||
'ossPath' => $ossPath,
|
||||
'status' => 'success'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
// 记录保存失败不影响备份成功,只记录日志
|
||||
Log::error('保存备份记录失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user