挑战30分钟写个用户登录注册系统(后端)- 我的全栈独立开发日记

前言

后端使用ThinkPHP框架+MySQL数据库

本教程的视频版本已发布在B站,欢迎交流~

戳我看视频:静夜Coding-无人声编程-机械键盘纯享版

准备工作

创建项目

  • 使用包管理 composer基于ThinkPHP最新源码创建工程

    composer create-project topthink/think user-account

  • 新建文件 app/controller/user/Account.php, 继承基础控制器类 \app\BaseController

  • \app\BaseController新增两个方法,以方便返回统一的JSON数据

        public function success($data = null, $msg = 'success')
        {
            return json([
                'errcode' => 0, // 错误码。规定:errcode不等于0时表示发生了异常
                'msg' => $msg, // 错误信息
                'data' => $data, // 返回数据
            ]);
        }
    
        public function fail($msg = 'fail', $errcode = 1, $data = null)
        {
            return json([
                'errcode' => $errcode,
                'msg' => $msg,
                'data' => $data
            ]);
        }

写一个测试方法

   function test()
    {
        return $this->success([
            'content' => '静夜Coding @B站'
        ]);
    }

启动本地服务

cd user-account && php think run

手把手带你写全栈-用户登录注册-后端API

打开API调试工具

默认路由规则根据user.account/test找到对应控制器app/controller/user/Account.php的test方法

POST localhost:8000/user.account/test

成功返回如下

手把手带你写全栈-用户登录注册-后端API

设计数据库模型

SQL语句创建一个用户表

