PHP+MySQL构建用户身份验证和个性化


前言

在此项目中我们要实现的功能有:用户登录、用户身份验证、管理密码、记录用户个人喜好、个性化内容、基于已用用户信息,展示用户可能感兴趣的内容


一、解决方案组件

1.用户识别和个性化

用户身份验证有许多可供选择的方法,由于希望将用户和个性化信息联系起来,使用就要将用户登录名和密码保存在一个MySQL数据库中,需要时候进行验证

如果要让用户以用户名和密码登录,需要如下组件

  1. 用户能够注册一个用户用户名和密码。我们需要限制用户名和密码的长度和格式
  2. 用户应该能够用注册过程提供的详细信息进行登录
  3. 用户完成用户访问之后能够登出。出于隐私考虑,如果用户使用自己家中的PC,这并不是很重要,但是在公用PC上访问就相当重要了,例如图书馆。
  4. 网站能够检测用户是否登录,并访问登录用户的数据
  5. 为了安全起见,用户可以修改密码

2.保存书签

要保存用户书签,需要在MySQL数据库创建关系表。必须实现以下功能

  1. 用户应该能够读取和浏览书签。
  2. 用户应该能够添加书签。站点需要检查这些 URL 是否有效。
  3. 用户应该能够删除书签

因此要为以上功能编写函数。

3.推荐书签

我们可以采取不同的方式向用户推荐书签。可以推荐最流行的或者关于某个主题最流行的站点。对于这个项目,我使用的是“相似意向”推荐系统,该系统可以查找与已登录用户具有相同书签的用户,并将他们的书签推荐给用户。为了避免推荐任何个人的书签,我只推荐几个用户同时拥有的书签。
因此,需要为这个功能编写函数。

二、解决方案概述

1.系统流程图

在这里插入图片描述需要将该图中每个方块创建一个模块;其中一些模块需要一个脚本,而另外一些需要更多的脚本

2.建立函数库

还需要创建函数库,函数库包括

  1. 用户身份验证
  2. 书签保存与检索
  3. 数据验证
  4. 数据库链接
  5. 输出至浏览器。可以由该函数库提供所有 HTML 输出功能,确保在网站里所有可视外观是一致的

3.所需文件

文件名描述
bookmarks.sql创建 PHPbookmark 数据库的 SQL 语句
login.php系统登录表单后的登录页面
register_form.php用户注册表单
register_new.php处理注册信息的脚本
member.php用户主页面,包含该用户所有当前书签
add_bm_form.php添加新书签表单
add_bms.php将书签真正添加到数据库的脚本
delete_bms.php将用户书签列表中删除选定的书签的脚本
recommend.php基于用户以前的操作,推荐用户可能感兴趣的书签的脚本
change_passwd_form.php用户修改密码时要填的表单
change_passwd,php修改数据库中用户密码的表单
logout.php用户登出脚本
bookmark_fns.php应用包含文件集合
data_valid_fns.php用户输入数据有效性验证函数
db_fns.php连接数据库的函数
user_auth_fns.php用户身份验证的函数
url_fns,php增加和删除书签以及推荐的书签
output_fns.php以 HTML 形式格式化输出的函数

三、实现数据库

对于 PHPbookmark 数据库来说,只需要一个非常简单的数据库模式。在程序中,要保存用户名、邮箱地址以及用户密码,还要保存书签的 URL 。一个用户可能有许多书签,许多用户也可能注册了同一个书签。因此需要两个表, user 表和 bookmark 表。

CREATE DATABASE bookmarks;
USE bookmarks;

CREATE TABLE user (
    username varchar(16) not null primary key,
    passwd char(40) not null,
    email varchar(100) not null
);

CREATE TABLE bookmark (
    username VARCHAR(16) NOT NULL,
    bm_URL VARCHAR(255) NOT NULL,
    index (username),
    index (bm_URL),
    primary key (username, bm_URL)
);

GRANT SELECT, INSERT, UPDATE, DELETE
ON bookmarks.* TO bm_user@localhost identified by 'password';

以MySQL root 用户身份运行以上命令将在系统创建的数据库。以上命令可以通过系统命令行运行,如下所示:

