php项目 个人中心 第一章:项目搭建

#『技术文档』写作方法征文挑战赛#

前言

该项目是php的基础入门案例。我也是边复习边写的。算个小而比较全的练手案例吧。中间遇到什么问题大家可以评论留言。文档我用的markdown语法写的,排版啥的请大家见谅。还有前置知识,php基础,json,session,类和对象,pdo之类的我默认大家是已经学会了。嗯,这篇文章适合刚刚学完php所有基础做过一两个类似计算器并且没有结构目录案例的朋友。对了,我的构思是这样的。简单的个人中心升级到MVC加发布文章功能加缓存在升级到laravel博客API项目。没错,之前的node接口我不满意,改成php来写了。嘻嘻 ~

PHP 原生 + MySQL 用户个人中心系统

语言:php
数据库: mysql
本项目是一个使用 PHP 原生 + MySQL 开发的用户注册、登录与个人中心管理系统,适合初学者学习用户认证流程、会话管理、数据库操作和基本的安全设计。

🔐 用户注册

  • 输入用户名、密码、确认密码
  • 检查用户名重复、密码长度(≥6)
  • 自动记录注册 IP
  • 注册成功后跳转到登录页并自动填充信息

🧾 用户登录

  • 输入用户名、密码
  • 验证失败计数、记录登录 IP
  • 登录成功后保存登录历史
  • 登录失败时给出清晰提示

👤 用户中心

  • 显示用户名、最后登录时间和 IP
  • 支持修改用户名(自动检查重复)
  • 显示最近 10 次登录记录(支持排序和分页扩展)
  • 修改密码功能(输入旧密码,新密码需确认)
  • 密码修改成功后强制退出登录
  • 退出按钮返回登录页面

项目信息

personal-center-php-mysql/
├── api/
│   ├── register.php
│   ├── login.php
│   ├── logout.php
│   ├── user.php
│   └── change_password.php
├── func/
│   ├── db.class.php
│   ├── utils.php
│   └── auth.php
├── config/
│   └── config.php
├── public/
│   └── css
│   ├── js
├── templates/
│   ├── register.html
│   ├── login.html
│   └── user_center.html
├── index.php
└── .htaccess

文件说明
    api/:包含所有后端 API 脚本。
    func/:包含通用函数和类。
    config/:包含配置文件。
    templates/:包含前端模板文件。
    index.php:前端入口文件。
    .htaccess:用于配置 URL 重写。

创表语句
CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户唯一标识',
  `username` varchar(50) NOT NULL COMMENT '用户名,必须唯一',
  `password` varchar(255) NOT NULL COMMENT '用户密码,存储哈希值',
  `last_login_ip` varchar(45) DEFAULT NULL COMMENT '用户最后一次登录的IP地址',
  `last_login_time` datetime DEFAULT NULL COMMENT '用户最后一次登录的时间',
  `registration_ip` varchar(45) DEFAULT NULL COMMENT '用户注册时的IP地址',
  `failed_login_attempts` int(11) DEFAULT '0' COMMENT '失败的登录尝试次数',
  `created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '用户创建时间',
  `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '用户信息最后更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4

CREATE TABLE `login_history` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '登录历史记录唯一标识',
  `user_id` int(11) NOT NULL COMMENT '关联的用户ID',
  `login_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '登录时间',
  `ip_address` varchar(45) DEFAULT NULL COMMENT '登录时的IP地址',
  PRIMARY KEY (`id`),
  KEY `user_id` (`user_id`),
  CONSTRAINT `login_history_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

conf文件
server {
        listen        8001;
        server_name  personal-center-php-mysql;
        root   "D:/phpstudy_pro/WWW/personal-center-php-mysql";
		index index.php;
        location / {
            index index.php index.html error/index.html;
            error_page 400 /error/400.html;
            error_page 403 /error/403.html;
            error_page 404 /error/404.html;
            error_page 500 /error/500.html;
            error_page 501 /error/501.html;
            error_page 502 /error/502.html;
            error_page 503 /error/503.html;
            error_page 504 /error/504.html;
            error_page 505 /error/505.html;
            error_page 506 /error/506.html;
            error_page 507 /error/507.html;
            error_page 509 /error/509.html;
            error_page 510 /error/510.html;
            include D:/phpstudy_pro/WWW/personal-center-php-mysql/nginx.htaccess;
            autoindex  off;
			try_files $uri $uri/ /index.php?$query_string;
        }
        location ~ \.php(.*)$ {
            fastcgi_pass   127.0.0.1:9000;
            fastcgi_index  index.php;
            fastcgi_split_path_info  ^((?U).+\.php)(/?.+)$;
            fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
            fastcgi_param  PATH_INFO  $fastcgi_path_info;
            fastcgi_param  PATH_TRANSLATED  $document_root$fastcgi_path_info;
            include        fastcgi_params;
        }
}

