WebProject

🚗 Dự Án Website Tư Vấn / Mua Bán Ô Tô

👤 Thông Tin Sinh Viên


📋 Giới Thiệu Dự Án

Dự án xây dựng một website chuyên về tư vấn và mua bán ô tô, kết nối giữa người muabán, mang đến trải nghiệm giao dịch:

✨ Tính năng hỗ trợ:


💻 Công Nghệ Sử Dụng


🧠 Sơ Đồ

📌 Sơ Đồ Khối

📌 Sơ Đồ Chức Năng

📌 Sơ Đồ Thuật Toán


🧩 Code Chính Minh Họa

📦 Model

User

class User extends Authenticatable
{
    /** @use HasFactory<\Database\Factories\UserFactory> */
    use HasFactory, Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var list<string>
     */
    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var list<string>
     */
    protected $hidden = [
        'password',
        'remember_token',
    ];

    /**
     * Get the attributes that should be cast.
     *
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'email_verified_at' => 'datetime',
            'password' => 'hashed',
        ];
    }

    public function cart()
    {
    return $this->hasMany(Cart::class);
    }
}

Car


class Car extends Model
{
    protected $fillable = [
        'image',
        'name',
        'brand',
        'year',
        'price',
        'note',
    ];
}

Cart


class Cart extends Model
{
    use HasFactory;

    protected $fillable = ['user_id', 'car_id', 'quantity', 'status'];

    public function user()
    {
        return $this->belongsTo(User::class);
    }

    public function car()
    {
        return $this->belongsTo(Car::class);
    }
}

CartItem


class CartItem extends Model
{
    use HasFactory;

    protected $fillable = ['cart_id', 'car_id', 'quantity'];

    public function cart()
    {
        return $this->belongsTo(Cart::class);
    }

    public function car()
    {
        return $this->belongsTo(Car::class);
    }
}

🧠 Controller

ProfileController


class ProfileController extends Controller
{
    /**
     * Display the user's profile form.
     */
    public function edit(Request $request): View
    {
        return view('profile.edit', [
            'user' => $request->user(),
        ]);
    }

    /**
     * Update the user's profile information.
     */
    public function update(ProfileUpdateRequest $request): RedirectResponse
    {
        $request->user()->fill($request->validated());

        if ($request->user()->isDirty('email')) {
            $request->user()->email_verified_at = null;
        }

        $request->user()->save();

        return Redirect::route('profile.edit')->with('status', 'profile-updated');
    }

    /**
     * Delete the user's account.
     */
    public function destroy(Request $request): RedirectResponse
    {
        $request->validateWithBag('userDeletion', [
            'password' => ['required', 'current_password'],
        ]);

        $user = $request->user();

        Auth::logout();

        $user->delete();

        $request->session()->invalidate();
        $request->session()->regenerateToken();

        return Redirect::to('/');
    }
}

CarController


class CarController extends Controller
{
    public function index(Request $request)
    {
        //Tìm kiếm xe
        $search = $request -> input('search');
        $query = Car::query();

        if($search) {
            $query -> where('name', 'like', "%{$search}%")
                    -> orWhere('brand', 'like', "%{$search}%");
        }

        //Phân trang (5/1t)
        $cars = $query -> orderBy('id', 'desc') -> paginate(5);
        $cars -> appends(['search' => $search]);

        return view('cars.index', compact('cars', 'search'));
    }

    public function create() {
        return view('cars.create');
    }
    public function store(Request $request)
    {
        $validated = $request->validate([
            'image' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg|max:2048',
            'name' => 'required|string|max:255',
            'brand' => 'required|string|max:255',
            'year' => 'required|digits:4|integer|min:1900|max:' . date('Y'),
            'price' => 'required|numeric|min:0',
            'note' => 'nullable|string',
        ]);

        // Xử lý upload ảnh nếu có
        if ($request->hasFile('image')) {
            $path = $request->file('image')->store('cars', 'public');
            $validated['image'] = $path;
        }

        Car::create($validated);

        return redirect()->route('cars.index')->with('success', 'Đã thêm thành công một mẫu xe mới!');
    }

