[PHP] 一個簡單的使用者登入驗證並列出使用者狀態的資訊

因為最近找工作的關係,有份工作面試完後他們裡面的主管寄了封信要我試著實作一個簡單的會員網站,希望有註冊、驗證登入、帳號管理三個功能
其中註冊、登入都不難,而帳號管理他還希望能有圖表的展現,所以多作了一些功夫在上面。

首先他有定義會員的資料有「帳號」「密碼」「姓名」「性別」「年齡」「身高」「體重」及「E-Mail」等資料,並且需要有個欄位判斷該帳號是否「停用中」,所以我就建構相對應的資料表:
delimiter $$

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `account` varchar(45) DEFAULT NULL,
  `password` char(40) DEFAULT NULL,
  `name` varchar(45) DEFAULT NULL,
  `sexid` tinyint(4) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `height` int(11) DEFAULT NULL,
  `weight` int(11) DEFAULT NULL,
  `email` varchar(255) DEFAULT NULL,
  `disabled` tinyint(4) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `id_UNIQUE` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8$$

以上是我定義的資料表格式。
然後先展示一下我的資料庫共用函式庫
<?PHP

 $dbhost = 'localhost';
 $dbuser = 'test';
 $dbpass = 'test';
 $dbname = 'test';

function quotes($content){
 if(!get_magic_quotes_gpc()){
  if(is_array($content)){ //檢查是否為陣列,若是的話用foreach全部解決
   foreach($content as $key => $value){
    $content[$key] = addslashes($value);
   }
  }else{
   addslashes($content);
  }
 }else{
  //magic_quotes_gpc=on,不處理
 }
 return $content;
}

?>

以上程式碼定義我資料庫的連線,當然,一些機敏資料(如帳密)我是有取代掉的。
接下來介紹我的註冊頁
<?PHP
session_start();
include("db.inc.php");

$account = $password = $confirmpassword = $name = $sexid = $age = $height = $weight = $email = $errmsg = '';

function register()
{
 global $dbhost, $dbuser, $dbpass, $dbname, $account, $password, $confirmpassword, $name, $sexid, $age, $height, $weight, $email, $errmsg;
 $db = new mysqli($dbhost, $dbuser, $dbpass, $dbname) or die('Could not connect: ' . mysqli_error($db));

 if ($db->connect_errno) {
     $errmsg = '<p align="center"><font color="red">資料庫連線錯誤!'.$db->connect_error.'</font><p>';
     $db->close();
     return;
 }

 $sql = "SELECT * FROM `test`.`user` where `account`='$account'";
 if ($db->query($sql)->num_rows != 0) {
     $errmsg = '<p align="center"><font color="red">帳號已經存在!</font><p>';
     $db->close();
     return;
 }

 $sql = "INSERT INTO `test`.`user` (`account`, `password`, `name`, `sexid`, `age`, `height`, `weight`, `email`, `disabled`) VALUES ('$account', '$password', '$name', '$sexid', '$age', '$height', '$weight', '$email', '0');";
 $result = $db->query($sql);
 if(!$result)
 {
  $errmsg = '<p align="center"><font color="red">新增帳號失敗'.$db->error.'!</font><p>';
 }

 $db->close();

 $_SESSION['account'] = $account;
 header('Location: list.php');
}


