WebGL Tutorial
and more

PHP MySQL Tutorial

撰写时间:2023-05-01

修订时间:2023-06-25

本节使用mysqli接口来连接MySQL数据库。它以面向对象的方式来与MySQL交互。

MAMP

MAMP使用脚本文件来启动及关闭MySQL,位于/Applications/MAMP/bin路径下面。

启动MySQL的文件名为startMysql.sh,内容如下:

#!/bin/sh /Applications/MAMP/Library/bin/mysqld_safe --port=3306 --socket=/Applications/MAMP/tmp/mysql/mysql.sock --pid-file=/Applications/MAMP/tmp/mysql/mysql.pid --log-error=/Applications/MAMP/logs/mysql_error.log &

关闭MySQL的文件名为stopMysql.sh,内容如下:

#!/bin/sh /Applications/MAMP/Library/bin/mysqladmin -u <USERNAME> -p<USER_PASSWORD> --socket=/Applications/MAMP/tmp/mysql/mysql.sock shutdown

MAMP使用MySQL的配置文件为/Applications/MAMP/tmp/mysql/my.cnf

MySQL的安装路径

可使用whereis mysql来查看MySQL的安装路径。在笔者的Linux系统中,它安装在/etc/mysql路径下。

MySQL的的启动与关闭

启动:

sudo mysqld

关闭:

mysqladmin -uroot -p shutdown

MySQL中文字符问题

查看MySQL中文字符设置

在终端输入:

mysql -uroot -p

mysql是一个运行在终端窗口中的交互程序,可用于连接MySQL服务器,查询并查看结果。

进入mysql终端程序后,运行:

SHOW VARIABLES LIKE '%char%';

将列出所有与字符相关的变量。如:

+--------------------------+----------------------------+ | Variable_name | Value | +--------------------------+----------------------------+ | character_set_client | utf8 | | character_set_connection | utf8 | | character_set_database | latin1 | | character_set_filesystem | binary | | character_set_results | utf8 | | character_set_server | latin1 | | character_set_system | utf8 | | character_sets_dir | /usr/share/mysql/charsets/ | +--------------------------+----------------------------+

从上面可看出,数据库及服务器的字符均为latin1

查看可用的字符集及其对应的collation:

SHOW CHRACTER SET;

显示:

