2018年10月30日 星期二

《面試官別再問》PHP實作CI3 + JQuery前後端分離

前後端分離的核心概念

在傳統的 CI3 開發中,通常由 CI3 的視圖 (View) 直接輸出 HTML,並包含 JavaScript 程式碼。但在前後端分離的模式下:

  • 後端 (CI3):主要負責提供 API 服務(如 RESTful API),處理業務邏輯、資料庫操作、身份驗證等。它只返回資料(通常是 JSON 格式),而不是完整的 HTML 頁面。

  • 前端 (jQuery):負責 使用者介面 (UI) 的呈現和互動。它透過 AJAX 請求從後端取得資料,然後動態地將資料渲染到頁面上。


CI3 後端實作

1. 設定 CI3 專案

確保你已經安裝並設定好 CI3 專案。

2. 建立 API 控制器

建立一個新的控制器來處理 API 請求。例如,我們建立一個 Api.php 控制器:

PHP
<?php
defined('BASEPATH') OR exit('No direct script access allowed');

class Api extends CI_Controller {

    public function __construct() {
        parent::__construct();
        // 載入模型或輔助函數
        $this->load->model('your_data_model'); // 假設你有一個處理資料的模型
        $this->load->helper('url');
    }

    // 範例:取得所有產品的 API
    public function products() {
        $products = $this->your_data_model->get_all_products(); // 從模型取得資料

        // 設定回應頭為 JSON
        header('Content-Type: application/json');
        echo json_encode(['status' => 'success', 'data' => $products]);
    }

    // 範例:新增產品的 API (POST 請求)
    public function add_product() {
        if ($this->input->method() === 'post') {
            $product_name = $this->input->post('name');
            $product_price = $this->input->post('price');

            if ($product_name && $product_price) {
                $data = [
                    'name' => $product_name,
                    'price' => $product_price
                ];
                $insert_id = $this->your_data_model->insert_product($data); // 插入資料

                header('Content-Type: application/json');
                echo json_encode(['status' => 'success', 'message' => '產品新增成功', 'id' => $insert_id]);
            } else {
                header('Content-Type: application/json');
                echo json_encode(['status' => 'error', 'message' => '名稱和價格為必填']);
            }
        } else {
            header('Content-Type: application/json');
            echo json_encode(['status' => 'error', 'message' => '只允許 POST 請求']);
        }
    }

    // 範例:處理找不到方法或錯誤的預設回應
    public function _remap($method, $params = array()) {
        if (method_exists($this, $method)) {
            return call_user_func_array(array($this, $method), $params);
        } else {
            header('Content-Type: application/json');
            echo json_encode(['status' => 'error', 'message' => 'API 方法不存在']);
        }
    }
}

3. 設定路由 (routes.php)

application/config/routes.php 中設定 API 的路由,讓 URL 更簡潔。

PHP
$route['api/products'] = 'api/products';
$route['api/add_product'] = 'api/add_product';

4. 建立資料模型 (Model)

建立一個模型來處理資料庫操作。例如 Your_data_model.php

PHP
<?php
defined('BASEPATH') OR exit('No direct script access allowed');

class Your_data_model extends CI_Model {

    public function __construct() {
        parent::__construct();
        $this->load->database();
    }

    public function get_all_products() {
        $query = $this->db->get('products'); // 假設你的產品資料表名為 'products'
        return $query->result_array();
    }

    public function insert_product($data) {
        $this->db->insert('products', $data);
        return $this->db->insert_id();
    }
}

jQuery 前端實作

前端部分會使用 HTML、CSS 和 jQuery 來構建使用者介面,並透過 AJAX 請求與 CI3 後端進行互動。

1. HTML 結構

建立一個 HTML 檔案(例如 index.html 或在 CI3 的 views 資料夾下建立一個簡單的視圖 frontend_view.php,但不渲染資料):