    public function show($id)
    {
        $car = Car::findOrFail($id);
        return view('cars.show', compact('car'));
    }

    public function edit($id)
    {
        $car = Car::findOrFail($id);
        return view('cars.edit', compact('car'));
    }

    public function update(Request $request, $id)
    {
        $car = Car::findOrFail($id);

        $validated = $request->validate([
            'image' => 'nullable|image',
            'name' => 'required|string|max:255',
            'brand' => 'required|string|max:255',
            'year' => 'required|digits:4|integer|min:1900|max:' . date('Y'),
            'price' => 'required|numeric|min:0',
            'note' => 'nullable|string',
        ]);

        // Nếu có upload ảnh mới, xóa ảnh cũ rồi lưu ảnh mới
        if ($request->hasFile('image')) {
            if ($car->image && Storage::disk('public')->exists($car->image)) {
                Storage::disk('public')->delete($car->image);
            }
            $path = $request->file('image')->store('cars', 'public');
            $validated['image'] = $path;
        }

        $car->update($validated);

        return redirect()->route('cars.index')->with('success', 'Cập nhật lại thông tin xe thành công!');
    }

    public function destroy($id)
    {
        $car = Car::findOrFail($id);

        // Xóa ảnh khi xoá xe
        if ($car->image && Storage::disk('public')->exists($car->image)) {
            Storage::disk('public')->delete($car->image);
        }

        $car->delete();

        return redirect()->route('cars.index')->with('success', 'Mẫu xe đã được xóa thành công');
    }
}

CartController

class CartController extends Controller
{
    // Thêm xe vào giỏ hàng
    public function addToCart($carId)
    {
        $user = Auth::user();

        $existingItem = $user->cart()
            ->where('car_id', $carId)
            ->where('status', 'active')
            ->first();

        if ($existingItem) {
            $existingItem->increment('quantity');
        } else {
            $user->cart()->create([
                'car_id' => $carId,
                'quantity' => 1,
                'status' => 'active',
            ]);
        }

        return redirect()->back()->with('success', 'Đã thêm xe vào giỏ hàng!');
    }

    // Hiển thị giỏ hàng
    public function index()
    {
        $user = Auth::user();
        $cartItems = $user->cart()
            ->where('status', 'active')
            ->with('car')
            ->get();

        return view('cart.index', compact('cartItems'));
    }

    // Cập nhật số lượng
    public function update(Request $request, $id)
    {
        $request->validate([
            'quantity' => 'required|integer|min:1',
        ]);

        $cartItem = Cart::findOrFail($id);

        if ($cartItem->user_id !== Auth::id() || $cartItem->status !== 'active') {
            abort(403);
        }

        $cartItem->quantity = $request->quantity;
        $cartItem->save();

        return redirect()->route('cart.index')->with('success', 'Cập nhật số lượng thành công!');
    }

    // Xóa khỏi giỏ hàng
    public function removeFromCart($id)
    {
        $cartItem = Cart::findOrFail($id);

        if ($cartItem->user_id !== Auth::id() || $cartItem->status !== 'active') {
            abort(403);
        }

        $cartItem->delete();

        return redirect()->back()->with('success', 'Đã xóa khỏi giỏ hàng!');
    }

    // Hiển thị form thanh toán
    public function showCheckoutForm()
    {
        $user = Auth::user();
        $cartItems = $user->cart()
            ->where('status', 'active')
            ->with('car')
            ->get();

        if ($cartItems->isEmpty()) {
            return redirect()->route('cart.index')->with('error', 'Giỏ hàng của bạn đang trống.');
        }

        $total = $cartItems->sum(fn($item) => $item->quantity * $item->car->price);

        return view('cart.checkout', compact('cartItems', 'total'));
    }