mysql -u youruser -p < bookmars.sql

系统将要求输入 root 用户的密码。


四、实现基本站点

创建第一页成为 login.php,因为它向用户提供登录系统的机会。该页面的代码如下所示。

<?php
require_once('bookmark_fns.php');
do_html_header('');

display_site_info();
display_login_form();

do_html_footer();

以上脚本输出如图所示
在这里插入图片描述
该站点调用的函数包含在 bookmark_fns.php文件中,如下所示。

<?php
// We can include this file in all our files
// this way, every file will contain all our functions and exceptions
require_once('data_valid_fns.php');
require_once('db_fns.php');
require_once('user_auth_fns.php');
require_once('output_fns.php');
require_once('url_fns.php');

可以看到,该文件就是本应用将要使用到的 5 个其他引入文件的“容器”。因为函数可以进行逻辑分组,因此可以按此方式构建书签引动。某些函数分组可能可供其他项目使用,因此将每个函数组保存在不同的文件,这样需要的时候可以找到相应的函数库。创建 bookmark_fns.php 文件是因为大部分脚本都要用到这 5 个函数文件。在每个脚本里包含这一文件比使用 5 个 require 语句更容易一些

我们使用的函数都来自 output_fns.php 文件。这些函数相当只管,其输出都是非常简单的 HTML。改文件包含了在 login.php中使用的4个函数。函数内容如下

<?php
function do_html_header($title)
{
    // print an HTML header
    ?>
    <!doctype html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title><?php echo $title; ?></title>
        <style>
            div.formblock {
                background: #ccc;
                width: 300px;
                padding: 6px;
                border: 1px solid #000;
            }
        </style>
    </head>
    <body>
    <div>
        <h1>PHPbookmark</h1>
    </div>
    <hr/>
    <?php
    if ($title) {
        do_html_heading($title);
    }
}

function do_html_footer()
{
    // print footer
    ?>
    </body>
    </html>
    <?php
}

function do_html_heading($heading)
{
    // print heading
    ?>
    <h2><?php echo $heading; ?></h2>
    <?php
}

function display_site_info()
{
    // display some marketing info
    ?>
    <ul>
        <li>1</li>
        <li>2</li>
        <li>3</li>
    </ul>
    <?php
}

五、实现用户身份验证

1.用户注册

要注册一个用户,需要通过一个表单获取用户详细信息,并且将这些信息保存到数据库中。
当用户点击 login.ph页面的 “Not a member?” 链接时,就会出现一个由 register_form.php产生的注册表单。该脚本如下所示

<?php
require_once('bookmark_fns.php');
do_html_header('User Registration');

display_registration_form();

do_html_footer();
?>

可以看到,该页面非常简单,只调用了来自 output_fns.php 函数库的函数。该脚本输出如图所示。
在这里插入图片描述
灰色表单是由 display_registration_form() 函数输出。该函数也包含在 output_fns.php中,该函数如下所示

function display_registration_form()
{
    ?>
    <form action="register_new.php" method="post">
        <div class="formblock">
            <h2>Register Now</h2>

            <p><label for="email">Email Address:</label><br/>
                <input type="email" name="email" id="email"
                       size="30" maxlength="100" required /></p>

            <p><label for="username">Prefeered Username<br>(max 16 chars):</label><br/>
                <input type="text" name="username" id="username"
                       size="16" maxlength="16" required/></p>

            <p><label for="passwd">Passwd <br>(between 6 and 16 chars):</label><br/>
                <input type="password" name="passwd" id="passwd"
                       size="16" maxlength="16" required/></p>

            <p><label for="passwd2">Confirm Password:</label><br/>
                <input type="password" name="passwd2" id="passwd2"></p>

            <button type="submit">Register</button>
        </div>
    </form>
    <?php


}

当用户点击 “Register” 按钮时, register_new.php脚本将运行。该脚本,如下所示

<?php
// include function files for this application
require_once('bookmark_fns.php');

// create short variable names
$email = $_POST['email'];
$username = $_POST['username'];
$passwd = $_POST['passwd'];
$passwd2 = $_POST['passwd2'];

