vue 找回密码_资讯项目:找回密码

本文详细介绍了在Vue.js中实现找回密码的功能,包括前端交互模式、业务流程和代码实现。用户输入邮箱地址,点击发送邮件,前端通过Ajax请求发送邮件,邮件中包含重置密码的链接。服务端接收到请求后使用itsdangerous库加密数据并发送邮件。用户点击链接后,前端获取链接中的token并验证,随后用户可以重置密码。同时,文章还涉及了使用Celery进行异步发送邮件以提高效率。
摘要由CSDN通过智能技术生成

前后端交互模式:

查询字符串: 前端: location.search/params(放的url?后面的东西) 后端接收 request.query_params

请求体: 前端: 表单提交/ajax的put putch post 的第二个参数{} 后端接收: request.data.get()put。。。 也可以用location.search发查询字段串 后端用request.query_params

找回密码(忘记密码)

业务流程实现vue客户端提供找回密码的页面,供用户输入当前帐号绑定的邮箱地址

在用户点击发送邮件以后,ajax请求api服务端发送邮件到指定邮箱,并指定邮件内容为链接地址(链接中附带访问票据参数),点击链接地址跳转到荏苒网的修改密码页面,新页面提供修改密码的表单。

提供用户修改密码的服务端接口。

代码实现

显示找回密码页面

用邮箱重置密码

import "../../static/js/TCaptcha";

export default {

name: "FindPassword",

data(){

return {

email:"",

password:"",

}

},

methods:{

send_email(){

if(!/^\w+@\w+\.\w+$/.test(this.email)){

this.$message.error("邮箱地址格式有误!");

return false;

}

},

show_captcha(){

// 显示验证码

}

},

}

input{

outline: none;

}

*, :after, :before {

box-sizing: border-box;

}

.sign {

height: 100%;

min-height: 750px;

text-align: center;

font-size: 14px;

background-color: #f1f1f1

}

.sign:before {

content: "";

display: inline-block;

height: 85%;

vertical-align: middle

}

.sign .disable,.sign .disable-gray {

opacity: .5;

pointer-events: none

}

.sign .disable-gray {

background-color: #969696

}

.sign .tooltip-error {

font-size: 14px;

line-height: 25px;

white-space: nowrap;

background: none

}

.sign .tooltip-error .tooltip-inner {

max-width: 280px;

color: #333;

border: 1px solid #ea6f5a;

background-color: #fff

}

.sign .tooltip-error .tooltip-inner i {

position: static;

margin-right: 5px;

font-size: 20px;

color: #ea6f5a;

vertical-align: middle

}

.sign .tooltip-error .tooltip-inner span {

vertical-align: middle;

display: inline-block;

white-space: normal;

max-width: 230px

}

.sign .tooltip-error.right .tooltip-arrow-border {

border-right-color: #ea6f5a

}

.sign .tooltip-error.right .tooltip-arrow-bg {

left: 2px;

border-right-color: #fff

}

.sign .slide-error {

position: relative;

padding: 10px 0;

border: 1px solid #c8c8c8;

border-radius: 4px

}

.sign .slide-error i {

position: static!important;

margin-right: 10px;

color: #ea6f5a!important;

vertical-align: middle

}

.sign .slide-error span {

font-size: 15px;

vertical-align: middle

}

.sign .slide-error div {

margin-top: 10px;

font-size: 13px

}

.sign .slide-error a {

color: #3194d0

}

.sign .js-sign-up-forbidden {

color: #999;

padding: 80px 0 100px

}

.sign .js-sign-up-container .slide-error {

border-bottom: none;

border-radius: 0

}

.sign .logo {

position: absolute;

top: 56px;

margin-left: 50px

}

.sign .logo img {

width: 100px

}

.sign .main {

width: 400px;

margin: 60px auto 0;

padding: 50px 50px 30px;

background-color: #fff;

border-radius: 4px;

box-shadow: 0 0 8px rgba(0,0,0,.1);

vertical-align: middle;

display: inline-block

}

.sign .reset-title,.sign .title {

margin: 0 auto 50px;

padding: 10px;

font-weight: 400;

color: #969696

}

.sign .reset-title a,.sign .title a {

padding: 10px;

color: #969696

}

.sign .reset-title a:hover,.sign .title a:hover {

border-bottom: 2px solid #ea6f5a

}

.sign .reset-title .active,.sign .title .active {

font-weight: 700;

color: #ea6f5a;

border-bottom: 2px solid #ea6f5a

}

.sign .reset-title b,.sign .title b {

padding: 10px

}

.sign .reset-title {

color: #333;

font-weight: 700

}

.sign form {

margin-bottom: 30px

}

.sign form .input-prepend {

position: relative;

width: 100%

}

.sign form .input-prepend input {

width: 100%;

height: 50px;

margin-bottom: 0;

padding: 4px 12px 4px 35px;

border: 1px solid #c8c8c8;

border-radius: 0 0 4px 4px;

background-color: hsla(0,0%,71%,.1);

vertical-align: middle

}

.sign form .input-prepend i {

position: absolute;

top: 14px;

left: 10px;

font-size: 18px;

color: #969696

}

