用 Laravel 制作 URL 缩短器
大家好,你们是否使用过 https://bitly.com/ 来缩短 URL 呢?在本文中,我将使用 Laravel 框架 制作一个像 bitly 一样的 URL 缩短应用。
让我们开始吧!
首先,安装 Laravel。
composer create-project laravel/laravel laravel-url-shortener
cd laravel-url-shortener
php artisan serve
新建 Laravel 项目
**安装 Laravel Breeze。**接下来,我们将安装 Laravel Breeze,它是 Laravel 的所有身份验证功能的最小、简单实现,包括登录、注册、密码重置、电子邮件验证和密码确认。安装后,你可以自定义这些组件以适应你的需求。
composer require laravel/breeze --dev
php artisan breeze:install blade
php artisan migrate
**注册账户。**访问 URL /register 来注册你的账户。
注册页面
**重定向到登录页面(编辑路由)。**接下来,我将在第一次运行应用程序时重定向到登录页面。
Route::get('/', function () {
return view('auth.login');
});
登录页面
创建 URL 缩短器的模型、迁移和控制器。 为了让用户创建 URL 缩短器,我们需要创建模型、迁移和控制器。让我们深入探讨一下这些概念:
- 模型为你与数据库中的表交互提供了一个强大而愉悦的接口。
- 迁移让你可以轻松地创建和修改数据库中的表。它们确保同样的数据库结构存在于应用程序运行的任何地方。
- 控制器负责处理发往你的应用程序的请求并返回响应。
php artisan make:model -mrc Url
这个命令将为你创建三个文件:
app/Models/Url.php
- Eloquent 模型。database/migrations/<timestamp>_create_urls_table.php
- 将创建你的数据库表的数据库迁移。app/Http/Controller/UrlController.php
- HTTP 控制器,它将处理传入的请求并返回响应。
编辑迁移文件。 打开文件夹 database/migrations/<timestamp>_create_urls_table.php
,然后进行以下编辑,并在编辑后使用命令 php artisan migrate
执行迁移。
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('urls', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->string('title');
$table->string('original_url');
$table->string('shortener_url');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('urls');
}
};
**编辑模型文件。**打开文件夹 app/Models/Url.php 中的模型文件,并像这样进行编辑。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Url extends Model
{
use HasFactory;
protected $fillable = [
'user_id', 'title', 'original_url', 'shortener_url'
];
public function user()
{
return $this->belongsTo(User::class);
}
}
**为 URL 缩短器功能添加路由。**我们还需要为控制器创建 URL。我们可以通过添加“路由”来实现这一点,这些路由在你的项目的 routes
目录中管理。因为我们使用了资源控制器,所以我们可以使用单个 Route::resource()
语句来定义遵循常规 URL 结构的所有路由。
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\UrlController;
use App\Http\Controllers\ProfileController;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
// route for login
Route::get('/', function () {
return view('auth.login');
});
// route for dashboard
Route::get('/dashboard', function () {
return view('dashboard');
})->middleware(['auth', 'verified'])->name('dashboard');
// route for urls
Route::resource('urls', UrlController::class)
->middleware(['auth', 'verified']);
// route for get shortener url
Route::get('{shortener_url}', [UrlController::class, 'shortenLink'])->name('shortener-url');
Route::middleware('auth')->group(function () {
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
});
require __DIR__.'/auth.php';
**编辑 UrlController.php。**打开 app/Http/Controllers/UrlController.php 文件,控制器中已经创建了 7 个主要函数,分别是:index、create、store、edit、update 和 destroy。
为了节省时间,我将在控制器中创建一个完整的函数,像这样:
<?php
namespace App\Http\Controllers;
use App\Models\Url;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class UrlController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
return view('urls.index', [
'urls' => Url::with('user')->latest()->get(),
]);
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$request->validate([
'title' => 'required|string|max:255',
'original_url' => 'required|string|max:255',
]);
$data = $request->all();
$data['user_id'] = Auth::user()->id;
$data['title'] = Str::ucfirst($request->title);
$data['original_url'] = $request->original_url;
$data['shortener_url'] = Str::random(5);
Url::create($data);
return redirect(route('urls.index'));
}
/**
* Display the specified resource.
*
* @param \App\Models\Url $url
* @return \Illuminate\Http\Response
*/
public function show(Url $url)
{
//
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Url $url
* @return \Illuminate\Http\Response
*/
public function edit(Url $url)
{
return view('urls.edit', [
'url' => $url,
]);
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param \App\Models\Url $url
* @return \Illuminate\Http\Response
*/
public function update(Request $request, Url $url)
{
$validated = $request->validate([
'title' => 'required|string|max:255',
'original_url' => 'required|string|max:255',
]);
$validated['shortener_url'] = Str::random(5);
$url->update($validated);
return redirect(route('urls.index'));
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Url $url
* @return \Illuminate\Http\Response
*/
public function destroy(Url $url)
{
$url->delete();
return redirect(route('urls.index'));
}
public function shortenLink($shortener_url)
{
$find = Url::where('shortener_url', $shortener_url)->first();
return redirect($find->original_url);
}
}
**添加导航菜单。**打开文件 resources/views/layouts/navigation.blade.php,并为 URL 缩短器功能添加 navigation。
<nav x-data="{ open: false }" class="bg-white border-b border-gray-100">
<!-- Primary Navigation Menu -->
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<div class="flex">
<!-- Logo -->
<div class="shrink-0 flex items-center">
<a href="{{ route('dashboard') }}">
<x-application-logo class="block h-9 w-auto fill-current text-gray-800" />
</a>
</div>
<!-- Navigation Links -->
<div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
<x-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')">
{{ __('Dashboard') }}
</x-nav-link>
{{-- Add This For URL Shortener Feature (Desktop) --}}
<x-nav-link :href="route('urls.index')" :active="request()->routeIs('urls.index')">
{{ __('Urls') }}
</x-nav-link>
</div>
</div>
<!-- Settings Dropdown -->
<div class="hidden sm:flex sm:items-center sm:ml-6">
<x-dropdown align="right" width="48">
<x-slot name="trigger">
<button class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-500 bg-white hover:text-gray-700 focus:outline-none transition ease-in-out duration-150">
<div>{{ Auth::user()->name }}</div>
<div class="ml-1">
<svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
</svg>
</div>
</button>
</x-slot>
<x-slot name="content">
<x-dropdown-link :href="route('profile.edit')">
{{ __('Profile') }}
</x-dropdown-link>
<!-- Authentication -->
<form method="POST" action="{{ route('logout') }}">
@csrf
<x-dropdown-link :href="route('logout')"
onclick="event.preventDefault();
this.closest('form').submit();">
{{ __('Log Out') }}
</x-dropdown-link>
</form>
</x-slot>
</x-dropdown>
</div>
<!-- Hamburger -->
<div class="-mr-2 flex items-center sm:hidden">
<button @click="open = ! open" class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 focus:text-gray-500 transition duration-150 ease-in-out">
<svg class="h-6 w-6" stroke="currentColor" fill="none" viewBox="0 0 24 24">
<path :class="{'hidden': open, 'inline-flex': ! open }" class="inline-flex" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
<path :class="{'hidden': ! open, 'inline-flex': open }" class="hidden" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
</div>
<!-- Responsive Navigation Menu -->
<div :class="{'block': open, 'hidden': ! open}" class="hidden sm:hidden">
<div class="pt-2 pb-3 space-y-1">
<x-responsive-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')">
{{ __('Dashboard') }}
</x-responsive-nav-link>
{{-- Add This For URL Shortener Feature (Mobile) --}}
<x-responsive-nav-link :href="route('urls.index')" :active="request()->routeIs('urls.index')">
{{ __('Urls') }}
</x-responsive-nav-link>
</div>
<!-- Responsive Settings Options -->
<div class="pt-4 pb-1 border-t border-gray-200">
<div class="px-4">
<div class="font-medium text-base text-gray-800">{{ Auth::user()->name }}</div>
<div class="font-medium text-sm text-gray-500">{{ Auth::user()->email }}</div>
</div>
<div class="mt-3 space-y-1">
<x-responsive-nav-link :href="route('profile.edit')">
{{ __('Profile') }}
</x-responsive-nav-link>
<!-- Authentication -->
<form method="POST" action="{{ route('logout') }}">
@csrf
<x-responsive-nav-link :href="route('logout')"
onclick="event.preventDefault();
this.closest('form').submit();">
{{ __('Log Out') }}
</x-responsive-nav-link>
</form>
</div>
</div>
</div>
</nav>
**为 URL 缩短器功能创建页面。**在 resources/views 中创建文件夹 urls,并创建 2 个文件 index.blade.php 和 edit.blade.php。
文件夹结构
使用以下代码添加文件 index.blade.php:
<x-app-layout>
<div class="max-w-2xl mx-auto p-4 sm:p-6 lg:p-8">
<form method="POST" action="{{ route('urls.store') }}">
@csrf
<input type="text"
name="title"
required
maxlength="255"
placeholder="{{ __('Title') }}"
class="block w-full border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm"
value="{{ old('title') }}"
/>
<x-input-error :messages="$errors->store->get('title')" class="mt-2" />
<input type="text"
name="original_url"
required
maxlength="255"
placeholder="{{ __('Original Url') }}"
class="block w-full border-gray-300 mt-5 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm"
value="{{ old('original_url') }}"
/>
<x-input-error :messages="$errors->store->get('original_url')" class="mt-2" />
<x-primary-button class="mt-4">{{ __('Save') }}</x-primary-button>
</form>
<div class="mt-6 bg-white shadow-sm rounded-lg divide-y">
@foreach ($urls as $item)
<div class="p-6 flex space-x-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-600 -scale-x-100" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
</svg>
<div class="flex-1">
<div class="flex justify-between items-center">
<div>
<span class="text-gray-800">{{ $item->user->name }}
<small class="ml-2 text-sm text-gray-600">{{ $item->created_at->format('j M Y, g:i a') }}</small>
@unless ($item->created_at->eq($item->updated_at))
<small class="text-sm text-gray-600"> · {{ __('edited') }}</small>
@endunless
</div>
@if ($item->user->is(auth()->user()))
<x-dropdown>
<x-slot name="trigger">
<button>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-gray-400" viewBox="0 0 20 20" fill="currentColor">
<path d="M6 10a2 2 0 11-4 0 2 2 0 014 0zM12 10a2 2 0 11-4 0 2 2 0 014 0zM16 12a2 2 0 100-4 2 2 0 000 4z" />
</svg>
</button>
</x-slot>
<x-slot name="content">
<x-dropdown-link :href="route('urls.edit', $item)">
{{ __('Edit') }}
</x-dropdown-link>
<form method="POST" action="{{ route('urls.destroy', $item) }}">
@csrf
@method('delete')
<x-dropdown-link :href="route('urls.destroy', $item)" onclick="event.preventDefault(); this.closest('form').submit();">
{{ __('Delete') }}
</x-dropdown-link>
</form>
</x-slot>
</x-dropdown>
@endif
</div>
<p class="mt-4 text-lg text-gray-900">{{ __('Title: ') }} {{ $item->title }}</p>
<p class="mt-4 text-lg text-gray-900">{{ __('Original Url: ') }}{{ $item->original_url }}</p>
<p class="mt-4 text-lg text-gray-900">{{ __('Shortener Url: ') }}
<a href="{{ route('shortener-url', $item->shortener_url) }}" target="_blank">
{{ route('shortener-url', $item->shortener_url) }}
</a>
</p>
</div>
</div>
@endforeach
</div>
</div>
</x-app-layout>
使用以下代码添加文件 edit.blade.php:
<x-app-layout>
<div class="max-w-2xl mx-auto p-4 sm:p-6 lg:p-8">
<form method="POST" action="{{ route('urls.update', $url) }}">
@csrf
@method('patch')
<input type="text"
name="title"
required
maxlength="255"
placeholder="{{ __('Title') }}"
class="block w-full border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm"
value="{{ old('title', $url->title) }}"
/>
<x-input-error :messages="$errors->store->get('title')" class="mt-2" />
<input type="text"
name="original_url"
required
maxlength="255"
placeholder="{{ __('Original Url') }}"
class="block w-full border-gray-300 mt-5 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm"
value="{{ old('original_url', $url->original_url) }}"
/>
<x-input-error :messages="$errors->store->get('original_url')" class="mt-2" />
<div class="mt-4 space-x-2">
<x-primary-button>{{ __('Save') }}</x-primary-button>
<a href="{{ route('urls.index') }}">{{ __('Cancel') }}</a>
</div>
</form>
</div>
</x-app-layout>
测试 URL 缩短器功能: 填写标题和原始 URL 的表单。
URL 缩短器功能
URL 缩短器功能
然后点击保存按钮。
URL 缩短器功能结果
编辑页面。
编辑 URL 缩短器功能
然后点击保存按钮。 你会看到缩短器 URL 部分已更改。
最后,我们得出结论。我们已经完成了创建类似于https://bitly.com/网站的使用 Laravel 制作 URL 缩短器项目。你可以复制 URL 缩短器部分并用于你需要的目的。如果你需要此项目的源代码,我将在此链接中分享此项目的 GitHub 仓库,并且你必须阅读文件 YOU-MUST-README.txt。
谢谢大家。
译自:https://medium.com/@andreelm/make-url-shortener-with-laravel-4983511c7a9d
评论(0)