    // Xử lý gửi yêu cầu tư vấn (checkout)
    public function checkout(Request $request)
    {
        $user = Auth::user();
        $cartItems = $user->cart()
            ->where('status', 'active')
            ->with('car')
            ->get();

        if ($cartItems->isEmpty()) {
            return redirect()->route('cart.index')->with('error', 'Giỏ hàng của bạn đang trống.');
        }

        $request->validate([
            'name' => 'required|string|max:255',
            'email' => 'required|email',
            'phone' => 'nullable|string|max:20',
            'address' => 'nullable|string|max:255',
            'message' => 'nullable|string|max:1000',
        ]);

        $data = [
            'user' => $user,
            'name' => $request->name,
            'email' => $request->email,
            'phone' => $request->phone,
            'address' => $request->address,
            'message' => $request->message,
            'cartItems' => $cartItems,
            'total' => $cartItems->sum(fn($item) => $item->quantity * $item->car->price),
        ];

        Mail::to('daylaaccclone39@gmail.com')->send(new CheckoutMail($data));

        $user->cart()->delete();
        
        // Cập nhật trạng thái các item thành 'checked_out'
        // foreach ($cartItems as $item) {
        //     $item->status = 'checked_out';
        //     $item->save();
        // }

        return redirect()->route('cart.index')->with('success', 'Yêu cầu tư vấn của bạn đã được gửi. Chúng tôi sẽ liên hệ bạn sớm nhất!');
    }
}

ContactController


class ContactController extends Controller
{
    public function index()
    {
        return view('contact.index');
    }

    public function send(Request $request)
    {
        $request->validate([
            'name'    => 'required|string|max:255',
            'email'   => 'required|email',
            'subject' => 'required|string|max:255',
            'message' => 'required|string',
        ]);

        $contactData = $request->only(['name', 'email', 'subject', 'message']);

        Mail::to('daylaaccclone39@gmail.com')->send(new ContactMail($contactData));

        return redirect()->back()->with('success', 'Cảm ơn bạn đã liên hệ với chúng tôi, chúng tôi sẽ phản hồi lại bạn sớm!');
    }
}

ScheduleController


class ScheduleController extends Controller
{
    // Hiển thị form đặt lịch
    public function showForm()
    {
        return view('schedule.form');
    }

    // Xử lý lưu thông tin đặt lịch
    public function store(Request $request)
    {
        $validated = $request->validate([
            'name' => 'required|string|max:255',
            'phone' => 'required|string|max:20',
            'email' => 'nullable|email',
            'service' => 'required|string',
            'date' => 'required|date|after_or_equal:today',
            'note' => 'nullable|string',
        ]);

        // Gửi mail cho admin
        Mail::to('daylaaccclone39@gmail.com')->send(new ScheduleNotification($validated));

        // Schedule::create($validated);

        return redirect()->route('schedule.form')->with('success', 'Đặt lịch thành công! Chúng tôi sẽ phản hồi bạn sớm.');
    }
}


📄 Blade Template (View)

view


🌐 Routes


// Trang welcome (mặc định)
Route::get('/', fn () => view('welcome'))->name('welcome');

// Dashboard redirect (cần đăng nhập + xác thực email)
Route::get('/dashboard', fn () => redirect()->route('home.homepage'))
    ->middleware(['auth', 'verified'])
    ->name('dashboard');

// Trang chính (Home)
Route::get('/home', fn () => view('home.homepage'))->name('home.homepage');

// Trang liên hệ (Contact)
Route::get('/contact', fn () => view('contact.index'))->name('contact');
Route::post('/contact/send', [ContactController::class, 'send'])->name('contact.send');

// =======================
// Quản lý Giỏ hàng (Cart)
// =======================
Route::middleware('auth')->group(function () {
    Route::get('/cart', [CartController::class, 'index'])->name('cart.index');
    Route::post('/cart/add/{carId}', [CartController::class, 'addToCart'])->name('cart.add');
    Route::patch('/cart/{id}', [CartController::class, 'update'])->name('cart.update');
    Route::delete('/cart/remove/{cartId}', [CartController::class, 'removeFromCart'])->name('cart.remove');

    // Hiển thị form checkout (gửi yêu cầu tư vấn)

    Route::get('/cart/checkout', [CartController::class, 'showCheckoutForm'])->name('cart.checkout.form');

    // Xử lý submit form checkout gửi mail
    Route::post('/cart/checkout', [CartController::class, 'checkout'])->name('cart.checkout.submit');
});