.sign form .input-prepend span {

color: #333

}

.sign form .input-prepend .ic-show {

top: 18px;

left: auto;

right: 8px;

font-size: 12px

}

.sign form .geetest-placeholder {

height: 44px;

border-radius: 4px;

background-color: hsla(0,0%,71%,.1);

text-align: center;

line-height: 44px;

font-size: 14px;

color: #999

}

.sign form .restyle {

margin-bottom: 0

}

.sign form .restyle input {

border-bottom: none;

border-radius: 4px 4px 0 0

}

.sign form .no-radius input {

border-radius: 0

}

.sign form .slide-security-placeholder {

height: 32px;

background-color: hsla(0,0%,71%,.1);

border-radius: 4px

}

.sign form .slide-security-placeholder p {

padding-top: 7px;

color: #999;

margin-right: -7px

}

.sign .overseas-btn {

font-size: 14px;

color: #999

}

.sign .overseas-btn:hover {

color: #2f2f2f

}

.sign .remember-btn {

float: left;

margin: 15px 0

}

.sign .remember-btn span {

margin-left: 5px;

font-size: 15px;

color: #969696;

vertical-align: middle

}

.sign .forget-btn {

float: right;

position: relative;

margin: 15px 0;

font-size: 14px

}

.sign .forget-btn a {

color: #999

}

.sign .forget-btn a:hover {

color: #333

}

.sign .forget-btn .dropdown-menu {

top: 20px;

left: auto;

right: 0;

border-radius: 4px

}

.sign .forget-btn .dropdown-menu a {

padding: 10px 20px;

color: #333

}

.sign #sign-in-loading {

position: relative;

width: 20px;

height: 20px;

vertical-align: middle;

margin-top: -4px;

margin-right: 2px;

display: none

}

.sign #sign-in-loading:after {

content: "";

position: absolute;

left: 0;

top: 0;

width: 100%;

height: 100%;

background-color: transparent

}

.sign #sign-in-loading:before {

content: "";

position: absolute;

top: 50%;

left: 50%;

width: 20px;

height: 20px;

margin: -10px 0 0 -10px;

border-radius: 10px;

border: 2px solid #fff;

border-bottom-color: transparent;

vertical-align: middle;

-webkit-animation: rolling .8s infinite linear;

animation: rolling .8s infinite linear;

z-index: 1

}

.sign .sign-in-button,.sign .sign-up-button {

margin-top: 20px;

width: 100%;

padding: 9px 18px;

font-size: 18px;

border: none;

border-radius: 25px;

color: #fff;

background: #42c02e;

cursor: pointer;

outline: none;

display: block;

clear: both

}

.sign .sign-in-button:hover,.sign .sign-up-button:hover {

background: #3db922

}

.sign .sign-in-button {

background: #3194d0

}

.sign .sign-in-button:hover {

background: #187cb7

}

.sign .btn-in-resend,.sign .btn-up-resend {

position: absolute;

top: 7px;

right: 7px;

width: 100px;

height: 36px;

font-size: 13px;

color: #fff;

background-color: #42c02e;

border-radius: 20px;

line-height: 36px

}

.sign .btn-in-resend {

background-color: #3194d0

}

.sign .sign-up-msg {

margin: 10px 0;

padding: 0;

text-align: center;

font-size: 12px;

line-height: 20px;

color: #969696

}

.sign .sign-up-msg a,.sign .sign-up-msg a:hover {

color: #3194d0

}

.sign .overseas input {

padding-left: 110px!important

}

.sign .overseas .overseas-number {

position: absolute;

top: 0;

left: 0;

width: 100px;

height: 50px;

font-size: 18px;

color: #969696;

border-right: 1px solid #c8c8c8

}

.sign .overseas .overseas-number span {

margin-top: 17px;

padding-left: 35px;

text-align: left;

font-size: 14px;

display: block

}

.sign .overseas .dropdown-menu {

width: 100%;

max-height: 285px;

font-size: 14px;

border-radius: 0 0 4px 4px;

overflow-y: auto

}

.sign .overseas .dropdown-menu li .nation-code {

width: 65px;

display: inline-block

}

.sign .overseas .dropdown-menu li a {

padding: 6px 20px;

font-size: 14px;

line-height: 20px

}

.sign .overseas .dropdown-menu li a::hover {

color: #fff;

background-color: #f5f5f5

}

.sign .more-sign {

margin-top: 50px

}

.sign .more-sign h6 {

position: relative;

margin: 0 0 10px;

font-size: 12px;

color: #b5b5b5

}

.sign .more-sign h6:before {

left: 30px

}

.sign .more-sign h6:after,.sign .more-sign h6:before {

content: "";

border-top: 1px solid #b5b5b5;

display: block;

position: absolute;

width: 60px;

top: 5px

}

.sign .more-sign h6:after {

right: 30px

}

.sign .more-sign ul {

margin-bottom: 10px;

list-style: none

}

.sign .more-sign ul li {

margin: 0 5px;

display: inline-block

}

.sign .more-sign ul a {

width: 50px;

height: 50px;

line-height: 50px;

display: block

}

