顯示具有 laravel 標籤的文章。 顯示所有文章
顯示具有 laravel 標籤的文章。 顯示所有文章

2026年4月11日 星期六

想修改 chcweb 出現的問題解決集

系列文章:
1.Ubuntu 24.04 安裝 chcweb 專案的過程記錄
2.Ubuntu 24.04 安裝 laravel 5.7 的過程記錄
3.laravel 5.7 初始相關設定與注意事項
4.想修改 chcweb 出現的問題解決集
 

問題一: UnexpectedValueException : The stream or file "/home/webadmin/html/chcweb/storage/logs/laravel-2026-04-11.log" could not be opened in append mode: failed to open stream: Permission denied

解決方法:
錯誤意思是 Laravel 沒辦法寫入 log 檔,通常跟 目錄權限 有關。 

1.檢查檔案與目錄權限

Laravel 日誌檔案路徑:

/home/webadmin/html/chcweb/storage/logs/laravel-2026-04-16.log

需要 Web 伺服器用戶能夠寫入。通常 Linux 系統 Web 伺服器用戶是:

  • Debian/Ubuntu:www-data
  • CentOS/Fedora:apachenginx

檢查目錄權限:

ls -l /home/webadmin/html/chcweb/storage/logs/

2.設定正確的擁有者

假設你的 Web 伺服器用戶是 www-data

sudo chown -R www-data:www-data /home/webadmin/html/chcweb/storage
這樣 Web 伺服器就能寫入。

3.設定正確的權限

Laravel 需要 storagebootstrap/cache 可寫:
sudo chmod -R 775 /home/webadmin/html/chcweb/storage
sudo chmod -R 775 /home/webadmin/html/chcweb/bootstrap/cache
  • 775:擁有者與群組可讀寫執行,其他人可讀與執行
  • 如果 775 仍然不行,可以暫時用 777 測試,但不建議長期使用(安全性較低) 

4.清除快取(可選)

Laravel 有時會快取檔案路徑,建議清除:

php artisan config:clear
php artisan cache:clear

然後再重新載入網站看看是否正常。

小技巧
Laravel 的 storagebootstrap/cache 目錄必須可寫,這是最常見的「Permission denied」原因。 

一行命令,直接安全地修復 Laravel 所有目錄的權限和擁有者,幾乎可以立即解決這種錯誤:

sudo chown -R www-data:www-data /home/webadmin/html/chcweb && sudo find /home/webadmin/html/chcweb/storage -type d -exec chmod 775 {} \; && sudo find /home/webadmin/html/chcweb/storage -type f -exec chmod 664 {} \; && sudo find /home/webadmin/html/chcweb/bootstrap/cache -type d -exec chmod 775 {} \; && sudo find /home/webadmin/html/chcweb/bootstrap/cache -type f -exec chmod 664 {} \; 

問題二:要在資料表setups 新增遺個欄位 bg_image

1.建立 Migration 

php artisan make:migration add_bg_image_to_setups_table --table=setups

2. 修改 Migration 檔案

<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class AddBgImageToSetupsTable extends Migration {
  public function up() {
      Schema::table('setups', function (Blueprint $table) {
          $table->string('bg_image', 255)->nullable()->after('bg_color')->comment('背景圖片');
      }); 
}

  public function down() {
       Schema::table('setups', function (Blueprint $table) {
          $table->dropColumn('bg_image');
       });
    }
}

3.執行 Migration

php artisan migrate 

問題三: UnexpectedValueException  : The stream or file "/home/webadmin/html/chcweb/storage/logs/laravel-2026-04-17.log" could not be opened in append mode: failed to open stream: Permission denied

問題解析:這個錯誤是 Laravel 在寫 log 檔案時發生的權限問題,完全跟你新增欄位無關,但它會阻止 migration 或任何程式寫入日誌。解決方式是調整 storagebootstrap/cache 的權限。

1.確認目錄

Laravel 需要以下目錄可讀寫:

  • storage
  • storage/logs
  • storage/framework
  • bootstrap/cache

 2.修改權限(Linux)

假設你的專案目錄是 /home/webadmin/html/chcweb

cd /home/webadmin/html/chcweb
# 設定 owner 為 web server 使用者,通常是 www-data (Ubuntu/Debian) 或 apache/nginx
sudo chown -R webadmin:www-data storage bootstrap/cache
# 設定目錄可寫權限
sudo chmod -R 775 storage bootstrap/cache
#注意:如果你的 web server 使用者不是 www-data,請改成實際使用者。

3.Laravel 快速清理快取(建議做)

php artisan config:clear
php artisan cache:clear
php artisan route:clear
php artisan view:clear

4. 測試

重新執行 migration:

php artisan migrate

如果權限設定正確,就不會再出現 Permission denied

💡小技巧

在開發環境中,設定 storagebootstrap/cache 777(所有人可寫)雖然方便,但生產環境建議限制為 775 或更安全的方式,以免安全風險。 

 6.一行指令
既改權限,又保證 Laravel 的 storagebootstrap/cache 可以被 web server 寫入,同時保持相對安全(775)

sudo chown -R webadmin:www-data /home/webadmin/html/chcweb/storage /home/webadmin/html/chcweb/bootstrap/cache && sudo chmod -R 775 /home/webadmin/html/chcweb/storage /home/webadmin/html/chcweb/bootstrap/cache 

問題四:   ErrorException  : file_put_contents(/home/webadmin/html/chcweb/database/migrations/2026_04_18_132842_add_bg_image_to_setups_table.php): failed to open stream: No such file or directory