// =======================
// Quản lý Xe (Cars)
// =======================

// Các route cần phân quyền Admin
Route::middleware(['auth', IsAdmin::class])->group(function () {
    Route::get('/cars/create', [CarController::class, 'create'])->name('cars.create');
    Route::post('/cars', [CarController::class, 'store'])->name('cars.store');
    Route::get('/cars/{car}/edit', [CarController::class, 'edit'])->name('cars.edit');
    Route::put('/cars/{car}', [CarController::class, 'update'])->name('cars.update');
    Route::delete('/cars/{car}', [CarController::class, 'destroy'])->name('cars.destroy');
});

// Các route ai cũng xem được (Danh sách + Chi tiết)
Route::resource('cars', CarController::class)->only(['index', 'show']);

// Đặt lịch
Route::get('/schedule', [ScheduleController::class, 'showForm'])->name('schedule.form');
Route::post('/schedule', [ScheduleController::class, 'store'])->name('schedule.store');

// =======================
// Quản lý Profile Người dùng
// =======================
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');
});

// =======================
// Auth routes (Laravel Breeze / Fortify / Jetstream)
// =======================
require __DIR__.'/auth.php';


🔒 Bảo Mật

CSRF & XSS Token bảo vệ form (ví dụ: car.index)


    @if(Auth::user() && Auth::user()->is_admin)
        <a href="" class="btn-edit">Sửa</a>
        <form action="" method="POST"
            class="inline-block"
            onsubmit="return confirm('Bạn có chắc muốn xóa xe này?');">
            @csrf
            @method('DELETE')
            <button type="submit" class="btn-delete">Xóa</button>
        </form>
    @endif

    <form action="" method="POST"
        class="inline-block">
        @csrf
        <button type="submit" class="btn-cart">Mua Bán</button>
    </form>

Query Builder chống SQL Injection (ví dụ: CartController)


    public function addToCart($carId)
    {
        $user = Auth::user();

        $existingItem = $user->cart()
            ->where('car_id', $carId)
            ->where('status', 'active')
            ->first();

        if ($existingItem) {
            $existingItem->increment('quantity');
        } else {
            $user->cart()->create([
                'car_id' => $carId,
                'quantity' => 1,
                'status' => 'active',
            ]);
        }

        return redirect()->back()->with('success', 'Đã thêm xe vào giỏ hàng!');
    }

Middleware phân quyền Admin


class IsAdmin
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next)
    {
        if (!auth()->check() || !auth()->user()->is_admin) {
            abort(403, 'Bạn không có quyền truy cập,');
        }
        return $next($request);
    }
}

// Các route cần phân quyền Admin
Route::middleware(['auth', IsAdmin::class])->group(function () {
    Route::get('/cars/create', [CarController::class, 'create'])->name('cars.create');
    Route::post('/cars', [CarController::class, 'store'])->name('cars.store');
    Route::get('/cars/{car}/edit', [CarController::class, 'edit'])->name('cars.edit');
    Route::put('/cars/{car}', [CarController::class, 'update'])->name('cars.update');
    Route::delete('/cars/{car}', [CarController::class, 'destroy'])->name('cars.destroy');
});

// Các route ai cũng xem được (Danh sách + Chi tiết)
Route::resource('cars', CarController::class)->only(['index', 'show']);


🖼️ Giao Diện Website

🔐 Trang Xác Thực

🏠 Trang Chủ



🚘 Trang Sản Phẩm


🛠️ Tư Vấn - Dịch Vụ


✉️ Gửi Gmail Tự Động


🔗 Liên Kết


📜 License

Laravel framework được phát hành theo giấy phép MIT License.