.sign .more-sign ul i {

font-size: 28px

}

.sign .more-sign .ic-weibo {

color: #e05244

}

.sign .more-sign .ic-wechat {

color: #00bb29

}

.sign .more-sign .ic-qq_connect {

color: #498ad5

}

.sign .more-sign .ic-douban {

color: #00820f

}

.sign .more-sign .ic-more {

color: #999

}

.sign .more-sign .weibo-loading {

pointer-events: none;

cursor: pointer;

position: relative

}

.sign .more-sign .weibo-loading:after {

content: "";

position: absolute;

left: 0;

top: 0;

width: 100%;

height: 100%;

background-color: #fff

}

body.reader-night-mode .sign .more-sign .weibo-loading:after {

background-color: #3f3f3f

}

.sign .more-sign .weibo-loading:before {

content: "";

position: absolute;

top: 50%;

left: 50%;

width: 20px;

height: 20px;

margin: -10px 0 0 -10px;

border-radius: 10px;

border: 2px solid #e05244;

border-bottom-color: transparent;

vertical-align: middle;

-webkit-animation: rolling .8s infinite linear;

animation: rolling .8s infinite linear;

z-index: 1

}

@keyframes rolling {

0% {

-webkit-transform: rotate(0deg);

transform: rotate(0deg)

}

to {

-webkit-transform: rotate(1turn);

transform: rotate(1turn)

}

}

@-webkit-keyframes rolling {

0% {

-webkit-transform: rotate(0deg)

}

to {

-webkit-transform: rotate(1turn)

}

}

.sign .reset-password-input {

border-radius: 4px!important

}

.sign .return {

margin-left: -8px;

color: #969696

}

.sign .return:hover {

color: #333

}

.sign .return i {

margin-right: 5px

}

.sign .icheckbox_square-green {

display: inline-block;

*display: inline;

vertical-align: middle;

margin: 0;

padding: 0;

width: 18px;

height: 18px;

background: url(/static/image/green.png) no-repeat;

border: none;

cursor: pointer;

background-position: 0 0

}

.sign .icheckbox_square-green.hover {

background-position: -20px 0

}

.sign .icheckbox_square-green.checked {

background-position: -40px 0

}

.sign .icheckbox_square-green.disabled {

background-position: -60px 0;

cursor: default

}

.sign .icheckbox_square-green.checked.disabled {

background-position: -80px 0

}

.geetest_panel_box>* {

box-sizing: content-box

}

@media (max-width:768px) {

body {

min-width: 0

}

.sign {

height: auto;

min-height: 0;

background-color: transparent

}

.sign .logo {

display: none

}

.sign .main {

position: absolute;

left: 50%;

margin: 0 0 0 -200px;

box-shadow: none

}

}

在登录组件Login.vue文件中,提供找回密码的跳转页面按钮。Login.vue,代码:a标签改为router-link,href改为to

登录遇到问题?

路由代码:

import Vue from 'vue'

import Router from 'vue-router'

Vue.use(Router)

// ...import FindPassword from "../components/FindPassword"

export default new Router({

mode: "history",

routes: [

// .... ,{

path: '/find_password',

name: "FindPassword",

component: FindPassword,

},

]

})

服务端提供接收客户端请求和发送邮件的api接口

视图代码:

import re

class FindPasswordAPIView(APIView):

def get(self,request):

"""发送邮件"""

email = request.query_params.get("email")

# 验证邮件 xx@xx.xxx

if not re.match("^\w+@\w+\.\w+$",email):

return Response({"detail":"邮箱格式不正确!"}, status=status.HTTP_400_BAD_REQUEST)

try:

user = User.objects.get(email=email)

except User.DoesNotExist:

return Response({"detail": "邮箱没有被注册,请重新确认!"}, status=status.HTTP_400_BAD_REQUEST)

# 发送邮件

return Response({"detail":"ok"})

路由地址:

from django.urls import path,re_path

from rest_framework_jwt.views import obtain_jwt_token

from . import views

urlpatterns = [

path("login/", obtain_jwt_token),

path("captcha/", views.CaptchaAPIView.as_view() ),

path("", views.UserAPIView.as_view() ),

re_path("sms/(?P1[3-9]\d{9})/", views.SMSAPIView.as_view() ),

re_path("mobile/(?P1[3-9]\d{9})/", views.MobileAPIView.as_view() ),

re_path("find_password/", views.FindPasswordAPIView.as_view() ),

]

使用Django发送邮件

Django中内置了邮件发送功能,被定义在django.core.mail模块中。发送邮件需要使用SMTP服务器,常用的免费服务器有:163、126、QQ,下面以163邮件为例。SMTP,简单邮件发送协议,是程序发送邮件必须实现的协议。

1)注册163邮箱,登录后设置。

2)在新页面中点击“客户端授权密码”,勾选“开启”,弹出新窗口填写手机验证码。

3)填写授权码。

4)提示开启成功。

5) 在Django配置文件settings/dev.py中,设置邮箱的配置信息

EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'

EMAIL_HOST = 'smtp.163.com'

