目录
这里以Python中的django框架为例(简略版)
实现登录注册的准备分析:
- 用户模块
- 模型类:继承AbstractUser
- 注册:验证用户信息(验证手机号和用户名的重复性,图形验证码)
- 登录:验证用户名、密码是否正确,生成token
django
1.创建User模型类
from django.db import models
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
mobile = models.CharField('手机号',max_length=11)
def __str__(self):
return self.username
class Meta:
db_table = 'user_tb'
verbose_name = '用户表'
verbose_name_plural = verbose_name
修改setting中的配置项
AUTH_USER_MODEL = 'users.User'
2.生成图片验证码
生成图片验证码流程图
import string
import random
import redis
from captcha.image import ImageCaptcha
from django.http import HttpResponse
class GenImageCode(APIView):
def get(self,request,uuid):
# 1.生成随机字符串
alpha_number = string.ascii_letters + string.digits
img_code = "".join(random.sample(alpha_number,4))
# 2.生成图片验证码
img_captcha = ImageCaptcha()
img_code_capucha = img_captcha.generate(img_code)
# 3. 存储图片验证码
redis_conn = redis.Redis(host='localhost',port=6379,db=0)
key = 'img_code:%s'%uuid
redis_conn.set(key,img_code,ex=300)
redis_conn.close()
return HttpResponse(img_code_capucha,content_type='image/png')
3.注册
- 获取参数
- 校验参数
- 存入数据库
- 返回响应
import re
import redis
import string
import random
from users.models import *
from django.http import HttpResponse
from captcha.image import ImageCaptcha
from rest_framework.views import APIView
from rest_framework.response import Response
class CheckUsername(APIView):
def get(self,request,username):
# 1.查询当前用户名是否存在
if not re.findall(r'^[a-zA-Z][a-zA-Z0-9_]{4,15}$',username):
return Response({'code':204,'msg':'用户名格式不正确'})
try:
User.objects.get(username=username)
except Exception as e:
return Response({'code':200,'msg':'用户名可以使用'})
return Response({'code':204,'msg':'用户已存在'})
# 校验手机号的重复性
class CheckMobile(APIView):
def get(self,request,mobile):
if not re.findall(r'^1[3-9]\d{9}$',mobile):
return Response({'code':204,'msg':'手机号格式不合法'})
user_query_set = User.objects.filter(mobile=mobile)
if not user_query_set:
return Response({'code':200,'msg':'可以正常注册'})
return Response({'code':204,'msg':'手机号已注册'})
# 图片验证码
class GenImageCode(APIView):
def get(self,request,uuid):
# 1.生成随机字符串
alpha_number = string.ascii_letters + string.digits
img_code = "".join(random.sample(alpha_number,4))
# 2.生成图片验证码
img_captcha = ImageCaptcha()
img_code_capucha = img_captcha.generate(img_code)
# 3. 存储图片验证码
redis_conn = redis.Redis(host='localhost',port=6379,db=0)
key = 'img_code:%s'%uuid
redis_conn.set(key,img_code,ex=300)
redis_conn.close()
return HttpResponse(img_code_capucha,content_type='image/png')
class CheckImageCode(APIView):
def get(self,request):
# 1.获取前端数据
uuid_ = request.query_params.get('imageCodeID')
image_code = request.query_params.get('imageCode')
# 2.根据uuid构造key
key = "img_code:%s"%uuid_
# 3.读取redis存储的验证码
redis_conn = redis.Redis(host='localhost',port=6379,db=0)
stored_image_code = redis_conn.get(key)
if not stored_image_code:
return Response({'code':204,"msg":'图片验证码已过期'})
if stored_image_code.decode().lower() == image_code.lower():
return Response({'code':200,'msg':'图片验证成功'})
return Response({'code':204,"msg":'验证码输入错误'})
# 用户注册
class Register(APIView):
def post(self,request):
# 1.获取前端数据
username = request.data.get('userName')
password = request.data.get('pwd')
# confirm_password = request.data.get('pass')
mobile = request.data.get('mobile')
aggrement = request.data.get('agree')
# 2.简单验证
if not all([username,password,mobile,aggrement]):
return Response({'code':204,'msg':'用户信息不完整'})
# if password != confirm_password:
# return Response({'code':204,'msg':'两次密码不一致'})
# 3.创建用户对象,并存储
User.objects.create_user(username=username,password=password,mobile=mobile)
return Response({'code':200,'msg':'注册成功'})
4.登录
- 获取参数
- 校验参数
- 生成token
- 返回响应
import jwt
import time
from users.models import *
from django.db import models
from rest_framework.response import Response
from django.contrib.auth.hashers import check_password
# 用户登录
class Login(APIView):
def post(self,request):
# 获取前端数据
username = request.data.get('user')
password = request.data.get('pwd')
# 2.查询用户
try:
user = User.objects.get(models.Q(username=username) |
models.Q(mobile=username))
except:
return Response({'code':204,"msg":'用户不存在'})
# 3.验证密码
validate = check_password(password,user.password)
if validate:
# 验证通过,允许登录
request.session['username'] = username
token = self.gen_token(user)
return Response({'code':200,'msg':'欢迎%s'%username,'user':
{'userName':user.username},'token':token})
return Response({'code':204,'msg':'用户名或密码错误'})
@staticmethod
def gen_token(user):
# 第一种方案
# 组织payload数据
payload = {
'username':user.username,
'exp':time.time()+30000
}
# 使用jwt编码,生成token
token = jwt.encode(payload=payload,key=settings.SECRET_KEY,algorithm='HS256')
return token
vue
1.注册视图(element-ui)
- 使用v-model绑定表单,获取用户信息
- 点击注册按钮,执行注册方法
-
axios的post请求提交参数
-
接收响应
<div id="register">
<el-dialog title="注册" width="300px" center :visible.sync="isRegister">
<el-form
:model="RegisterUser"
:rules="rules"
status-icon
ref="ruleForm"
class="demo-ruleForm"
>
<!-- 用户名 -->
<el-form-item prop="name">
<el-input
prefix-icon="el-icon-user-solid"
placeholder="请输入账号"
v-model="RegisterUser.name"
></el-input>
</el-form-item>
<!-- 密码 -->
<el-form-item prop="pass">
<el-input
prefix-icon="el-icon-view"
type="password"
placeholder="请输入密码"
v-model="RegisterUser.pass"
></el-input>
</el-form-item>
<!-- 确认密码 -->
<el-form-item prop="confirmPass">
<el-input
prefix-icon="el-icon-view"
type="password"
placeholder="请再次输入密码"
v-model="RegisterUser.confirmPass"
></el-input>
</el-form-item>
<!-- 输入手机号 -->
<el-form-item prop="mobile">
<el-input
prefix-icon="el-icon-user-solid"
placeholder="请输入手机号"
v-model="RegisterUser.mobile"
></el-input>
</el-form-item>
<!-- 增加的内容 -->
<el-form-item prop='imageCode'>
<!-- 图片验证码 -->
<el-input
placeholder="输入验证码"
v-model="RegisterUser.imageCode"
:style="{width:'60%'}"
></el-input>
<img class='imageCode' v-bind:src="iamgeCodeUrl" alt="图形验证码"
@click="genImageCode">
</el-form-item>
<!-- 点击注册 -->
<el-form-item>
<el-button
size="medium"
type="primary"
@click="Register"
style="width: 100%"
>注册</el-button
>
</el-form-item>
</el-form>
</el-dialog>
</div>
<script>
// 生成uuid
import {v4 as uuid4} from 'uuid'
export default {
name: "MyRegister",
props: ["register"],
data() {
// 用户名的校验方法
let validateName = (rule, value, callback) => {
if (!value) {
return callback(new Error("请输入用户名"));
}
// 用户名以字母开头,长度在5-16之间,允许字母数字下划线
const userNameRule = /^[a-zA-Z][a-zA-Z0-9_]{4,15}$/;
if (userNameRule.test(value)) {
// 前端校验,用户名复合规则
//请求后端, 判断用户名是否重复
this.$axios
.get("/users/check/username/" + this.RegisterUser.name + "/"
)
.then((res) => {
// 200代表用户名不重复,可以注册
console.log("校验用户名是否重复:", res);
if (res.data.code == 200) {
this.$refs.ruleForm.validateField("checkPass");
return callback();
} else {
// 用户名重复或者不符合规则
return callback(new Error(res.data.msg));
}
})
.catch((err) => {
return Promise.reject(err);
});
} else {
// 前端校验,用户名不符合规则
return callback(new Error("字母开头,长度5-16之间,允许字母数字下划线"));
}
};
// 手机号的校验方法
let validateMobile = (rule, value, callback) => {
if (value === "") {
return callback(new Error("请输入手机号"));
}
// 手机号以1开头,第二位3-9之间的数字,长度为11,只允许数字
const mobileRule = /^1[3-9]\d{9}$/;
if (mobileRule.test(value)) {
this.$axios.get("/users/check/mobile/" + this.RegisterUser.mobile + "/")
.then(res=>{
console.log("验证手机号是否可用:", res)
if(res.data.code == 200){
this.$refs.ruleForm.validateField("checkPass");
return callback();
}else{
return callback(new Error(res.data.msg))
}
}).catch(err=>{
return Promise.reject(err)
})
} else {
return callback(new Error("手机号不符合格式"));
}
};
// 密码的校验方法
let validatePass = (rule, value, callback) => {
if (value === "") {
return callback(new Error("请输入密码"));
}
// 密码以字母开头,长度在6-18之间,允许字母数字和下划线
const passwordRule = /^[a-zA-Z]\w{5,17}$/;
if (passwordRule.test(value)) {
this.$refs.ruleForm.validateField("checkPass");
return callback();
} else {
return callback(
new Error("字母开头,长度6-18之间,允许字母数字和下划线")
);
}
};
// 确认密码的校验方法
let validateConfirmPass = (rule, value, callback) => {
if (value === "") {
return callback(new Error("请输入确认密码"));
}
// 校验是否以密码一致
if (this.RegisterUser.pass != "" && value === this.RegisterUser.pass) {
this.$refs.ruleForm.validateField("checkPass");
return callback();
} else {
return callback(new Error("两次输入的密码不一致"));
}
};
// 校验图片验证码
let validateImageCode = (rule, value, callback) => {
if (value === "") {
return callback(new Error("请输入图片验证码"));
}
// 图片验证码是由字母、数字组成,长度为4
const iamgeCodeRule = /^[a-zA-Z0-9]{4}$/;
if (iamgeCodeRule.test(value)) {
this.$axios.get("/users/check_image_code/", {
params:{
imageCodeID: this.imageCodeID,
imageCode: this.RegisterUser.imageCode,
}
})
.then(res => {
if(res.data.code == 200){
this.$refs.ruleForm.validateField("checkPass");
return callback();
}else{
return callback(new Error(res.data.msg))
}
}).catch(err => {
return Promise.reject(err)
})
} else {
return callback(new Error("图片验证码不正确!"));
}
};
return {
imageCodeID: "",//即生成的uuid
iamgeCodeUrl: "", //图形验证码的地址
isRegister: false, // 控制注册组件是否显示
flag: false,
// 返回的是注册用户信息
RegisterUser: {
name: "",
pass: "",
confirmPass: "",
mobile: "",
imageCode: "",//用户输入的图片验证码
},
// 用户信息校验规则,validator(校验方法),trigger(触发方式),blur为在组件 Input 失去焦点时触发
rules: {
// 这里的属性值,是prop的值
name: [{ validator: validateName, trigger: "blur" }],
pass: [{ validator: validatePass, trigger: "blur" }],
confirmPass: [{ validator: validateConfirmPass, trigger: "blur" }],
mobile: [{ validator: validateMobile, trigger: "blur" }],
imageCode: [{validator: validateImageCode, trigger: "blur"}],
},
};
},
watch: {
// 监听父组件传过来的register变量,设置this.isRegister的值
register: function (val) {
if (val) {
this.isRegister = val;
}
},
// 监听this.isRegister变量的值,更新父组件register变量的值
isRegister: function (val) {
if (!val) {
this.$refs["ruleForm"].resetFields();
this.$emit("fromChild", val);
}
},
},
mounted(){
// DOM节点刚刚完成挂载,生成默认的图形验证码
this.genImageCode()
},
methods: {
// 生成图片验证码地址
genImageCode(){
// 生成一个uuid
this.imageCodeID = uuid4(),
// 生成一个图片验证码地址
this.iamgeCodeUrl = "/users/image_code/" + this.imageCodeID + "/"
},
// 用户注册
Register() {
// 通过element自定义表单校验规则,校验用户输入的用户信息
this.$refs["ruleForm"].validate((valid) => {
//如果通过校验开始注册
if (valid) {
this.$axios
.post("/users/register/", {
userName: this.RegisterUser.name,
pwd: this.RegisterUser.pass,
mobile: this.RegisterUser.mobile,
agree: this.aggree,
})
.then((res) => {
// 200代表注册成功,其他的均为失败
if (res.data.code == 200) {
// 隐藏注册组件
this.isRegister = false;
// 弹出通知框提示注册成功信息
this.notifySucceed(res.data.msg);
} else {
// 弹出通知框提示注册失败信息
this.notifyError(res.data.msg);
}
})
.catch((err) => {
return Promise.reject(err);
});
} else {
return false;
}
});
},
},
};
</script>
2.登录视图 (element-ui)
- 使用v-model绑定表单,获取用户信息
- 点击登录按钮,执行登录方法
-
axios的post请求提交参数
-
接收响应
-
登录成功,状态保持
<div id="myLogin">
<el-dialog title="登录" width="300px" center :visible.sync="isLogin">
<el-form
:model="LoginUser"
:rules="rules"
status-icon
ref="ruleForm"
class="demo-ruleForm"
>
<!-- 用户名 -->
<el-form-item prop="name">
<el-input
prefix-icon="el-icon-user-solid"
placeholder="请输入账号"
v-model="LoginUser.name"
></el-input>
</el-form-item>
<!-- 用户密码 -->
<el-form-item prop="pass">
<el-input
prefix-icon="el-icon-view"
type="password"
placeholder="请输入密码"
v-model="LoginUser.pass"
></el-input>
</el-form-item>
<!-- 点击登录 -->
<el-form-item>
<el-button
size="medium"
type="primary"
@click="Login"
style="width: 100%"
>登录</el-button
>
</el-form-item>
</el-form>
</el-dialog>
</div>
<script>
import { mapActions } from "vuex";
export default {
// 登录组件
name: "MyLogin",
data() {
// 用户名的校验方法
let validateName = (rule, value, callback) => {
if (!value) {
return callback(new Error("请输入用户名"));
}
// 用户名以字母开头,长度在5-16之间,允许字母数字下划线
const userNameRule = /^[a-zA-Z][a-zA-Z0-9_]{4,15}$|^1\d{10}$/;
// 正则测试输入的用户名
if (userNameRule.test(value)) {
// 选择节点,设置验证通过
this.$refs.ruleForm.validateField("checkPass");
return callback();
} else {//用户名验证未通过
return callback(new Error("字母开头,长度5-16之间,允许字母数字下划线"));
}
};
// 密码的校验方法
let validatePass = (rule, value, callback) => {
if (value === "") {
return callback(new Error("请输入密码"));
}
// 密码以字母开头,长度在6-18之间,允许字母数字和下划线
const passwordRule = /^[a-zA-Z]\w{5,17}$/;
if (passwordRule.test(value)) {
this.$refs.ruleForm.validateField("checkPass");
return callback();
} else {//密码验证未通过
return callback(
new Error("字母开头,长度6-18之间,允许字母数字和下划线")
);
}
};
return {
LoginUser: {
name: "",
pass: ""
},
// 用户信息校验规则,validator(校验方法),trigger(触发方式),blur为在组件 Input 失去焦点时触发
rules: {
name: [{ validator: validateName, trigger: "blur" }],
pass: [{ validator: validatePass, trigger: "blur" }]
}
};
},
computed: {
// 获取vuex中的showLogin,控制登录组件是否显示
isLogin: {
get() {
return this.$store.getters.getShowLogin;
},
set(val) {
this.$refs["ruleForm"].resetFields();
this.setShowLogin(val);
}
}
},
methods: {
//将store中的actions方法映射到methods
...mapActions(["setUser", "setShowLogin"]),
// 点击登录触发
Login() {
// 通过element自定义表单校验规则,校验用户输入的用户信息
this.$refs["ruleForm"].validate(valid => {
//如果通过校验开始登录
if (valid) {
// 发送ajax
this.$axios.post("/users/login/", {
user: this.LoginUser.name,
pwd: this.LoginUser.pass,
// withCredentials: true,
})
.then(res => {
console.log("@@登录的响应:", res)
// 200代表登录成功,其他的均为失败
if (res.data.code == 200) {
// res.data为后端响应的json
// 隐藏登录组件
this.isLogin = false;
// 登录信息存到本地缓存
let user = JSON.stringify(res.data.user);
console.log("@@user", user)
// 前端存储用户信息,表示登录成功
localStorage.setItem("user", user);
localStorage.setItem("token", res.data.token);
// sessionStorage.setItem("")
// 登录信息存到vuex,控制页面欢迎信息
// console.log("@@res.data.user", res.data.user)
this.setUser(res.data.user);
// 弹出通知框提示登录成功信息
this.notifySucceed(res.data.msg);
} else {//响应不是200
// 清空输入框的校验状态
this.$refs["ruleForm"].resetFields();
// 弹出通知框提示登录失败信息
this.notifyError(res.data.msg);
}
})
.catch(err => {
console.log(err)
return Promise.reject(err);
});
} else {//未通过用户校验
return false;
}
});
}
}
};
</script>