0108 luffy登录注册接口
目录
昨日回顾
1、git的协同操作
1)拿公钥换源远程仓库源链接 - 成为项目开发者
2)根据源链接克隆远程仓库 - git clone 源地址
3)参与项目的团队开发,永远要遵循 先pull后push,在pull之前要将所有修改的代码提交到本地版本库
2、冲突解决
1)当不同开发者协同开发,导致远程仓库与本地仓库的版本不一致,在pull远程仓库到本地仓库时,会出现版本冲突
2)两个仓库不同版本中,出现了相同文件的修改情况,会出现文件的冲突
3)同一文件的修改,代码有重叠,一定会产生代码冲突,打开冲突的文件,文件中会表示冲突的开始与结束,分割线上下分别是冲突的代码
>>>>>>>>>>header
===========
<<<<<<<<<<1321adsa21
4)冲突的解决没有固定的结果,但是要将冲突的标识删除,根据代码实际情况,线下沟通,整合代码即可
3、登录业务
1)多方式登录
2)短信验证码
腾讯短信服务 - 创建短信服务应用(appid、appkey),申请签名与模板
-- 安装对应sdk
-- 通过短信服务应用得到短信发送者sender
-- 结合签名与模板、手机、验证码、其他所需参数,发送验证码
3)手机验证码登录
4、注册业务
1)手机注册验证
2)短信验证码
3)手机验证码密码注册
luffy后台
验证手机号是否已注册
// url.py
urlpatterns = [
path('mobile/',views.MobileCheckAPIView.as_view()),
]
----------------------------------------------------------
// views.py
# 手机验证码是否已注册
class MobileCheckAPIView(APIView):
def get(self,request,*args,**kwargs):
# 从拼接参数中获取手机号
mobile = request.query_paramas.get('mobile')
# 对获取的数据进行校验
if not mobile:
return APIResponse(1,'mobile必须提供',http_status=400)
# 对手机号格式进行校验
if not re.match(r'1[3-9][0-9]{9}$',mobile):
return APIResponse(1,msg='mobile格式有误',http_status=400)
try:
# 只要数据库中有,就代表已注册
models.User.objects.get(mobile=mobile)
return APIResponse(2,msg='手机号已注册')
except:
return APIResponse(0,msg='手机号未注册')
---------------------------------------------------------
使用手机号与验证码注册
'''urls.py'''
# 手机号验证码方式的注册接口
path('register/mobile/',views.RegisterMobileAPIView.as_view()),
-------------------------------------------------------
'''views.py'''
# 手机号与验证码注册
class RegisterMobileAPIView(APIView):
def post(self,request):
# 将前端传的数据进行反序列化处理(保存数据库)
serializer = serializers.RegisterMobileserializer(data=request.data)
serializer.is_valid(raise_exception=True)
# 校验通过保存获取obj对象
user_obj = serializer.save
# 将user对象序列化输出得到data,当做返回数据
return APIResponse(results=serializers.RegisterMobileserializer(user_obj).data)
----------------------------------------------------------
'''serializers.py'''
# 手机号验证码注册的序列化与反序列化
class RegisterMobileserializer(serializers.ModelSerializer):
# 反序列化的字段code(保存数据库)
code = serializers.CharField(write_only=True,min_length=6,max_length=6)
class Meta:
model = models.User
fields = ('username', 'mobile','password','code')
# username,mobile序列化与反序列化,password不进行序列化给前端
extra_kwargs = {
'password': {
'write_only': True
},
'username': {
'read_only': True
}
}
# 每一个反序列化字段都可以配置一个局部钩子
# 注册提交后对手机号进行校验,对验证码格式进行校验,校验验证码一致
# 手机号校验钩子
def validate_mobile(self,value):
if not re.match(r'^1[3-9][0-9]{9}$', value.username): # 电话
raise serializers.ValidationError('手机号格式不正确')
return value
# 验证码格式内容有误就不需要进行 取服务器存储的验证码(IO操作) 进行校验
def validate_code(self, value):
try:
# 验证码格式数字
int(value)
return value
except:
raise serializers.ValidationError('验证码格式有误')
# 全局校验
def validate(self, attrs):
mobile = attrs.get('mobile')
code = attrs.pop('code')
# 封装的获取云短信的函数方法
old_code = cache.get(settings.SMS_CACHE_FORMAT % mobile)
if code != old_code:
raise serializers.ValidationError({'code': '验证码有误'})
# 创建用户需要一些额外的信息,比如username
attrs['username'] = mobile
return attrs
#create方法是否需要重写: 默认入库的密码是明文(重写)
def create(self, validated_data):
# auth组件的create_user方法进行密码密文创建
return models.User.objects.create_user(**validated_data)
luffy前台
避免页面横向产生滚动条
会随着屏幕的缩放而缩放'overflow: hidden;'
/*避免横向产生滚动条*/
body {
overflow: hidden;
}
header.vue
<template>
<div class="header">
<div class="slogan">
<p>老男孩IT教育 | 帮助有志向的年轻人通过努力学习获得体面的工作和生活</p>
</div>
<div class="nav">
<ul class="left-part">
<li class="logo">
<router-link to="/">
<img src="../assets/img/head-logo.svg" alt="">
</router-link>
</li>
<li class="ele">
<span @click="goPage('/free-course')" :class="{active: url_path === '/free-course'}">免费课</span>
</li>
<li class="ele">
<span @click="goPage('/course')" :class="{active: url_path === '/course'}">实战课</span>
</li>
<li class="ele">
<span @click="goPage('/light-course')" :class="{active: url_path === '/light-course'}">轻课</span>
</li>
</ul>
<div class="right-part">
<div v-if="!token">
<span @click="pull_login">登录</span>
<span class="line">|</span>
<span @click="pull_register">注册</span>
</div>
<div v-else>
<span>{{ username }}</span>
<span class="line">|</span>
<span @click="logout">注销</span>
</div>
</div>
</div>
<Login v-if="is_login" @close="close_login" @go="pull_register" @success="login_success"/>
<Register v-if="is_register" @close="close_register" @go="pull_login" @success="register_success"/>
</div>
</template>
<script>
import Login from './Login'
import Register from './Register'
export default {
name: "Header",
components: {
Login,
Register,
},
data() {
return {
url_path: sessionStorage.url_path || '/',
is_login: false,
is_register: false,
token: $cookies.get('token') || '',
username: $cookies.get('username') || '',
}
},
methods: {
goPage(url_path) {
// 已经是当前路由就没有必要重新跳转
if (this.url_path !== url_path) {
this.$router.push(url_path);
}
sessionStorage.url_path = url_path;
},
// 显示登录模态框
pull_login() {
this.is_login = true;
this.close_register();
},
close_login() {
this.is_login = false;
},
pull_register() {
this.is_register = true;
this.close_login();
},
close_register() {
this.is_register = false;
},
login_success() {
this.close_login();
this.token = this.$cookies.get('token') || '';
this.username = this.$cookies.get('username') || '';
},
register_success() {
this.pull_login();
},
logout() {
this.$cookies.remove('token');
this.$cookies.remove('username');
this.token = '';
this.username = '';
}
},
created() {
sessionStorage.url_path = this.$route.path;
this.url_path = this.$route.path;
// 也可以在data中只做空声明,钩子中初始化
// this.token = this.$cookies.get('token') || '';
// this.username = this.$cookies.get('username') || '';
}
}
</script>
<style scoped>
.header {
background-color: white;
box-shadow: 0 0 5px 0 #aaa;
}
.header:after {
content: "";
display: block;
clear: both;
}
.slogan {
background-color: #eee;
height: 40px;
}
.slogan p {
width: 1200px;
margin: 0 auto;
color: #aaa;
font-size: 13px;
line-height: 40px;
}
.nav {
background-color: white;
user-select: none;
width: 1200px;
margin: 0 auto;
}
.nav ul {
padding: 15px 0;
float: left;
}
.nav ul:after {
clear: both;
content: '';
display: block;
}
.nav ul li {
float: left;
}
.logo {
margin-right: 20px;
}
.ele {
margin: 0 20px;
}
.ele span {
display: block;
font: 15px/36px '微软雅黑';
border-bottom: 2px solid transparent;
cursor: pointer;
}
.ele span:hover {
border-bottom-color: orange;
}
.ele span.active {
color: orange;
border-bottom-color: orange;
}
.right-part {
float: right;
}
.right-part .line {
margin: 0 10px;
}
.right-part span {
line-height: 68px;
cursor: pointer;
}
.search {
float: right;
position: relative;
margin-top: 22px;
}
.search input, .search button {
border: none;
outline: none;
background-color: white;
}
.search input {
border-bottom: 1px solid black;
}
.search input:focus {
border-bottom-color: orange;
}
.search input:focus + button {
color: orange;
}
.search .tips {
position: absolute;
bottom: 3px;
left: 0;
}
.search .tips span {
border-radius: 11px;
background-color: #eee;
line-height: 22px;
display: inline-block;
padding: 0 3px;
margin-right: 3px;
cursor: pointer;
color: #666;
font-size: 14px;
}
</style>
登录模态框
<template>
<div class="login">
<div class="box">
<i class="el-icon-close" @click="close_login"></i>
<div class="content">
<div class="nav">
<span :class="{active: login_method === 'is_pwd'}"
@click="change_login_method('is_pwd')">密码登录</span>
<span :class="{active: login_method === 'is_sms'}"
@click="change_login_method('is_sms')">短信登录</span>
</div>
<el-form v-if="login_method === 'is_pwd'">
<el-input
placeholder="用户名/手机号/邮箱"
prefix-icon="el-icon-user"
v-model="username"
clearable>
</el-input>
<el-input
placeholder="密码"
prefix-icon="el-icon-key"
v-model="password"
clearable
show-password>
</el-input>
<el-button type="primary" @click="login">登录</el-button>
</el-form>
<el-form v-if="login_method === 'is_sms'">
<el-input
placeholder="手机号"
prefix-icon="el-icon-phone-outline"
v-model="mobile"
clearable
@blur="check_mobile">
</el-input>
<el-input
placeholder="验证码"
prefix-icon="el-icon-chat-line-round"
v-model="sms"
clearable>
<template slot="append">
<span class="sms" @click="send_sms">{{ sms_interval }}</span>
</template>
</el-input>
<el-button type="primary" @click="login_mobile">登录</el-button>
</el-form>
<div class="foot">
<span @click="go_register">立即注册</span>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "Login",
data() {
return {
username: '',
password: '',
mobile: '',
sms: '',
login_method: 'is_pwd',
sms_interval: '获取验证码',
is_send: false,
}
},
methods: {
close_login() {
this.$emit('close')
},
go_register() {
this.$emit('go')
},
change_login_method(method) {
this.login_method = method;
},
// 校验手机对应用户是否存在
check_mobile() {
// 前台校验手机格式
if (!this.mobile) return;
if (!this.mobile.match(/^1[3-9][0-9]{9}$/)) {
this.$message({
message: '手机号有误',
type: 'warning',
duration: 1000,
onClose: () => {
this.mobile = '';
}
});
return false;
}
// 访问后台校验手机号对应用户是否存在
this.$axios({
url: this.$settings.base_url + '/user/mobile/',
method: 'get',
params: {
mobile: this.mobile
}
}).then(response => {
if (response.data.status === 0) {
this.$message({
message: response.data.msg,
type: 'warning',
duration: 1000,
})
} else {
// 注册过的手机才允许发送验证码
this.is_send = true;
}
}).catch(error => {
this.$message({
message: error.response.data.msg,
type: 'error'
})
});
},
// 发送验证码
send_sms() {
if (!this.is_send) return;
this.is_send = false;
this.sms_interval = "发送中...";
// 倒计时
let sms_interval_time = 60;
let timer = setInterval(() => {
if (sms_interval_time <= 1) {
clearInterval(timer);
this.sms_interval = "获取验证码";
this.is_send = true; // 重新回复点击发送功能的条件
} else {
sms_interval_time -= 1;
this.sms_interval = `${sms_interval_time}秒后再发`;
}
}, 1000);
this.$axios({
url: this.$settings.base_url + '/user/sms/',
method: 'post',
data: {
mobile: this.mobile
}
}).then(response => {
if (response.data.status === 0) {
// 成功
this.$message({
message: '验证码发送成功',
type: 'success',
})
} else {
// 失败
this.$message({
message: '验证码发送失败',
type: 'error',
})
}
}).catch(() => {
// 异常
this.$message({
message: '获取验证码异常',
type: 'error',
})
});
},
// 验证码登录
login_mobile() {
if (!this.mobile || !this.sms) return false;
this.$axios({
url: this.$settings.base_url + '/user/login/mobile/',
method: 'post',
data: {
mobile: this.mobile,
code: this.sms,
}
}).then(response => {
// 要将响应的用户信息和token存储到cookies中
this.$cookies.set('token', response.data.results.token, '1d');
this.$cookies.set('username', response.data.results.username, '1d');
// 弹出框提示后,关闭登录界面
this.$message({
message: '登录成功',
type: 'success',
duration: 1500,
onClose: () => {
this.$emit('success')
}
});
}).catch(() => {
// 异常
this.$message({
message: '登录失败',
type: 'error',
duration: 1500,
onClose: () => {
this.mobile = '';
this.sms = '';
}
})
});
},
// 密码登录
login() {
if (!this.username || !this.password) return false;
this.$axios({
url: this.$settings.base_url + '/user/login/',
method: 'post',
data: {
username: this.username,
password: this.password,
}
}).then(response => {
// 要将响应的用户信息和token存储到cookies中
this.$cookies.set('token', response.data.results.token, '1d');
this.$cookies.set('username', response.data.results.username, '1d');
// 弹出框提示后,关闭登录界面
this.$message({
message: '登录成功',
type: 'success',
duration: 1500,
onClose: () => {
this.$emit('success')
}
});
}).catch(() => {
// 异常
this.$message({
message: '登录失败',
type: 'error',
duration: 1500,
onClose: () => {
this.username = '';
this.password = '';
}
})
});
},
}
}
</script>
<style scoped>
.login {
width: 100vw;
height: 100vh;
position: fixed;
top: 0;
left: 0;
z-index: 10;
background-color: rgba(0, 0, 0, 0.3);
}
.box {
width: 400px;
height: 420px;
background-color: white;
border-radius: 10px;
position: relative;
top: calc(50vh - 210px);
left: calc(50vw - 200px);
}
.el-icon-close {
position: absolute;
font-weight: bold;
font-size: 20px;
top: 10px;
right: 10px;
cursor: pointer;
}
.el-icon-close:hover {
color: darkred;
}
.content {
position: absolute;
top: 40px;
width: 280px;
left: 60px;
}
.nav {
font-size: 20px;
height: 38px;
border-bottom: 2px solid darkgrey;
}
.nav > span {
margin: 0 20px 0 35px;
color: darkgrey;
user-select: none;
cursor: pointer;
padding-bottom: 10px;
border-bottom: 2px solid darkgrey;
}
.nav > span.active {
color: black;
border-bottom: 3px solid black;
padding-bottom: 9px;
}
.el-input, .el-button {
margin-top: 40px;
}
.el-button {
width: 100%;
font-size: 18px;
}
.foot > span {
float: right;
margin-top: 20px;
color: orange;
cursor: pointer;
}
.sms {
color: orange;
cursor: pointer;
display: inline-block;
width: 70px;
text-align: center;
user-select: none;
}
</style>
注册模态框
<template>
<div class="register">
<div class="box">
<i class="el-icon-close" @click="close_register"></i>
<div class="content">
<div class="nav">
<span class="active">新用户注册</span>
</div>
<el-form>
<el-input
placeholder="手机号"
prefix-icon="el-icon-phone-outline"
v-model="mobile"
clearable
@blur="check_mobile">
</el-input>
<el-input
placeholder="密码"
prefix-icon="el-icon-key"
v-model="password"
clearable
show-password>
</el-input>
<el-input
placeholder="验证码"
prefix-icon="el-icon-chat-line-round"
v-model="sms"
clearable>
<template slot="append">
<span class="sms" @click="send_sms">{{ sms_interval }}</span>
</template>
</el-input>
<el-button type="primary" @click="register">注册</el-button>
</el-form>
<div class="foot">
<span @click="go_login">立即登录</span>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "Register",
data() {
return {
mobile: '',
password: '',
sms: '',
sms_interval: '获取验证码',
is_send: false,
}
},
methods: {
close_register() {
this.$emit('close', false)
},
go_login() {
this.$emit('go')
},
// 校验手机对应用户是否注册
check_mobile() {
// 前台校验手机格式
if (!this.mobile) return;
if (!this.mobile.match(/^1[3-9][0-9]{9}$/)) {
this.$message({
message: '手机号有误',
type: 'warning',
duration: 1000,
onClose: () => {
this.mobile = '';
}
});
return false;
}
// 访问后台校验手机号对应用户是否存在
this.$axios({
url: this.$settings.base_url + '/user/mobile/',
method: 'get',
params: {
mobile: this.mobile
}
}).then(response => {
if (response.data.status === 2) {
this.$message({
message: response.data.msg,
type: 'warning',
duration: 1000,
})
} else {
// 未注册过的手机才允许发送验证码
this.is_send = true;
}
}).catch(error => {
this.$message({
message: error.response.data.msg,
type: 'error'
})
});
},
// 发送验证码
send_sms() {
if (!this.is_send) return;
this.is_send = false;
this.sms_interval = "发送中...";
// 倒计时
let sms_interval_time = 60;
let timer = setInterval(() => {
if (sms_interval_time <= 1) {
clearInterval(timer);
this.sms_interval = "获取验证码";
this.is_send = true; // 重新回复点击发送功能的条件
} else {
sms_interval_time -= 1;
this.sms_interval = `${sms_interval_time}秒后再发`;
}
}, 1000);
this.$axios({
url: this.$settings.base_url + '/user/sms/',
method: 'post',
data: {
mobile: this.mobile
}
}).then(response => {
if (response.data.status === 0) {
// 成功
this.$message({
message: '验证码发送成功',
type: 'success',
})
} else {
// 失败
this.$message({
message: '验证码发送失败',
type: 'error',
})
}
}).catch(() => {
// 异常
this.$message({
message: '获取验证码异常',
type: 'error',
})
});
},
// 注册
register() {
if (!this.mobile || !this.password || !this.sms) return false;
this.$axios({
url: this.$settings.base_url + '/user/register/mobile/',
method: 'post',
data: {
mobile: this.mobile,
code: this.sms,
password: this.password,
}
}).then(response => {
// 弹出框提示后,关闭注册界面,前台登录页面
this.$message({
message: '注册成功',
type: 'success',
duration: 1500,
onClose: () => {
this.$emit('success')
}
});
}).catch(() => {
// 异常
this.$message({
message: '注册失败',
type: 'error',
duration: 1500,
onClose: () => {
this.mobile = '';
this.password = '';
this.sms = '';
}
})
});
}
}
}
</script>
<style scoped>
.register {
width: 100vw;
height: 100vh;
position: fixed;
top: 0;
left: 0;
z-index: 10;
background-color: rgba(0, 0, 0, 0.3);
}
.box {
width: 400px;
height: 480px;
background-color: white;
border-radius: 10px;
position: relative;
top: calc(50vh - 240px);
left: calc(50vw - 200px);
}
.el-icon-close {
position: absolute;
font-weight: bold;
font-size: 20px;
top: 10px;
right: 10px;
cursor: pointer;
}
.el-icon-close:hover {
color: darkred;
}
.content {
position: absolute;
top: 40px;
width: 280px;
left: 60px;
}
.nav {
font-size: 20px;
height: 38px;
border-bottom: 2px solid darkgrey;
}
.nav > span {
margin-left: 90px;
color: darkgrey;
user-select: none;
cursor: pointer;
padding-bottom: 10px;
border-bottom: 2px solid darkgrey;
}
.nav > span.active {
color: black;
border-bottom: 3px solid black;
padding-bottom: 9px;
}
.el-input, .el-button {
margin-top: 40px;
}
.el-button {
width: 100%;
font-size: 18px;
}
.foot > span {
float: right;
margin-top: 20px;
color: orange;
cursor: pointer;
}
.sms {
color: orange;
cursor: pointer;
display: inline-block;
width: 70px;
text-align: center;
user-select: none;
}
</style>
用户模块三大认证处理
# dev.py中设置三大认证
# drf框架的配置
REST_FRAMEWORK = {
# 异常模块
'EXCEPTION_HANDLER': 'utils.exception.exception_handler',
# 三大认证模块
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
],
# 拥有具体权限限制的视图类局部配置权限
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny',
],
# 拥有具体频率限制的视图类局部配置频率
'DEFAULT_THROTTLE_CLASSES': [],
# 频率限制scope的规则
'DEFAULT_THROTTLE_RATES': {
'sms': '1/min'
},
}
---------------------------------------------------------
# 新建重写的throttles文件
from rest_framework.throttling import SimpleRateThrottle
class SMSRateThrottle(SimpleRateThrottle):
scope = 'sms'
def get_cache_key(self, request, view):
mobile = request.query_params.get('mobile') or request.data.get('mobile')
if not mobile:
return None # 没有提供手机号不进行限制
return self.cache_format % {
'scope': self.scope,
'ident': mobile
}
# 对views中的发送验证码接口进行频率限制
前后台交互
登录
前台登录手机号校验
<script>
......
// 校验手机对应用户是否存在
check_mobile() {
// 前台校验手机格式
if (!this.mobile) return;
if (!this.mobile.match(/^1[3-9][0-9]{9}$/)) {
this.$message({
message: '手机号有误',
type: 'warning',
duration: 1000,
onClose: () => {
this.mobile = '';
}
});
return false;
}
// 访问后台校验手机号对应用户是否存在
this.$axios({
url: this.$settings.base_url + '/user/mobile/',
method: 'get',
params: {
mobile: this.mobile
}
// 后台返回数据
}).then(response => {
if (response.data.status === 0) {
// 弹出框,当后台校验的是0,时
this.$message({
message: response.data.msg,
type: 'warning',
duration: 1000, // 弹出框停留时间
})
} else {
// 注册过的手机才允许发送验证码
this.is_send = true;
}
// 前台校验码发送失败
}).catch(error => {
this.$message({
message: error.response.data.msg,
type: 'error'
})
});
},
</script>
前台获取验证码
<script>
// 发送验证码
send_sms() {
if (!this.is_send) return;
this.is_send = false;
this.sms_interval = "发送中...";
// 倒计时
let sms_interval_time = 60;
let timer = setInterval(() => {
if (sms_interval_time <= 1) {
clearInterval(timer);
this.sms_interval = "获取验证码";
this.is_send = true; // 重新回复点击发送功能的条件
} else {
sms_interval_time -= 1;
this.sms_interval = `${sms_interval_time}秒后再发`;
}
}, 1000);
// 向后台发送数据
this.$axios({
url: this.$settings.base_url + '/user/sms/',
method: 'post',
data: {
mobile: this.mobile
}
// 获取返回值
}).then(response => {
if (response.data.status === 0) {
// 成功
this.$message({
message: '验证码发送成功',
type: 'success',
})
} else {
// 失败
this.$message({
message: '验证码发送失败',
type: 'error',
})
}
}).catch(() => {
// 异常
this.$message({
message: '获取验证码异常',
type: 'error',
})
});
},
</script>
前台短信验证码登录
cookies的使用
在组件逻辑中使用
this.$cookies.set(key, value, exp) // exp: '1s' | '1h' | '1d' 默认过期时间1天
this.$cookies.get(key)
this.$cookies.remove(key)
后台设置token的jwt设置
# dev.py
# drf-jwt配置
# 设置token的过期时间
import datetime
JWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
'JWT_ALLOW_REFRESH': False,
'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7),
}
前台代码
header中设置组件头,根据token显示登录注册或者用户名注销头部(v-if)
<script>
// 验证码登录
login_mobile() {
// 判断手机号与验证码是否为空
if (!this.mobile || !this.sms) return false;
// 后台数据
this.$axios({
url: this.$settings.base_url + '/user/login/mobile/',
method: 'post',
data: {
mobile: this.mobile,
code: this.sms,
}
}).then(response => {
// 要将响应的用户信息和token存储到cookies中
this.$cookies.set('token', response.data.results.token, '1d');
this.$cookies.set('username', response.data.results.username, '1d');
// 弹出框提示后,关闭登录界面
this.$message({
message: '登录成功',
type: 'success',
duration: 1500,
onClose: () => {
// 向header组件发送事件,登录成功跳转刷新
this.$emit('success')
}
});
}).catch(() => {
// 异常
this.$message({
message: '登录失败',
type: 'error',
duration: 1500,
// 错误,将输入框置为空
onClose: () => {
this.mobile = '';
this.sms = '';
}
})
});
},
</script>
前台登录注销
前台的逻辑(删除前台cookies中的token与username即可)
前台密码登录
<script>
// 密码登录
login() {
if (!this.username || !this.password) return false;
this.$axios({
url: this.$settings.base_url + '/user/login/',
method: 'post',
data: {
username: this.username,
password: this.password,
}
}).then(response => {
// 要将响应的用户信息和token存储到cookies中
this.$cookies.set('token', response.data.results.token, '1d');
this.$cookies.set('username', response.data.results.username, '1d');
// 弹出框提示后,关闭登录界面
this.$message({
message: '登录成功',
type: 'success',
duration: 1500,
onClose: () => {
this.$emit('success')
}
});
}).catch(() => {
// 异常
this.$message({
message: '登录失败',
type: 'error',
duration: 1500,
onClose: () => {
this.username = '';
this.password = '';
}
})
</script>
注册
<template>
<div class="register">
<div class="box">
<i class="el-icon-close" @click="close_register"></i>
<div class="content">
<div class="nav">
<span class="active">新用户注册</span>
</div>
<el-form>
<el-input
placeholder="手机号"
prefix-icon="el-icon-phone-outline"
v-model="mobile"
clearable
@blur="check_mobile">
</el-input>
<el-input
placeholder="密码"
prefix-icon="el-icon-key"
v-model="password"
clearable
show-password>
</el-input>
<el-input
placeholder="验证码"
prefix-icon="el-icon-chat-line-round"
v-model="sms"
clearable>
<template slot="append">
<span class="sms" @click="send_sms">{{ sms_interval }}</span>
</template>
</el-input>
<el-button type="primary" @click="register">注册</el-button>
</el-form>
<div class="foot">
<span @click="go_login">立即登录</span>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "Register",
data() {
return {
mobile: '',
password: '',
sms: '',
sms_interval: '获取验证码',
is_send: false,
}
},
methods: {
close_register() {
this.$emit('close', false)
},
go_login() {
this.$emit('go')
},
// 校验手机对应用户是否注册
check_mobile() {
// 前台校验手机格式
if (!this.mobile) return;
if (!this.mobile.match(/^1[3-9][0-9]{9}$/)) {
this.$message({
message: '手机号有误',
type: 'warning',
duration: 1000,
onClose: () => {
this.mobile = '';
}
});
return false;
}
// 访问后台校验手机号对应用户是否存在
this.$axios({
url: this.$settings.base_url + '/user/mobile/',
method: 'get',
params: {
mobile: this.mobile
}
}).then(response => {
if (response.data.status === 2) {
this.$message({
message: response.data.msg,
type: 'warning',
duration: 1000,
})
} else {
// 未注册过的手机才允许发送验证码
this.is_send = true;
}
}).catch(error => {
this.$message({
message: error.response.data.msg,
type: 'error'
})
});
},
// 发送验证码
send_sms() {
if (!this.is_send) return;
this.is_send = false;
this.sms_interval = "发送中...";
// 倒计时
let sms_interval_time = 60;
let timer = setInterval(() => {
if (sms_interval_time <= 1) {
clearInterval(timer);
this.sms_interval = "获取验证码";
this.is_send = true; // 重新回复点击发送功能的条件
} else {
sms_interval_time -= 1;
this.sms_interval = `${sms_interval_time}秒后再发`;
}
}, 1000);
this.$axios({
url: this.$settings.base_url + '/user/sms/',
method: 'post',
data: {
mobile: this.mobile
}
}).then(response => {
if (response.data.status === 0) {
// 成功
this.$message({
message: '验证码发送成功',
type: 'success',
})
} else {
// 失败
this.$message({
message: '验证码发送失败',
type: 'error',
})
}
}).catch(() => {
// 异常
this.$message({
message: '获取验证码异常',
type: 'error',
})
});
},
// 注册
register() {
if (!this.mobile || !this.password || !this.sms) return false;
this.$axios({
url: this.$settings.base_url + '/user/register/mobile/',
method: 'post',
data: {
mobile: this.mobile,
code: this.sms,
password: this.password,
}
}).then(response => {
// 弹出框提示后,关闭注册界面,前台登录页面
this.$message({
message: '注册成功',
type: 'success',
duration: 1500,
onClose: () => {
this.$emit('success')
}
});
}).catch(() => {
// 异常
this.$message({
message: '注册失败',
type: 'error',
duration: 1500,
onClose: () => {
this.mobile = '';
this.password = '';
this.sms = '';
}
})
});
}
}
}
</script>
<style scoped>
.register {
width: 100vw;
height: 100vh;
position: fixed;
top: 0;
left: 0;
z-index: 10;
background-color: rgba(0, 0, 0, 0.3);
}
.box {
width: 400px;
height: 480px;
background-color: white;
border-radius: 10px;
position: relative;
top: calc(50vh - 240px);
left: calc(50vw - 200px);
}
.el-icon-close {
position: absolute;
font-weight: bold;
font-size: 20px;
top: 10px;
right: 10px;
cursor: pointer;
}
.el-icon-close:hover {
color: darkred;
}
.content {
position: absolute;
top: 40px;
width: 280px;
left: 60px;
}
.nav {
font-size: 20px;
height: 38px;
border-bottom: 2px solid darkgrey;
}
.nav > span {
margin-left: 90px;
color: darkgrey;
user-select: none;
cursor: pointer;
padding-bottom: 10px;
border-bottom: 2px solid darkgrey;
}
.nav > span.active {
color: black;
border-bottom: 3px solid black;
padding-bottom: 9px;
}
.el-input, .el-button {
margin-top: 40px;
}
.el-button {
width: 100%;
font-size: 18px;
}
.foot > span {
float: right;
margin-top: 20px;
color: orange;
cursor: pointer;
}
.sms {
color: orange;
cursor: pointer;
display: inline-block;
width: 70px;
text-align: center;
user-select: none;
}
</style>
验证码的一次性处理
后台序列化中全局钩子,校验严验证码
# 验证码校验通过,验证码失效(验证码一次性使用)
cache.set(settings.SMS_CACHE_FORMAT % mobile, None, 0)