// start session which may be needed later
// start it now because it must go before headers
session_start();
try{
    // check forms filled in
    if (!filled_out($_POST)) {
        throw new Exception('You have not filled the form out correctly - please go back and try again.');
    }
    // email address not valid
    if (!valid_email($email)) {
        throw new Exception('That is not a valid email address.Please go back and try again');
    }
    // password not the same
    if ($passwd != $passwd2) {
        throw new Exception('The passwords you entered do not match - please go back and try again.');
    }

    // check password length is ok
    // ok if username truncates, but passwords will get
    // munged if they are too long.
    if ((strlen($passwd)< 6) || (strlen($passwd) > 16)) {
        throw new Exception('Your password must be between 6 and 16 characters. Please go back and try again.');
    }

    // attempt to register
    // this function can also throw an exception
    register($username, $email, $passwd);
    // register session variable
    $_SESSION['valid_user'] = $username;


    // provide link to members page
    do_html_header('Registration successful');
    echo 'Your registration was successful. Go to the members page to start setting up your bookmarks!';
    do_html_url('member.php','Go to members page');

    // end page
    do_html_footer();
} catch(Exception $e) {
    do_html_header('Problem:');
    echo $e->getMessage();
    do_html_footer();
    exit;
}

脚本的主体有 try 语句块,因为需要检查许多条件。如果任何一个条件失败,执行将进入 catch 语句块。
接下来验证用户输入数据。在此过程中,需要检测修过条件,如下所示
检查表单是否完全添加。 调用 filled_out() 函数检查, 如下所示

if (!filled_out($_POST))

检查邮件地址是否有效,如下所示

if (!valid_email($email))

以上函数是自定义函数,包含在 data_valid_fns.php文件中。该代码如下所示

<?php
function filled_out($form_vars) {
    // test that each variable has a value
    foreach ($form_vars as $key => $value) {
        if ((!isset($key)) || ($value == '')) {
            return false;
        }
        return true;
    }
}
function valid_email($address) {
    // check an email address is possibly valid
    if (preg_match('/^[a-zA-Z0-9_\.\-]+@[a-zA-Z0-9\-]+\.[a-zA-Z0-9\-\.]+$/',$address)) {
        return true;
    } else {
        return false;
    }
}

filled_out() 函数需要一个数组变量作为输入参数;通常是 $_POST 或 $_GET变量数组。它检查表单是否完全填写,如果完全填写返回 true,否则返回 false

验证用户两次输入的密码是否一致,如下所示

if ($passwd != $passwd2)

验证用户名和密码长度是否在规定范围之内,如下所示

if ((strlen($passwd)< 6) || (strlen($passwd) > 16))

在验证了输入数据之后,我们就可以尝试注册该用户。回顾 register_new.php脚本,会发现它是按如下方式实现的

register($username, $email, $passwd);
    // register session variable
    $_SESSION['valid_user'] = $username;


    // provide link to members page
    do_html_header('Registration successful');
    echo 'Your registration was successful. Go to the members page to start setting up your bookmarks!';
    do_html_url('member.php','Go to members page');

    // end page
    do_html_footer();

可以看到,我们使用用户输入用户名、邮件地址和密码作为参数调用了 register()函数。如果函数执行成功,我们就将用户名注册为绘画变量,并为用户提供一个指向用户主页的链接(如果函数执行失败,它将抛出一个在 catch 语句块捕获的异常)。其输出图所示
在这里插入图片描述

register()函数包含在 user_auth_fns.php 函数库中,如下所示

function register($username, $email, $password)
{
    // register new person with db
    // return true or error message

    // connect to db
    $conn = db_connect();

    // check if username is unique
    $result = $conn->query("SELECT * FROM user WHERE username='" . $username . "'");
    if (!$result) {
        throw new Exception('Could not execute query');
    }

    if ($result -> num_rows > 0) {
        throw new Exception('That username is taken - go back and choose another one.');
    }

    // if ok, put in db
    $result = $conn->query("INSERT INTO user VALUES('" . $username . "','" . $password . "','" . $email . "')");

    if (!$result) {
        throw new Exception('Could not register you in database - please try again later.');
    }

    return true;
}