+----------+---------------------------------+---------------------+--------+ | Charset | Description | Default collation | Maxlen | +----------+---------------------------------+---------------------+--------+ | big5 | Big5 Traditional Chinese | big5_chinese_ci | 2 | | dec8 | DEC West European | dec8_swedish_ci | 1 | | cp850 | DOS West European | cp850_general_ci | 1 | | hp8 | HP West European | hp8_english_ci | 1 | | koi8r | KOI8-R Relcom Russian | koi8r_general_ci | 1 | | latin1 | cp1252 West European | latin1_swedish_ci | 1 | | latin2 | ISO 8859-2 Central European | latin2_general_ci | 1 | | swe7 | 7bit Swedish | swe7_swedish_ci | 1 | | ascii | US ASCII | ascii_general_ci | 1 | | ujis | EUC-JP Japanese | ujis_japanese_ci | 3 | | sjis | Shift-JIS Japanese | sjis_japanese_ci | 2 | | hebrew | ISO 8859-8 Hebrew | hebrew_general_ci | 1 | | tis620 | TIS620 Thai | tis620_thai_ci | 1 | | euckr | EUC-KR Korean | euckr_korean_ci | 2 | | koi8u | KOI8-U Ukrainian | koi8u_general_ci | 1 | | gb2312 | GB2312 Simplified Chinese | gb2312_chinese_ci | 2 | | greek | ISO 8859-7 Greek | greek_general_ci | 1 | | cp1250 | Windows Central European | cp1250_general_ci | 1 | | gbk | GBK Simplified Chinese | gbk_chinese_ci | 2 | | latin5 | ISO 8859-9 Turkish | latin5_turkish_ci | 1 | | armscii8 | ARMSCII-8 Armenian | armscii8_general_ci | 1 | | utf8 | UTF-8 Unicode | utf8_general_ci | 3 | | ucs2 | UCS-2 Unicode | ucs2_general_ci | 2 | | cp866 | DOS Russian | cp866_general_ci | 1 | | keybcs2 | DOS Kamenicky Czech-Slovak | keybcs2_general_ci | 1 | | macce | Mac Central European | macce_general_ci | 1 | | macroman | Mac West European | macroman_general_ci | 1 | | cp852 | DOS Central European | cp852_general_ci | 1 | | latin7 | ISO 8859-13 Baltic | latin7_general_ci | 1 | | utf8mb4 | UTF-8 Unicode | utf8mb4_general_ci | 4 | | cp1251 | Windows Cyrillic | cp1251_general_ci | 1 | | utf16 | UTF-16 Unicode | utf16_general_ci | 4 | | utf16le | UTF-16LE Unicode | utf16le_general_ci | 4 | | cp1256 | Windows Arabic | cp1256_general_ci | 1 | | cp1257 | Windows Baltic | cp1257_general_ci | 1 | | utf32 | UTF-32 Unicode | utf32_general_ci | 4 | | binary | Binary pseudo charset | binary | 1 | | geostd8 | GEOSTD8 Georgian | geostd8_general_ci | 1 | | cp932 | SJIS for Windows Japanese | cp932_japanese_ci | 2 | | eucjpms | UJIS for Windows Japanese | eucjpms_japanese_ci | 3 | | gb18030 | China National Standard GB18030 | gb18030_chinese_ci | 4 | +----------+---------------------------------+---------------------+--------+

可以用SHOW COLLATION WHERE Charset LIKE '%utf8%';来看各个collation的细节。

设置MySQL中文字符

MySQL按下列顺序读取配置文件:

/etc/my.cnf /etc/mysql/my.cnf ~/.my.cnf

默认情况下,没有第1及第3个文件,但存在第2个文件。

第2件文件,/etc/mysql/my.cnf/etc/alternatives/my.cnf文件的快捷方式,而后者又是/etc/mysql/mysql.cnf的快捷方式。因此,/etc/mysql/my.cnf实际上指向了其所在目录下的/etc/mysql/mysql.cnf文件。

/etc/mysql/mysql.cnf的内容为:

!includedir /etc/mysql/conf.d/ !includedir /etc/mysql/mysql.conf.d/

它导入了两个路径。第一个是/etc/mysql/conf.d,在其路径下,有一个名为mysql.cnf的文件,其内容为:

[mysql]

显然,这是用以设置MySQL客户端的配置,默认情况下,没有额外的配置。

第二个是/etc/mysql/mysql.conf.d,在其路径下,有一个名为mysqld.cnf的文件,其主要内容为:

[mysqld_safe] socket = /var/run/mysqld/mysqld.sock nice = 0 [mysqld] # # * Basic Settings # user = mysql pid-file = /var/run/mysqld/mysqld.pid socket = /var/run/mysqld/mysqld.sock port = 3306 basedir = /usr datadir = /var/lib/mysql tmpdir = /tmp lc-messages-dir = /usr/share/mysql skip-external-locking # # Instead of skip-networking the default is now to listen only on # localhost which is more compatible and is not less secure. bind-address = 127.0.0.1

这是MySQL服务器的配置。

要查看MySQL服务器加载了哪些配置,使用以下命令:

mysqld --print-default

显示:

mysqld would have been started with the following arguments: --user=mysql --pid-file=/var/run/mysqld/mysqld.pid --socket=/var/run/mysqld/mysqld.sock --port=3306 --basedir=/usr --datadir=/var/lib/mysql --tmpdir=/tmp --lc-messages-dir=/usr/share/mysql --skip-external-locking --character-set-server=utf8 --bind-address=127.0.0.1 --key_buffer_size=16M --max_allowed_packet=16M --thread_stack=192K --thread_cache_size=8 --myisam-recover-options=BACKUP --query_cache_limit=1M --query_cache_size=16M --log_error=/var/log/mysql/error.log --expire_logs_days=10 --max_binlog_size=100M --user=mysql --pid-file=/var/run/mysqld/mysqld.pid --socket=/var/run/mysqld/mysqld.sock --port=3306 --basedir=/usr --datadir=/var/lib/mysql --tmpdir=/tmp --lc-messages-dir=/usr/share/mysql --skip-external-locking --bind-address=127.0.0.1 --key_buffer_size=16M --max_allowed_packet=16M --thread_stack=192K --thread_cache_size=8 --myisam-recover-options=BACKUP --query_cache_limit=1M --query_cache_size=16M --log_error=/var/log/mysql/error.log --expire_logs_days=10 --max_binlog_size=100M

更改服务器配置

修改/etc/mysql/mysql.conf.d/mysqld.cnf文件的内容如下:

[mysqld] ... character-set-server=utf8mb4 collation-server=utf8mb4_general_ci ...

如果查看系统变量,将可看到:

+--------------------------+----------------------------+ | Variable_name | Value | +--------------------------+----------------------------+ | character_set_client | utf8 | | character_set_connection | utf8 | | character_set_database | utf8mb4 | | character_set_filesystem | binary | | character_set_results | utf8 | | character_set_server | utf8mb4 | | character_set_system | utf8 | | character_sets_dir | /usr/share/mysql/charsets/ | +--------------------------+----------------------------+

可以看到,它改变了character_set_databasecharacter_set_server的值。

character_set_system是数据库系统存储系统元数据的字符集,该属性是只读的,值为utf8

更改客户端配置

修改/etc/mysql/conf.d/mysql.cnf文件的内容如下:

[mysql] default-character-set=utf8mb4

如果查看系统变量,将可看到:

+--------------------------+----------------------------+ | Variable_name | Value | +--------------------------+----------------------------+ | character_set_client | utf8mb4 | | character_set_connection | utf8mb4 | | character_set_database | utf8mb4 | | character_set_filesystem | binary | | character_set_results | utf8mb4 | | character_set_server | utf8mb4 | | character_set_system | utf8 | | character_sets_dir | /usr/share/mysql/charsets/ | +--------------------------+----------------------------+

可以看到,它改变了character_set_client, character_set_connectioncharacter_set_results的值。

这与在终端的mysql程序中运行:

mysql> SET NAMES 'utf8mb4';

的效果是一样的。

为特定的数据库创建专属用户

安装好MySQL后,默认用户为root。这是一个超级用户,它可以操作MySQL所有数据库。因此,一是应当及时修改root的密码,二是不可轻易将此密码泄露给别人。在一个普通的PHP应用中,我们需要为应用创建单独的数据库,并为此数据库创建专属的用户并配置其权限,这样可避免使用默认的root用户来操作所有数据库。步骤如下:

  1. 以root身份连接MySQL中的mysql数据库。
  2. 创建PHP应用专属用户并设置其密码。
  3. 创建PHP应用的数据库并赋于新用户对该数据库的权限
  4. 以新用户的身份连接新的数据库

下面以NetBeans为例来实施上述步骤。

以root身份连接MySQL中的mysql数据库

Connect to mysql database

Databases节点下创建一个用以连接mysql数据库的节点。

创建PHP应用专属用户并设置其密码。

对上述节点按右键,点击Execute Command...菜单项。

Execute command on mysql

输入以下命令:

CREATE USER 'tutuser'@'localhost' IDENTIFIED BY 'tut0898'

结果是在mysql数据库中将创建一个tutuser用户,其密码为tut0898

创建PHP应用的数据库并赋于新用户对该数据库的权限