EMAIL_PORT = 25

#发送邮件的邮箱

EMAIL_HOST_USER = 'mooluoo@163.com'

#在邮箱中设置的客户端授权密码

EMAIL_HOST_PASSWORD = 'renranwang2020'

#收件人看到的发件人

EMAIL_FROM = 'renran<649641514@163.com>'

6) 使用Django提供的模块发送邮件

在django.core.mail模块提供了send_mail来发送邮件。

send_mail(subject, message, from_email, recipient_list,html_message=None)subject 邮件标题

message 普通邮件正文, 普通字符串

from_email 发件人

recipient_list 收件人列表

html_message 多媒体邮件正文,可以是html字符串

视图代码:

import re

from django.core.mail import send_mail

from django.conf import settings

class FindPasswordAPIView(APIView):

def get(self,request):

"""发送邮件"""

email = request.query_params.get("email")

# 验证邮件 xx@xx.xxx

if not re.match("^\w+@\w+\.\w+$",email):

return Response({"detail":"邮箱格式不正确!"}, status=status.HTTP_400_BAD_REQUEST)

try:

user = User.objects.get(email=email)

except User.DoesNotExist:

return Response({"detail": "邮箱没有被注册,请重新确认!"}, status=status.HTTP_400_BAD_REQUEST)

# 发送邮件

url = settings.CLIENT_HOST + "/reset_password"

send_mail(subject="【荏苒】找回密码",message="",

html_message='尊敬的%s,您好!
您在荏苒网申请了找回密码,

请点击重置密码进行密码修改操

作.
如果不是您本人的操作,请忽略本次邮件!' % (user.username,url),

from_email=settings.EMAIL_FROM, recipient_list=[email])

return Response({"detail":"ok"})

setting/dev.py配置:

# 客户端相关配置

CLIENT_HOST = "http://www.moluo.net:8080"

客户端ajax请求发送邮件,FindPassword.vue,代码:

用邮箱重置密码

import "../../static/js/TCaptcha";

export default {

name: "FindPassword",

data(){

return {

email:"",

password:"",

}

},

methods:{

send_email(){

if(!/^\w+@\w+\.\w+$/.test(this.email)){

this.$message.error("邮箱地址格式有误!");

return false;

}

// 请求发送邮件

this.$axios.get(`${this.$settings.Host}/users/find_password/`,{

params:{

email: this.email,

}

}).then(response=>{

this.$message.success("发送邮件成功!请留意您的邮箱!");

}).catch(error=>{

this.$message.error("网络异常,无法发送邮件!");

})

},

show_captcha(){

// 显示验证码

if(this.email.length<1){

// 阻止代码继续往下执行

return false;

}

var captcha1 = new TencentCaptcha(this.$settings.TC_captcha.app_id, res=>{

this.$axios.get(`${this.$settings.Host}/users/captcha/`,{

params:{

ticket: res.ticket,

randstr: res.randstr

}

}).then(response=>{

if(response.data.detail){

// 继续进行登录处理

this.send_email();

}

}).catch(error=>{

this.$message.error("对不起,验证码校验不通过!");

});

});

captcha1.show(); // 显示验证码

}

},

}

客户端提供重置密码的页面

ResetPassword.vue

使用邮箱重置密码

export default {

name: "ResetPassword",

data(){

return {

email: "",

password:"",

password2:"",

}

},

created() {

},

methods:{

// 重置密码

passwordhander(){

},

show_captcha(){

}

},

}

input{

outline: none;

}

*, :after, :before {

box-sizing: border-box;

}

.sign {

height: 100%;

min-height: 750px;

text-align: center;

font-size: 14px;

background-color: #f1f1f1

}

.sign:before {

content: "";

display: inline-block;

height: 85%;

vertical-align: middle

}

.sign .disable,.sign .disable-gray {

opacity: .5;

pointer-events: none

}

.sign .disable-gray {

background-color: #969696

}

.sign .tooltip-error {

font-size: 14px;

line-height: 25px;

white-space: nowrap;

background: none

}

.sign .tooltip-error .tooltip-inner {

max-width: 280px;

color: #333;

border: 1px solid #ea6f5a;

background-color: #fff

}

.sign .tooltip-error .tooltip-inner i {

position: static;

margin-right: 5px;

font-size: 20px;

color: #ea6f5a;

vertical-align: middle

}

.sign .tooltip-error .tooltip-inner span {

vertical-align: middle;

display: inline-block;

white-space: normal;

max-width: 230px

}

.sign .tooltip-error.right .tooltip-arrow-border {

border-right-color: #ea6f5a

}

.sign .tooltip-error.right .tooltip-arrow-bg {

left: 2px;

border-right-color: #fff

}

.sign .slide-error {

position: relative;

padding: 10px 0;

border: 1px solid #c8c8c8;

border-radius: 4px

}

.sign .slide-error i {

position: static!important;

margin-right: 10px;

color: #ea6f5a!important;

vertical-align: middle

}

.sign .slide-error span {

font-size: 15px;

vertical-align: middle

}

.sign .slide-error div {

margin-top: 10px;

font-size: 13px

}