if(isset($_POST['submit']) && $_POST['submit'])
{
 $account = quotes($_POST['account']);
 $password = quotes(sha1($_POST['password']));
 $confirmpassword = quotes($_POST['confirmpassword']);
 $name = quotes($_POST['name']);
 $sexid = quotes($_POST['sexid']);
 $age = quotes($_POST['age']);
 $height = quotes($_POST['height']);
 $weight = quotes($_POST['weight']);
 $email = quotes($_POST['email']);

 register();
}
?>
<html>
<head>
 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
 <script type="text/javascript" src="http://code.jquery.com/jquery-latest.min.js"></script>
 <script type="text/javascript">
  function onSubmit()
  {
   var valid = true;
   var regInt = /^\d+$/;
   
   if($("#account").val().length == 0)
   {
    $("#accountMsg").show();
    valid = false;
   }
   else
    $("#accountMsg").hide();

   if($("#password").val().length == 0)
   {
    $("#passwordMsg").show();
    valid = false;
   }
   else
    $("#passwordMsg").hide();

   if($("#password").val() != $("#confirmpassword").val())
   {
    $("#confirmpasswordMsg").show();
    valid = false;
   }
   else
    $("#confirmpasswordMsg").hide();

   if($("#name").val().length == 0)
   {
    $("#nameMsg").show();
    valid = false;
   }
   else
    $("#nameMsg").hide();

   if($("#age").val().length == 0)
   {
    $("#ageMsg").text("(請輸入年齡)");
    $("#ageMsg").show();
    valid = false;
   }
   else if(!regInt.test($("#age").val()))
   {
    $("#ageMsg").text("(年齡需為數字)");
    $("#ageMsg").show();
    valid = false;
   }
   else
    $("#ageMsg").hide();

   var sexid = $("input[name='sexid']:checked").val();
   if(typeof(sexid) == "undefined")
   {
    $("#sexidMsg").show();
    valid = false;
   }
   else
    $("#sexidMsg").hide();

   if($("#height").val().length == 0)
   {
    $("#weightMsg").text("(請輸入身高)");
    $("#heightMsg").show();
    valid = false;
   }
   else if(!regInt.test($("#height").val()))
   {
    $("#heightMsg").text("(身高需為數字)");
    $("#heightMsg").show();
    valid = false;
   }
   else
    $("#heightMsg").hide();

   if($("#weight").val().length == 0)
   {
    $("#weightMsg").text("(請輸入體重)");
    $("#weightMsg").show();
    valid = false;
   }
   else if(!regInt.test($("#weight").val()))
   {
    $("#weightMsg").text("(體重需為數字)");
    $("#weightMsg").show();
    valid = false;
   }
   else
    $("#weightMsg").hide();

   return valid;
  }
 </script>
</head>
<body>

<form action="register.php" method="POST" id="form1" onsubmit="return onSubmit() && flase;">
 <table width="100%">
  <tr>
   <td colspan="2">註冊會員</td>
  </tr>
  <tr>
   <td>帳號</td>
   <td>
    <input id="account" name="account" type="text" value="<?PHP echo $account;?>">
    <span id="accountMsg" style="display: none; color: red;">(請輸入帳號)</span>
   </td>
  </tr>
  <tr>
   <td>密碼</td>
   <td>
    <input id="password" name="password" type="password" value="">
    <span id="passwordMsg" style="display: none; color: red;">(請輸入密碼)</span>
    </td>
  </tr>
  <tr>
   <td>確認密碼</td>
   <td>
    <input id="confirmpassword" name="confirmpassword" type="password" value="">
    <span id="confirmpasswordMsg" style="display: none; color: red;">(密碼不相同)</span>
   </td>
  </tr>
  <tr>
   <td>姓名</td>
   <td>
    <input id="name" name="name" type="text" value="<?PHP echo $name;?>">
    <span id="nameMsg" style="display: none; color: red;">(請輸入姓名)</span>
   </td>
  </tr>
  <tr>
   <td>性別</td>
   <td>
    <input type="radio" id="sexid_0" name="sexid" value="0" <?PHP echo $sexid == "0" ? "checked" : "";?>/><label for="sexid_0">男</label>
    <input type="radio" id="sexid_1" name="sexid" value="1" <?PHP echo $sexid == "1" ? "checked" : "";?>/><label for="sexid_1">女</label>
    <span id="sexidMsg" style="display: none; color: red;">(請選擇性別)</span>
   </td>
  </tr>
  <tr>
   <td>年齡</td>
   <td>
    <input id="age" name="age" type="text" value="<?PHP echo $age;?>">
    <span id="ageMsg" style="display: none; color: red;">(請輸入年齡)</span>
   </td>
  </tr>
  <tr>
   <td>身高</td>
   <td>
    <input id="height" name="height" type="text" value="<?PHP echo $height;?>">
    <span id="heightMsg" style="display: none; color: red;">(請輸入身高)</span>
   </td>
  </tr>
  <tr>
   <td>體重</td>
   <td>
    <input id="weight" name="weight" type="text" value="<?PHP echo $weight;?>">
    <span id="weightMsg" style="display: none; color: red;">(請輸入體重)</span>
   </td>
  </tr>
  <tr>
   <td>E-Mail</td>
   <td>
    <input id="email" name="email" type="text" value="<?PHP echo $email;?>">
   </td>
  </tr>
<?PHP
 if($errmsg != '')
 {
  echo "<tr><td colspan=\"2\">$errmsg</td></tr>";
 }
?>
  <tr>
   <td colspan="2">
    <input id="submit" type="submit" name="submit" value="註冊" />
   </td>
  </tr>
 </table>
</form>

</body>
</html>
我上面有結合一些簡易的驗證判斷,會直接於 client 上判斷帳號及密碼的登入。
他們提出的要求是
.除 email 外,其餘欄位皆為必填
.密碼與確認密碼需一致
.年齡、身高、體重僅能輸入數字
若資料輸入正確後即能成功註冊,且帳號狀態會是啟用中
那這個程式其實在我們前端工程師眼中,他還是是有 Bug 的,因為他的驗證全透過 Client 端,而 Server 端完全沒有作任何驗證,只是我希望快點結束掉他,結束掉這場複試XD