Create database

在弹出的窗口中输入要创建的数据库名称,并将该数据库的所有权限都赋与tutuser@localhost用户。

Grant access

单击OK按钮。

将在Databases节点下创建一个新的连接mysql_tutorial_db数据库的连接。但该连接的用户是root。因此,我们可以先将该连接节点删除,然后再以tutuser身份来新建一个连接。

以新用户的身份连接新的数据库

Dabases节点按右键,选New Connection...菜单项。

New Connection

在弹出的New Connection Wizard窗口中,Driver列表框选择MySQL (Connector/J driver)。单击Next按钮。

New Connection Wizard 1

在此步,输入相应的数据库名称,以及用户姓名及密码。可以勾选Remember password,让NetBeans记住密码。点击Test Connection按钮,以测试该配置是否正确。若无问题,则在左下角出现Connection Succeeded.的提示。单击Next按钮。

New Connection Wizard 2

由于我们尚未在mysql_tutorial_db数据库中创建表,因此这一步可不管它。单击Next按钮。

New Connection Wizard 3

此步设置连接名称,可按下图修改。单击Finish按钮。

New Connection Wizard 4

现在,mysql_tutorial_db的连接已经成功创建并显示在Databases节点之下。

Connection setuped

使用此视图,我们可以很方便地创建表了。

MySQL密码字段的加密与解密

下面使用AES算法来加密字符串abcAES算法需指定密钥,这里作为例子,密钥随便取值为mykey

SET @cript_str = AES_ENCRYPT('abc', 'mykey'); SELECT @cript_str;

加密后,将结果存储至变量@cript_str中,然后再显示其值。输出结果为:

<BLOB 16 bytes>

结果已经被加密为二进制。下面显示解密后的字符串。

SELECT AES_DECRYPT(@cript_str, 'mykey');

结果为:

<BLOB 3 bytes>

说明abc这3个字符占用了3个字节的空间。对于中文的UTF-8编码,如海上生明月这5个汉字,则会占去15个字节。对于二进制数据,可用CAST AS语句转换为字符:

SELECT CAST(AES_DECRYPT(@cript_str, 'mykey') AS CHAR);

显示:

abc

在数据库中创建表

在此应用中,需要达到什么目的?

  1. 用户登录
  2. 班主任录入各科成绩
  3. 学生观看自己的成绩,并有机会给班主任评论
  4. 学生管理自己的帐号

用户角色表:role

先创建用户角色表,只有两个角色:adminstudent

DROP TABLE IF EXISTS role; CREATE TABLE role ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, role_name CHAR(10) ); INSERT INTO role (role_name) VALUES ('admin'); INSERT INTO role (role_name) VALUES ('student');

用户表:user

DROP TABLE IF EXISTS role; CREATE TABLE user ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, name VARCHAR(20) NOT NULL, password BLOB NOT NULL, role_id INT NOT NULL, FOREIGN KEY (role_id) REFERENCES role (id) ); INSERT INTO user (name, password, role_id) VALUES ('黄老师', AES_ENCRYPT('12345', 'mykey'), 1);

注意,这里对密码12345进行了加密,而较为适合存储加密的字段类型为BLOB,如果选取的字段类型不对,则导致经解密后的字段文本无法正常显示。

这种情况下,查看解密后的password字段的代码为:

SELECT password FROM user WHERE id = 1 INTO @decript_password ; SELECT CAST(AES_DECRYPT(@decript_password, 'mykey') AS CHAR);

在实际的应用代码中,可以这样编写:

SELECT id, name, CAST(AES_DECRYPT(password, "mykey") AS CHAR) AS password FROM user WHERE name = '黄老师';

将所有初始化表的SQL语句放在一个文件中

在初始化数据库数据时,由于各表可能存在外键约束关系,因此删除含有外键的表时会失败。此时可将所有SQL语句都放一个文件中,先解除外键约束,删除各表,再恢复外键约束,再创建表并插入数据。

