利用阿里云邮件推送打造一个找回密码功能吧

Posted ·1455 Views·5165 Words

背景

什么?做找回密码功能对于一个  SaaS 平台来说还需要考虑背景问题吗?

最近在看一本书叫 —— Work Rules 谷歌工作法 ,其中介绍到了一些企业软件设计,提到了很有趣的一点。为什么许多现在的 SaaS 平台都不在用户注册页面强制用户输入两次密码来使其记牢 (听起来没啥用)?调查发现,这些没有双重验证的产品的用户粘性大多比其他同类应用高,因为用户有更大的几率去使用「找回密码」功能。潜意识中就使其更容易记住也愿意去花时间使用这个产品,因为「不然不值我花了找回密码的宝贵 30 秒」... (主要是让写此功能的程序员能得到一点慰藉)。

 

代码

我已经在 Eugrade (原名 Pokers)实现了这个功能,使用了阿里云邮件推送产品,每天 2000 封良心:)

Pokers | 全面高效的教育类沟通协作平台

ID: 450  发布于: 2019-06-27 18:38:42

之后可能还会完善一下各种验证、邮件推送、消息推送等,于是干脆建立了一个新表 —— Temp,五个字段:

/*
* @var int id 自增序号
* @var string k 唯一标记
* @var string v 内容值
* @var integer d 过期时间
* @var string e 冗余
*/

找回密码的逻辑大概是:

  1. 输入邮箱,带随机参数请求后端
  2. 后端验证邮箱用户存在,根据随机参数生成唯一的 Token
  3. 保存数据至数据库,k 为上述 Token,v 为随机验证码,d 为一天之后的 unix 时间戳
  4. 调用阿里云邮件推送发送带验证码的邮件,这里直接使用 PHP SDK (https://help.aliyun.com/document_detail/29460.html)
  5. 后端返回 Token,前端保存
  6. 用户前往邮箱接收,前端输入验证码请求后端
  7. 后端根据 Token 与验证码值判断正确性
  8. 重置密码为 12345678
if (!!$array && !!$array_user) { //存在验证与用户
                    if ((int) $time <= (int) $array[0]['d']) { //未过期
                        if ((int) $array[0]['v'] == (int) $input) { //判断正确

                            //重设密码
                            $t = Lazer::table('users')->limit(1)->where('email', '=', (string) $email)->find();
                            $t->set(array(
                                'pwd' => md5(md5('12345678') . md5('12345678')),
                            ));
                            $t->save();

                            //删除验证记录
                            Lazer::table('temp')->limit(1)->where('k', '=', (string) $token)->delete();

                            $status = 1;
                            $code = 107;
                            $mes = 'Successfully reset your password to 12345678';
                        } else {
                            $status = 0;
                            $code = 111;
                            $mes = 'Incorrect verification code';
                        }
                    } else {
                        $status = 0;
                        $code = 106;
                        $mes = 'This request has been depreciated';
                    }
                } else {
                    $status = 0;
                    $code = 108;
                    $mes = 'Illegal request';
                }

↑ 验证的核心部分 PHP 代码

 

include_once 'database/aliyun-php-sdk-core/Config.php';

use Dm\Request\V20151123 as Dm;

$iClientProfile = DefaultProfile::getProfile("cn-hangzhou", "xxx", "xxx");
$client = new DefaultAcsClient($iClientProfile);
$request = new Dm\SingleSendMailRequest();
$request->setAccountName("[email protected]");
$request->setFromAlias("Eugrade");
$request->setAddressType(1);
$request->setReplyToAddress("true");

//生成保存参数
    $token = md5(md5($ran) . md5($name));
    $expire_date = strtotime('+1day', time());
    $rand = rand(0, 32768);

    //判断验证目的
    switch ($name) {
        case 'reset_pwd': //重设密码
            if (!empty($_POST['email']) && filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)) {
                $email_get = input($_POST['email']);
                $array = Lazer::table('users')->limit(1)->where('email', '=', $email_get)->find()->asArray();
                if (!!$array) {
                    //建立验证记录
                    $this_id = Lazer::table('temp')->lastId() + 1;
                    $row = Lazer::table('temp');
                    $row->id = $this_id;
                    $row->k = (string) $token; //标记
                    $row->v = (string) $rand; //值
                    $row->d = (int) $expire_date; //过期时间
                    $row->save();

                    //发送邮件
                    $request->setTagName("resetpwd");
                    $request->setToAddress($email_get);
                    $request->setSubject("Eugrade Password Reset");
                    $request->setHtmlBody("Hi there,<br/><br/>This is your verification code for Eugrade password reset request: <b style='font-size:20px'>" . (string) $rand . "</b><br/>Please verify your request within one day, thank you.<br/><br/><a href='https://www.eugrade.com'>www.eugrade.com</a>");
                    $client = $client->getAcsResponse($request);

                    $status = 1;
                    $code = 105;
                    $mes = 'Successfully sent a request';
                } else {
                    $status = 0;
                    $code = 107;
                    $mes = 'No such user with this email address';
                }
            } else {
                $status = 0;
                $code = 106;
                $mes = 'Illegal Request';
            }
            break;
        default:
            $status = 0;
            $code = 104;
            $mes = 'Illegal request';
            break;
    }