.sign .slide-error a {

color: #3194d0

}

.sign .js-sign-up-forbidden {

color: #999;

padding: 80px 0 100px

}

.sign .js-sign-up-container .slide-error {

border-bottom: none;

border-radius: 0

}

.sign .logo {

position: absolute;

top: 56px;

margin-left: 50px

}

.sign .logo img {

width: 100px

}

.sign .main {

width: 400px;

margin: 60px auto 0;

padding: 50px 50px 30px;

background-color: #fff;

border-radius: 4px;

box-shadow: 0 0 8px rgba(0,0,0,.1);

vertical-align: middle;

display: inline-block

}

.sign .reset-title,.sign .title {

margin: 0 auto 50px;

padding: 10px;

font-weight: 400;

color: #969696

}

.sign .reset-title a,.sign .title a {

padding: 10px;

color: #969696

}

.sign .reset-title a:hover,.sign .title a:hover {

border-bottom: 2px solid #ea6f5a

}

.sign .reset-title .active,.sign .title .active {

font-weight: 700;

color: #ea6f5a;

border-bottom: 2px solid #ea6f5a

}

.sign .reset-title b,.sign .title b {

padding: 10px

}

.sign .reset-title {

color: #333;

font-weight: 700

}

.sign form {

margin-bottom: 30px

}

.sign form .input-prepend {

position: relative;

width: 100%

}

.sign form .input-prepend input {

width: 100%;

height: 50px;

margin-bottom: 0;

padding: 4px 12px 4px 35px;

border: 1px solid #c8c8c8;

border-radius: 0 0 4px 4px;

background-color: hsla(0,0%,71%,.1);

vertical-align: middle

}

.sign form .input-prepend i {

position: absolute;

top: 14px;

left: 10px;

font-size: 18px;

color: #969696

}

.sign form .input-prepend span {

color: #333

}

.sign form .input-prepend .ic-show {

top: 18px;

left: auto;

right: 8px;

font-size: 12px

}

.sign form .geetest-placeholder {

height: 44px;

border-radius: 4px;

background-color: hsla(0,0%,71%,.1);

text-align: center;

line-height: 44px;

font-size: 14px;

color: #999

}

.sign form .restyle {

margin-bottom: 0

}

.sign form .restyle input {

border-bottom: none;

border-radius: 4px 4px 0 0

}

.sign form .no-radius input {

border-radius: 0

}

.sign form .slide-security-placeholder {

height: 32px;

background-color: hsla(0,0%,71%,.1);

border-radius: 4px

}

.sign form .slide-security-placeholder p {

padding-top: 7px;

color: #999;

margin-right: -7px

}

.sign .overseas-btn {

font-size: 14px;

color: #999

}

.sign .overseas-btn:hover {

color: #2f2f2f

}

.sign .remember-btn {

float: left;

margin: 15px 0

}

.sign .remember-btn span {

margin-left: 5px;

font-size: 15px;

color: #969696;

vertical-align: middle

}

.sign .forget-btn {

float: right;

position: relative;

margin: 15px 0;

font-size: 14px

}

.sign .forget-btn a {

color: #999

}

.sign .forget-btn a:hover {

color: #333

}

.sign .forget-btn .dropdown-menu {

top: 20px;

left: auto;

right: 0;

border-radius: 4px

}

.sign .forget-btn .dropdown-menu a {

padding: 10px 20px;

color: #333

}

.sign #sign-in-loading {

position: relative;

width: 20px;

height: 20px;

vertical-align: middle;

margin-top: -4px;

margin-right: 2px;

display: none

}

.sign #sign-in-loading:after {

content: "";

position: absolute;

left: 0;

top: 0;

width: 100%;

height: 100%;

background-color: transparent

}

.sign #sign-in-loading:before {

content: "";

position: absolute;

top: 50%;

left: 50%;

width: 20px;

height: 20px;

margin: -10px 0 0 -10px;

border-radius: 10px;

border: 2px solid #fff;

border-bottom-color: transparent;

vertical-align: middle;

-webkit-animation: rolling .8s infinite linear;

animation: rolling .8s infinite linear;

z-index: 1

}

.sign .sign-in-button,.sign .sign-up-button {

margin-top: 20px;

width: 100%;

padding: 9px 18px;

font-size: 18px;

border: none;

border-radius: 25px;

color: #fff;

background: #42c02e;

cursor: pointer;

outline: none;

display: block;

clear: both

}

.sign .sign-in-button:hover,.sign .sign-up-button:hover {

background: #3db922

}

.sign .sign-in-button {

background: #3194d0

}

.sign .sign-in-button:hover {

background: #187cb7

}

.sign .btn-in-resend,.sign .btn-up-resend {

position: absolute;

top: 7px;

right: 7px;

width: 100px;

height: 36px;

font-size: 13px;

color: #fff;

background-color: #42c02e;

border-radius: 20px;

line-height: 36px

}

.sign .btn-in-resend {

background-color: #3194d0

}

.sign .sign-up-msg {

margin: 10px 0;

padding: 0;

text-align: center;

font-size: 12px;

line-height: 20px;

color: #969696

}