set @@FOREIGN_KEY_CHECKS = 0; DROP TABLE IF EXISTS role; DROP TABLE IF EXISTS user; DROP TABLE IF EXISTS score; set @@FOREIGN_KEY_CHECKS = 1; -- table role CREATE TABLE role ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, role_name CHAR(10) ); INSERT INTO role (role_name) VALUES ('admin'); INSERT INTO role (role_name) VALUES ('student'); -- table user CREATE TABLE user ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, name VARCHAR(20) NOT NULL, password BLOB NOT NULL, role_id INT NOT NULL, FOREIGN KEY (role_id) REFERENCES role (id) ); INSERT INTO user (name, password, role_id) VALUES ('黄老师', AES_ENCRYPT('12345', 'mykey'), 1); INSERT INTO user (name, password, role_id) VALUES ('张三', AES_ENCRYPT('abc', 'mykey'), 2); INSERT INTO user (name, password, role_id) VALUES ('李四', AES_ENCRYPT('abc', 'mykey'), 2); INSERT INTO user (name, password, role_id) VALUES ('王五', AES_ENCRYPT('abc', 'mykey'), 2); -- SELECT password -- FROM user -- WHERE id = 2 -- INTO @decript_password -- ; -- -- SELECT CAST(AES_DECRYPT(@decript_password, 'mykey') AS CHAR); -- table score CREATE TABLE score ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, semester INT NOT NULL, chinese INT NOT NULL, maths INT NOT NULL, english INT NOT NULL, student_id INT NOT NULL, FOREIGN KEY (student_id) REFERENCES user (id) ); INSERT INTO score (semester, chinese, maths, english, student_id) VALUES (1, 92, 78, 85, 2); INSERT INTO score (semester, chinese, maths, english, student_id) VALUES (1, 63, 81, 75, 3); INSERT INTO score (semester, chinese, maths, english, student_id) VALUES (1, 83, 65, 92, 4);

连接MySQL数据库

新建连接对象

创建对象时自动连接

$db_host = 'localhost'; $db_user = 'root'; $db_password = 'root'; $db_db = 'wishlist'; $db_port = '3306'; $mysqli = new mysqli($db_host, $db_user, $db_password, $db_db, $db_port);

上面的代码新建了一个mysqli对象,用以连接数据库。

$db_host的值为localhost时,PHP是通过UNIX socket来进行连接的。通过查看mysqlihost_info属性以验证:

echo '

Host information: '.$mysqli->host_info."

";

显示:

Host information: Localhost via UNIX socket

而当$db_host的值为127.0.0.1时,PHP将通过TCP/IP来进行连接:

Host information: 127.0.0.1 via TCP/IP

上面的代码,当新建了mysqli对象后,它会立即使用构造函数的参数自动连接数据库。

手工连接

有时我们需要进行特定的设置后再连接,则可进行手工连接。

$mysqli = mysqli_init(); if (!$mysqli) { die('mysqli_init failed'); } if(!$mysqli->options(MYSQLI_INIT_COMMAND, 'SET AUTOCOMMIT = 0')) { die('Setting MYSQLI_INIT_COMMAND failed'); } if (!$mysqli->options(MYSQLI_OPT_CONNECT_TIMEOUT, 5)) { die('Setting MYSQLI_OPT_CONNECT_TIMEOUT failed'); } $mysqli->real_connect($db_host, $db_user, $db_password, $db_db, $db_port);

为何有mysqli_real_connect()函数名?这是因为已经存在mysqli_connect()的函数,而它是构造器的别名。

检查是否成功连接

下面的代码检查连接数据库是否成功:

