一、系統定位
本系統使用 Laravel 12 建立一套 CSV 匯入系統,同時兼顧 資料驗證、安全性、API 設計與效能可將學生帳號資料寫入資料庫,並支援以「年班座號」查詢帳號 或 以「帳號」反查年班座號
二、功能清單
1.CSV 上傳 UI(Blade)
2.資料驗證(CSV 欄位、重複資料)
3.密碼加密(Hash)
4.API 回傳時隱藏密碼
5.RESTful API 設計
6.錯誤處理(找不到資料)
三、建立 Laravel 12 專案
指令:composer create-project laravel/laravel csv-demo
cd csv-demo
四、建立資料表 create_student_accounts_table
指令:php artisan make:migration create_student_accounts_table
編輯 database/migrations/xxxx_create_student_accounts_table.php
=>為了提升查詢效能,針對查詢欄位建立 unique index
=>為了提升查詢效能,針對查詢欄位建立 unique index
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('student_accounts', function (Blueprint $table) {
$table->id();
$table->string('grade_class_seat_no')->unique()->index();
$table->string('account')->unique()->index();
$table->string('password');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('student_accounts');
}
};
五、建立Model StudentAccount
指令:php artisan make:model StudentAccount
編輯 app/Models/StudentAccount.php
=>API 回傳時自動隱藏密碼,避免敏感資料外洩
編輯 app/Models/StudentAccount.php
=>API 回傳時自動隱藏密碼,避免敏感資料外洩
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class StudentAccount extends Model
{
//
protected $fillable = [
'grade_class_seat_no',
'account',
'password',
];
protected $hidden = [
'password',
];
}
六、建立Controller StudentAccountController
指令:php artisan make:controller StudentAccountController
編輯 app/Http/Controllers/StudentAccountController.php
=> 使用 Hash::make 保護密碼
七、建立路由 與 Api 路由
=> 使用 Hash::make 保護密碼
使用 firstOrFail 統一錯誤處理
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use App\Models\StudentAccount;
class StudentAccountController extends Controller
{
/**
* CSV 匯入(Web + API 共用)
*/
public function import(Request $request)
{
$request->validate([
'csv' => 'required|file|mimes:csv,txt',
]);
$file = $request->file('csv');
$handle = fopen($file->getRealPath(), 'r');
$header = fgetcsv($handle);
if ($header !== ['GradeClassSeatNo', 'Account', 'Password']) {
return back()->withErrors(['CSV 欄位格式錯誤']);
}
$count = 0;
while (($row = fgetcsv($handle)) !== false) {
$data = array_combine($header, $row);
StudentAccount::updateOrCreate(
['grade_class_seat_no' => $data['GradeClassSeatNo']],
[
'account' => $data['Account'],
'password' => Hash::make($data['Password']),
]
);
$count++;
}
fclose($handle);
/**
* 判斷來源:Web or API
*/
if ($request->is('api/*')) {
return response()->json([
'message' => 'CSV 匯入完成',
'count' => $count,
]);
}
return redirect('/import-page')
->with('success', "匯入成功,共 {$count} 筆資料");
}
/**
* 用年班座號查(API)
*/
public function findBySeat($seatNo)
{
return StudentAccount::where('grade_class_seat_no', $seatNo)
->firstOrFail();
}
/**
* 用帳號查(API)
*/
public function findByAccount($account)
{
return StudentAccount::where('account', $account)
->firstOrFail();
}
/**
* Blade 查詢頁
*/
public function search(Request $request)
{
$request->validate([
'seat_no' => 'nullable|string',
'account' => 'nullable|string',
]);
$student = null;
if ($request->filled('seat_no')) {
$student = StudentAccount::where(
'grade_class_seat_no',
$request->seat_no
)->first();
}
if ($request->filled('account')) {
$student = StudentAccount::where(
'account',
$request->account
)->first();
}
return view('search', compact('student'));
}
}
七、建立路由 與 Api 路由
同一個匯入邏輯同時支援 Web 與 API,但依照請求來源回傳不同格式,兼顧 UX 與 API 設計。
編輯 routes/web.php
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\StudentAccountController;
Route::get('/', function () {
return view('welcome');
});
// CSV 匯入頁
Route::get('/import-page', function () {
return view('import');
});
Route::post('/import', [StudentAccountController::class, 'import'])
->name('students.import');
// 查詢頁
Route::get('/search', function () {
return view('search');
});
Route::post('/search', [StudentAccountController::class, 'search']);
編輯 routes/web.php
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\StudentAccountController;
Route::prefix('students')->group(function () {
Route::post('/import', [StudentAccountController::class, 'import']);
Route::get('/seat/{seatNo}', [StudentAccountController::class, 'findBySeat']);
Route::get('/account/{account}', [StudentAccountController::class, 'findByAccount']);
});
八、建立Blade 上傳頁面 與 Blade 查詢頁面
編輯 resources/views/import.blade.php
<!DOCTYPE html>
<html>
<head>
<title>CSV 匯入</title>
</head>
<body>
<h2>學生帳號 CSV 匯入</h2>
<form action="{{ route('students.import') }}" method="POST" enctype="multipart/form-data">
@csrf
<input type="file" name="csv" required>
<button type="submit">匯入</button>
</form>
@if ($errors->any())
<p style="color:red">{{ $errors->first() }}</p>
@endif
@if (session('success'))
<p style="color:green">{{ session('success') }}</p>
@endif
</body>
</html>
編輯 resources/views/search.blade.php
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>學生帳號查詢系統</title>
<style>
body {
font-family: Arial;
margin: 40px;
}
input {
padding: 6px;
width: 250px;
}
button {
padding: 6px 12px;
}
.box {
margin-bottom: 20px;
}
.result {
margin-top: 20px;
padding: 15px;
border: 1px solid #ccc;
}
.error {
color: red;
}
</style>
</head>
<body>
<h2>學生帳號查詢(Demo)</h2>
<form method="POST" action="/search">
@csrf
<div class="box">
<label>GradeClassSeatNo:</label><br>
<input type="text" name="seat_no" placeholder="例如:30101">
</div>
<div class="box">
<label>Account:</label><br>
<input type="text" name="account" placeholder="例如:student01">
</div>
<button type="submit">查詢</button>
</form>
{{-- 查詢結果 --}}
@if(isset($student) && $student)
<div class="result">
<h4>查詢結果</h4>
<p><strong>GradeClassSeatNo:</strong> {{ $student->grade_class_seat_no }}</p>
<p><strong>Account:</strong> {{ $student->account }}</p>
</div>
@endif
{{-- 查無資料 --}}
@if(isset($student) && !$student)
<p class="error">查無資料</p>
@endif
{{-- 顯示錯誤訊息 --}}
@if ($errors->any())
<ul class="error">
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
@endif
</body>
</html>