注册功能
注册功能分析
分析:
前端:携带数据格式{mobile: ,code: ,password: }
后端:
1、视图类---》注册方法
2、序列化类---》校验、保存
视图类
class UserLoginView(GenericViewSet): serializer_class = UserLoginSerializer # queryset没有可以不写 #多方式登录 @action(methods=['POST'], detail=False) def mul_login(self, request, *args, **kwargs): return self._login(request, *args, **kwargs) # 短信登录 @action(methods=['POST'], detail=False) def sms_login(self, request, *args, **kwargs): return self._login(request, *args, **kwargs) #如果没有就注册,如果有就登录 @action(methods=['POST'], detail=False) def mobile_register(self,request,*args,**kwargs): return self._login(request,*args,**kwargs) #不同路由不同序列化 def get_serializer_class(self): if self.action == 'sms_login': return SMSLoginSerializer elif self.action == 'mul_login': return UserLoginSerializer elif self.action == 'mobile_register': return MobileRegisterSerializer else: return super().get_serializer_class() #登录接口 def _login(self, request, *args, **kwargs): ser = self.get_serializer(data=request.data) ser.is_valid(raise_exception=True) username = ser.context.get('username') token = ser.context.get('token') icon = ser.context.get('icon') # icon 必须是字符串形式,不能是对象相似 # {code:100,msg:成功,token:asdfasf,icon:asdfasdf,username:asdfasd} return APIResponse(username=username, token=token, iocn=icon) # {code:100,msg:成功,token:asdfasf,user:{id:1,username:xxx,icon:ssss}} # return APIResponse(token=token, user=ser.data) # 如果执行ser.data,就会走序列化 # def password_change(self,request,*args, **kwargs): # #从密码修改表中找数据,如果旧密码和新密码一致,就不能修改 # #找到旧密码 # mobile = request.data.get('mobile') # password = request.data.get('password') # 继承CreateModelMixin的create class RegisterView(GenericViewSet, CreateModelMixin): serializer_class = UserRegisterSerializer @action(methods=['POST'], detail=False) def user_register(self, request, *args, **kwargs): # 内部执行了它:serializer.data---》走序列化---》基于create返回的user做序列化---》按照'mobile', 'code', 'password' 做序列化---》code不是表中字段,就报错了 res = super().create(request, *args, **kwargs) return APIResponse(msg='注册成功') # ser = self.get_serializer(data=request.data) # print(111) # ser.is_valid(raise_exception=True) # ser.save() # return APIResponse(msg='恭喜您注册成功')
序列化类
# 用户名密码注册序列化 class UserRegisterSerializer(serializers.ModelSerializer): code = serializers.CharField(max_length=6, write_only=True) class Meta: model = User fields = ['mobile', 'password', 'code'] def validate(self, attrs): mobile = attrs.get('mobile') code = attrs.get('code') old_code = cache.get('cache_mobile_%s' % mobile) if old_code == code or (settings.DEBUG and code == '8888'): attrs['username'] = mobile attrs.pop('code') else: raise ValidationError('验证码错误') return attrs # 重写create def create(self, validated_data): user = User.objects.create_user(**validated_data) return user # 短信注册序列化 class MobileRegisterSerializer(serializers.ModelSerializer, LoginSerializer): code = serializers.CharField(max_length=6, write_only=True) # 数据库中没有数据,所以反序列化 class Meta: model = User fields = ['mobile', 'code'] def _get_user(self, attrs): print(attrs) mobile = attrs.get('mobile') print(mobile) code = attrs.get('code') print(code) # 验证code是否正确 old_code = cache.get('cache_mobile_%s' % mobile) if old_code == code or (settings.DEBUG and code == '8888'): # 根据手机号,取用户 user = User.objects.filter(mobile=mobile).first() print(user) if user: return user else: attrs['username'] = mobile attrs['mobile'] = mobile attrs.pop('code') attrs['password'] = '123456' print(attrs) user = User.objects.create_user(**attrs) return user else: raise ValidationError('验证码不正确')
为什么写media才能访问?
django 默认是不允许前端直接访问某个文件夹的
但是有个static文件夹例外,需要额外配置
如果想让用户访问,必须配置路由,使用serve函数放开
path('media/<path:path>', serve, {'document_root': settings.MEDIA_ROOT})
浏览器中访问media/icon/1.png---》能把settings.MEDIA_ROOT对应的文件夹下的icon/1.png返回给前端
path('lqz/<path:path>', serve, {'document_root':os.path.join(BASE_DIR, 'lqz')})
浏览器中访问 lqz/a.txt----->能把项目根路径下lqz对应的文件夹下的a.txt返回给前端
配置文件中debug作用
开发阶段,debug为True,信息会显示的更丰富
访问的路由如果不存在,就会把能访问的路由都显示出来
程序出现异常,错误信息就会直接在浏览器上
自动重启,只要改了代码,就会自动重启
上线阶段,要改成False
ALLOWED_HOSTS的作用
只有debug为False时,这个必须填
限制后端项目(django项目)能够部署在哪个ip的机器上,写个*,表示所有地址都可以
万能验证码
在项目中,为了知道是在调试模式还是上线模式,所以才用debug这个字段
判断,如果是开发阶段,可以有个万能验证码
短信登录或者注册接口
app只需要输入手机号和验证码,如果账号不存在,直接注册成功,并且登录成功,如果账号存在就是登录
前端传入:{mobiel,code} ---》验证验证码是否正确--》拿着mobile去数据库查询,如果能查到,就说明注册过了,直接签发token返回———》如果查不到,没有注册过---》走注册逻辑完成注册——》再签发token,返回给前端
这个接口,随机生成6位密码,以短信形式通知用户
这个接口,密码为空,下次用账号密码登录,不允许登录
后续改密码:user.set_password(new_password)
查密码check_password()
前端登录注册及页面
登录,注册,都写成组件----》在任意页面中,都能点击显示登录模态框
写好的组件,应该放在那个组件中----》不是页面组件(小组件)
点击登录按钮,把Login.vue 通过定位,占满全屏,透明度设为 0.5 ,纯黑色悲剧,覆盖在组件上
在Login.vue点关闭,要把Login.vue隐藏起来,父子通信之子传父,自定义事件
## 页面中 <span @click="go_login">登录</span> <Login v-if="login_show" @close_login="close_login"></Login> ## data login_show: false ### methods go_login() { this.login_show = true }, close_login() { this.login_show = false }
<template> <div class="login"> <span @click="close">X</span> </div> </template> <script> export default { name: "Login", methods: { close() { this.$emit('close_login') } } } </script> <style scoped> .login { width: 100vw; height: 100vh; position: fixed; top: 0; left: 0; z-index: 10; background-color: rgba(0, 0, 0, 0.5); } </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 @click="mobile_login" type="primary">登录</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; // js正则:/正则语法/ // '字符串'.match(/正则语法/) 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/check_mobile/?mobile=' + this.mobile, method: 'get', }).then(response => { if (response.data.code == 100) { this.$message({ message: '账号正常,可以发送短信', type: 'success', duration: 1000, }); // 发生验证码按钮才可以被点击 this.is_send = true; } else { this.$message({ message: '账号不存在', type: 'warning', duration: 1000, onClose: () => { this.mobile = ''; } }) } }).catch(() => { }); }, send_sms() { // this.is_send必须允许发生验证码,才可以往下执行逻辑 if (!this.is_send) return; // 按钮点一次立即禁用 this.is_send = false; let sms_interval_time = 60; this.sms_interval = "发送中..."; // 定时器: setInterval(fn, time, args) // 往后台发送验证码 this.$axios({ url: this.$settings.BASE_URL + 'user/mobile/send_sms/', method: 'post', data: { mobile: this.mobile } }).then(response => { if (response.data.code == 100) { // 发送成功 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); } else { // 发送失败 this.sms_interval = "重新获取"; this.is_send = true; this.$message({ message: '短信发送失败', type: 'warning', duration: 3000 }); } }).catch(() => { this.sms_interval = "频率过快"; this.is_send = true; }) }, login() { if (!(this.username && this.password)) { this.$message({ message: '请填好账号密码', type: 'warning', duration: 1500 }); return false // 直接结束逻辑 } this.$axios({ url: this.$settings.BASE_URL + 'user/login/mul_login/', method: 'post', data: { username: this.username, password: this.password, } }).then(response => { let username = response.data.username; let token = response.data.token; this.$cookies.set('username', username, '7d'); this.$cookies.set('token', token, '7d'); this.$emit('success', response.data); }).catch(error => { console.log(error.response.data) }) }, mobile_login() { if (!(this.mobile && this.sms)) { this.$message({ message: '请填好手机与验证码', type: 'warning', duration: 1500 }); return false // 直接结束逻辑 } this.$axios({ url: this.$settings.BASE_URL + 'user/login/sms_login/', method: 'post', data: { mobile: this.mobile, code: this.sms, } }).then(response => { if (response.data.code == 100) { let username = response.data.username; let token = response.data.token; this.$cookies.set('username', username, '7d'); this.$cookies.set('token', token, '7d'); this.$emit('success', response.data); } else { this.$message({ message: '登录失败', type: 'warning', duration: 1500 }); } }).catch(error => { console.log(error.response.data) }) } } } </script> <style scoped> .login { width: 100vw; height: 100vh; position: fixed; top: 0; left: 0; z-index: 10; background-color: rgba(0, 0, 0, 0.5); } .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 @click="register" type="primary">注册</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; // js正则:/正则语法/ // '字符串'.match(/正则语法/) 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/check_mobile/?mobile='+this.mobile, method: 'get', }).then(response => { if (response.data.code!=100) { this.$message({ message: '欢迎注册我们的平台', type: 'success', duration: 1500, }); // 发生验证码按钮才可以被点击 this.is_send = true; } else { this.$message({ message: '账号已存在,请直接登录', type: 'warning', duration: 1500, }) } }).catch(() => { }); }, send_sms() { // this.is_send必须允许发生验证码,才可以往下执行逻辑 if (!this.is_send) return; // 按钮点一次立即禁用 this.is_send = false; let sms_interval_time = 60; this.sms_interval = "发送中..."; // 定时器: setInterval(fn, time, args) // 往后台发送验证码 this.$axios({ url: this.$settings.BASE_URL + 'user/mobile/send_sms/', method: 'post', data: { mobile: this.mobile } }).then(response => { if (response.data.code==100) { // 发送成功 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); } else { // 发送失败 this.sms_interval = "重新获取"; this.is_send = true; this.$message({ message: '短信发送失败', type: 'warning', duration: 3000 }); } }).catch(() => { this.sms_interval = "频率过快"; this.is_send = true; }) }, register() { if (!(this.mobile && this.sms && this.password)) { this.$message({ message: '请填好手机、密码与验证码', type: 'warning', duration: 1500 }); return false // 直接结束逻辑 } this.$axios({ url: this.$settings.BASE_URL + 'user/register/register/', method: 'post', data: { mobile: this.mobile, code: this.sms, password: this.password } }).then(response => { this.$message({ message: '注册成功,3秒跳转登录页面', type: 'success', duration: 3000, showClose: true, onClose: () => { // 去向成功页面 this.$emit('success') } }); }).catch(error => { this.$message({ message: '注册失败,请重新注册', type: 'warning', duration: 1500, showClose: true, 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>
<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('/actual-course')" :class="{active: url_path === '/actual-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="!username"> <span @click="handleLogin">登录</span> <span class="line">|</span> <span @click="handleRegister">注册</span> </div> <div v-else> <span>{{ username }}</span> <span class="line">|</span> <span @click="logOut">注销</span> </div> </div> <Login v-if="loginShow" @close="handleClose" @success="success_login" @go="go_register"></Login> <Register v-if="registerShow" @close="handleRegisterClose" @success="success_register" @go="success_register"></Register> </div> </div> </template> <script> import Login from "@/components/Login.vue"; import Register from "@/components/Register.vue"; export default { name: "Header", data() { return { url_path: sessionStorage.url_path || '/', loginShow: false, registerShow: false, username: '', token: '' } }, methods: { handleRegister() { this.registerShow = true }, handleRegisterClose() { this.registerShow = false }, success_register() { this.registerShow = false this.loginShow = true }, go_register() { this.registerShow = true this.loginShow = false }, logOut() { this.username = '' this.token = '' this.$cookies.remove('username') this.$cookies.remove('token') }, goPage(url_path) { // 已经是当前路由就没有必要重新跳转 if (this.url_path !== url_path) { this.$router.push(url_path); } sessionStorage.url_path = url_path; }, handleLogin() { this.loginShow = true }, handleClose() { this.loginShow = false }, success_login(data) { this.loginShow = false; // 模态框消耗 this.username = data.username; this.token = data.token; } }, created() { sessionStorage.url_path = this.$route.path; this.url_path = this.$route.path; this.username = this.$cookies.get('username') }, components: { Register, Login } } </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; } </style>
mysql 关系型数据库(oracle,sqlserver,postgrasql)
非关系型数据(no sql):redis,mongodb,clickhouse,infludb,elasticsearch,hadoop。。。
没有sql:没有sql语句
not olny sql 不仅仅是sql
redis:一款纯内存存储的非关系型数据库(数据都在内存),速度非常快
2、redis特点:
https://www.cnblogs.com/liuqingzheng/articles/9833534.html
redis是一个key-value存储系统
数据类型丰富,支持5大数据类型:字符串,列表,hash(字典),集合,有序集合
纯内存操作
可以持久化:能都把内存数据,保存到硬盘上永久存储
3、redis为什么这快
1、 纯内存,减少io
2 、使用了 io多路复用的 epoll 网络模型
3、数据操作是单线程,避免了线程间切换
多个客户端同时操作,不会存在并发安全问题
4、安装
redis:最新是7, 公司里5,6比较多
redis:开源软件,免费的,他们不支持win
epoll模型不支持win
微软官方:基于源码修改---》编译成可执行文件
第三方:https://github.com/tporadowski/redis/releases/
win:下载安装包,一路下一步
安装目录在环境变量中:任意路径敲 redis-server reidis-cli 都能找到
把redis做成了服务,以后通过服务启动即可
mac:官网下载,解压即可
win,mac:两个可执行文件:
redis-server :等同于 mysqld
reidis-cli :等同于mysql
5 、启动,连接
5.1 启动方式
使用服务启动
redis-server redis.windows-service.conf
使用命令启动
redis-server
5.2 连接
redis-cli
redis-cli -h 地址 -p 端口(默认端口6379)
5.3 图形化客户端(Navicate)
resp:后来收费了
连接上发现有16个库
6 放值
使用resp放入值
7、取值
cmd中 连接:get key