PHP 實戰留言板


Posted by tzutzu858 on 2020-09-07

規劃產品路由與功能

檔案 功能
index.php 觀看所有留言
handle_add_comment.php 處理新增留言
conn.php 連接資料庫
handle_login.php 處理登入邏輯
handle_register.php 處理註冊邏輯
login.php 登入頁面
logout.php 登出
register.php 註冊頁面
utils.php 重複用的 function 放這

規劃資料結構以及建置資料庫

三個資料表 :

資料表 功用 欄位
users 會員資料 1.id 2.nickname 3.username 4.password 5.created_at
comments 留言資料 1.id 2.nickname 3.content 4.created_at
tokens token 對照 username 1.id 2.token 3.username

id 記得都要設 A_I AUTO_INCREMENT 自動累加
如果使用 php 內建的 session 可以不用 tokens 那張資料表


切板完串接資料庫顯示留言

conn.php

$conn = new mysqli($server_name, $username, $password, $db_name);
// conn 為 connection 的簡寫,第一個參數是 server 名稱,第二個是帳號,第三個是密碼,第四個是 database
if ($conn->connect_error) { // 物件存取屬性是用 -> 來表示
    die('資料庫連線錯誤:' . $conn->connect_error);
}
$conn->query('SET NAMES UTF8'); // 設定編碼
$conn->query('SET time_zone = "+8:00"'); // 設定台灣時間

index.php
顯示留言板面

$result = $conn->query("SELECT * FROM comments ORDER BY id DESC");
if (!$result) {
    die('Error:' . $conn->error);
}

在 HTML 想要安插的地方放暱稱、建立時間、內容

