Laravel 新項目避坑指南10 大基礎(chǔ)設(shè)置讓代碼半年不崩
Laravel 新項目避坑指南10 大基礎(chǔ)設(shè)置讓代碼半年不崩
有沒有遇到過這種 Laravel 項目:剛上線那會兒干干凈凈,過三個月就變成無法收拾的災(zāi)難?Controller 動不動就 500 多行、慢得要命的數(shù)據(jù)庫查詢隨處可見,甚至有人把 .env 推上 GitHub,所有密鑰一夜之間全線暴露。
別以為只有你栽過這種坑。來自開發(fā)者論壇和 Stack Overflow 的統(tǒng)計顯示:Laravel 應(yīng)用里的性能問題有 70% 可以在一開始就避免,接近 50% 的安全事故也都是初始配置不到位造成的。
原文鏈接 Laravel 新項目避坑指南:10 大基礎(chǔ)設(shè)置讓代碼半年不崩
別只會復(fù)制 .env——先搞懂它!
常見問題
很多新手只會把 .env.example 復(fù)制成 .env,填上數(shù)據(jù)庫賬號就直接 php artisan serve。問題是,這個文件里藏著許多直接影響線上環(huán)境的配置,稍不留神就會出事。
真實案例:有開發(fā)者上線后忘了把 APP_DEBUG=true 改成 false,結(jié)果所有錯誤信息包括 SQL 查詢、文件路徑、敏感數(shù)據(jù)全都暴露,黑客看到簡直像打開盲盒。
正確做法
# 線上環(huán)境一定要設(shè)成 false!
APP_DEBUG=false
APP_ENV=production
# 生成獨一無二的 key,別用默認(rèn)值
APP_KEY=base64:xxxxx
# 線上別寫 127.0.0.1
APP_URL=https://yourdomain.com
# 數(shù)據(jù)庫憑據(jù)千萬別提交到 Git!
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=your_database_name
DB_USERNAME=database_user
DB_PASSWORD=strong_password_please
# 會話與緩存驅(qū)動
SESSION_DRIVER=redis
CACHE_DRIVER=redis
QUEUE_CONNECTION=redis
實用提醒
- 生成強隨機 APP_KEY:每個新項目都要跑一遍
php artisan key:generate,它可是用來加密 Session 等敏感數(shù)據(jù)的。 - 維護完整的
.env.example:把所有必須的變量都列出來,但不要寫真實值,方便團隊協(xié)作。 - 確認(rèn)
.env沒進 Git:默認(rèn).gitignore會忽略它,但還是要確認(rèn)。如果不小心提交了,趕緊這么做:
git rm --cached .env
git commit -m "Remove .env from version control"
從第一天就定下命名規(guī)范
常見問題
// 糊里糊涂的 Controller
class Users extends Controller {
public function GetUserData() {
$All_Users = User::all();
return view('user-list', ['data' => $All_Users]);
}
}
// 奇怪的 migration
Schema::create('User', function (Blueprint $table) {
$table->id();
$table->string('UserName');
$table->string('user-email');
});
如果有新人接手這種代碼,第一反應(yīng)肯定是“這是邊吃飯邊寫的吧?”。
正確做法
Laravel 社區(qū)已經(jīng)約定俗成了一套命名規(guī)范,照著走就省事。
模型
// ? 使用單數(shù)、PascalCase
class Article extends Model {
// hasMany 關(guān)系 → 復(fù)數(shù)
public function comments() {
return $this->hasMany(Comment::class);
}
// belongsTo 關(guān)系 → 單數(shù)
public function author() {
return $this->belongsTo(User::class);
}
}
// ? 錯誤示范
class Articles extends Model { }
class article extends Model { }
控制器
// ? 單數(shù)、PascalCase,并以 Controller 結(jié)尾
class ArticleController extends Controller {
// 方法用 camelCase
public function showLatest() {
// 變量也用 camelCase
$latestArticles = Article::latest()
->take(5)
->get();
return view('articles.latest', [
'articles' => $latestArticles
]);
}
}
// ? 錯誤示范
class ArticlesController { } // 復(fù)數(shù)
class articleController { } // 小寫開頭
class Article_Controller { } // 下劃線
遷移和數(shù)據(jù)庫
// ? 復(fù)數(shù)、snake_case
Schema::create('blog_posts', function (Blueprint $table) {
$table->id();
$table->string('post_title');
$table->text('post_content');
$table->foreignId('category_id')
->constrained('categories')
->onDelete('cascade');
$table->timestamps();
});
// ? 單數(shù)或 PascalCase
Schema::create('BlogPost', function (Blueprint $table) {
$table->string('PostTitle');
});
路由
// ? 復(fù)數(shù)、kebab-case
Route::get('/blog-posts', [BlogPostController::class, 'index'])
->name('blog-posts.index');
// ? 錯誤示范
Route::get('/BlogPosts', [BlogPostController::class, 'index']);
Route::get('/blog_posts', [BlogPostController::class, 'index']);
為什么要這么做?
想象你和 5 個開發(fā)一起合作,每個人有一套命名習(xí)慣,整個代碼庫就像水果撈一樣亂七八糟。統(tǒng)一命名帶來的好處:
- 新人上手快,不用猜。
- 排查 Bug 更輕松,一眼就知道誰是模型誰是控制器。
- IDE 自動補全更精準(zhǔn),PHPStorm、VS Code 都會更懂你。
外鍵約束必須上,別全靠代碼兜底!
常見問題
// ? 敷衍的建表
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id'); // 只是普通字段
$table->string('title');
$table->timestamps();
});
看起來沒毛病?等等,這里有三個隱患:
- 孤兒數(shù)據(jù):用戶刪了,帖子還在,
$post->user直接報錯Trying to get property of null。 - 數(shù)據(jù)不一致:別人可以插入不存在的
user_id,數(shù)據(jù)庫全是垃圾數(shù)據(jù)。 - 調(diào)試噩夢:出問題時只能一個一個查數(shù)據(jù)。
有人說“我們可以靠 Laravel Observer 或 Events 處理邏輯”。確實,但這不是 100% 保險:
- 原生 SQL 會繞過 Eloquent。
- 批量導(dǎo)入直接寫數(shù)據(jù)庫。
- 任何一個忘記觸發(fā)事件的 Bug。
數(shù)據(jù)照樣會爛掉。
正確做法
// ? 建表時加外鍵約束
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')
->constrained('users')
->onDelete('cascade'); // 也可以是 restrict、set null 等
$table->string('title');
$table->text('content');
$table->timestamps();
});
// 更復(fù)雜的關(guān)系
Schema::create('comments', function (Blueprint $table) {
$table->id();
$table->foreignId('post_id')
->constrained() // 自動推斷 posts 表
->onDelete('cascade');
$table->foreignId('user_id')
->constrained()
->onDelete('restrict'); // 有評論的用戶禁止刪除
$table->text('comment_body');
$table->timestamps();
});
你需要理解的選項
// 1. CASCADE - 刪除父記錄時一起刪
->onDelete('cascade')
// 2. RESTRICT - 阻止刪除父記錄
->onDelete('restrict')
// 3. SET NULL - 設(shè)置為 NULL(記得列要 nullable)
->onDelete('set null')
->nullable()
// 4. NO ACTION - MySQL 默認(rèn)行為
->onDelete('no action')
什么時候可以不加?
- 分片數(shù)據(jù)庫:比如 PlanetScale 不支持外鍵。
- 一人維護的小項目:你能保證數(shù)據(jù)一致性。
- 極端性能場景:外鍵會增加一點點開銷(其實很少見)。
對 99% 的項目來說,加外鍵一定是最佳選擇。
起步就躲開 N+1 查詢
常見問題
這是 Laravel 性能問題 Top 1。最危險的是,你上線前根本察覺不到。
// ? 典型的 N+1 查詢
public function showAllPosts() {
$posts = Post::all();
return view('posts.index', compact('posts'));
}
{{-- Blade 視圖 --}}
@foreach($posts as $post)
<div class="post">
<h2>{{ $post->title }}</h2>
<p>By: {{ $post->user->name }}</p> <!-- 危險! -->
<p>{{ $post->comments->count() }} comments</p> <!-- 也危險! -->
</div>
@endforeach
如果有 100 篇帖子?
- 1 次取 posts。
- 100 次取用戶。
- 100 次統(tǒng)計 comments。
總共 201 條查詢,服務(wù)器直接崩潰。
正確做法
// ? 預(yù)加載
public function showAllPosts() {
$posts = Post::with(['user', 'comments'])
->latest()
->get();
return view('posts.index', compact('posts'));
}
// ? 只需要數(shù)量時用 withCount
public function showAllPosts() {
$posts = Post::with('user')
->withCount('comments')
->latest()
->get();
return view('posts.index', compact('posts'));
}
只需要 3 條查詢,性能翻倍都不止。
從第一天就裝 Laravel Debugbar
在開發(fā)環(huán)境安裝它:
composer require barryvdh/laravel-debugbar --dev
Debugbar 會實時顯示:
- 當(dāng)前頁面跑了多少條查詢。
- 每條查詢耗時。
- 是否有重復(fù)查詢。
黃金法則:單頁查詢超過 20 條就要警覺。
小技巧:Lazy Eager Loading
// 即使你已經(jīng)拿到了 $posts
$posts = Post::all();
// 仍然可以臨時加載關(guān)聯(lián)
$posts->load(['user', 'comments']);
建 Service 層,別把所有邏輯塞進 Controller!
常見問題
// ? 典型的胖 Controller
class OrderController extends Controller
{
public function checkout(Request $request)
{
$validated = $request->validate([
'items' => 'required|array',
'payment_method' => 'required|string',
'shipping_address' => 'required|string'
]);
DB::beginTransaction();
try {
// 50 行計算邏輯
$total = 0;
foreach ($validated['items'] as $item) {
$product = Product::find($item['id']);
$total += $product->price * $item['quantity'];
$product->stock -= $item['quantity'];
$product->save();
}
// 30 行支付邏輯
if ($validated['payment_method'] === 'credit_card') {
// 處理信用卡
} elseif ($validated['payment_method'] === 'bank_transfer') {
// 處理銀行轉(zhuǎn)賬
}
// 40 行物流邏輯
$shippingCost = $this->calculateShipping(...);
// 20 行創(chuàng)建訂單邏輯
$order = Order::create([...]);
// 15 行發(fā)郵件邏輯
Mail::to($user)->send(new OrderConfirmation($order));
// 10 行發(fā)通知邏輯
DB::commit();
return redirect()->route('orders.success');
} catch (\Exception $e) {
DB::rollback();
return back()->withErrors('Order failed');
}
}
}
這種 Controller 動輒 150+ 行,維護成本爆炸,邏輯也沒法復(fù)用。
正確做法:拆到 Service 層
// ? 精簡后的 Controller
class OrderController extends Controller
{
public function __construct(
private OrderService $orderService
) {}
public function checkout(CheckoutRequest $request)
{
try {
$order = $this->orderService->processCheckout(
$request->validated()
);
return redirect()
->route('orders.success', $order->id)
->with('success', '訂單處理成功!');
} catch (InsufficientStockException $e) {
return back()->withErrors('商品庫存不足');
} catch (PaymentFailedException $e) {
return back()->withErrors('支付失敗');
}
}
}
看到了嗎?Controller 只做它該做的:接請求、調(diào)用服務(wù)、給出響應(yīng)。
// app/Services/OrderService.php
class OrderService
{
public function __construct(
private CartService $cartService,
private PaymentService $paymentService,
private ShippingService $shippingService,
private NotificationService $notificationService
) {}
public function processCheckout(array $data): Order
{
return DB::transaction(function () use ($data) {
// 校驗庫存
$this->cartService->validateStock($data['items']);
// 計算總價
$orderTotal = $this->cartService->calculateTotal($data['items']);
// 計算運費
$shippingCost = $this->shippingService->calculate(
$data['shipping_address']
);
// 處理支付
$payment = $this->paymentService->charge(
$data['payment_method'],
$orderTotal + $shippingCost
);
// 創(chuàng)建訂單
$order = $this->createOrder($data, $payment);
// 減庫存
$this->cartService->reduceStock($data['items']);
// 發(fā)通知
$this->notificationService->sendOrderConfirmation($order);
return $order;
});
}
private function createOrder(array $data, Payment $payment): Order
{
// 專注處理創(chuàng)建訂單的細(xì)節(jié)
return Order::create([
'user_id' => auth()->id(),
'payment_id' => $payment->id,
'total_amount' => $payment->amount,
'status' => OrderStatus::PENDING,
// ... 其他字段
]);
}
}
推薦目錄結(jié)構(gòu)
app/
├── Http/
│ ├── Controllers/
│ │ └── OrderController.php
│ └── Requests/
│ └── CheckoutRequest.php
├── Services/
│ ├── OrderService.php
│ ├── CartService.php
│ ├── PaymentService.php
│ └── ShippingService.php
├── Models/
│ ├── Order.php
│ ├── Product.php
│ └── Payment.php
└── Exceptions/
├── InsufficientStockException.php
└── PaymentFailedException.php
好處一籮筐
- 可測試性:每個 Service 都能單獨寫測試。
- 可復(fù)用:邏輯可以給 API、命令、隊列復(fù)用。
- 可維護:Bug 和新需求都能快速定位。
- 團隊協(xié)作:大家分模塊開發(fā),沖突更少。
請求驗證交給 Form Request,別堆在 Controller!
常見問題
// ? Controller 里塞滿驗證
public function store(Request $request)
{
$validated = $request->validate([
'title' => 'required|max:255|unique:posts',
'slug' => 'required|unique:posts',
'content' => 'required|min:100',
'category_id' => 'required|exists:categories,id',
'tags' => 'required|array',
'tags.*' => 'exists:tags,id',
'featured_image' => 'required|image|max:2048',
'meta_title' => 'required|max:60',
'meta_description' => 'required|max:160',
// ... 還有一堆
]);
// 后面才是創(chuàng)建邏輯
}
問題在于:
- Controller 變得臃腫。
- 驗證規(guī)則無法復(fù)用。
- 自定義提示語很難管理。
- 復(fù)雜邏輯不好寫。
正確做法:使用 Form Request
php artisan make:request StorePostRequest
// app/Http/Requests/StorePostRequest.php
class StorePostRequest extends FormRequest
{
public function authorize(): bool
{
// 判斷當(dāng)前用戶是否能創(chuàng)建文章
return auth()->user()->can('create-post');
}
public function rules(): array
{
return [
'title' => ['required', 'max:255', 'unique:posts,title'],
'slug' => ['required', 'unique:posts,slug', 'regex:/^[a-z0-9-]+$/'],
'content' => 'required|min:100',
'category_id' => 'required|exists:categories,id',
'tags' => 'required|array|min:1|max:5',
'tags.*' => 'exists:tags,id',
'featured_image' => 'required|image|mimes:jpg,png,webp|max:2048',
'meta_title' => 'nullable|max:60',
'meta_description' => 'nullable|max:160',
'publish_at' => 'nullable|date|after:now',
];
}
public function messages(): array
{
return [
'title.required' => '文章標(biāo)題是必填項!',
'title.unique' => '標(biāo)題已存在,換個更有創(chuàng)意的吧!',
'slug.regex' => 'Slug 只能包含小寫字母、數(shù)字和連字符',
'tags.min' => '至少選擇 1 個標(biāo)簽方便分類',
'tags.max' => '最多 5 個標(biāo)簽,別太貪心!',
'featured_image.max' => '圖片太大了,最大 2MB',
];
}
public function attributes(): array
{
return [
'category_id' => '分類',
'featured_image' => '封面圖',
'publish_at' => '發(fā)布時間',
];
}
// 自定義驗證邏輯
protected function prepareForValidation(): void
{
// 自動根據(jù)標(biāo)題生成 slug
if (!$this->slug) {
$this->merge([
'slug' => Str::slug($this->title)
]);
}
}
}
// Controller 變得超級干凈!
class PostController extends Controller
{
public function store(StorePostRequest $request)
{
// 數(shù)據(jù)自動驗證、自動授權(quán)
$post = Post::create($request->validated());
// 關(guān)聯(lián)標(biāo)簽
$post->tags()->attach($request->tags);
return redirect()
->route('posts.show', $post)
->with('success', '文章發(fā)布成功!');
}
}
.gitignore 要寫完整
常見問題
總有人把不該進倉庫的文件提交進去:
# ? 常見事故
.env # 數(shù)據(jù)庫密碼全暴露!
/vendor # 100MB+ 的依賴
node_modules/ # 幾千個文件
.DS_Store # macOS 垃圾文件
Thumbs.db # Windows 垃圾文件
*.log # 巨大的日志
/storage/*.key # SSL 密鑰
真實事件:有個創(chuàng)業(yè)團隊把 .env 推上了 GitHub,公開倉庫 2 小時內(nèi)服務(wù)器就被人拿去挖礦,AWS 賬單直接漲到 15,000 美元。
完整的 .gitignore 參考
# Laravel
/node_modules
/public/hot
/public/storage
/storage/*.key
/vendor
# 環(huán)境文件
.env
.env.backup
.env.production
.env.testing
.env.*.php
.phpunit.result.cache
# IDE & 編輯器
.idea/
.vscode/
*.sublime-project
*.sublime-workspace
.phpstorm.meta.php
_ide_helper.php
_ide_helper_models.php
# 操作系統(tǒng)
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
desktop.ini
# 日志 & 數(shù)據(jù)庫
*.log
*.sql
*.sqlite
*.sqlite-journal
# 構(gòu)建產(chǎn)物
/public/build
/public/mix-manifest.json
/public/js/app.js
/public/css/app.css
npm-debug.log
yarn-error.log
# 部署
.deploy/
.rocketeer/
# 測試
/coverage
/phpunit.xml
# 安全 & 密鑰
*.pem
*.key
.cert
如果 .env 已經(jīng)被提交
別慌,但動作要快:
# 1. 從 Git 歷史里移除
git rm --cached .env
# 2. 提交變更
git commit -m "Remove .env from version control"
# 3. 推上遠(yuǎn)端
git push origin main
# 4. 重點!所有泄露的密鑰都要立刻更換
# - 改數(shù)據(jù)庫密碼
# - 重生成 API Key
# - 輪換所有密鑰
如果倉庫是公開的,密鑰在 Git 歷史里就算曝光了,爬蟲早晚會找到。
制定清晰的日志策略
常見問題
不少人靠 dd()、var_dump() 調(diào)試,線上出錯卻沒任何記錄;反過來也有人把所有東西都往日志里塞,結(jié)果 log 文件動輒幾個 G。
// ? 粗糙的日志
public function processPayment($orderId)
{
try {
// 處理支付
echo "Processing order: " . $orderId; // 不專業(yè)
var_dump($paymentData); // 生產(chǎn)環(huán)境危險
// 支付邏輯
} catch (\Exception $e) {
// 錯誤直接消失
return false;
}
}
正確做法
// app/Services/PaymentService.php
use Illuminate\Support\Facades\Log;
class PaymentService
{
public function processPayment(Order $order): bool
{
Log::info('開始處理支付', [
'order_id' => $order->id,
'amount' => $order->total,
'user_id' => $order->user_id,
'timestamp' => now()
]);
try {
$payment = $this->chargeCustomer($order);
Log::info('支付成功', [
'order_id' => $order->id,
'payment_id' => $payment->id,
'gateway' => $payment->gateway
]);
return true;
} catch (PaymentGatewayException $e) {
Log::error('支付網(wǎng)關(guān)失敗', [
'order_id' => $order->id,
'error' => $e->getMessage(),
'gateway' => $e->getGateway(),
'trace' => $e->getTraceAsString()
]);
throw $e;
} catch (\Exception $e) {
Log::critical('支付發(fā)生未知異常', [
'order_id' => $order->id,
'error' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine()
]);
throw $e;
}
}
}
在 config/logging.php 里配置多通道
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['daily', 'slack'],
'ignore_exceptions' => false,
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'days' => 14, // 14 天自動清理
],
'payment' => [
'driver' => 'daily',
'path' => storage_path('logs/payment.log'),
'level' => 'info',
'days' => 90, // 支付日志保留 3 個月
],
'security' => [
'driver' => 'daily',
'path' => storage_path('logs/security.log'),
'level' => 'warning',
'days' => 365, // 安全日志留一年
],
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => 'Laravel Log',
'emoji' => ':boom:',
'level' => 'critical', // 只有嚴(yán)重問題才打到 Slack
],
],
別等出 Bug 再想起測試
常見問題
“測試?項目跑起來再說吧……”
這通常意味著:
- Bug 是線上用戶幫你發(fā)現(xiàn)的。
- 每次發(fā)版都提心吊膽。
- 修一個 Bug 引入三個新 Bug。
- 團隊信心值直接跌到負(fù)數(shù)。
搭好測試環(huán)境
# 安裝 Pest(更現(xiàn)代的測試框架)
composer require pestphp/pest --dev --with-all-dependencies
php artisan pest:install
# 或者堅持用 PHPUnit
dcomposer require phpunit/phpunit --dev
.env.testing 示例:
APP_ENV=testing
APP_KEY=base64:testing-key-here
DB_CONNECTION=sqlite
DB_DATABASE=:memory:
CACHE_DRIVER=array
SESSION_DRIVER=array
QUEUE_CONNECTION=sync
MAIL_MAILER=array
寫點像樣的測試
// tests/Feature/OrderTest.php
<?php
use App\Models\User;
use App\Models\Product;
use App\Models\Order;
test('用戶可以在庫存充足時創(chuàng)建訂單', function () {
// Arrange
$user = User::factory()->create();
$product = Product::factory()->create([
'price' => 100000,
'stock' => 10
]);
// Act
$response = $this->actingAs($user)
->post('/orders', [
'product_id' => $product->id,
'quantity' => 2
]);
// Assert
$response->assertStatus(201);
$response->assertJsonStructure([
'order_id',
'total_amount',
'status'
]);
$this->assertDatabaseHas('orders', [
'user_id' => $user->id,
'total_amount' => 200000
]);
// 庫存減少
expect($product->fresh()->stock)->toBe(8);
});
test('庫存為 0 時無法下單', function () {
$user = User::factory()->create();
$product = Product::factory()->create(['stock' => 0]);
$response = $this->actingAs($user)
->post('/orders', [
'product_id' => $product->id,
'quantity' => 1
]);
$response->assertStatus(422);
$response->assertJsonValidationErrors(['product_id']);
});
test('未登錄用戶無法下單', function () {
$product = Product::factory()->create();
$response = $this->post('/orders', [
'product_id' => $product->id,
'quantity' => 1
]);
$response->assertStatus(401);
});
常用測試命令
# 跑全部測試
php artisan test
# Pest 可執(zhí)行文件
./vendor/bin/pest
# 跑指定測試
php artisan test --filter OrderTest
# 生成覆蓋率
php artisan test --coverage
用 Vite 管理前端資源
Laravel 9+ 默認(rèn)的 Vite 設(shè)置
如果項目里還沒有,先安裝依賴:
npm install
vite.config.js:
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
export default defineConfig({
plugins: [
laravel({
input: [
'resources/css/app.css',
'resources/js/app.js',
],
refresh: true,
}),
],
server: {
host: '0.0.0.0',
hmr: {
host: 'localhost',
},
},
});
Blade 模板里引入:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ config('app.name') }}</title>
{{-- ? 就這句最簡單 --}}
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body>
<!-- Content -->
</body>
</html>
必備命令
# 開發(fā)調(diào)試(熱更新)
npm run dev
# 構(gòu)建生產(chǎn)包(壓縮優(yōu)化)
npm run build
# 預(yù)覽生產(chǎn)包
npm run preview
總結(jié):最好的投資,就是起步時的扎實準(zhǔn)備
項目初期多花點時間打基礎(chǔ),看似慢,其實能省掉后面一堆返工。
好代碼不是一次成型,而是不斷打磨出來的。沒人一上來就完美無缺,真正重要的是:
- 先把地基打牢:項目初始設(shè)置要到位。
- 持續(xù)迭代:定期重構(gòu)、優(yōu)化。
- 從錯誤里學(xué)習(xí):每個 Bug 都是提醒。
- 保持更新:框架一直在進化。
現(xiàn)在就打開你的 Laravel 項目,逐條打勾看看這 10 項設(shè)置落實了多少。如果還一項沒做,先從最關(guān)鍵的兩個開始:外鍵約束 和 Service 層架構(gòu)。

浙公網(wǎng)安備 33010602011771號