問題解析:

這個錯誤訊息告訴我們 Laravel 無法寫入 migration 檔案,因為目標目錄不存在或沒有權限。重點在這兩件事:

  1. 目錄 database/migrations 是否存在
  2. 你的使用者是否有寫入權限

問題解答:

1.確認目錄存在

執行:

ls -ld /home/webadmin/html/chcweb/database/migrations
  • 如果出現 No such file or directory,表示這個目錄真的不存在,需要建立:
mkdir -p /home/webadmin/html/chcweb/database/migrations

2.確認權限

Laravel 需要該目錄可寫入:

sudo chown -R webadmin:www-data /home/webadmin/html/chcweb/database/migrations
sudo chmod -R 775 /home/webadmin/html/chcweb/database/migrations

替換 webadminwww-data 為你的實際使用者與 web server 群組。

3. 再次建立 migration

php artisan make:migration add_bg_image_to_setups_table --table=setups

這次 Laravel 就能成功在 database/migrations 產生檔案了。

💡 小提醒:

  • storagebootstrap/cachedatabase/migrations 都需要對 Laravel 可寫。
  • 開發環境可以用 775,生產環境更安全的做法是限制非必要使用者寫入。

 4. 一次性指令

把 Laravel 專案的 storagebootstrap/cachedatabase/migrations 全部權限一次調整好。

sudo mkdir -p /home/webadmin/html/chcweb/database/migrations && sudo chown -R webadmin:www-data /home/webadmin/html/chcweb/storage /home/webadmin/html/chcweb/bootstrap/cache /home/webadmin/html/chcweb/database/migrations && sudo chmod -R 775 /home/webadmin/html/chcweb/storage /home/webadmin/html/chcweb/bootstrap/cache /home/webadmin/html/chcweb/database/migrations

 

 

 

 

2026年2月21日 星期六

在LAMP + laravel 12 的虛擬機 ,輸入YT URL,就可下載YT 影片到自己電腦

        請參考 1.Ubuntu 24.04 安裝 laravel 12 的過程記錄,完成Ubuntu 24.04 + Apache2 + PHP 8.4 + Mariadb + Laravel 12 的相關設定。
        一、在 Ubuntu 24.04 安裝 yt-dlp:
        由於Ubuntu 24.04 內建套件通常偏舊,建議用官方 binary。
1.指令如下:
sudo apt update
sudo apt install ffmpeg -y
sudo curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp
sudo chmod a+rx /usr/local/bin/yt-dlp
2.測試是否安裝成功,指令:
yt-dlp --version

        二、Laravel 設定
1.建立 YouTubeController.php
指令:php artisan make:controller 
YouTubeController  
內容:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Illuminate\Support\Str;

class YouTubeController extends Controller
{
public function showForm()
{
return view('youtube.form');
}

public function download(Request $request){
$request->validate([
'url' => 'required|url'
]);

$url = $request->input('url');

// 產生安全檔名
$filename = Str::uuid() . '.mp4';
$filePath = storage_path('app/downloads/' . $filename);

try {

$process = new Process([
'/usr/local/bin/yt-dlp', // 改成你實際路徑
'-f', 'mp4',
'-o', $filePath,
$url
]);

$process->setTimeout(300); // 最多跑 5 分鐘
$process->run();

if (!$process->isSuccessful()) {
throw new ProcessFailedException($process);
}

return response()->download($filePath)->deleteFileAfterSend(true);

} catch (\Exception $e) {
return back()->with('error', '下載影片失敗: ' . $e->getMessage());
}
}
}

2.建立 form.blade.php
路徑:resources/views/youtube/form.blade.php
內容:

<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>YouTube 影片下載</title>
</head>
<body>
<h1>下載 YouTube 影片</h1>

@if(session('error'))
<p style="color: red;">{{ session('error') }}</p>
@endif

<form action="{{ route('youtube.download') }}" method="POST">
@csrf
<label for="url">YouTube 影片網址:</label>
<input type="text" id="url" name="url" required>
<button type="submit">下載</button>
</form>
</body>
</html>

3.寫入 web.php
路徑:routes/web.php
內容:

<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\YouTubeController;

Route::get('/', function () {
return view('welcome');
});
Route::get('/youtube', [YouTubeController::class, 'showForm']);
Route::post('/youtube/download', [YouTubeController::class, 'download'])->name('youtube.download');


4.測試:http://Server IP/youtube




2026年1月26日 星期一

Laravel 12 CSV 匯入與雙向查詢系統

 一、系統定位
        本系統使用 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

<?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 回傳時自動隱藏密碼,避免敏感資料外洩
<?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 保護密碼
          使用 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('index');
});

// 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/api.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>


    編輯 resources/views/index.blade.php
<!DOCTYPE html>
<html lang="zh-TW">
<head>
    <meta charset="UTF-8">
    <title>首頁</title>
</head>
<body>
    <h1>學生帳號管理系統</h1>

    <ul>
        <li>
            <a href="{{ url('/import-page') }}">CSV 匯入學生資料</a>
        </li>
        <li>
            <a href="{{ url('/search') }}">查詢學生資料</a>
        </li>
    </ul>
</body>
</html>






























只要點兩下,就能將input資料夾內所有m4a 轉檔為 mp3

         最近需要將m4a檔案轉檔為mp3,所以寫python程式來處理。希望將很多的m4a放進input資料夾內,只要點兩下滑鼠就能將這些m4a 通通轉檔為 mp3。          Recently, I needed to convert M4A files in...