if ($mysqli->connect_error) { echo '

Errno: {$mysqli->connect_errno}

'; echo '

Error: {$mysqli->connect_error}

'; exit; }

检索数据

$result = $mysqli->query("SELECT * FROM wishers ORDER BY id ASC"); for ($row_no = 0; $row_no < $result->num_rows; $row_no++) { $result->data_seek($row_no); $row = $result->fetch_assoc(); echo "

id: {$row['id']}, name: {$row['name']}

"; } $result->free();

检索单行数据:

$result = $mysqli->query("SELECT id, label FROM test WHERE id = 1"); $row = $result->fetch_assoc();

关闭连接

$mysqli->close();

默认情况下,由脚本所发起的连接,要么由用户显式地关闭,或在脚本结束后由系统自动释放。而持续化的连接(persistent connection)则不会自动关闭,它们将被放进连接池使用。

因此,上面的语句也可以省略。

登录表单的设计

在设计表单时,我们需要考虑5个问题。一是在哪里进行表单校验的问题。可直接跳转到另外的页面,但这给表单校验带来了不便。较好的方案是直接在原表单中进行检验后再跳转。二是表单提交方式是GET还是POST的问题。三是如何显示表单错误信息的问题。四是如何判断表单是初次显示还是用户提交了表单数据。五是在回显页面时,如何保存用户已经填入的表单数据的问题。

<form method='POST'> <label for='name'>姓名:</label><input type="text" value='<?php echo $name ?>' name='name' required /> <span class='error' id='name-error-msg'><?php echo $name_error_msg ?></span> <label for='password'>密码:</label><input type="password" value='<?php echo $password ?>' name='password' required /> <span class='error' id='password-error-msg'><?php echo $password_error_msg ?></span> <input type="submit" /> </form>

首先,此表单提交后,由该页面自己负责校验,并且表单提交的方式是POST,用户录入的数据不会在浏览器地址栏中显示。

其次,两个文本框,其value属性值均由PHP在服务器端来提供。页面初始化显示时,由PHP在服务器端设置为空值或特定的初始化值。而如果是用户提交表单后有校验问题需要回显页面,则由PHP将这些变量值设置为用户已经录入的值,这样便实现了保存用户已经录入数据的效果。

第三,表单检验错误信息,也照样由PHP提供。则由于span标签是行内元素,当为空值时,在界面中什么都不显示,而如果有错误信息,就会显示出来。一般情况下,这些显示表单校验错误的信息应放在相应的文本框下面。

上面的表单,仅从要实现的功能的方面予以考虑,不考虑页面布局效果。满足了功能方面的需求后,界面代码确定下来了,再用CSS来给其化妆。

form { max-width: 400px; margin: 0 auto; padding: 1em; background-color: rgb(57, 83, 56); border-radius: 0.5em; display: grid; border: 1px solid gray; grid-template-areas: "name-label name-input" ". name-error" "pass-label pass-input" ". pass-error" ". submit "; grid-template-columns: auto 1fr; row-gap: 1em; } form label { text-align: right; color: rgb(100, 219, 214); width: max-content; } form input[type='text'], form input[type='password'] { background-color: #333C3E; } label[for="name"] { grid-area: name-label; } input[type='text'] { grid-area: name-input; } #name-error-msg { grid-area: name-error; text-align: left; } label[for="password"] { grid-area: pass-label; } input[type='password'] { grid-area: pass-input; } #password-error-msg { grid-area: pass-error; text-align: left; } form input[type='submit'] { grid-area: submit; justify-self: right; padding: 0.2em 0; width: 100px; } .error { color: rgb(255, 168, 214); }

最后是服务器端的PHP代码,就放在页面代码的最上面即可。

<?php $name = '黄老师'; $password = '12345'; $name_error_msg = ''; $password_error_msg = ''; // if submitted, validate if ($_SERVER['REQUEST_METHOD'] == 'POST') { $error = false; $name = htmlspecialchars(trim($_POST['name'])); if (empty($name)) { $name_error_msg = '名字不能为空'; $error = true; } $password = htmlspecialchars(trim($_POST['password'])); if (empty($password)) { $password_error_msg = '密码不能为空'; $error = true; } // check database data $db_host = 'localhost'; $db_user = 'tutuser'; $db_password = 'tut0898'; $db_db = 'mysql_tutorial_db'; $db_port = '3306'; $mysqli = new mysqli($db_host, $db_user, $db_password, $db_db, $db_port); if ($mysqli->connect_error) { printf('

Errno: %s

', $mysqli->connect_errno); printf('

Error: %s

', $mysqli->connect_error); exit; } $query = 'SELECT id, name, CAST(AES_DECRYPT(password, "mykey") AS CHAR) AS password FROM user WHERE name = "' .$name .'"'; $result = $mysqli->query($query); if ($result->num_rows == 0) { $name_error_msg = '用户不存在'; $error = true; } else { while($row = $result->fetch_assoc()) { if ($row['password'] !== $password) { $password_error_msg = '密码不正确'; $error = true; } else { session_start(); $_SESSION['user'] = $name; $_SESSION['user_id'] = $row['id']; } } } $result->free(); $mysqli->close(); if ($error == false) { header('Location: list-score.php'); exit; } } ?>

首先,为页面中要在表单中显示值的4个变量$name, $password, $name_error_msg, $password_error_msg设置了初始值。这样,页面在首次显示时,也可利用这些变量的初始值。

其次,使用$_SERVER['REQUEST_METHOD'] == 'POST'的代码来判断页面是首次显示还是用户已经提交了表单数据。如果是页面首次显示,则不执行条件语句下的代码块,直接显示原始的页面内容。

而如果是用户提交表单,则依序校验各个字段是否正确。从逻辑结构上,我们先设置了一个变量$error的值为false,即没有错误。在校验过程中,若有任何错误,则将此变量的值设置为true。而在最后,放了下面的把关语句:

if ($error == false) { header('Location: list-score.php'); exit; }

意为,如果上面的校验没有任何错误,则跳转至list-score.php文件,否则,运行并显示下面的HTML代码以显示表单。

而在表单各字段的校验过程中,一但发现错误,一是设置相应的错误信息,二是将变量$error的值设置为true

对于用户姓名及密码字段,先统一检查它们的值是否为空值。然而再根据它们的值读取数据库中的信息进行进一步的校验。

最后,如果姓名及密码都正确了,则将用户名及用户Id保存进HTML的session中:

session_start(); $_SESSION['user'] = $name; $_SESSION['user_id'] = $row['id'];

session_start()的意思是,从现在开始,允许在session中以Map名值对的方式保存或读取相应的变量。我们将当前登录用户的姓名及其id都存入session中。这样,后续同一session的网页均可访问这些变量以判断当前登录用户是谁。

至此,如果前面均未出现错误,我们已将用户登录信息保存进session中,并立即跳转至list-score.php文件。

列出成绩单的页面设计

列出成绩单的页面相对比较简单。

同上节一样,在运行网页前,我们需要在服务器端作些准备工作。

<?php session_start(); if (!array_key_exists("user", $_SESSION)) { header('Location: login.php'); exit; } $db_host = 'localhost'; $db_user = 'tutuser'; $db_password = 'tut0898'; $db_db = 'mysql_tutorial_db'; $db_port = '3306'; $mysqli = new mysqli($db_host, $db_user, $db_password, $db_db, $db_port); if ($mysqli->connect_error) { printf('<p>Errno: %s</p>', $mysqli->connect_errno); printf('<p>Error: %s</p>', $mysqli->connect_error); exit(); } ?>

先运行session_start()以访问存在session中的变量。array_key_exists函数检查数组中是否存在键名或索引值。如果session中没有user此键名,则说明并非通过登录窗口界面而访问本页,例如直接在地址栏中输入网址后访问的本页,故此需重新导航至login.php

然后准备数据库连接,以在页面中可以访问数据库。

<h2>学生成绩单</h2> <p>欢迎您,<?php echo $_SESSION['user'] ?>,以下是所有学生的成绩单。</p> <table> <tr><th>学生姓名</th><th>语文</th><th>数学</th><th>英语</th></tr> <?php $query = ' SELECT user.name AS 学生姓名, score.chinese, score.maths, score.english FROM score, user WHERE score.student_id = user.id and user.role_id = 2; '; $result = $mysqli->query($query); while($row = $result->fetch_assoc()) { printf("<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>", $row['学生姓名'], $row['chinese'], $row['maths'], $row['english']); } $result->free(); $mysqli->close(); ?> </table> <form action="logoff.php"> <input type="submit" value="退出登录" /> </form>

先从session中读取user的值并显示出来。然后,通过PHP从数据库读取数据并填充表格。最后,设立一个退出登录的表单,交由logoff.php来处理。

退出登录页面的设计

logoff.php的内容如下:

<?php session_start(); $_SESSION = array(); session_destroy(); header('Location: login.php'); exit; ?>

先调用session_start函数,然后,代码$_SESSION = array()取消session中的键名设置,session_destroy函数将session中的数据全部清空。最后,根据业务逻辑的需要,将页面重新导航至login.php

数据库安全问题

隐藏用以连接的敏感信息

上面logon.phplist-score.php均将用以连接的敏感信息放在了普通的网页文件中,而这些文件位于服务器的根目录下面,意味着别人可以直接访问得到这些敏感数据,从而有较大的风险。

好的做法是,将数据库敏感信息不要放在服务器允许外界访问的根目录下。例如,如果服务器允许外界访问的根目录名称为WWWROOT,我们就可以在WWWROOT文件夹旁边新建一个名为include的文件夹,且在里面新建一个名为db.inc.php的文件,内容如下:

<?php $db_host = 'localhost'; $db_user = 'tutuser'; $db_password = 'tut0898'; $db_db = 'mysql_tutorial_db'; $db_port = '3306'; ?>

然后,将logon.phplist-score.php连接数据库的代码:

$db_host = 'localhost'; $db_user = 'tutuser'; $db_password = 'tut0898'; $db_db = 'mysql_tutorial_db'; $db_port = '3306'; $mysqli = new mysqli($db_host, $db_user, $db_password, $db_db, $db_port);

改为:

include $_SERVER['DOCUMENT_ROOT'].'/../include/db.inc.php'; $mysqli = new mysqli($db_host, $db_user, $db_password, $db_db, $db_port);

其次,在数据库连接错误时,不要轻易将诸如数据库表名、表结构等敏感信息暴露出来。

防止SQL注入

第一步是接收用户数据时,一概经过htmlspecialchars函数的转换后才使用。该函数将HTML中的5个特殊字符进行转换:&转换为&amp;,双引号 " 转换为&quot;,单引号 ' 转换为&#039;<转换为&lt;>转换为&gt;htmlentities函数除了转换上述5个特殊字符后,对于任意字符,如果有对应的HTML entity,则自动转换为相应的HTML entity

有些时候,如果是从数据库取出的数据,也有可能带有这些特殊字符,在需要的时候,也可以用上述两个函数进行转换。

参考资源

MySQL

  1. MySQL Tutorial
  2. MySQL 8.0 Reference Manual
  3. MySQL: Encryption and Compression Functions
  4. MySQL: Alternative Storage Engines
  5. Netbeans: Creating a Database Driven Application With NetBeans IDE PHP Editor
  6. MySQL插入数据 “Incorrect string value“ 错误解析
  7. Connection Character Sets and Collations
  8. Full-Text Search Functions
  9. Using Option Files
  10. Mysql UTF8之utf8mb3 utf8mb4

PHP

  1. PHP TUTORIAL HOME
  2. MySQL Improved Extension
  3. PHP笔记-AES加解密(PHP7)
  4. SQL Injection