WuYongJie 9595267ee7
Some checks failed
Tests / PHP 8.1 (push) Has been cancelled
Tests / PHP 8.2 (push) Has been cancelled
Tests / PHP 8.3 (push) Has been cancelled
chore: 导入 Akaunting 初始源码与团队协作规则
2026-07-20 18:20:25 +08:00

196 lines
6.7 KiB
PHP

<?php
namespace App\Http\Controllers\SystemSettings;
use App\Models\Settings\DatabaseConnection;
use App\Utilities\SystemAccess;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\Rule;
class Databases extends Controller
{
public function __construct()
{
$this->middleware(function ($request, $next) {
abort_unless(SystemAccess::canOpenSettings(), 403);
return $next($request);
});
}
public function index()
{
return view('system_settings.databases.index', [
'connections' => DatabaseConnection::where('company_id', company_id())->orderBy('id')->get(),
]);
}
public function create()
{
return view('system_settings.databases.form', ['connection' => null]);
}
public function store(Request $request)
{
$data = $this->validateConnection($request);
$data['company_id'] = company_id();
$data['enabled'] = (bool) ($data['enabled'] ?? false);
DatabaseConnection::create($data);
flash('数据库配置已新增。')->success();
return redirect()->route('system-settings.databases.index');
}
public function edit(int $id)
{
return view('system_settings.databases.form', [
'connection' => $this->findConnection($id),
]);
}
public function update(int $id, Request $request)
{
$connection = $this->findConnection($id);
$data = $this->validateConnection($request, $connection);
$data['enabled'] = (bool) ($data['enabled'] ?? false);
if (empty($data['password'])) {
unset($data['password']);
}
$connection->update($data);
flash('数据库配置已修改。')->success();
return redirect()->route('system-settings.databases.index');
}
public function destroy(int $id)
{
$this->findConnection($id)->delete();
flash('数据库配置已删除。')->success();
return redirect()->route('system-settings.databases.index');
}
public function export()
{
$connections = DatabaseConnection::where('company_id', company_id())->orderBy('id')->get();
return response()->streamDownload(function () use ($connections) {
$stream = fopen('php://output', 'w');
fwrite($stream, "\xEF\xBB\xBF");
fputcsv($stream, ['name', 'driver', 'host', 'port', 'database_name', 'username', 'password', 'charset', 'enabled']);
foreach ($connections as $connection) {
fputcsv($stream, [
$connection->name,
$connection->driver,
$connection->host,
$connection->port,
$connection->database_name,
$connection->username,
'',
$connection->charset,
$connection->enabled ? 1 : 0,
]);
}
fclose($stream);
}, 'database-connections.csv', ['Content-Type' => 'text/csv; charset=UTF-8']);
}
public function import(Request $request)
{
$request->validate(['file' => ['required', 'file', 'mimes:csv,txt', 'max:5120']]);
$handle = fopen($request->file('file')->getRealPath(), 'r');
$header = fgetcsv($handle);
$header = array_map(fn ($value) => trim((string) $value, "\xEF\xBB\xBF \t\n\r\0\x0B"), $header ?: []);
$required = ['name', 'driver', 'database_name'];
if (array_diff($required, $header)) {
fclose($handle);
return back()->withErrors(['file' => '文件表头不正确,请先导出模板后填写。']);
}
$count = 0;
DB::transaction(function () use ($handle, $header, &$count) {
while (($row = fgetcsv($handle)) !== false) {
if (count(array_filter($row, fn ($value) => trim((string) $value) !== '')) === 0) {
continue;
}
$row = array_pad($row, count($header), '');
$data = array_combine($header, array_slice($row, 0, count($header)));
$driver = in_array($data['driver'] ?? '', ['mysql', 'pgsql', 'sqlite', 'sqlsrv'], true)
? $data['driver']
: 'mysql';
$attributes = [
'driver' => $driver,
'host' => $data['host'] ?: null,
'port' => ($data['port'] ?? '') !== '' ? (int) $data['port'] : null,
'database_name' => trim((string) ($data['database_name'] ?? '')),
'username' => $data['username'] ?: null,
'charset' => $data['charset'] ?: 'utf8mb4',
'enabled' => filter_var($data['enabled'] ?? true, FILTER_VALIDATE_BOOL),
];
if (($data['password'] ?? '') !== '') {
$attributes['password'] = $data['password'];
}
if (trim((string) ($data['name'] ?? '')) === '' || $attributes['database_name'] === '') {
continue;
}
DatabaseConnection::updateOrCreate(
['company_id' => company_id(), 'name' => trim($data['name'])],
$attributes
);
$count++;
}
});
fclose($handle);
flash("已导入 {$count} 条数据库配置。")->success();
return redirect()->route('system-settings.databases.index');
}
private function findConnection(int $id): DatabaseConnection
{
return DatabaseConnection::where('company_id', company_id())->findOrFail($id);
}
private function validateConnection(Request $request, ?DatabaseConnection $connection = null): array
{
return $request->validate([
'name' => [
'required',
'string',
'max:191',
Rule::unique('system_database_connections', 'name')
->where(fn ($query) => $query->where('company_id', company_id()))
->ignore($connection?->id),
],
'driver' => ['required', Rule::in(['mysql', 'pgsql', 'sqlite', 'sqlsrv'])],
'host' => ['nullable', 'string', 'max:191'],
'port' => ['nullable', 'integer', 'between:1,65535'],
'database_name' => ['required', 'string', 'max:191'],
'username' => ['nullable', 'string', 'max:191'],
'password' => ['nullable', 'string', 'max:1000'],
'charset' => ['required', 'string', 'max:30'],
'enabled' => ['nullable', 'boolean'],
]);
}
}