YII2 邮箱找回密码简单示例
邮箱找回密码简单示例
不可直接复制,都是伪代码!!
第一步:YII2配置邮箱
在backend/config/main-local.php 文档中添加邮箱设置
注意,邮箱设置的代码要在‘components’里面
$config = [
'components' => [
/*-------------------配置mailer-----------------*/
'mailer' => [
'class' => 'yii\swiftmailer\Mailer',
'useFileTransport' =>false,//这句一定有,false发送邮件,true只是生成邮件在runtime文件夹下,不发邮件
'transport' => [
'class' => 'Swift_SmtpTransport',
'host' => 'smtp.163.com', //每种邮箱的host配置不一样
'username' => '******@163.com',
'password' => '******', //邮箱是授权码,不是登录密码
'port' => '25',
'encryption' => 'tls',
],
'messageConfig'=>[
'charset'=>'UTF-8',
'from'=>['me_dennis@163.com'=>'贝阳科技'] // 发件人名称
],
],
/*-------------------END-----------------*/
],
];
第二步:控制器中调用mailer类
在发送邮件的时候,comppose可以调用指定的邮件模版。
示例:Yii::$app->mailer->compose('resetPwdEmail', ['uid'=>$user->fuserid,'ptk' => $pwdtoken]);
- 这个模版文件默认路径在 common/mail/resetPwdEmail.php
- 第二个参数array,携带的参数
- 和控制器加载视图的原理差不多
// 发送找回密码的邮件
public function actionSendPasswordEmail()
{
$rtn = new ReturnValue(); // 这个是自定义的一个回复类
$rtn -> code = -1;
// 用户名和邮箱
$username = $_POST['username'];
$email = $_POST['email'];
// 匹配用户名和邮箱
$user = UserModel::findOne(['fusername'=>$username, 'femail'=>$email]);
if (!$user) {
$rtn -> message = '用户名和邮箱不匹配';
return JsonUtils::encode($rtn);
}
// 生成password token , 有效期2天
$pwdtoken = $this->makeptk($user);
// 发送邮件,这里开始调用mailer
$mail = Yii::$app->mailer->compose('resetPwdEmail', ['uid'=>$user->fuserid,'ptk' => $pwdtoken]);
$mail->setTo($email);
$mail->setSubject('密码找回邮件'); // 邮件主题
// $mail->setTextBody('beyondscrren'); // 设置文本内容,调用模版可以不用这个
// $mail->setHtmlBody('whatthisis'); // 设置HTML内容,调用模版可以不用这个
if ($mail->send()) {
$rtn -> code = 0;
$rtn -> message = '邮件发送成功,请在48小时内进行密码重置操作!';
return JsonUtils::encode($rtn);
} else {
$rtn -> message = '邮件发送失败!请重试';
return JsonUtils::encode($rtn);
}
}
/*------------------好吧,回复类加上-------------------------------*/
/*----------------------ReturnValue----------------------------------*/
class ReturnValue
{
//0:成功,-1失败
public $code = 0;
public $message = '';
//返回给前台的记录数,Row count
public $Total = 0;
//记录数组
public $Rows = array();
//翻页对象
public $otherData = '';
//由于前台不能获取php服务器的根路径,此处传给前台js使用
public $webRootUrl = '';
public $ftpRootUrl = '';
public $host = '';
public $rt;
public function __construct()
{
$this->webRootUrl = Yii::$app->baseUrl;
$this->ftpRootUrl = FileUtils::getDBFileWebRoot();
$this->host = Yii::app()->request->hostInfo;
}
}
第三步:生成邮箱的链接密钥
这里面最关键的就是加密/解密函数 encrypt($str, 加/解密, $key)
- 参数说明,'D' 是解密,其是加密
- $key 为自己保存的key,类似salt
**先放出输出结果:**
加密后的字符串 $a = izATeQXrIzTiR4M83/qbkZ2mnzcNYIk8z+QfN/R2Kjojcw
$b = zhengneng6?time=1492063529
$c = 1492063529
$d = zhengneng6
注意: 用户名里面带有 '?time=' 会出Bug哦! 在注册和登录的时候,需要过滤特殊字符
$username = 'zhengneng6';
$str = $username.'?time='.time();
$key = '1490868820'; // 用户加密的salt
$a = encrypt($str,'E',$key);
echo $a,'<br/>';
$b = encrypt($a,'D',$key);
echo $b,'<br/>';
$c = substr($b,strpos($b, '?time=')+6);
echo $c,'<br/>';
$d = substr($b,0,strpos($b, '?time='));
echo $d;
function encrypt($string,$operation,$key=''){
$key=md5($key);
$key_length=strlen($key);
$string=$operation=='D'?base64_decode($string):substr(md5($string.$key),0,8).$string;
$string_length=strlen($string);
$rndkey=$box=array();
$result='';
for($i=0;$i<=255;$i++){
$rndkey[$i]=ord($key[$i%$key_length]);
$box[$i]=$i;
}
for($j=$i=0;$i<256;$i++){
$j=($j+$box[$i]+$rndkey[$i])%256;
$tmp=$box[$i];
$box[$i]=$box[$j];
$box[$j]=$tmp;
}
for($a=$j=$i=0;$i<$string_length;$i++){
$a=($a+1)%256;
$j=($j+$box[$a])%256;
$tmp=$box[$a];
$box[$a]=$box[$j];
$box[$j]=$tmp;
$result.=chr(ord($string[$i])^($box[($box[$a]+$box[$j])%256]));
}
if($operation=='D'){
if(substr($result,0,8)==substr(md5(substr($result,8).$key),0,8)){
return substr($result,8);
}else{
return'';
}
}else{
return str_replace('=','',base64_encode($result));
}
}
第四步:配置邮件模版的内容,也就是携带密钥的连接
<?php
use yii\helpers\Html;
/* @var $this yii\web\View */
/* @var $user common\models\User */
$resetLink = Yii::$app->urlManager->createAbsoluteUrl(['URL路径', 'uid'=>$uid, 'token' => $ptk]);
?>
<div class="password-reset">
<p>hello ,</p>
<p>Follow the link below to reset your password:</p>
<p><?= Html::a(Html::encode($resetLink), $resetLink) ?></p>
</div>
第五步:验证密钥,重置密码
这一步的关键是decodeptk()
- 这个函数里面,调用的是encrypt
public function actionResetPassword()
{
// 接收参数
if (empty($_GET['uid']) || empty($_GET['token'])) return false;
$uid = intval($_GET['uid']);
$token = $_GET['token'];
$ptk = $this->decodeptk($uid, $token); // 这一步调用
if (!$ptk) return $this->render('pastDue',['type'=>1]); // 加载报错页面
// 先判断是否过期,有效期 2天
$sendtime = substr($ptk, strpos($ptk, '?time=') + 6);
if (time()-$sendtime>(3600*24*2)) return $this->render('pastDue',['type'=>2]); // 加载报错页面
return $this->render('resetPassword', [ // 加载重置密码页面
'uid' => $uid,
'token' => $token,
]);
}
加载重置密码的页面:
重置密码页面用表单提交新密码和密钥
<style>
/* 表单样式 */
/* 表单样式 */
header{ margin: 0;padding: 10px 15px;background: #20a3dd;color: #fff;}
.logo img{ width: 38%;margin: 0 auto;}
.content input{ padding: 20px;margin: 20px 0; color: #8F8F8F; box-shadow: none;border: none; border-bottom:1px solid #D7D7D7;padding-left: 50px;font-size: 16px; }
</style>
<div class="body">
<header>
<h4 class="pname1 text-center"> 找回密码功能</h4>
</header>
<div>
<h2>邮箱找回密码功能</h2>
</div>
<form action="editPassword.html" method="post" name="getpassword" >
<div>
<p>输入密码:<input name="password" type="password" placeholder="输入密码"></p>
<p>再次输入:<input name="confirm" type="password" placeholder="确认密码"></p>
</div>
<div>
<input name="uid" type="hidden" value="<?php echo $uid ?>">
<input name="token" type="hidden" value="<?php echo $token ?>">
<input name="submit" type="submit" value="确认提交">
</div>
</form>
</div>
最后一步,重置密码
public function actionEditPassword()
{
if (isset($_POST['uid']) && isset($_POST['token'])) {
$uid = intval($_POST['uid']);
$ptk = $_POST['token'];
}
else throw new NotFoundHttpException();
// 验证token
$ptk = $this->decodeptk($uid, $ptk);
if (!$ptk) return $this->render('pastDue',['type'=>1]); // uid和token不合法
$sendtime = substr($ptk, strpos($ptk, '?time=') + 6);
if (time()-$sendtime>(3600*24*2)) return $this->render('pastDue',['type'=>2]); // 时间过期
// 重置密码
$password = $_POST['password'];
$confirm = $_POST['confirm'];
if ($password != $confirm) return false; // 绕过前端验证的忽略
$trans = DbUtils::beginTransaction(); // 事务
try {
$user = ByUsersTfansModel::findOne(['fuserid'=>$uid]);
$newpsd = ByUsersTfansModel::setPassword($password,$user->fregtime);
$user -> fpassword = $newpsd;
if ($user->save()) {
$trans -> commit();
return $this->render('pastDue', ['type' => 0]); //修改成功
}
} catch (Exception $e) {
$trans -> rollBack();
return $this->render('pastDue', ['type' => 1]); // 修改失败
}
}
这个里面的pastDue,为修改后返回信息的页面。
自定义样式和type,根据不同的type返回不同的结果给用户。