该函数没有特别的新内容;只是将它链接到前面已建的数据库。如果选定用户名已经存在,或者数据库不能被更行,它将抛出一个异常。否则,它将更新数据库并返回 true。
需要注意的是,我们使用了自定义函数 db_connect() 来执行数据库连接操作。该函数提供了用户使用用户名和密码连接数据库的唯一入口。这样,如果需要修改数据库密码,只需改变应用的一个文件即可。 db_connect() 函数如下所示

<?php
function db_connect() {
    $result = new mysqli('localhost', 'bm_user','password','bookmarks');
    if (!$result) {
        throw new Exception('Could not connect to database server');
    } else {
        return $result;
    }
}

?>

用户注册之后,可以通过常规登录或退出页面登录和退出网站

2.登录

如果用户将其信息输入到 login.php 表单,并提交给系统,系统将运行 member.php脚本。如果用户信息正确,该脚本允许用户登录。同时显示所有登录用户相关的书签。该脚本(member.php) 如下所示

<?php
// include function files for this application
require_once('bookmark_fns.php');
session_start();

//create short variable names
if (!isset($_POST['username']))  {
    //if not isset -> set with dummy value
    $_POST['username'] = " ";
}
$username = $_POST['username'];
if (!isset($_POST['passwd']))  {
    //if not isset -> set with dummy value
    $_POST['passwd'] = " ";
}
$passwd = $_POST['passwd'];
if ($username && $passwd) {
// they have just tried logging in
    try  {
        login($username, $passwd);
        // if they are in the database register the user id
        $_SESSION['valid_user'] = $username;
    }
    catch(Exception $e)  {
        // unsuccessful login
        do_html_header('Problem:');
        echo 'You could not be logged in.<br>
          You must be logged in to view this page.';
        do_html_url('login.php', 'Login');
        do_html_footer();
        exit;
    }
}

do_html_header('Home');
check_valid_user();
// get the bookmarks this user has saved
if ($url_array = get_user_urls($_SESSION['valid_user'])) {
    display_user_urls($url_array);
}

// give menu of options
display_user_menu();

do_html_footer();
?>

首先,检查用户是否来自首页,既用户是否填写了表单,然后尝试将其登录,如下所示:

if ($username && $passwd) {
// they have just tried logging in
    try  {
        login($username, $passwd);
        // if they are in the database register the user id
        $_SESSION['valid_user'] = $username;
    }

以上代码调用了 login() 函数登录。该函数定义在user_auth_fns.php库中,该代码如下所示

function login($username, $password ){
    // check username and password with db
    // if yer, return true
    // else throw exception

    //connect to db
    $conn = db_connect();

    // check if username is unique
    $result = $conn->query("SELECT * FROM user WHERE username='"
        .$username."' AND passwd='".$password."'");

    if (!$result) {
        throw new Exception('Could not log you in.');
    }

    if ($result->num_rows>0) {
        return true;
    } else {
        throw new Exception('Could not log you in.');
    }
}

如果用户登录成功,我们将注册其绘画。并将用户名保存到会话变量 balid_user 中。
如果一切顺利,为该用户显示会员页面,如下代码所示:

```php
do_html_header('Home');
check_valid_user();
// get the bookmarks this user has saved
if ($url_array = get_user_urls($_SESSION['valid_user'])) {
    display_user_urls($url_array);
}

// give menu of options
display_user_menu();

do_html_footer();

该页面也是通过输出函数创建的。注意,上述代码使用了几个新函数。他们分别是 user_auth_fns.php 文件中的 check_valid_user() 函数。 check_valid_user()函数将检查当前用户是否拥有一个注册的会话。这是针对还没登录却处于会话中的用户。 get_user_urls() 函数将从数据库中获得该用户标签。
以上的新函数代码如下

function check_valid_user() {
    // see if somebody is logged in and notify them if not
    if (isset($_SESSION['valid_user'])) {
        echo "Logged in as ".$_SESSION['valid_user'].".<br>";
    } else {
        // they are not logged in
        do_html_header('Problem: ');
        echo "You are not logged in.<br>";
        do_html_url('login.php','Login');
        do_html_footer();
        exit;
    }
}

function get_user_urls($username)
{
    // extract from the database all the names this user has stored

    $conn = db_connect();
    $result = $conn->query("SELECT bm_URL FROM bookmark WHERE username='" . $username . "'");
    if (!$result) {
        return false;
    }

    // create an array of the URLs
    $url_array = array();
    for ($count = 1; $row = $result->fetch_row(); ++$count) {
        $url_array[$count] = $row[0];
    }
    return $url_array;
}
function display_user_urls($url_array)
{
    // display the table of USERs

    // set global variable, so we can test later if this is on the page
    global $bm_table;
    $bm_table = true;
    ?>
    <br/>
    <form action="delete_bms.php" method="post" name="bm_table">
        <table style="width: 300px; padding: 2px;margin: 0">
            <?php
            $color = "#cccccc";
            echo "<tr style='background-color:" . $color . "'><td><strong>User</strong></td>";
            echo "<td><strong>Delete?</strong></td></tr>";
            if ((is_array($url_array)) && (count($url_array) > 0)) {
                foreach ($url_array as $url) {
                    if ($color == "#cccccc") {
                        $color = "#ffffff";
                    } else {
                        $color = "#cccccc";
                    }
                    // remember to call htmlspecialchars() when we are displaying user data
                    echo "<tr style='background-color:" . $color . "'><td>" . htmlspecialchars($url) . "</td>
                          <td><input type=\"checkbox\" name=\"del_me[]\" value=\"" . $url . "\"></td>
                          </tr>";
                }
            } else {
                echo "<tr><td>No user on record</td></tr>";
            }
            ?>
        </table>
    </form>
    <?php
}

登录成功后显示的页面如图所示
在这里插入图片描述

3.退出

你可能已经注意到,上图中的菜单选项有一个 “logout” 的链接。该链接指向 logout.php。该脚本源码如下所示

<?php
// include function files for this application
require_once('bookmark_fns.php');
session_start();
$old_user = $_SESSION['valid_user'];

// store to test if they *were* logged in
unset($_SESSION['valid_user']);
$result_dest = session_destroy();

// start output html
do_html_header('Logging Out');

if (!empty($old_user)) {
    if ($result_dest) {
        // if they were logged in and are now logged out
        echo 'Logged out.<br>';
        do_html_url('login.php', 'Login');
    } else {
        // they were logged in and could not be logged out
        echo 'Could not log you out.<br>';
    }
} else {
    // if they weren't logged in but came to this page somehow
    echo 'You were not logged in, and so have not logged out.<br>';
    do_html_url('login.php','Login');;
}

do_html_footer();

?>

4.修改密码

如果一个用户点击“Change Password” 菜单选项,系统将打开如下图所示的表单
在这里插入图片描述
该表单是由脚本 change_passwd_form.php 生成,源码如下

<?php
require_once('bookmark_fns.php');
session_start();
do_html_header('Change password');
check_valid_user();

display_password_form();

display_user_menu();
do_html_footer();
?>

用户提交改表单将除法 change_passwd.php 脚本,源码如下所示

<?php
require_once('bookmark_fns.php');
session_start();
do_html_header('Change password');

// create short variable names
$old_passwd = $_POST['old_passwd'];
$new_passwd = $_POST['new_passwd'];
$new_passwd2 = $_POST['new_passwd2'];

try {
    check_valid_user();
    if (!filled_out($_POST)) {
        throw new Exception(' You have not filled out form completely. Please try again.');
    }

    if($new_passwd != $new_passwd2) {
        throw new Exception('Passwords entered were not the same. Not changed');
    }

    if ((strlen($new_passwd) > 16 || (strlen($new_passwd) < 6))) {
        throw new Exception(' New password must be between 6 and 16 characters. Try again.');
    }

    // attempt update
    change_password($_SESSION['valid_user'], $old_passwd, $new_passwd);
    echo 'Password changed.';
}
catch (Exception $e) {
    echo $e->getMessage();
}
display_user_menu();

do_html_footer();
?>

其中 chage_password()函数来自 user_auth_fns.php 函数库,源码如下

function change_password($username, $old_password, $new_password) {
    // change password for username/old new_password
    // return true of false

    // if the old password is right
    // change their password to new_password and return true
    // else throw an exception
    login($username, $old_password);
    $coon=db_connect();
    $result = $coon->query("UPDATE USER SET PASSWD ='".$new_password."' WHERE username='".$username."'");

    if (!$result) {
        throw new Exception('Password could not be changed.');
    } else {
        return true;  // changed successfully
    }
}


六、实现书签存储和读取

在实现了与用户账户相关的功能后,接下来写如何保存、读取和删除书签

1. 添加书签

用户点击用户菜单的 “Add BM” 链接可以添加书签。该链接进入如下图所示的页面
在这里插入图片描述
该页面(add_bm_form.php)的源码如下

<?php
// include function files for this application
require_once('bookmark_fns.php');
session_start();

// start output html
do_html_header('Add Bookmarks');

check_valid_user();
display_add_bm_form();

display_user_menu();
do_html_footer();

?>

该源码中的新函数 display_add_bm_form() 出自output_fns.php函数库,该函数源码如下

function display_add_bm_form()
{
    // display the form for people to ener a new user in
    ?>
    <form action="add_bms.php" method="post" name="bm_table">
        <div class="formblock">
            <h2>New Bookmark</h2>

            <p>
                <input type="text" name="new_url" id="new_url"
                       size="40"  maxlength="255" value="http://" required /></p>

            <button type="submit">Add Bookmark</button>

        </div>
    </form>
    <?php
}

提交表单后,系统将调用 add_bms.php 脚本,该脚本添加新书签到个人页面,该脚本如下所示

<?php
require_once('bookmark_fns.php');
session_start();
// create short variable name
$new_url = $_POST['new_url'];
do_html_header('Adding username');

try {
    check_valid_user();
    if (!filled_out($_POST)) {
        throw new Exception('Form not completely filled out.');
    }
    // check URL format
    if (strstr($new_url, 'http://') === false) {
        $new_url = 'http://'.$new_url;
    }

    // check URL is valid
    if (!(@fopen($new_url, 'r'))) {
        throw new Exception('Not a valid URL.');
    }

    // try to add bm
    add_bm($new_url);
    echo 'Bookmark added.';

    // get the bookmarks this user has saved
    if ($url_array = get_user_urls($_SESSION['valid_user'])) {
        display_user_urls($url_array);
    }
}
catch(Exception $e) {
    echo $e->getMessage();
}
display_user_menu();
do_html_footer();
?>

该表单中的add_bm() 函数出自 url_fns.php文件,该函数将用户提交的新书签添加到数据库,函数源码如下

function add_bm($new_url)
{
    // Add new bookmark to the database
    echo "Attempting to add" . htmlspecialchars($new_url) . "<br/>";
    $valid_user = $_SESSION['valid_user'];
    $conn = db_connect();
    // check not a repeat bookmark
    $result = $conn->query("SELECT * FROM bookmark WHERE username='$valid_user' AND 
                             bm_URL='" . $new_url . "'");
    if ($result && ($result->num_rows > 0)) {
        throw new Exception('Bookmark already exists.');
    }

    // insert the new bookmark
    if (!$conn->query("INSERT INTO bookmark VALUES('" . $valid_user . "','" . $new_url . "')")) {
        throw new Exception('Bookmark could not be inserted.');
    }
    return true;
}

2.显示书签

member.php 脚本和 add_bm() 函数使用了函数 get_user_urls() 和 display_user_ruls()。它们分别从数据库中读取用户书签和现实这些书签。
display_user_urls() 函数包含在 output_fns.php库中;get_user_urls() 函数包含在 url_fns.php库中
其中get_user_urls() 函数是从数据库中读取书签,如下代码所示

function get_user_urls($username)
{
    // extract from the database all the names this user has stored

    $conn = db_connect();
    $result = $conn->query("SELECT bm_URL FROM bookmark WHERE username='" . $username . "'");
    if (!$result) {
        return false;
    }

    // create an array of the URLs
    $url_array = array();
    for ($count = 1; $row = $result->fetch_row(); ++$count) {
        $url_array[$count] = $row[0];
    }
    return $url_array;
}

get_user_ursl() 函数返回一个 URL 数组, display_user_urls()函数根据这些 URL 显示相关的数据。

3.删除书签

用户将一些书签标记问删除并点击菜单选项的“Delete BM” 时,就将提交一个包含 URL 的表单。每个复选框由 display_user_urls() 函数生成,如下代码所示

echo "<tr style='background-color:" . $color . "'><td>" . htmlspecialchars($url) . "</td>
                          <td><input type=\"checkbox\" name=\"del_me[]\" value=\"" . $url . "\"></td>
                          </tr>";

每次输入框名称是 del_me[]。这就是说,在该表单触发执行的 PHP 的脚本中,我们可以访问名 $del_me 的数组,该数组改行所有要删除的书签。
点击“Delete BM” 就触发了 delete_bms.php脚本,该脚本源码如下

<?php
require_once('bookmark_fns.php');
session_start();
// create short variable names
$del_me = $_POST['del_me'];
$valid_user = $_SESSION['valid_user'];

do_html_header('Deleting bookmarks');
check_valid_user();

if(!filled_out($_POST)) {
    echo '<p>You have not chosen any bookmarks to delete.<br>
            Please try again.</p>';
    display_user_menu();
    do_html_footer();
    exit;
} else {
    if(count($del_me) > 0) {
        foreach ($del_me as $url) {
            if (delete_bm($valid_user, $url)) {
                echo 'Deleted'.htmlspecialchars($url).'.<br>';
            } else {
                echo 'Could not delete'.htmlspecialchars($url).'.<br>';
            }
        }
    } else {
        echo 'No bookmarks selected for deletion';
    }
}

// get the bookmarks this user has saved
if ($url_array = get_user_urls($valid_user)) {
    display_user_urls($url_array);
}
display_user_menu();
do_html_footer();
?>

在脚本的开始,我们执行了常规验证操作。当确定用户已经删除选中的书签时,将通过循环删除书签,如下所示:

foreach ($del_me as $url) {
            if (delete_bm($valid_user, $url)) {
                echo 'Deleted'.htmlspecialchars($url).'.<br>';
            } else {
                echo 'Could not delete'.htmlspecialchars($url).'.<br>';
            }
        }

可以看到, delete_bm() 函数真正执行力从数据库删除书签的操作。该函数保存在url_fns.php文件中,源码如下所示

function delete_bm($user, $url) {
    // delete one URL from the database
    $conn = db_connect();

    // delete the bookmark
    if (!$conn->query("DELETE FROM bookmark WHERE username='".$user."' AND bm_url='".$url."'")) {
        throw new Exception('Bookmark could not be deleted');
    }
    return true;
}

在系统中运行删除脚本输出结果如下图所示
在这里插入图片描述

与在 add_bms.php 文件中的脚本操作类似,当数据库被修改之后,我们将调用 get_user_urls() 函数和 display_user_urls() 函数来显示新书签


七、实现书签推荐

最后,我们将讨论书签推荐脚本 recommend.php。实现书签推荐有多中方法。在此我应用“相似意向”的推荐,既查找与给定用户至少有一个相同书签的其他用户,其他用户的书签也可能对特性用户有吸引力。
将“相似意向”应用到 SQL 查询最简单的办法就是使用子查询。第一个子查询如下所示:

SELECT DISTINCT(b2.username)
                                               FROM bookmark b1, bookmark b2 WHERE b1.username
                                                                             ='" . $valid_user . "'
                                                                             AND b1.username != b2.username
                                                                             AND b1.bm_URL = b2.bm_URL

以上查询使用别名将数据库表 bookmark 进行自身链接,这是一个奇怪但是有时候有用的概念。假设我有两个书签表,b1和b2。b1查询当前用户及其书签。b2查询所有其他用户标签。我们需要查找的是用户书签中有一个 URL 与当前用户相同(b1.bm_URL = b2.bm_URL) 的其他用户 (b2.username),并且这个其他用户不是当前用户 (b1.username != b2.username)。
该查询将给出一个与当前用户意向相似的用户列表。得到这个用户列表后,就可以用下面的查询语句搜索他们的其他书签了:

SELECT bm_URL FROM bookmark WHERE username in (SELECT DISTINCT(b2.username)
                                               FROM bookmark b1, bookmark b2 WHERE b1.username
                                                                             ='" . $valid_user . "'
                                                                             AND b1.username != b2.username
                                                                             AND b1.bm_URL = b2.bm_URL)

可以添加第二个子查询来过滤当前用户的书签;如果用户已经有了这些书签,就不必再将该书签推荐给他。最后,对$popularity 变量进行书签过滤。我们不大希望推荐太个性化的 URL ,因此只将一定数量的其他用户书签 URL 推荐给用户。
实现书签推荐的完整脚本如下
recommend.php(向用户推荐可能喜欢的书签)

<?php
require_once('bookmark_fns.php');
session_start();
do_html_header('Recommending URL');

try {
    check_valid_user();
    $urls = recommend_urls($_SESSION['valid_user']);
    display_recommended_urls($urls);
} catch (Exception $e) {
    echo $e -> getMessage();
}
display_user_menu();
do_html_footer();
?>

url_fns.php文件的recommend_url()函数

function recommend_urls($valid_user, $popularity = 1)
{
    // We will provide semi intelligent recommendations to people
    // If they have an URL in common with other users, they may like
    // other URLs that these people like
    $conn = db_connect();

    // find other matching users
    // with an url the same as you
    // as a simple way of excluding people's private pages, and
    // increasing the chance of recommending appealing URLs, we
    // specify a minimum popularity level
    // if $popularity = 1, then more than one person must have
    // an URL before we will recommend it

    $query = "SELECT bm_URL FROM bookmark WHERE username in (SELECT DISTINCT(b2.username)
                                               FROM bookmark b1, bookmark b2 WHERE b1.username
                                                                             ='" . $valid_user . "'
                                                                             AND b1.username != b2.username
                                                                             AND b1.bm_URL = b2.bm_URL) 
                                                                             AND bm_URL NOT IN
                                                                             (SELECT bm_URL FROM bookmark
                                                                             WHERE username='" . $valid_user . "')
                                                                             GROUP BY bm_URL
                                                                             HAVING COUNT(bm_url)>".$popularity;
    if (!($result = $conn->query($query))) {
        throw new Exception('Could not find any bookmarks to remmend');
    }
    if ($result->num_rows==0) {
        throw new Exception('Could not find any bookmarks to recommend.');
    }

    $urls = array();
    // bulid an array of the relevant urls
    for ($count = 0; $row=$result->fetch_object(); $count++) {
        $urls[$count] = $row->bm_URL;
    }
    return $urls;
}

如果说数据库中没有两位及以上的用户收藏某网址为书签则recommend.php输入示例如图所示
在这里插入图片描述


以上就是本项目的基础功能,其中还有保存在output_fns.php库中的菜单栏(display_user_menu()) 函数,源码如下

function display_user_menu()
{
    // display the menu options on this page
    ?>
    <hr>
    <a href="member.php">Home</a> |
    <a href="add_bm_form.php">Add BM</a> |
    <?php
    // only offer the delete option if user table is on this page
    global $bm_table;
    if ($bm_table === true) {
        echo "<a href=\"#\" onClick =\"bm_table.submit();\">Delete BM</a> &nbsp;|&nbsp;";
    } else {
        echo "<span style=\"color: #cccccc\">Delete BM</span> &nbsp;|&nbsp;";
    }
    ?>
    <a href="change_passwd_form.php">Change password</a><br/>
    <a href="recommend.php">Recommend URLs to me</a> |
    <a href="logout.php">Logout</a>
    <?php
}

最后谢谢,你的收看,这是我写的第一个PHP+MySQL项目,第一次发博客,有许多不足的地方,请多多指点,谢谢观看。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值