while ($row = $result->fetch_assoc()) {
<?php echo $row['nickname']; ?>
<?php echo $row['created_at'] ?>
<?php echo $row['content'] ?>

加入新增留言功能

$_POST 取資料放入資料庫
handle_add_comment.php

require_once 'conn.php';
if (empty($_POST['content'])) {
    die('資料不齊全');
}
$content = $_POST['content'];
$sql = sprintf("INSERT INTO comments(content) VALUE('%s')", $content);
$result = $conn->query($sql);
if (!$result) {
    die($conn->error);
}
header("Location: index.php");

實作註冊功能

在 index.php 放個連結
<a href="register.php">註冊</a>
登入、登出以此類推

register.php 頁面長得跟 index.php 差不多,所以拿 index.php 來改一改就好
要有暱稱、帳號、密碼框供使用者填入

handle_register.php : 將註冊資料寫入資料庫,長得跟 handle_add_comment.php 很像,所以複製改一改就好,username 在資料表設唯一,並且做檢查
MYSQL ERROR CODE 錯誤編號的意義
1062:欄位值重複,入庫失敗

if (!$result) {
    $code = $conn->errno;
    if ($code === 1062) {
        header('Location: register.php?errCode=2');
    }
    die($conn->error);
}

handle_register.php

require_once 'conn.php';

if (empty($_POST['nickname']) || empty($_POST['username']) || empty($_POST['password'])) {
    die('資料不齊全');
}

$nickname = $_POST['nickname'];
$username = $_POST['username'];
$password = $_POST['password'];

$sql = sprintf("INSERT INTO users(nickname, username, password) VALUE('%s', '%s', '%s')", $nickname, $username, $password);
$result = $conn->query($sql);
if (!$result) {
    $code = $conn->errno;
    if ($code === 1062) {
        header('Location: register.php?errCode=2');
    }
    die($conn->error);
}

header("Location: index.php");

實作登入功能

login.php 和 register.php 頁面長差不多,複製拿來改一改
handle_login.php 和 handle_register.php 處理邏輯也差不多,複製拿來改一改

num_rows 代表結果有多少筆資料
如果輸入錯誤 帶參數回去 header("Location: login.php?errCode=2");
在 login.php 找個地方做判斷,並顯示帳號密碼有誤出來

 <?php
if (!empty($_GET['errCode'])) {
    $code = $_GET['errCode'];
    $msg = 'Error';
    if ($code === '2') {
        $msg = '帳號密碼有誤';
    }
    echo '<h3 class="error">錯誤 : ' . $msg . '</h3>';
}
?>

handle_login.php

<?php
require_once 'conn.php';

if (empty($_POST['username']) || empty($_POST['password'])) {
    die('資料不齊全');
}

$username = $_POST['username'];
$password = $_POST['password'];

$sql = sprintf("SELECT * FROM users WHERE username='%s' and password='%s'", $username, $password);
$result = $conn->query($sql);
if (!$result) {
    die($conn->error);
}
if ($result->num_rows) {
    $result = $conn->query($sql);
    if (!$result) {
        die($conn->error);
    }    
    header("Location: index.php");
} else {
    header("Location: login.php?errCode=2");
}

該怎麼記住登入狀態?Cookie 簡介與實作

HTTP 狀態健忘特性
如何看 Cookie : 打開 DevTools --> application --> 左邊版面 storage 有 cookies 可以看,其實就是 name 跟 value
瀏覽器會自動把符合條件的 Cookie 帶上來,符合條件指的是沒有過期,domain 和路徑要符合
所以在處理登入頁面的 php ,handle_login.php

$token = generateToken();
$expire = time() + 3600 * 24 * 30;
setcookie("token", $token, $expire);
header("Location: index.php");

utils.php

function generateToken()
{
    $s = '';
    for ($i = 1; $i <= 16; $i++) {
        $s .= chr(rand(65, 90));
    }
    return $s;
}

在 handle_login.php 裡面 setcookie 後,在 index.php 要拿到用 $_COOKIE

$username = null;
if (!empty($_COOKIE['token'])) {
    $user = getUserFromToken($_COOKIE['token']);
    $username = $user['username'];
}

登出就把 cookie 清空

require_once "conn.php";
$token = $_COOKIE['token'];
$sql = sprintf("DELETE FROM tokens WHERE token = '%s'", $token);
$conn->query($sql);
setcookie("token", "", 0);
header("Location: index.php");

handle_login.php

require_once 'conn.php';
require_once 'utils.php';

if (empty($_POST['username']) || empty($_POST['password'])) {
    die('資料不齊全');
}

$username = $_POST['username'];
$password = $_POST['password'];

$sql = sprintf("SELECT * FROM users WHERE username='%s' and password='%s'", $username, $password);
$result = $conn->query($sql);
if (!$result) {
    die($conn->error);
}
if ($result->num_rows) {
    // 建立 token 並儲存
    $token = generateToken();
    $sql = sprintf(
        "INSERT INTO tokens(token,username) VALUES('%s', '%s')",$token, $username
    );
    $result = $conn->query($sql);
    if (!$result) {
        die($conn->error);
    }    
    // 登入成功
    $expire = time() + 3600 * 24 * 30;
    setcookie("token", $token, $expire);
    header("Location: index.php");
} else {
    header("Location: login.php?errCode=2");
}

發布留言的顯示暱稱
所以在 handle_add_comment.php

if (!empty($_COOKIE['token'])) {
    $user = getUserFromToken($_COOKIE['token']);
    $username = $user['username'];
}

utils.php

function getUserFromToken($token)
{
    global $conn;
    $sql = sprintf("SELECT username FROM tokens WHERE token = '%s'", $token);
    $result = $conn->query($sql);
    $row = $result->fetch_assoc();
    $username = $row['username'];

    $sql = sprintf("SELECT * FROM users WHERE username = '%s'", $username);
    $result = $conn->query($sql);
    $row = $result->fetch_assoc();
    return $row; // username, id, nickname
}









Related Posts

Python Table Manners - 虛擬環境和套件管理

Python Table Manners - 虛擬環境和套件管理

第二週(04/19 ~ 04/25):程式基礎(上)

第二週(04/19 ~ 04/25):程式基礎(上)

Covariance and Contravariance in Generics

Covariance and Contravariance in Generics


Comments