HTML
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>產品列表 - 前後端分離</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        #product-list { margin-top: 20px; border: 1px solid #ccc; padding: 10px; }
        .product-item { margin-bottom: 5px; padding: 5px; background-color: #f9f9f9; }
        form { margin-top: 20px; padding: 10px; border: 1px solid #eee; }
        input[type="text"], input[type="number"] { padding: 8px; margin-right: 10px; }
        button { padding: 8px 15px; background-color: #007bff; color: white; border: none; cursor: pointer; }
        button:hover { background-color: #0056b3; }
        .message { margin-top: 10px; padding: 10px; border-radius: 5px; }
        .message.success { background-color: #d4edda; color: #155724; border-color: #c3e6cb; }
        .message.error { background-color: #f8d7da; color: #721c24; border-color: #f5c6cb; }
    </style>
</head>
<body>
    <h1>產品管理系統 (前後端分離)</h1>

    <h2>新增產品</h2>
    <form id="add-product-form">
        <label for="product-name">產品名稱:</label>
        <input type="text" id="product-name" name="name" required>
        <label for="product-price">產品價格:</label>
        <input type="number" id="product-price" name="price" step="0.01" required>
        <button type="submit">新增產品</button>
        <div id="add-product-message" class="message" style="display: none;"></div>
    </form>

    <h2>產品列表</h2>
    <div id="product-list">
        載入中...
    </div>

    <script>
        $(document).ready(function() {
            const baseUrl = '<?php echo base_url(); ?>'; // 假設前端頁面是由 CI3 載入

            // 函式:載入產品列表
            function loadProducts() {
                $.ajax({
                    url: baseUrl + 'api/products', // CI3 後端 API 網址
                    method: 'GET',
                    dataType: 'json', // 預期回應為 JSON 格式
                    success: function(response) {
                        $('#product-list').empty(); // 清空現有列表
                        if (response.status === 'success' && response.data.length > 0) {
                            $.each(response.data, function(index, product) {
                                $('#product-list').append(
                                    '<div class="product-item">' +
                                    'ID: ' + product.id + ', ' +
                                    '名稱: ' + product.name + ', ' +
                                    '價格: $' + product.price +
                                    '</div>'
                                );
                            });
                        } else if (response.status === 'success' && response.data.length === 0) {
                            $('#product-list').append('<p>目前沒有產品。</p>');
                        } else {
                            $('#product-list').append('<p>載入產品失敗: ' + response.message + '</p>');
                            console.error('API Error:', response.message);
                        }
                    },
                    error: function(xhr, status, error) {
                        $('#product-list').empty().append('<p>與後端通訊失敗,請檢查網路或伺服器。</p>');
                        console.error('AJAX Error:', status, error, xhr.responseText);
                    }
                });
            }

            // 初始載入產品
            loadProducts();

            // 表單提交事件:新增產品
            $('#add-product-form').submit(function(e) {
                e.preventDefault(); // 阻止表單預設提交行為

                const productName = $('#product-name').val();
                const productPrice = $('#product-price').val();
                const messageDiv = $('#add-product-message');

                messageDiv.hide().removeClass('success error').text(''); // 清空並隱藏訊息

                $.ajax({
                    url: baseUrl + 'api/add_product', // 新增產品 API 網址
                    method: 'POST',
                    dataType: 'json',
                    data: {
                        name: productName,
                        price: productPrice
                    },
                    success: function(response) {
                        if (response.status === 'success') {
                            messageDiv.addClass('success').text(response.message).fadeIn();
                            $('#add-product-form')[0].reset(); // 清空表單
                            loadProducts(); // 重新載入產品列表
                        } else {
                            messageDiv.addClass('error').text(response.message).fadeIn();
                        }
                    },
                    error: function(xhr, status, error) {
                        messageDiv.addClass('error').text('新增產品失敗,請檢查網路或伺服器。').fadeIn();
                        console.error('AJAX Error:', status, error, xhr.responseText);
                    }
                });
            });
        });
    </script>
</body>
</html>

注意事項:

  • 如果你的前端頁面不是由 CI3 控制器載入,則 const baseUrl = '<?php echo base_url(); ?>'; 這行需要替換為你的 CI3 專案的實際根 URL,例如 const baseUrl = 'http://localhost/your_ci3_project/';

  • 你需要確保 CI3 專案能夠正確處理 AJAX 請求。如果遇到 CORS (Cross-Origin Resource Sharing) 問題,你需要在 CI3 後端配置允許跨域請求。


部署與測試

  1. 資料庫設定:在 CI3 的 application/config/database.php 中設定你的資料庫連接。

  2. 建立資料表:在你的資料庫中建立一個 products 表格:

    SQL
    CREATE TABLE products (
        id INT AUTO_INCREMENT PRIMARY KEY,
        name VARCHAR(255) NOT NULL,
        price DECIMAL(10, 2) NOT NULL,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    );
    
  3. 運行 CI3 專案:透過 Apache 或 Nginx 配置你的 CI3 專案。

  4. 訪問前端頁面

    • 如果你把 HTML 頁面放在 CI3 的 views 資料夾下,並透過 CI3 控制器載入(例如 application/controllers/Welcome.php 中的 index() 方法載入 frontend_view),那麼訪問 http://localhost/your_ci3_project/ 或對應的 URL。

    • 如果你把 HTML 頁面獨立放置,則直接打開該 HTML 檔案,但需注意 baseUrl 的設定。


優點與考量

優點:

  • 職責分離:前後端開發人員可以獨立工作,互不影響。

  • 提高開發效率:前端和後端可以並行開發。

  • 更好的擴展性:後端 API 可以為多個前端應用(如 Web、Mobile App)提供服務。

  • 彈性:前端可以使用更現代的框架 (如 React, Vue, Angular) 而不只是 jQuery。

  • 效能優化:前端可以實現更細粒度的頁面更新,減少完整頁面載入。

考量:

  • CORS 問題:如果前後端部署在不同的域名或埠,可能會遇到跨域問題,需要在後端進行配置。

  • 路由管理:需要為後端 API 設計清晰的路由。

  • 狀態管理:在純前端應用中,需要考慮前端的狀態管理(例如使用瀏覽器儲存或更複雜的狀態管理庫)。

  • SEO 挑戰:對於爬蟲不支援 JavaScript 的情況,動態載入的內容可能對 SEO 不利。可以考慮 SSR (Server-Side Rendering) 或預渲染。

  • 安全性:需要確保 API 的安全性,例如身份驗證、授權、防止 XSS 和 CSRF 攻擊。


進一步的優化與學習

  • API 版本控制:為 API 引入版本控制(例如 /api/v1/products)。

  • 身份驗證與授權:可以使用 JWT (JSON Web Tokens) 或其他基於 Token 的驗證機制。

  • 錯誤處理:定義統一的 API 錯誤回應格式。

  • 表單驗證:在前端和後端都進行資料驗證。

  • 安全性:實作輸入過濾、輸出編碼等防止常見 Web 攻擊的措施。

  • 前端框架:考慮使用更現代的前端框架(React, Vue, Angular)來取代 jQuery,它們提供了更強大的組件化、狀態管理和開發工具,更適合大型專案。

透過上述步驟,你就可以在 PHP CI3 專案中成功實現前後端分離,並透過 jQuery 進行前端互動。

沒有留言:

張貼留言

熱門文章