Я действительно взволнован Laravel Spark . К тому времени, когда вы прочитаете это, вероятно, будет множество постов, объясняющих, как вы можете настроить это. Это не так интересно для меня, как путешествие, которое я собираюсь предпринять для создания реального бизнеса со Spark!
Идея проста. Я создал модуль Pagekit, который вы можете использовать для резервного копирования и восстановления данных сайта. Модуль позволяет легко сохранять и загружать эти резервные копии, а также восстанавливать их на разных серверах.
Проблема в том, что получение этих файлов резервных копий на удаленный сервер занимает много времени и немного хлопот. Мне часто хотелось, чтобы можно было быстро и безболезненно перенести это состояние приложения с одного сервера на другой и сделать автоматическое резервное копирование за пределы площадки. Поэтому я собираюсь настроить это для себя, и, возможно, другие найдут это достаточно полезным, чтобы заплатить за это.
Начиная
Я использую Stripe и намерен иметь единый план без проб. Настройка для этого довольно проста, но я запомнил идентификатор плана. Мне нужно это, чтобы настроить план в Spark …
Затем я сбрасываю свои секретные и открытые ключи Stripe и обновляю до последней версии API (через тот же экран, https://dashboard.stripe.com/account/apikeys ).
Я забыл, что настройки в .env
У Spark есть несколько ожидаемых полей регистрации / профиля, но я хочу добавить еще несколько. Я хотел бы спросить пользователей, хотят ли они автоматическое резервное копирование, и я также хотел бы получить их платежный адрес, чтобы я мог указать его в своем счете. Сначала я должен создать миграцию для нового поля:
php artisan make:migration add_should_backup_field
Для этого мы можем добавить столбец (убедитесь, что удалили его, если миграция откатывается):
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddShouldBackupField extends Migration
{
public function up()
{
Schema::table("users", function (Blueprint $table) {
$table->boolean("should_backup");
});
}
public function down()
{
Schema::table("users", function (Blueprint $table) {
$table->dropColumn("should_backup");
});
}
}
Сбор платежных адресов так же прост, как один вызов метода, в app/Providers/SparkServiceProvider.php
/**
* @inheritdoc
*/
public function booted()
{
Spark::useStripe();
Spark::collectBillingAddress();
Spark::plan("[Stripe plan name]", "[Stripe plan ID]")
->price(4.99)
->features([
"Backups"
]);
}
Это добавляет поля адреса выставления счета к форме регистрации:
Не забудьте настроить остальную часть SparkServiceProvider.php
Хранение резервных копий
Весь смысл этого приложения в том, чтобы дать авторам контента возможность загружать, перечислять и загружать свои резервные копии. Далее я создам модель Backup
php artisan make:migration create_backups_table
php artisan make:model Backups
Таблица резервных копий нуждается в нескольких полях:
- Имя каждой резервной копии (возможно, содержит имя сайта, дату и включает ли оно базу данных и данные для загрузки).
- Загруженный файл резервной копии.
- Размер каждой резервной копии.
- Поле для связи резервной копии с пользователем, который ее создал.
Вот как выглядит настроенный файл миграции:
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\Schema;
class CreateBackupsTable extends Migration
{
public function up()
{
Schema::create("backups", function (Blueprint $table) {
$table->increments("id");
$table->string("name");
$table->string("file");
$table->integer("size");
$table->integer("user_id");
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists("backups");
}
}
Учитывая эту структуру базы данных, я хочу предоставить набор конечных точек JSON, с помощью которых пользователи могут просматривать и управлять резервными копиями. Опять же, мы можем использовать команду make, чтобы начать работу:
php artisan make:controller BackupsController --resource
Spark специально разработан, чтобы стать хорошей отправной точкой для создания аутентифицированного API. Когда вы входите в систему с учетной записью Spark, вы можете перейти в раздел, в котором вы можете создавать токены API:
С помощью этого токена и создав контроллер ресурсов, я могу заставить метод index отображать некоторый отладочный текст. Тогда я могу сделать к нему запросы curl:
curl -XGET http://localhost:8000/api/backup
Чтобы это работало, мне нужно определить маршрут API (в app/Http/api.php
Route::group([
"prefix" => "api",
"middleware" => "auth:api"
], function () {
Route::resource("backup", "BackupsController");
});
Поскольку я не предоставил параметр GET api_token
Если я вошел в систему в то время, я перенаправлен обратно на экран приборной панели.
Если я определяю параметр api_token
Я выбрал простой механизм загрузки, который очень мало проверяет файлы (пока я работаю с прототипом). Я хочу уточнить это позже с некоторой проверкой, но сейчас у меня есть:
public function store(Request $request)
{
$file = $request->file("file");
$name = $request->get("name");
if (!$file || !$name) {
return [
"status" => "error",
"error" => "file or name missing",
];
}
$fileExtension = "." . $file->getClientOriginalExtension();
$fileName = str_slug(time() . " " . $name) . $fileExtension;
try {
$file->move(storage_path("backups"), $fileName);
} catch (Exception $exception) {
return [
"status" => "error",
"error" => "could not store file",
];
}
$backup = Backup::create([
"name" => $name,
"file" => $fileName,
"size" => $file->getClientSize(),
"user_id" => Auth::user()->id,
]);
return $backup;
}
Я могу проверить это, создав пустой файл резервной копии и затем отправив его в store
touch backup.zip
curl
-X POST
-F name="My First Backup"
-F file=@backup.zip
"http://localhost:8000/api/backup?api_token=[my token]"
Я получаю обратно что-то похожее:
{
"name": "My First Backup",
"file": "1461802753-my-first-backup.zip",
"size": 0,
"user_id": 1,
"updated_at": "2016-04-28 00:19:13",
"created_at": "2016-04-28 00:19:13",
"id": 8
}
Обратите внимание, как файл называется по-разному, чтобы избежать коллизий? Он на самом деле назван в честь имени резервной копии, а не исходного имени файла, и я добавил временную метку, чтобы можно было загружать резервные копии с одинаковыми именами без конфликта имен.
Существует еще условие гонки, когда пользователи загружают резервные копии с одинаковым именем в одну секунду. Риск этого меньше, и я готов иметь дело с этим, когда это становится проблемой.
Теперь я могу настроить действие index
public function index()
{
return [
"status" => "ok",
"data" => Auth::user()->backups,
];
}
Мне также пришлось создать эти отношения между пользователями и резервными копиями, добавив метод backups
User
public function backups()
{
return $this->hasMany(Backup::class);
}
Загрузка резервных копий
Я загружаю резервные копии в непубличную папку по причине. Я не хочу, чтобы пользователи могли угадывать (независимо от того, насколько маловероятно, что они догадаются правильно), как получить доступ к резервным копиям других пользователей.
Поэтому я должен создать специальную конечную точку загрузки. Я могу, однако, отыграть от действия show
public function show($id)
{
$backup = Auth::user()->backups()->findOrFail($id);
$path = storage_path("backups/". $backup->file);
return response()->download($path, $backup->file);
}
Из-за того, как определяется область действия запроса, пользователям будет разрешено загружать только резервные копии, связанные с их учетной записью, и эти резервные копии не будут общедоступными за пределами этого одного маршрута.
Вывод
За невероятно короткий промежуток времени мне удалось использовать Spark для создания хостинговой платформы на основе подписки с аутентифицированным API для хранения и загрузки файлов резервных копий.
Предстоит проделать еще много работы, начиная от приложения и заканчивая повышением безопасности, внедрением ограничений хранилища и удалением резервной копии. Тем не менее, я воодушевлен началом, которое я сделал, и я могу вернуться к этому приложению в будущих уроках, так как я узнаю больше о Spark!
У вас есть какие-либо вопросы по поводу этого кода или комментарии о том, что бы вы сделали по-другому? Дайте нам знать об этом в комментариях!