必要软件

集成环境小皮面板
编辑器vscode
至于数据库可视化的话,面板里直接下载吧。
github仓库地址:https://github.com/sijidouyyf/personal-center-php-mysql.git
这里github打不开的话你们用下加速器,蹭下免费的用用,用完流量再找个新的就行。加速器

php环境搭建

这里说下我用的是nginx
在这里插入图片描述

面板点到网站,在按照图片上写就完事了
在这里插入图片描述
在点到设置–文件位置–nginx
他会打开一个文件
在这里插入图片描述
然后点开这个文件
D:\phpstudy_pro\Extensions\Nginx1.15.11\conf\vhosts
在这里插入图片描述
把信息里的配置粘贴复制进去,这里我解释一下

conf文件解释

我只加了这两句话

index index.php;
try_files $uri $uri/ /index.php?$query_string;

index index.php;
表示:默认首页文件是 index.php

当用户访问目录时(如访问 /),Nginx 会自动尝试打开 index.php 文件。

try_files $uri u r i / / i n d e x . p h p ? uri/ /index.php? uri//index.php?query_string;
这是伪静态处理的核心逻辑:

Nginx 按顺序尝试三个路径,直到找到一个可用的文件:

$uri → 直接尝试访问请求的原始地址(如 /about.html)

$uri/ → 如果是目录,就尝试加 /(如 /about/)