.sign .sign-up-msg a,.sign .sign-up-msg a:hover {

color: #3194d0

}

.sign .overseas input {

padding-left: 110px!important

}

.sign .overseas .overseas-number {

position: absolute;

top: 0;

left: 0;

width: 100px;

height: 50px;

font-size: 18px;

color: #969696;

border-right: 1px solid #c8c8c8

}

.sign .overseas .overseas-number span {

margin-top: 17px;

padding-left: 35px;

text-align: left;

font-size: 14px;

display: block

}

.sign .overseas .dropdown-menu {

width: 100%;

max-height: 285px;

font-size: 14px;

border-radius: 0 0 4px 4px;

overflow-y: auto

}

.sign .overseas .dropdown-menu li .nation-code {

width: 65px;

display: inline-block

}

.sign .overseas .dropdown-menu li a {

padding: 6px 20px;

font-size: 14px;

line-height: 20px

}

.sign .overseas .dropdown-menu li a::hover {

color: #fff;

background-color: #f5f5f5

}

.sign .more-sign {

margin-top: 50px

}

.sign .more-sign h6 {

position: relative;

margin: 0 0 10px;

font-size: 12px;

color: #b5b5b5

}

.sign .more-sign h6:before {

left: 30px

}

.sign .more-sign h6:after,.sign .more-sign h6:before {

content: "";

border-top: 1px solid #b5b5b5;

display: block;

position: absolute;

width: 60px;

top: 5px

}

.sign .more-sign h6:after {

right: 30px

}

.sign .more-sign ul {

margin-bottom: 10px;

list-style: none

}

.sign .more-sign ul li {

margin: 0 5px;

display: inline-block

}

.sign .more-sign ul a {

width: 50px;

height: 50px;

line-height: 50px;

display: block

}

.sign .more-sign ul i {

font-size: 28px

}

.sign .more-sign .ic-weibo {

color: #e05244

}

.sign .more-sign .ic-wechat {

color: #00bb29

}

.sign .more-sign .ic-qq_connect {

color: #498ad5

}

.sign .more-sign .ic-douban {

color: #00820f

}

.sign .more-sign .ic-more {

color: #999

}

.sign .more-sign .weibo-loading {

pointer-events: none;

cursor: pointer;

position: relative

}

.sign .more-sign .weibo-loading:after {

content: "";

position: absolute;

left: 0;

top: 0;

width: 100%;

height: 100%;

background-color: #fff

}

body.reader-night-mode .sign .more-sign .weibo-loading:after {

background-color: #3f3f3f

}

.sign .more-sign .weibo-loading:before {

content: "";

position: absolute;

top: 50%;

left: 50%;

width: 20px;

height: 20px;

margin: -10px 0 0 -10px;

border-radius: 10px;

border: 2px solid #e05244;

border-bottom-color: transparent;

vertical-align: middle;

-webkit-animation: rolling .8s infinite linear;

animation: rolling .8s infinite linear;

z-index: 1

}

@keyframes rolling {

0% {

-webkit-transform: rotate(0deg);

transform: rotate(0deg)

}

to {

-webkit-transform: rotate(1turn);

transform: rotate(1turn)

}

}

@-webkit-keyframes rolling {

0% {

-webkit-transform: rotate(0deg)

}

to {

-webkit-transform: rotate(1turn)

}

}

.sign .reset-password-input {

border-radius: 4px!important

}

.sign .return {

margin-left: -8px;

color: #969696

}

.sign .return:hover {

color: #333

}

.sign .return i {

margin-right: 5px

}

.sign .icheckbox_square-green {

display: inline-block;

*display: inline;

vertical-align: middle;

margin: 0;

padding: 0;

width: 18px;

height: 18px;

background: url(/static/image/green.png) no-repeat;

border: none;

cursor: pointer;

background-position: 0 0

}

.sign .icheckbox_square-green.hover {

background-position: -20px 0

}

.sign .icheckbox_square-green.checked {

background-position: -40px 0

}

.sign .icheckbox_square-green.disabled {

background-position: -60px 0;

cursor: default

}

.sign .icheckbox_square-green.checked.disabled {

background-position: -80px 0

}

.geetest_panel_box>* {

box-sizing: content-box

}

@media (max-width:768px) {

body {

min-width: 0

}

.sign {

height: auto;

min-height: 0;

background-color: transparent

}

.sign .logo {

display: none

}

.sign .main {

position: absolute;

left: 50%;

margin: 0 0 0 -200px;

box-shadow: none

}

}

注册路由:

import Vue from 'vue'

import Router from 'vue-router'

Vue.use(Router);

import Home from "@/components/Home";

import Login from "@/components/Login"

import Register from "@/components/Register"

import FindPassword from "@/components/FindPassword"

import ResetPassword from "@/components/ResetPassword"

export default new Router({

mode: "history",

routes: [

{

name:"Home",

path:"/",

component:Home,

},

{

name:"Home",

path:"/home",

component:Home,

},

{

name:"Login",

path:"/user/login",

component:Login,

},

{

name:"Register",

path:"/user/register",

component: Register,

},

{

name:"FindPassword",

path:"/find_password",

component: FindPassword,

},

{

name:"ResetPassword",

path:"/reset_password",

component: ResetPassword,

},

]

})

