做个用户管理系统(28)——发送绑定邮箱激活码邮件【20201217】

发表于 2020-12-17 22:34:24
阅读 100

介绍

介绍

福哥今天要带着大家完成绑定邮箱功能的前半部分——发送绑定邮箱激活码邮件。这里面有几件难题:1)要实现邮件激活码,需要设计一个可逆的加密/解密功能;2)激活链接必须是一个完整的URL地址;3)激活链接必须有时效性。

第一个问题,福哥经过再三考虑决定使用AES算法实现一个可逆的加密/解密功能,为了保证每次的激活码不一样,原数据增加随机串。

第二个问题,因为页面控制器和接口控制器不一定会配置在同一个目录下面(虽然大多数情况下是同一个),所以对外访问的URL前缀不能直接用接口控制器的作为页面控制器的。这里福哥在配置文件里面加了一个outUrl的配置参数,用这个参数就错不了。

第三个问题,福哥在激活码原数据里面增加了一个时间戳,精确到小时。这样在验证的时候,如果当前时间小时和激活码的一样才能正确解密,能正常解密就证明激活码是有效的。为了避免误差,除了测试当前时间还要测试一个小时前的时间。

模型user

sendBindEmail

public function sendBindEmail(int $userID, string $email):bool {
        $myMail = new TFMail($this->tfphp);
        $myAES = new TFAES($this->tfphp);

        $userInfo = $this->getByTable("user", array($userID));
        if($userInfo == null){

            return 1;
        }

        $userInfo = $this->getByTable("userByEmail", array($email));
        if($userInfo != null
            && $userInfo['userID'] != $userID){

            return 2;
        }

        $SMTPArgs = TFConfig::get("SMTPArgs", "system");
        $subject = "激活你的TFUMS系统账号的绑定邮箱";
        $linkData = serialize(array('id'=>$userID, 'email'=>$email, 'timestamp'=>time()));
        $linkDataEncrypted = $myAES->encrypt($linkData, TFConfig::get("projectAESPK", "system"). date("YmdH0000"), "");
        $link = TFConfig::get("outUrl", "system"). "member/bindEmailVerify.htm?data=". $linkDataEncrypted;
        $body = "你好!<br/>
<br/>
请点击下面的链接完成邮箱绑定查资!<br/><br/>
<a href=\"". $link. "\" target=\"_blank\">". $link. "</a><br/>
<br/>
TFUMS<br/>
". $SMTPArgs['email']. "<br/>";
        $myMail->setSMTP($SMTPArgs['host'], $SMTPArgs['port'], $SMTPArgs['user'], $SMTPArgs['pwd']);
        if(!$myMail->send($subject, $body, array(
            'from'=>$SMTPArgs['email'],
            'to'=>$email,
        ))){

            return 3;
        }

        return 0;
    }

接口控制器

doSend

private function doSend(){
    $req = $this->tfphp->getRequest();
    $post = $req->post;
    $user = new user($this->tfphp);
    $email = $post->get("email");

    try{
        // request test
        if($email == ""){

            return $this->tfphp->getResponse()->responseJSON_CM(200, 1001061, "错误请求");
        }

        // create user
        $ret = $user->sendBindEmail($this->permission->getLoginStatus()->userID,
            $email);
        switch ($ret){
            case 1:
                return $this->tfphp->getResponse()->responseJSON_CM(200, 1001062, "用户名不存在");
                break;
            case 2:
                return $this->tfphp->getResponse()->responseJSON_CM(200, 1001063, "邮箱地址已经存在");
                break;
            case 3:
                return $this->tfphp->getResponse()->responseJSON_CM(200, 1001064, "发送邮件失败");
                break;
        }
    }
    catch(\TypeError $e){

        return $this->tfphp->getResponse()->responseJSON_CM(200, 1001061, "错误请求");
    }

    // output
    return $this->tfphp->getResponse()->responseJSON_CM(200, 0, "OK");
}

视图模板

HTML代码

<!-- bind Email form begin -->
<div class="row login-form">
    <div class="col-sm-12">
        <h3 class="text-center">绑定邮箱</h3>
        <p>请输入您的电子邮箱,系统将向邮箱内发送一封邮件,通过邮件内的链接可以完成邮箱绑定操作</p>
        <form>
            <div class="form-group">
                <label>电子邮箱</label>
                <input class="form-control" type="text" name="email" />
            </div>
            <div class="form-group">
                <button class="btn btn-primary btn-sm form-control">发送邮件</button>
            </div>
        </form>
    </div>
</div>
<!-- bind Email form end -->

JS代码

$('form').form({
    url: "<% $TFReq->server->BASE_URI %>api/member/bindEmail/_send",
    method: "post",
    validations: [
        {type:"empty", name:"email", msg:"请填写电子邮箱地址"}
    ],
    onSuccess: function (d) {
        if(d.errcode == 0){
            document.location = '<% $TFReq->server->BASE_URI %>member/bindEmailOK.htm';
        }
        else{
            $('form').tips({
                text:d.errmsg
            });
        }
    },
    onError: function (d) {
        $('form').tips({
            text:"服务器响应错误"
        });
    },
    onValidationError: function (form, name, msg) {
        $('form').tips({
            text:msg
        });
        $('form').find('[name="'+ name +'"]').focus();
    }
});

讲解

模型user

sendBindEmail

首先检查用户是否存在,不存在就报错。

接着检查邮箱是否被占用了,如果被占用了就报错。

最后就是要组织激活码邮件了,激活码邮件的正文包含一个链接,这个链接最后有一串激活码,激活码就是一个包含用户信息的数组经过序列化后使用AES加密生成的字符串了。由于我们设置了一个私钥,所以即使知道加密原理的人也不能解密我们的激活码。

我们的目的是要用户点击这个链接,打开激活页面,完成绑定邮箱操作。

接口控制器

doSend

这里就是当用户提交了电子邮箱地址后,我们使用sendBindEmail发送激活码邮件。

视图模板

HTML代码

这是要给只有一个输入框的表单,用户输入电子邮箱后点击提交按钮发送激活码邮件。

JS代码

这是一个标准的表单JS驱动程序,如果处理成功会自动转到发送激活码邮件成功页面。

效果

发送激活码邮件

db541c88d22bad72.jpg

激活码邮件已发送

因为激活码邮件包含链接,所以会被客户端软件当成垃圾邮件处理,没有办法了。

26b5a9776a7cafd2.jpg

总结

今天福哥带着童鞋们完成了绑定邮箱第一部分功能——发送绑定邮箱激活码邮件的开发。这个功能涉及到的技术点很多,包括SMTP协议、AES算法、加密/解密处理等等。

下一课,福哥将带着大家实现绑定邮箱第二部分功能——验证激活码邮件的开发。