接下來有註冊當然是有登入頁,那登入失敗會有無此帳號、帳密錯誤或帳號目前禁用中的訊息:
<?PHP

session_start();
include("db.inc.php");

$errmsg = '';

if(isset($_GET['action']) && $_GET['action'] == 'logout')
{
 unset($_SESSION['account']);
 header('Location: login.php');
}

if(isset($_POST['submit']) && $_POST['submit'])
{
 $account = quotes($_POST['account']);
 $password = quotes(sha1($_POST['password']));

 if($account != '' && $password != '')
 {
  $db = new mysqli($dbhost, $dbuser, $dbpass, $dbname) or die('Could not connect: ' . mysqli_error($db));

  if ($db->connect_errno) {
      printf("Connect failed: %s\n", $db->connect_error);
      exit();
  }

  $sql = "SELECT * FROM `test`.`user` where `account`='$account' and `password`='$password'";

  if ($result = $db->query($sql)) {
   $row = $result->fetch_object();

   if(!$row)
   {
    $errmsg = '<p align=center><font color=red>帳號不存在或帳號密碼錯誤</font><p>';
   }
   else if($row->disabled)
   {
    $errmsg = '<p align=center><font color=red>帳號已被停用</font><p>';
   }
   else
   {
    $_SESSION['account'] = $row->account;
    header('Location: list.php');
   }
      /* free result set */
      $result->close();
      
  }
  $db->close();
 }
}

?>
<html>
<head>
 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>

<form action="login.php" method="POST" id="form1">
 <table width="100%">
  <tr>
   <td colspan="2">會員登入</td>
  </tr>
  <tr>
   <td>帳號</td>
   <td><input id="account" name="account" type="text" value=""></td>
  </tr>
  <tr>
   <td>密碼</td>
   <td><input id="password" name="password" type="password" value=""></td>
  </tr>
  <tr>
   <td><a href="register.php">註冊會員</a></td>
   <td><input id="submit" type="submit" name="submit" value="登入" /></td>
  </tr>
<?PHP
 if($errmsg != '')
 {
  echo "<tr><td colspan=\"2\">$errmsg</td></tr>";
 }
?>
 </table>
</form>

</body>
</html>

大概就是這樣
當然我作這麼久的前端工程師了,資料隱碼的問題實在不該讓他發生,所以有避掉
然後密碼有經過 sha1 加密,避免資料庫被竊取後所有的帳密就外洩,保護自己也保護所有的使用者。

最後就是介紹使用者清單頁了,他們的要求是:
.列出目前系統所有使用者的帳號資訊,含啟用狀態
.可對帳號啟用狀態做修改:改用中改為禁用,或禁用中改為啟用
.可刪除使用者帳號
.分別以年齡及性別列出各有幾人並以圓餅圖呈現。

<?PHP
session_start();
include("db.inc.php");

if(!isset($_SESSION['account']) || $_SESSION['account'] == '')
{
 header('Location: login.php');
}

$db = new mysqli($dbhost, $dbuser, $dbpass, $dbname) or die('Could not connect: ' . mysqli_error($db));
$msg = '';

if(isset($_POST) && $_POST)
{
 $action = $_POST['action'];
 $id = quotes($_POST['id']);
 $disabled = quotes($_POST['disabled']);

 if($action == "delete")
 {
  $sql = "DELETE FROM `test`.`user` WHERE `id` = '$id';";
  if($result = $db->query($sql))
  {
   $msg = '已刪除編號 ' + $id + ' 的使用者資料!';
  }
 }
 else if($action == "disabled")
 {
  if($disabled != "0" && $disabled != "1")
   $disabled = "0";

  $sql = "UPDATE `test`.`user` SET `disabled`='$disabled' WHERE `id`='$id';";
  $result = $db->query($sql);
 }

}

$sql = "SELECT * FROM `test`.`user`";
$result = $db->query($sql);

?>
<html>
<head>
 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
 <script type="text/javascript" src="http://code.jquery.com/jquery-latest.min.js"></script>
 <script type="text/javascript" src="https://www.google.com/jsapi"></script>
 <script type="text/javascript">
  function btn_delete(id)
  {
   if(!confirm('確定要刪除編號' + id + '的使用者資料?'))
    return;
   $("#form1_action").val("delete");
   $("#form1_id").val(id);
   $("#form1").submit();
  }

  function btn_disabled(id, disabled)
  {
   $("#form1_action").val("disabled");
   $("#form1_id").val(id);
   $("#form1_disabled").val(disabled);
   $("#form1").submit();
  }
 </script>