因为我们需要在重置页面组件中,需要识别当前用户身份而且不能给外界直接看到,所以我们可以把用户识别用户身份的数据使用itsdangerous进行加密

itsdangerous模块的官方文档:https://pythonhosted.org/itsdangerous/

安装

pip install -U itsdangerous

TimedJSONWebSignatureSerializer的使用

使用TimedJSONWebSignatureSerializer可以生成带有有效期的token

from itsdangerous import TimedJSONWebSignatureSerializer as Serializer

from django.conf import settings

"""加密"""

# serializer = Serializer(秘钥, 加密数据的有效期(秒))

serializer = Serializer(settings.SECRET_KEY, 300)

# serializer.dumps(数据), 返回bytes类型

access_token = serializer.dumps({'email': '649641514@qq.com'})

access_token = access_.decode()

# 检验token

# 验证失败,会抛出itsdangerous.BadData异常

serializer = Serializer(settings.SECRET_KEY, 300)

try:

data = serializer.loads(access_token)

except BadData:

return None

基于Itsdangerous修改视图中的发送邮件的api接口

import re

from django.core.mail import send_mail

from django.conf import settings

from itsdangerous import TimedJSONWebSignatureSerializer

class FindPasswordAPIView(APIView):

def get(self,request):

"""发送邮件"""

email = request.query_params.get("email")

# 验证邮件 xx@xx.xxx

if not re.match("^\w+@\w+\.\w+$",email):

return Response({"detail":"邮箱格式不正确!"}, status=status.HTTP_400_BAD_REQUEST)

try:

user = User.objects.get(email=email)

except User.DoesNotExist:

return Response({"detail": "邮箱没有被注册,请重新确认!"}, status=status.HTTP_400_BAD_REQUEST)

# 使用itsdangerous对链接中的数据进行加密

serializer = TimedJSONWebSignatureSerializer(secret_key=settings.SECRET_KEY, expires_in=constants.EMAIL_EXPIRE_TIME)

token = serializer.dumps({"email": email}).decode()

url = settings.CLIENT_HOST + "/reset_password?token=%s" % token

# 发送邮件