/index.php? q u e r y s t r i n g → 如果都找不到,就把请求交给 i n d e x . p h p 处理,并保留原始的 G E T 参数( query_string → 如果都找不到,就把请求交给 index.php 处理,并保留原始的 GET 参数( querystring如果都找不到,就把请求交给index.php处理,并保留原始的GET参数(query_string)

这就实现了 Laravel、ThinkPHP、WordPress 等的路由形式:
例如用户访问 /user/profile,实际 Nginx 会转发为:
index.php?path=/user/profile

这个其实是我们写入口文件要用的知识,以及我们为什么要学linux。毕竟win里我们可以直接粘贴复制。服务器上可是你自己要去配置的。还有跨域配置也是这个文件来写的。当然这个项目不涉及这么多。暂时够用了。下一步~

测试

新建index.php

<?php

echo phpinfo();

?>

开启服务,浏览器输入

http://localhost:8001/

页面输出以下信息成功,否则检查之前的操作,实在不行评论留言或者私信
在这里插入图片描述

项目环境搭建

项目目录

按照开始给的目录大家自己新建一下

数据库

按照开始给的sql语句建完库运行一下,名字最好用这个personal_center,为什么呢,因为我用的是这个。后面代码就可以直接抄了。

入口文件

改写index.php

<?php
// 入口文件
session_start();


// 定义项目根目录
define('ROOT_PATH', dirname(__FILE__));

// 包含配置文件
require_once ROOT_PATH . '/config/config.php';

// 包含数据库类
require_once ROOT_PATH . '/func/db.class.php';

// 包含工具类
require_once ROOT_PATH . '/func/utils.php';

// 包含认证类
require_once ROOT_PATH . '/func/auth.php';

// 获取请求URI
$requestUri = $_SERVER['REQUEST_URI'] ?? '';

// 路由分发
switch ($requestUri) {
    case '/register': 
        require_once ROOT_PATH . '/templates/register.html';
        break;
    case '/login':
        if ($_SERVER['REQUEST_METHOD'] == 'GET') {
            require_once ROOT_PATH . '/templates/login.html';
        } else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
            require_once ROOT_PATH . '/api/login.php';
        }
        break;
    case '/user':
        if (isAuthenticated()) {
            require_once ROOT_PATH . '/templates/user_center.html';
        } else {
            echo json_encode(['success' => false, 'message' => '未登录']);
        }
        break;
    case '/change_password':
        if (isAuthenticated()) {
            require_once ROOT_PATH . '/templates/change_password.html';
        } else {
            echo json_encode(['success' => false, 'message' => '未登录']);
        }
        break;
    case '/logout':
        session_unset();
        session_destroy();
        header('Location: /login');
        exit;
    default:
        // 默认重定向到登录页
        header('Location: /login');
        exit;
}

配置文件

新建 config/config.php
说下一般来说mysql默认密码是root。反正你们按照自己的改

<?php 

return [
    'host' => 'localhost',
    'username' => 'root',
    'password' => '123456',
    'database' => 'personal_center',
    'charset' => 'utf8mb4'
];

?>

数据库封装

新建func/db.class.php文件 。CURD的操作示范写在最下面。这里其实遇到不少事。环境搭建完了我一起说吧

<?php
class Db {
    private $host;
    private $username;
    private $password;
    private $database;
    private $charset;
    private $pdo;

    public function __construct() {
        $config = require __DIR__ . '/../config/config.php';

        $this->host = $config['host'];
        $this->username = $config['username'];
        $this->password = $config['password'];
        $this->database = $config['database'];
        $this->charset = $config['charset'];
        $this->connect();
    }

    private function connect() {
        try {
            $dsn = "mysql:host={$this->host};dbname={$this->database};charset={$this->charset}";
            $this->pdo = new PDO($dsn, $this->username, $this->password);
            $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            $this->pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
        } catch (PDOException $e) {
            die("数据库连接失败: " . $e->getMessage());
        }
    }

    public function query($sql, $params = []) {
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetchAll();
    }
    
    public function insert($table, $data) {
        $columns = implode(', ', array_keys($data));
        $placeholders = ':' . implode(', :', array_keys($data));
        $sql = "INSERT INTO {$table} ({$columns}) VALUES ({$placeholders})";

        $stmt = $this->pdo->prepare($sql);
        $stmt->execute($data);
        return $this->pdo->lastInsertId();
    }

    public function update($table, $data, $conditions) {
        $set = [];
        $params = []; // 用于存储绑定参数

        foreach ($data as $key => $value) {
            if ($value === 'NOW()') {
                $set[] = "$key = NOW()"; // 直接使用 NOW()
            } else {
                $set[] = "$key = :$key";
                $params[":$key"] = $value; // 仅将非 NOW() 的值添加到参数中
            }
        }
        $set = implode(', ', $set);

        $where = [];
        foreach ($conditions as $key => $value) {
            $where[] = "$key = :$key";
            $params[":$key"] = $value; // 将条件参数添加到参数中
        }
        $where = implode(' AND ', $where);

        $sql = "UPDATE {$table} SET {$set} WHERE {$where}";

        $stmt = $this->pdo->prepare($sql);
        $stmt->execute($params); // 只传递绑定参数
        return $stmt->rowCount();
    }

    public function delete($table, $conditions) {
        $where = [];
        foreach ($conditions as $key => $value) {
            $where[] = "$key = :$key";
        }
        $where = implode(' AND ', $where);

        $sql = "DELETE FROM {$table} WHERE {$where}";

        $stmt = $this->pdo->prepare($sql);
        $stmt->execute($conditions);
        return $stmt->rowCount();
    }

    public function userExists($username) {
        $sql = "SELECT COUNT(*) as count FROM users WHERE username = :username";
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute(['username' => $username]);
        $result = $stmt->fetch(PDO::FETCH_ASSOC);
        return $result['count'] > 0;
    }

    public function beginTransaction() {
        return $this->pdo->beginTransaction();
    }

    public function commit() {
        return $this->pdo->commit();
    }

    public function rollBack() {
        return $this->pdo->rollBack();
    }

    public function __destruct() {
        $this->pdo = null;
    }
}

// 查询
// $db = new Db();
// $results = $db->query("SELECT * FROM users");
// foreach ($results as $row) {
//     print_r($row);
// }

// 插入
// $db = new Db();
// $user_id = $db->insert('users', [
//     'username' => 'testuser',
//     'password' => password_hash('testpassword', PASSWORD_BCRYPT),
//     'registration_ip' => '127.0.0.1'
// ]);
// echo "插入的用户 ID: $user_id";

// 更新
// $db = new Db();
// $updated_rows = $db->update('users', ['password' => password_hash('newpassword', PASSWORD_BCRYPT)], ['id' => 1]);
// echo "更新的行数: $updated_rows";

// 删除
// $db = new Db();
// $deleted_rows = $db->delete('users', ['id' => 1]);
// echo "删除的行数: $deleted_rows";

工具函数和认证函数

新建func/utls.php.密码用的

<?php

function hashPassword($password) {
    return password_hash($password, PASSWORD_DEFAULT);
}

function verifyPassword($password, $hash) {
    return password_verify($password, $hash);
}

?>

新建func/auth.php 认证登陆用的

<?php

// 检查用户是否已登录
function isAuthenticated() {
    if (session_status() == PHP_SESSION_NONE) {
        session_start();
    }
    return isset($_SESSION['user_id']);
}

// 获取当前用户ID
function getCurrentUserId() {
    return $_SESSION['user_id'] ?? null;
}

// 获取当前用户名
function getCurrentUsername() {
    return $_SESSION['username'] ?? null;
}

// 登录用户
function loginUser($userId, $username) {
    $_SESSION['user_id'] = $userId;
    $_SESSION['username'] = $username;
}

// 登出用户
function logoutUser() {
    session_unset();
    session_destroy();
}

总结

到这里项目配置已经完成。我一个个来说。
涉及知识点:常量定义,包含文件之类的php基础知识不来说了。session,哈希加密,类和对象,PDO。其实该有的技术点差不多都有了,后期无非加功能模块了。
在来说说我遇到的三个坑。
第一个,加载不了db.class.php

require_once __DIR__ . '../func/db.class.php';
首先这段话,大家觉的能加载吗?

没错,不能,会报错
__DIR__  是绝对路径
../func/db.class.php 是相对路径
这两句话拼接必然报错,所以正确的是
require_once __DIR__ . '/../func/db.class.php';

然后我又自作聪明的把__DIR__ 去掉了。变成了
require_once '../func/db.class.php';
然后有一次莫名其妙的又加载不进来了。我想了想,这玩意依赖目录结构,网上找了一会,发现确实有人不知道
为什么会报错,最后就改成这样了
require_once __DIR__ . '/../func/db.class.php';

第二个,NOW()函数问题

NOW()函数是mysql函数,直接插入会报错。基础类这里我改成接受字符串,if判断来写了。
这个方式我觉的写的有点问题的。以后在来改进了

第三个,session会话的问题

session原理给大家说下

浏览器  ←→  服务器

第一次访问:
- 服务端生成 session 文件(保存在服务器)
- 返回给浏览器一个 session_id(通常通过 Cookie)

后续访问:
- 浏览器带着 session_id
- 服务器根据 session_id 找回 session 数据
- 所以可以跨页面共享数据

那问题来了。我的结构也就是说session_start();他在入口文件,对吧。也就是说所有请求都从入口文件走,
所以session是通用的。
然后我遇到坑了。
其实这个问题应该早就想到的。注册那里不会出问题,登陆那里没处理好,seesion肯定会出问题

登陆页面js请求 --- 后端判断设置session 返回成功 --- 跳转到用户中心 用户中心判断是否登陆
这条进程session必须是通的。 然后我这里js请求的路径是这样写的

// 使用fetch提交表单
    fetch('../api/login', { 
        method: 'POST',
        body: formData
    })
    .then(response => response.json())
    .then(data => {
        
        if (data.success) {
            alert('登录成功');
            // 登录成功后的操作
            window.location.href = '/user'; 
        } else {
            alert('登录失败:' + data.message);
        }
    })
    .catch(error => {
        console.error('Error:', error);
        alert('登录失败');
    });
没错,哥们混了头,直接去找了api/login.php 
这个时候session_start();开启在入口文件。我js进程跑到登陆后端,后端设置的

// 登录成功
loginUser($user['id'], $user['username']);

这句话直接无效了。也就是进程跑到用户中心,其实是没有session的。进到用户中心的时候

if (!isAuthenticated()) {
    echo json_encode(['success' => false, 'message' => '未登录']);
    exit;
}
直接给拦下来了
哎,哥们心塞了,测试了一个小时,才发现这个问题。最后改了入口文件
case '/login':
   if ($_SERVER['REQUEST_METHOD'] == 'GET') {
       require_once ROOT_PATH . '/templates/login.html';
   } else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
       require_once ROOT_PATH . '/api/login.php';
   }
   break;

只能说我这个项目是练手,复习大部分的知识吧。这框架设计的太差了。那里错了改那里。下一个用MVC设计框架

OK,第一章结束。注册,登陆,用户中心,四章结束这个项目。大家加油

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值