CREATE TABLE `user`
(
    `id`         int(10) unsigned NOT NULL AUTO_INCREMENT,
    `username`   varchar(30)  NOT NULL DEFAULT '' COMMENT '用户名',
    `password`   varchar(32)  NOT NULL DEFAULT '' COMMENT '密码',
    `salt`       varchar(10)  NOT NULL DEFAULT '' COMMENT '密码salt',
    `nickname`   varchar(30)  NOT NULL DEFAULT '' COMMENT '用户昵称',
    `avatar`     varchar(300) NOT NULL DEFAULT '' COMMENT '用户头像',
    `created_at` datetime     NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `updated_at` datetime     NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
    PRIMARY KEY (`id`),
    UNIQUE KEY `unq_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

password字段是基于散列算法对用户输入密码计算之后得到的字符串,基于数据安全考虑,数据库不能存储明文密码。

salt字段是随机字符串,用户混淆散列算法,提高密码的安全性。

created_at, updated_at字段记录了数据插入和更新的时间,这样定义可以让数据库自动记录数据变更时间,免去业务代码的额外工作,而且使用datetime类型而不是timestamp类型,有两个好处:
1)时间戳由于历史设计原因,在有生之年会达到最大值
2)datetime类型对人类友好,方便后期维护,提高效率

unq_username是唯一键约束,限制了用户名不能重复

注册逻辑

    // 注册
    function register()
    {
        $username = $this->request->post('username');
        $password = $this->request->post('password');

        // 检查用户是否存在
        $exist = (new UserModel())->where('username', $username)->find();
        if ($exist) {
            return $this->fail('用户名已存在');
        }

        $salt = Str::random(6); // 随机字符串
        $passwordEncrypted = md5($password . $salt); // 计算散列值
        $newUser = [
            'username' => $username,
            'password' => $passwordEncrypted,
            'salt' => $salt
        ];

        // 新建用户
        (new UserModel())->save($newUser);
        return $this->success();
    }

手把手带你写全栈-用户登录注册-后端API

登录逻辑

    // 登录
    function login()
    {
        $username = $this->request->post('username');
        $password = $this->request->post('password');

        // 检查用户名
        $exist = (new UserModel())->where('username', $username)->find();
        if (!$exist) {
            return $this->fail('用户名或密码不正确');
        }

        // 校验密码
        $correctPassword = $exist->password;
        $inputPassword = md5($password . $exist->salt);
        if ($correctPassword != $inputPassword) {
            return $this->fail('用户名或密码不正确');
        }

        return $this->success([
            'id' => $exist->id,
            'username' => $exist->username,
            // 生成TOKEN
            'token' => base64_encode(json_encode([
                'id' => $exist->id,
                'username' => $exist->username,
                'expired_at' => time() + 3600, // 登录态有效期一个小时
            ]))
        ]);
    }

首先校验用户名+密码,两者其一不正确,都返回同样的错误用户名或密码不正确, 这是出于对业务安全的考虑,防止有恶意用户暴力破解,提供他的作案成本,因为代码不会告诉他是具体是用户名还是密码不正确。

我们来看一下token生成逻辑。这个是给前端缓存下来,之后的每次请求都携带上这个token,后端就能根据token解析出正确的用户信息了。

对于现代化的前后端分离项目来说,与传统的web应用依赖浏览器的能力使用cookie维护登录态的方式不同,这种token登录态的方式更加灵活和便于调试。

这里出于教学目的,只是简单是用来两种编码来实现,即base64_encode和json_encode , 实际生产环境可能会使用别的方式比如JWT-Token, 他们之间的基本原理是相同的。

手把手带你写全栈-用户登录注册-后端API

获取用户信息

登录成功之后拿到了登录态,我们和客户端约定好,把token放在http协议的请求头也就是header里面传给来,再根据对应的解码方法解析出来用户信息。

    // 解析token登录态
    function checkToken()
    {
        $token = $this->request->header('token');
        if (empty($token)) {
            throw new Exception('请求header缺少token');
        }

        $tokenInfo = json_decode(base64_decode($token), 'true');
        if (!isset($tokenInfo['id'])) {
            throw new Exception('token解析失败');
        }

        if ($tokenInfo['expired_at'] < time()) {
            throw new Exception('登录态已过期, 请重新登录');
        }

        return $tokenInfo;
    }

解析可能会异常,统一使用异常捕获处理,如果发生了异常,就把错误信息返回给客户端。如果一切正常,就会走到下面逻辑去查询用户信息。

    // 获取用户信息
    public function getInfo()
    {
        try {
            $tokenInfo = $this->checkToken();
        } catch (\Exception $e) {
            return $this->fail($e->getMessage());
        }

        $user = (new UserModel())->field([
            'id', 'username', 'nickname', 'created_at', 'updated_at'
        ])->find($tokenInfo['id']);
        return $this->success($user);
    }

手把手带你写全栈-用户登录注册-后端API

修改用户信息

最后我们写一个接口来修改用户昵称nickname字段。

同样是需要先校验登录态,再执行修改信息的逻辑。

    // 修改用户信息
    function updateInfo()
    {
        try {
            $tokenInfo = $this->checkToken();
        } catch (\Exception $e) {
            return $this->fail($e->getMessage());
        }

        // 修改用户昵称
        $nickname = $this->request->post('nickname');
        $user = (new UserModel())->find($tokenInfo['id']);
        $user->nickname = $nickname;
        $user->save();

        return $this->success();
    }

手把手带你写全栈-用户登录注册-后端API

TODO

我是一名独立开发者,只工作不上班,带你探索业界正流行的现代化全栈项目。
以下是近期会更新的一系列文章,欢迎关注~

  • 用户-登录注册-后端
  • 用户-登录注册-前端
  • 用户-钱包充值-后端
  • 用户-钱包充值-前端
  • 营销-签到有礼-后端
  • 营销-签到有礼-前端
  • 营销-签到有礼-后端单元测试
  • 营销-限时秒杀-后端
  • 营销-限时秒杀-前端
  • 营销-优惠券-后端
  • 营销-优惠券-前端
本作品采用《CC 协议》,转载必须注明作者和本文链接
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
讨论数量: 2

你这样搞都要不了30分钟

1年前 评论
陈先生

我以为算上使用 React/Vue + Ts 写页面和前端逻辑。。。

不过不建议用明文格式将用户 ID 写到 Token 里面,除非你不介意别人猜测你的站点到底多少用户。

1年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!