</head>
<body>
 <table style="width: 100%;">
  <tr>
   <th colspan="11">會員帳號資訊</th>
  </tr>
  <tr>
   <td></td>
   <td colspan="2">帳號管理</td>
   <td>啟用狀態</td>
   <td>帳號</td>
   <td>姓名</td>
   <td>性別</td>
   <td>年齡</td>
   <td>身高</td>
   <td>體重</td>
   <td>E-mail</td>
  </tr>
<?PHP
while($row=$result->fetch_array(MYSQLI_ASSOC))
{
?>
  <tr>
   <td><?PHP echo $row["id"];?></td>
   <td><input type="button" value="<?PHP echo ($row["disabled"] == "0" ? "禁用" : "啟用"); ?>" onclick="btn_disabled(<?PHP echo "'$row[id]','".($row["disabled"]=="0"?"1":"0")."'";?>);"/></td>
   <td><input type="button" value="刪除" onclick="btn_delete('<?PHP echo "$row[id]";?>')" /></td>
   <td><?PHP echo ($row["disabled"] == "0" ? "啟用中" : "禁用中"); ?></td>
   <td><?PHP echo $row["account"]; ?></td>
   <td><?PHP echo $row["name"]; ?></td>
   <td><?PHP echo ($row["sexid"] == "0" ? "男" : "女"); ?></td>
   <td><?PHP echo $row["age"]; ?></td>
   <td><?PHP echo $row["height"]; ?></td>
   <td><?PHP echo $row["weight"]; ?></td>
   <td><?PHP echo $row["email"]; ?></td>
  </tr>
<?PHP
}
$result->close();

$sql = "SELECT `sexid`, COUNT(*) AS CNT FROM `test`.`user` GROUP BY `sexid`;";
$result = $db->query($sql)
?>
 </table>
 <hr />
 <table style="width: 100%;">
  <tr>
   <th colspan="2">性別統計</th>
  </tr>
<?PHP
$sexid_rows=array();
while($row=$result->fetch_array(MYSQLI_ASSOC))
{
 $sexid_rows[]=$row;
?>
  <tr>
   <td><?PHP echo ($row["sexid"] == "0" ? "男" : "女");?></td>
   <td><?PHP echo $row["CNT"];?></td>
  </tr>
<?PHP
}
$result -> close();
?>
  <tr>
   <td colspan="2">
    <div id="sexid_piechart" style="width: 900px; height: 500px;"></div>
   </td>
  </tr>
 </table>
 <hr />
 <table style="width: 100%;">
  <tr>
   <th colspan="2">年齡統計</th>
  </tr>
<?PHP
$sql  = "
SELECT
 age_range, COUNT(*) AS CNT
FROM
 (
  SELECT
   CASE
    WHEN age <= 10 THEN '1~10歲'
    WHEN age BETWEEN 11 AND 20 THEN '11~20歲'
    WHEN age BETWEEN 21 AND 30 THEN '21~30歲'
    WHEN age BETWEEN 31 AND 40 THEN '31~40歲'
    WHEN age BETWEEN 41 AND 50 THEN '41~50歲'
    WHEN age > 50 THEN '50歲以上'
   END AS age_range
  FROM
   `test`.`user`
 ) AS tmp
GROUP BY age_range
";
$age_rows=array();
$result = $db->query($sql);
while($row=$result->fetch_array(MYSQLI_ASSOC))
{
 $age_rows[]=$row;
?>
  <tr>
   <td><?PHP echo $row["age_range"];?></td>
   <td><?PHP echo $row["CNT"];?></td>
  </tr>
<?PHP
}
?>
  <tr>
   <td colspan="2">
    <div id="agerange_piechart" style="width: 900px; height: 500px;"></div>
   </td>
  </tr>
 </table>

<script type="text/javascript">
 google.load("visualization", "1", {packages:["corechart"]});
 google.setOnLoadCallback(drawChart);
 function drawChart()
 {
  var data = google.visualization.arrayToDataTable([
    ['性別', '人數']
<?PHP
for($i=0; $i<count($sexid_rows); $i++)
{
 echo "    ,['".($sexid_rows[$i]["sexid"] == "0" ? "男性" : "女性")."',".$sexid_rows[$i]["CNT"]."]\n";
}
?>
   ]);
  var options = {
   title: "性別統計"
  };

  var chart = new google.visualization.PieChart(document.getElementById('sexid_piechart'));
  chart.draw(data, options);

  data = google.visualization.arrayToDataTable([
    ['年齡', '人數']
<?PHP
for($i=0; $i<count($age_rows); $i++)
{
 echo "    ,['".$age_rows[$i]["age_range"]."',".$age_rows[$i]["CNT"]."]\n";
}
?>
   ]);
  options = {
   title: "年齡統計"
  };
  chart = new google.visualization.PieChart(document.getElementById('agerange_piechart'));
  chart.draw(data, options);
 }