↑ 创建验证记录/邮件发送核心 PHP 代码

 

send_email() {
            this.send.loading = true;
            var query_string = "email="+ this.input.email +"&name=reset_pwd&ran=" + Math.ceil(Math.random() * 100000);

            axios.post(
                    'interact/create_ver.php',
                    query_string
                )
                .then(res => {
                    if (res.data.status) {
                        this.send.status = true;
                        this.$message.success(res.data.mes);
                        this.send.token = res.data.token;
                        var interval = setInterval(function () {
                            if(antd.send.count > 0){
                                antd.send.count--;
                                antd.send.text = 'Resend Code('+antd.send.count+')';
                            }else{
                                antd.send.count = 60;
                                antd.send.loading = false;
                                antd.send.text = 'Resend Code';
                                clearInterval(interval);
                            }
                        }, 900);
                    } else {
                        this.send.loading = false;
                        this.$message.error(res.data.mes);
                    }
                })
        },
        check_code() {
            this.send.loading = true;
            var query_string = "email="+ this.input.email +"&name=reset_pwd&input=" + this.input.code + "&token=" + this.send.token;

            axios.post(
                    'interact/check_ver.php',
                    query_string
                )
                .then(res => {
                    if (res.data.status) {
                        this.$message.success(res.data.mes);
                        this.send.status = false;
                        setTimeout('window.location.href = "login.html"',1000);
                    } else {
                        if(this.send.count == 60){
                            this.send.loading = false;
                        }
                        this.$message.error(res.data.mes);
                    }
                })
        }

↑ 前端核心 Vue.js 代码

 

<a-form class="login-form"
        style="padding: 0px 20px">
        <a-form-item has-feedback style="margin-bottom: 15px;">
          <a-input size="large" v-model="input.email" placeholder="Email">
            <a-icon slot="prefix" type="mail" style="color: rgba(0,0,0,.25)" />
          </a-input>
        </a-form-item>

        <a-form-item v-if="send.status">
          <a-input size="large" placeholder="Verification Code" v-model="input.code">
            <a-icon slot="prefix" type="lock" style="color: rgba(0,0,0,.25)" />
          </a-input>
        </a-form-item>

        <a-form-item v-if="!send.status">
          <a-button @click="send_email" style="width:100%" :disabled="send.loading" type="primary" size="large" html-type="submit" class="login-form-button">
            Send Code
          </a-button>
        </a-form-item>
        
        <a-form-item v-if="send.status">
          <div style="display: flex">
            <a-button @click="check_code" style="width:49%;margin-right: 1%" type="primary" size="large" html-type="submit" class="login-form-button">
              Check Code
            </a-button>
            <a-button @click="send_email" v-html="send.text" :disabled="send.loading" style="width:49%" type="default" size="large" html-type="submit" class="login-form-button">
            </a-button>
        </div>
        </a-form-item>

        <p style="text-align: center;margin: 0px;margin-bottom: -15px;margin-top: 10px;"><a
          href="login.html">Return to Log in</a></p>

      </a-form>

↑ 前端核心 HTML 代码

 

后记

接下来真的要鸽好久的... 🙂

Comment

Leave a comment to join the discussion