send_mail(subject="【荏苒】找回密码",

message="",

html_message='尊敬的%s,您好!
您在荏苒网申请了找回密码,请点击重置密码进行密码修改操作.
如果不是您本人的操作,请忽略本次邮件!' % (user.username,url),from_email=settings.EMAIL_FROM, recipient_list=[email])

return Response({"detail":"ok"})

客户端重置密码页面组件ResetPassword.vue中接收并转发token到服务端验证toke。视图中接受来自客户端token数据,并进行校验

视图代码:

class CheckTokenAPIView(APIView):

def get(self,request):

token_string = request.query_params.get("token")

serializer = TimedJSONWebSignatureSerializer(secret_key=settings.SECRET_KEY,

expires_in=constants.EMAIL_EXPIRE_TIME)

try:

token = serializer.loads(token_string)

except BadData:

return Response({"detail":"对不起,当前链接地址不存在或者已过期!"}, status=status.HTTP_400_BAD_REQUEST)

return Response({"email": token.get("email")})

路由,代码:

from django.urls import path,re_path

from rest_framework_jwt.views import obtain_jwt_token

from . import views

urlpatterns = [

path("login/", obtain_jwt_token),

path("captcha/", views.CaptchaAPIView.as_view() ),

path("", views.UserAPIView.as_view() ),

re_path("sms/(?P1[3-9]\d{9})/", views.SMSAPIView.as_view() ),

re_path("mobile/(?P1[3-9]\d{9})/", views.MobileAPIView.as_view() ),

re_path("find_password/", views.FindPasswordAPIView.as_view() ),

re_path("token/", views.CheckTokenAPIView.as_view() ),

]

客户端发送ajax请求检测token是否正确并提取token中的信息!

使用邮箱重置密码

export default {

name: "ResetPassword",

data(){

return {

email: "",

password:"",

password2:"",

}

},

created() {

this.get_token();

},

methods:{

get_token(){

// 获取链接中的token

this.$axios.get(`${this.$settings.Host}/users/token/`+location.search).then(response=>{

this.email = response.data.email;

}).catch(error=>{

this.$message.error(error.response.data.detail);

})

},

// 重置密码

passwordhander(){

},

show_captcha(){

}

},

}

服务端提供修改重置密码接口

视图代码:

class ResetPasswordAPIView(APIView):

def put(self,request):

token_string = request.query_params.get("token")

serializer = TimedJSONWebSignatureSerializer(secret_key=settings.SECRET_KEY,

expires_in=constants.EMAIL_EXPIRE_TIME)

try:

token = serializer.loads(token_string)

except BadData:

return Response({"detail": "对不起,当前链接地址不存在或者已过期!"}, status=status.HTTP_400_BAD_REQUEST)

password = request.data.get("password")

password2 = request.data.get("password2")

if password != password2:

return Response({"detail":"密码和确认密码不一致!"}, status=status.HTTP_400_BAD_REQUEST)

user = User.objects.get(email=token.get("email"))

user.set_password(password)

user.save()

return Response({"detail":"修改密码成功!"}):

路由代码:

re_path("reset_password/", views.ResetPasswordAPIView.as_view() ),

客户端提交数据请求重置密码

使用邮箱重置密码

export default {

name: "ResetPassword",

data(){

return {

email: "",

password:"",

password2:"",

}

},

created() {

this.get_token();

},

methods:{

get_token(){

// 获取链接中的token

this.$axios.get(`${this.$settings.Host}/users/token/`+location.search).then(response=>{

this.email = response.data.email;

}).catch(error=>{

this.$message.error(error.response.data.detail);

})

},

// 重置密码

passwordhander(){

this.$axios.put(`${this.$settings.Host}/users/reset_password/`+location.search,{

password: this.password,

password2: this.password2

}).then(response=>{

this.$confirm('是否跳转到登录页面', '重置密码成功', {

confirmButtonText: '登录页面',

cancelButtonText: '返回首页',

type: 'success'

}).then(() => {

// 前往登录页面

this.$router.push("/login");

}).catch(() => {

// 返回站点首页

this.$router.push("/");

});

}).catch(error=>{

this.$message.error("重置密码失败!请重新尝试发送邮件找回密码!");

})

},

show_captcha(){

// 显示验证码

if(this.password.length<1){

// 阻止代码继续往下执行

this.$message.error("对不起,密码不能为空!");

return false;

}

if(this.password != this.password2){

this.$message.error("对不起,密码和米确认密码必须一致!");

return false;

}

var captcha1 = new TencentCaptcha(this.$settings.TC_captcha.app_id, res=>{

this.$axios.get(`${this.$settings.Host}/users/captcha/`,{

params:{

ticket: res.ticket,

randstr: res.randstr

}

}).then(response=>{

if(response.data.detail){

// 继续进行登录处理

this.passwordhander();

}

}).catch(error=>{

this.$message.error("对不起,验证码校验不通过!");

});

});

captcha1.show(); // 显示验证码

}

},

}

使用celery异步发送找回密码的邮件

在mycelery目录下创建异步发送邮件的任务目录email,并创建任务文件tasks.py,代码:

from mycelery.main import app

import logging

from django.conf import settings

log = logging.getLogger("django")

from django.core.mail import send_mail

@app.task(name="send_email")

def send_email(username, recipient_list, url):

"""异步发送找回密码的邮件"""

try:

send_mail(subject="【荏苒】找回密码",message="",html_message='尊敬的%s,您好!
您在荏苒网申请了找回密码, 请点击重置密码进行密码修改操作.
如果不是您本人的操作,请忽略本次邮件!' % (username,url),from_email=settings.EMAIL_FROM, recipient_list=recipient_list)

except:

log.error("发送邮件失败!")

注册任务

mycelery.main.py

# 主程序

import os

from celery import Celery

# 创建celery实例对象

app = Celery("renran")

# 把celery和django进行组合,识别和加载django的配置文件

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'renranapi.settings.dev')

# 对django框架执行初始化

import django

django.setup()

# 通过app对象加载配置

app.config_from_object("mycelery.config")

# 加载任务

# 参数必须必须是一个列表,里面的每一个任务都是任务的路径名称

# app.autodiscover_tasks(["任务1","任务2"])

app.autodiscover_tasks(["mycelery.sms","mycelery.email"])

# 启动Celery的命令

# 切换目录到mycelery的上一级目录下启动

# celery -A mycelery.main worker --loglevel=info

重启celery以后, 修改视图users.views.py中的发送邮件代码

from renranapi.mycelery.email.tasks import send_email

class FindPasswordAPIView(APIView):

def get(self,request):

"""发送邮件"""

email = request.query_params.get("email")

# 验证邮件 xx@xx.xxx

if not re.match("^\w+@\w+\.\w+$",email):

return Response({"detail":"邮箱格式不正确!"}, status=status.HTTP_400_BAD_REQUEST)

try:

user = User.objects.get(email=email)

except User.DoesNotExist:

return Response({"detail": "邮箱没有被注册,请重新确认!"}, status=status.HTTP_400_BAD_REQUEST)

# 使用itsdangerous对链接中的数据进行加密

serializer = TimedJSONWebSignatureSerializer(secret_key=settings.SECRET_KEY, expires_in=constants.EMAIL_EXPIRE_TIME)

token = serializer.dumps({"email": email}).decode()

url = settings.CLIENT_HOST + "/reset_password?token=%s" % token

# 发送邮件

# send_mail(subject="【荏苒】找回密码",message="",html_message='尊敬的%s,您好!
您在荏苒网申请了找回密码, 请点击重置密码进行密码修改操作.
如果不是您本人的操作,请忽略本次邮件!' % (user.username,url),from_email=settings.EMAIL_FROM, recipient_list=[email])

send_email.delay(user.username,[email], url )

return Response({"detail":"ok"})

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值