</script>
<form action="list.php" method="POST" id="form1">
 <input type="hidden" name="action" id="form1_action" value="" />
 <input type="hidden" name="id" id="form1_id" value="" />
 <input type="hidden" name="disabled" id="form1_disabled" value="" />
</form>
</body>
</html>
<?PHP
$db->close();
?>

那這份檔案的圓餅圖我是呼叫 google 的 chart api 來產生的,並結合一些子查詢及 group by 的語法產生統計資料。
只是後來發現他的題意有點像是要我們實作一個圓餅圖的 php ,所以我後來又補圓餅圖的 php 給他,不過我還是很喜歡 google api 作出來的圓餅圖,有夠漂亮的XD
<?PHP
session_start();
include("db.inc.php");

if(!isset($_SESSION['account']) || $_SESSION['account'] == '')
{
 header('Location: login.php');
}

$db = new mysqli($dbhost, $dbuser, $dbpass, $dbname) or die('Could not connect: ' . mysqli_error($db));
$sql = "SELECT `sexid`, COUNT(*) AS CNT FROM `test`.`user` GROUP BY `sexid`;";
$result = $db->query($sql);

$rows=array();
$count=0;
$colors=array();
$colors[]=array(255, 0, 0);
$colors[]=array(0, 0, 255);
while($row=$result->fetch_array(MYSQLI_ASSOC))
{
 $rows[]=$row;
 $count+=$row['CNT'];
}

header('Content-type:image/jpeg');
$img = imagecreatetruecolor(900, 500);
imagefill($img, 0, 0, imagecolorallocate($img, 255, 255, 255));
$alldeg = 0;
$len=count($rows) - 1;

foreach($rows as $key=>$row)
{
 $cnt = $row['CNT'];
 if($key == $len)
  $deg = 360-$alldeg;
 else
  $deg=($cnt/$count)*360;
 if($row["sexid"] == "0")
  $color = imagecolorallocate($img, 0, 0, 255);
 else
  $color = imagecolorallocate($img, 255, 0, 0); 
 if($deg > 0)
 {
  imagefilledarc($img, 125, 125, 250, 250, $alldeg - 90, $alldeg+$deg - 90, $color, IMG_ARC_PIE);
  imagettftext($img, 15, 0, 350, 130 + 50*$key, $color, './kaiu.ttf', '■'.($row["sexid"] == "0" ? "男性" : "女性"));
 }
 $alldeg += $deg;
}

imagejpeg($img);
imagedestory($img);

$result->close();
$db->close();
?>

這個就是後來補的 pie chart 程式碼,也算是我的 GD 繪圖作
原本不想附的,原因是因為其實我 PHP 並沒有把 GD 給掛進來,不過我沒有不為五斗米折腰的氣魄,所以還是把他掛進來
那在這份上面會看到我對角度都減 90 ,原因是因為在畫圓時他的角度 0 度是三點鐘方向而非12點鐘,所以減了 90 度讓他變成我們一般人習慣的 12 點鐘方位

後記:
其實這個東西不難,我也只是作個紀錄
最難的地方大概是在我平常都習慣 asp.net ,其實我並不是那麼的擅長 php
(雖然這麼說,但我想就算不到專精,應該也有一般水準之上吧XD)
那我寫這個其實花了我大概 4 個小時的時間吧... 嗯,不知不覺居然已經過了四小時了XD
至於有沒有學到東西呢?我想任何時候都是能學到東西的
像 google chart api 我就是今天第一次用,也第一次知道有這麼好用的 chart
(之前我都直接用 C# 相關的 Chart 物件,那個也很好用,所以一直沒去學外面一些 chart 相關的東西)

然後,因為這個程式的關係,我想昨天發下的豪語,今天要整理的 android D4 教學,我就不知生不生得出來了(望~
我會再努力看看的!

Reference:
PHP Manual
PHP Session 使用介紹,啟用與清除 session
PHP GD 畫圓餅圖 (不使用外掛Class)

留言

這個網誌中的熱門文章

DB 資料庫呈現復原中

Outlook 刪除大量重覆信件

[VB.Net] If vs IIf ,兩者的差異