luffy-------------------(三)
一.git冲突解决
1.码云master分支文件克隆到本地
git clone git@gitee.com:doctor_owen/luffyapi.git
2.本地配置线上的账号与邮箱
>: git config user.name "doctor_owen" >: git config user.email "doctor_owen@163.com"
3.创建并切换分支
git checkout -b dev
4.先同步云,再提交 (保证服务器有的本地都有再提交)
git pull oringe dev (两次,出现already)
git push oringe dev
5.冲突基本说明 (从云文件已有位置去pull替换本地没问题,但要在此位置本地去push替换云会发生冲突,需要add-commit同步后再pull)
冲突前提: 1)多个用户协同开发 2)远程仓库与本地仓库的版本不一致:远程仓库被其他开发者更新了,本地仓库你自己也更新了 3)远程仓库与本地仓库更新的文件是同一个文件 远程仓库与本地仓库交互原则: 1)教ssl秘钥提供给远程仓库管理者,拥有远程仓库开发权限 2)clone远程仓库形成开发者的本地仓库 3)交互流程:提交 - 拉取 - 提交 交互流程: 1)先将本地所有操作 提交(add - commit) 到本地版本库 2)拉取(pull) 远程仓库内容 3)如果出现冲突(线下沟通代码),解决冲突,重复 1 2 两个步骤 4)无冲突后 提交(push) 内容到远程仓库
二.登录注册页面搭建
1.在前台把登录注册路由配好
Header.vue:
router.js:
views/Login.vue:
<template> <div class="login box"> <img src="@/assets/img/Loginbg.jpg" alt=""> <div class="login"> <div class="login-title"> <img src="@/assets/img/Logotitle.png" alt=""> <p>帮助有志向的年轻人通过努力学习获得体面的工作和生活!</p> </div> <div class="login_box"> <div class="title"> <span :class="{active: a0}" @click="changeLogin(0)">密码登录</span> <span :class="{active: a1}" @click="changeLogin(1)">短信登录</span> </div> <div class="inp" v-if="login_type==0"> <input v-model="username" type="text" placeholder="用户名 / 手机号码" class="user"> <input v-model="password" type="password" name="" class="pwd" placeholder="密码"> <div id="geetest1"></div> <div class="rember"> <p> <input id="checkbox" type="checkbox" class="no" v-model="remember"/> <span>记住密码</span> </p> <p>忘记密码</p> </div> <button class="login_btn" @click="loginAction">登录</button> <p class="go_login">没有账号 <router-link to="/register">立即注册</router-link></p> </div> <div class="inp" v-show="login_type==1"> <input v-model="mobile" type="text" placeholder="手机号码" class="user"> <div class="sms"> <input v-model="sms" type="text" placeholder="输入验证码" class="user"> <span class="sms_btn" @click="send_sms">{{sms_interval_tips}}</span> </div> <button class="login_btn" @click="loginMobile">登录</button> <p class="go_login">没有账号 <router-link to="/register">立即注册</router-link></p> </div> </div> </div> </div> </template> <script> export default { name: 'Login', data() { return { a0: 1, a1: 0, login_type: 0, username: "", password: "", remember: false, mobile: "", sms: "", is_send: false, // 是否在60s内发送了短信 sms_interval_tips: "获取验证码", } }, methods: { changeLogin(i) { this.login_type = i; if (i) { this.a0 = 0; this.a1 = 1; } else { this.a0 = 1; this.a1 = 0; } }, loginAction() { if (!this.username || !this.password) { return } this.$axios({ url: this.$settings.Host + 'user/login/', method: 'post', data: { 'username': this.username, 'password': this.password } }).then((response) => { // 判断用户是否要记住密码 window.console.log(">>>>", response.data); if (this.remember) { // 记住密码 sessionStorage.clear(); localStorage.token = response.data.token; localStorage.user_name = response.data.user.username; localStorage.user_mobile = response.data.user.mobile; } else { /// 没记住密码 localStorage.clear(); sessionStorage.token = response.data.token; sessionStorage.user_id = response.data.user.username; sessionStorage.user_name = response.data.user.mobile; } // 页面跳转 let _this = this; this.$alert("欢迎回来!", "登录成功!", { confirmButtonText: '确定', callback() { // 跳转页面 _this.$router.go(-1); // 返回上一页 // 进行制定的网站内部地址跳转 // this.$router.push("站内地址"); } }) }).catch((error) => { window.console.log('失败:', error); // 页面跳转 let _this = this; this.$alert("检查账号密码!", "登录失败!", { confirmButtonText: '确定', callback() { _this.username = ''; _this.password = ''; } }); }) }, send_sms() { // 发送短信 if (!/^1[3-9]\d{9}$/.test(this.mobile)) { this.$message({ message: "对不起!手机号码格式有误!" }); return false; } // 判断是否在60s内发送过短信 if (this.is_send) { this.$message({ message: "对不起,不能频繁发送短信验证!" }); return false; } // 请求发送短信 this.$axios({ url: this.$settings.Host + 'user/sms/', method: 'get', params: { mobile: this.mobile } }).then(response => { let msg = response.data.result this.$message({ message: msg, }); if (msg === '短信发送失败') return // 修改短信的发送状态 this.is_send = true; // 设置间隔时间60s let sms_interval_time = 60; // 设置短信发送间隔倒计时,.60s后把is_send改成false let timer = setInterval(() => { if (sms_interval_time <= 1) { clearInterval(timer); this.sms_interval_tips = "获取验证码"; this.is_send = false; // 重新回复点击发送功能的条件 } else { sms_interval_time -= 1; this.sms_interval_tips = `${sms_interval_time}秒后再次获取`; } }, 1000); }).catch(error => { this.$message({ message: error.response.data.result, }) }); }, loginMobile() { // 注册信息提交 if (!/^1[3-9]\d{9}$/.test(this.mobile)) { this.$message({ message: "对不起!手机号码格式有误!" }); return false; } if (this.sms.length < 1) { this.$message({ message: "短信验证码不能为空!" }); return false; } this.$axios({ url: this.$settings.Host + 'user/login/mobile/', method: 'post', data: { mobile: this.mobile, sms: this.sms } }).then(response => { let _this = this; let status = response.data.status; let msg = response.data.msg; _this.$message({ message: msg, onClose() { if (status == 0) { // 保存登录状态 sessionStorage.token = response.data.token; sessionStorage.user_name = response.data.user.username; // sessionStorage.user_mobile = response.data.user.mobile; // 跳转到主页 _this.$router.push('/'); } } }); }).catch(error => { this.$message({ message: error.response.data.result }); }) }, }, }; </script> <style scoped> .box { width: 100%; height: 100%; position: relative; overflow: hidden; } .box img { width: 100%; min-height: 100%; } .box .login { position: absolute; width: 500px; height: 400px; left: 0; margin: auto; right: 0; bottom: 0; top: -338px; } .login .login-title { width: 100%; text-align: center; padding-top: 20px; } .login-title img { width: 190px; height: auto; } .login-title p { font-family: PingFangSC-Regular; font-size: 18px; color: #fff; letter-spacing: .29px; padding-top: 10px; padding-bottom: 50px; } .login_box { width: 400px; height: auto; background: #fff; box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .5); border-radius: 4px; margin: 0 auto; padding-bottom: 40px; } .login_box .title { font-size: 20px; color: #9b9b9b; letter-spacing: .32px; border-bottom: 1px solid #e6e6e6; display: flex; justify-content: space-around; padding: 50px 60px 0 60px; margin-bottom: 20px; cursor: pointer; } .login_box .title span.active { color: #4a4a4a; border-bottom: 2px solid #84cc39; } .inp { width: 350px; margin: 0 auto; } .inp input { outline: 0; width: 100%; height: 45px; border-radius: 4px; border: 1px solid #d9d9d9; text-indent: 20px; font-size: 14px; background: #fff !important; } .inp input.user { margin-bottom: 16px; } .inp .rember { display: flex; justify-content: space-between; align-items: center; position: relative; margin-top: 10px; } .inp .rember p:first-of-type { font-size: 12px; color: #4a4a4a; letter-spacing: .19px; margin-left: 22px; display: -ms-flexbox; display: flex; -ms-flex-align: center; align-items: center; /*position: relative;*/ } .inp .rember p:nth-of-type(2) { font-size: 14px; color: #9b9b9b; letter-spacing: .19px; cursor: pointer; } .inp .rember input { outline: 0; width: 30px; height: 45px; border-radius: 4px; border: 1px solid #d9d9d9; text-indent: 20px; font-size: 14px; background: #fff !important; } .inp .rember p span { display: inline-block; font-size: 12px; width: 100px; /*position: absolute;*/ /*left: 20px;*/ } #geetest { margin-top: 20px; } .login_btn { width: 100%; height: 45px; background: #84cc39; border-radius: 5px; font-size: 16px; color: #fff; letter-spacing: .26px; margin-top: 30px; } .inp .go_login { text-align: center; font-size: 14px; color: #9b9b9b; letter-spacing: .26px; padding-top: 20px; } .inp .go_login a { color: #84cc39; cursor: pointer; } #get_code { border: 0; width: 120px; height: 30px; background-color: antiquewhite; outline: none; } #get_code:active { color: white; } #checkbox { width: 20px; height: 20px; } .sms { position: relative; } .sms .sms_btn { position: absolute; top: -12px; right: 0; bottom: 0; margin: auto; width: 130px; text-align: center; height: 24px; color: #ff7000; cursor: pointer; border-left: 1px solid #999; } </style>
views/Register.vue:
<template> <div class="box"> <img src="@/assets/img/Loginbg.jpg" alt=""> <div class="register"> <div class="register_box"> <div class="register-title">注册路飞学城</div> <div class="inp"> <input v-model="mobile" @blur="checkMobile" type="text" placeholder="手机号码" class="user"> <input v-model="password" type="password" placeholder="用户密码" class="user"> <div class="sms"> <input v-model="sms" type="text" placeholder="输入验证码" class="user"> <span class="sms_btn" @click="send_sms">{{sms_interval_tips}}</span> </div> <div id="geetest"></div> <button class="register_btn" @click="registerMobile">注册</button> <p class="go_login">已有账号 <router-link to="/login">直接登录</router-link> </p> </div> </div> </div> </div> </template> <script> export default { name: 'Register', data() { return { sms: "", mobile: "", password: "", is_send: false, // 是否在60s内发送了短信 sms_interval_tips: "获取验证码", } }, created() { }, methods: { checkMobile() { if (this.mobile.length < 1) { return false; } // 手机号码格式是否正确 if (!/^1[3-9]\d{9}$/.test(this.mobile)) { this.$message({ message: "对不起!手机号码格式有误!" }); return false; } // 验证手机号码是否已经注册了 // this.$axios.get(this.$settings.Host+"/users/mobile/"+this.mobile+"/"); this.$axios({ url: this.$settings.Host + 'user/mobile/', method: 'get', params: { mobile: this.mobile } }).then(response => { let data = response.data; window.console.log(data); if (data.status != 0) { this.$message({ message: "对不起!手机号码已经被注册!" }); return false; } else { this.$message({ message: "期待您加入我们!" }); } }).catch(error => { let data = error.response.data; this.$message({ message: data.message }) }) }, send_sms() { // 发送短信 if (!/^1[3-9]\d{9}$/.test(this.mobile)) { this.$message({ message: "对不起!手机号码格式有误!" }); return false; } // 判断是否在60s内发送过短信 if (this.is_send) { this.$message({ message: "对不起,不能频繁发送短信验证!" }); return false; } // 请求发送短信 this.$axios({ url: this.$settings.Host + 'user/sms/', method: 'get', params: { mobile: this.mobile } }).then(response => { this.$message({ message: response.data.result, }); // 修改短信的发送状态 this.is_send = true; // 设置间隔时间60s let sms_interval_time = 60; // 设置短信发送间隔倒计时,.60s后把is_send改成false let timer = setInterval(() => { if (sms_interval_time <= 1) { clearInterval(timer); this.sms_interval_tips = "获取验证码"; this.is_send = false; // 重新回复点击发送功能的条件 } else { sms_interval_time -= 1; this.sms_interval_tips = `${sms_interval_time}秒后再次获取`; } }, 1000); }).catch(error => { this.$message({ message: error.response.data.result, }) }); }, registerMobile() { // 注册信息提交 if (!/^1[3-9]\d{9}$/.test(this.mobile)) { this.$message({ message: "对不起!手机号码格式有误!" }); return false; } if (this.sms.length < 1) { this.$message({ message: "短信验证码不能为空!" }); return false; } if (this.password.length < 6 || this.password.length > 16) { this.$message({ message: "对不起,密码长度必须在6-16个字符之间!" }); return false; } this.$axios({ url: this.$settings.Host + 'user/register/mobile/', method: 'post', data: { mobile: this.mobile, password: this.password, sms: this.sms } }).then(response => { let _this = this; let status = response.data.status; let msg = response.data.msg; _this.$message({ message: msg, onClose() { if (status == 0) { // 保存登录状态 sessionStorage.user_name = response.data.user.username; // sessionStorage.user_mobile = response.data.user.mobile; // 跳转到用户中心 // _this.$router.push('/user'); } } }); }).catch(error => { this.$message({ message: error.response.data.result }); }) } }, }; </script> <style scoped> .box { width: 100%; height: 100%; position: relative; overflow: hidden; } .box img { width: 100%; min-height: 100%; } .box .register { position: absolute; width: 500px; height: 400px; left: 0; margin: auto; right: 0; bottom: 0; top: -238px; } .register .register-title { width: 100%; font-size: 24px; text-align: center; padding-top: 30px; padding-bottom: 30px; color: #4a4a4a; letter-spacing: .39px; } .register-title img { width: 190px; height: auto; } .register-title p { font-size: 18px; color: #fff; letter-spacing: .29px; padding-top: 10px; padding-bottom: 50px; } .register_box { width: 400px; height: auto; background: #fff; box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .5); border-radius: 4px; margin: 0 auto; padding-bottom: 40px; } .register_box .title { font-size: 20px; color: #9b9b9b; letter-spacing: .32px; border-bottom: 1px solid #e6e6e6; display: flex; justify-content: space-around; padding: 50px 60px 0 60px; margin-bottom: 20px; cursor: pointer; } .register_box .title span:nth-of-type(1) { color: #4a4a4a; border-bottom: 2px solid #84cc39; } .inp { width: 350px; margin: 0 auto; } .inp input { outline: 0; width: 100%; height: 45px; border-radius: 4px; border: 1px solid #d9d9d9; text-indent: 20px; font-size: 14px; background: #fff !important; } .inp input.user { margin-bottom: 16px; } .inp .rember { display: flex; justify-content: space-between; align-items: center; position: relative; margin-top: 10px; } .inp .rember p:first-of-type { font-size: 12px; color: #4a4a4a; letter-spacing: .19px; margin-left: 22px; display: -ms-flexbox; display: flex; -ms-flex-align: center; align-items: center; /*position: relative;*/ } .inp .rember p:nth-of-type(2) { font-size: 14px; color: #9b9b9b; letter-spacing: .19px; cursor: pointer; } .inp .rember input { outline: 0; width: 30px; height: 45px; border-radius: 4px; border: 1px solid #d9d9d9; text-indent: 20px; font-size: 14px; background: #fff !important; } .inp .rember p span { display: inline-block; font-size: 12px; width: 100px; /*position: absolute;*/ /*left: 20px;*/ } #geetest { margin-top: 20px; } .register_btn { width: 100%; height: 45px; background: #84cc39; border-radius: 5px; font-size: 16px; color: #fff; letter-spacing: .26px; margin-top: 30px; } .inp .go_login { text-align: center; font-size: 14px; color: #9b9b9b; letter-spacing: .26px; padding-top: 20px; } .inp .go_login a { color: #84cc39; cursor: pointer; } .sms { position: relative; } .sms .sms_btn { position: absolute; top: -12px; right: 0; bottom: 0; margin: auto; width: 130px; text-align: center; height: 24px; color: #ff7000; cursor: pointer; border-left: 1px solid #999; } </style>
注:记得把一些背景图片放到静态文件的img下
三.腾讯云短信认证
登录:1******5@qq.com
密码:ss**********
短信模板:
使用pythonSDK:
install后在后台luffyapi/libs/txm/settings.py下完成配置:
# 短信应用 SDK AppID 以1400开头 appid = 1400256736 # 短信应用 SDK AppKey appkey = "130664880cd5cf1f3870c1909a95d300" # 需要发送短信的手机号码 # phone_numbers = ["21212313123", "12345678902", "12345678903"] # 短信模板ID,需要在短信控制台中申请 template_id = 420728 # 签名 参数使用的是`签名内容`,而不是`签名ID` sms_sign = "Owen的技术栈"
txm/sms.py:
from qcloudsms_py import SmsSingleSender from .settings import * from utils.logging import logger import random sender = SmsSingleSender(appid, appkey) # 短信发送成功的标识:发送后没有异常,响应的大字典中result值为0 # {'result': 0, 'errmsg': 'OK', 'ext': '', 'sid': '2028:f826a20b647e9cfa4100', 'fee': 1} def send_sms(mobile, code, exp): try: response = sender.send_with_param(86, mobile, template_id, (code, exp), sign=sms_sign, extend="", ext="") except Exception as e: logger.error('sms error: %s' % e) return False if response and response['result'] == 0: return True logger.error('sms error: %s' % response['errmsg']) return False def get_code(): code = '' for i in range(4): code += str(random.randint(0, 9)) return code
from .sms import get_code, send_sms
测试文件:t_sms.py
from libs.txm import send_sms, get_code if __name__ == '__main__': code = get_code() print(code) result = send_sms('18516575654', code, '5') print(result)
主路由配完后在user中完成子路由:
from django.urls import path from . import views urlpatterns = [ path('sms/', views.SMSAPIView.as_view()), ]
user短信视图:
from rest_framework.views import APIView import re from utils.response import APIResponse from libs.txm import get_code, send_sms from django.core.cache import cache class SMSAPIView(APIView): def post(self, request, *args, **kwargs): pass # 拿到前端的手机号 mobile = request.data.get('mobile') # 完成手机号的校验(是否合法) if not mobile or not re.match(r'^1[3-9][0-9]{9}$', mobile): return APIResponse(1, '手机号有误') # 产生验证码 code = get_code() # 通知第三方发送短信 result = send_sms(mobile, code, 5) # 失败:响应前台发送失败 if not result: return APIResponse(1, '短信发送失败') # 成功:缓存验证码(校验备用),响应前台发送成功 cache.set('%s_code' % mobile, code, 5 * 60) # print(cache.get('%s_code' % mobile)) return APIResponse(0, '短信发送成功')
四.缓存数据库Redis
redis: 内存数据库:数据存储在内存中,存取效率极高 nosql数据库:没有mysql那样的表关系,通过 类似字典方式,用 key-value 方式存储数据 高并发支持:单线程单进程并发 数据可持久化:redis中的数据可以保存在硬盘中,支持与mysql等数据库完成数据同步 支持的类型也较多:相比其他内存数据库(memcache) redis支持的数据类型: 字符串:String 字典:Hash 列表:List 无序集合:Set 有序集合:Sorted Set
redis数据库操作指令
前台启动redis服务 >: redis-server 连接redis指定数据库:连接第1个数据库 >: redis-cli -h 127.0.0.1 -p 6379 -n 1 切换数据库:切换到第2个数据库 127.0.0.1:6379[1]>:select 2
命令行简单使用redis:
set key value # 设置值
get key # 取出值
教程地址:https://www.runoob.com/redis/redis-tutorial.html
1.字符串重点命令:
SETEX key seconds value
将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。
MSET key value [key value ...]
同时设置一个或多个 key-value 对。
2.哈希:
HDEL key field1 [field2] 删除一个或多个哈希表字段 HKEYS key 获取所有哈希表中的字段 HGET key field 获取存储在哈希表中指定字段的值。 HGETALL key 获取在哈希表中指定 key 的所有字段和值 HMGET key field1 [field2] 获取所有给定字段的值 HMSET key field1 value1 [field2 value2 ] 同时将多个 field-value (域-值)对设置到哈希表 key 中。 HSET key field value 将哈希表 key 中的字段 field 的值设为 value 。
3.列表
LINDEX key index
通过索引获取列表中的元素
LLEN key
获取列表长度
LPOP key
移出并获取列表的第一个元素
LPUSH key value1 [value2]
将一个或多个值插入到列表头部
LSET key index value
通过索引设置列表元素的值
RPOP key
移除列表的最后一个元素,返回值为移除的元素。
RPOPLPUSH source destination
移除列表的最后一个元素,并将该元素添加到另一个列表并返回
RPUSH key value1 [value2]
在列表中添加一个或多个值
BLPOP key1 [key2 ] timeout
移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
4.集合
SADD key member1 [member2]
向集合添加一个或多个成员
SDIFF key1 [key2]
返回给定所有集合的差集
SINTER key1 [key2]
返回给定所有集合的交集
SINTERSTORE destination key1 [key2]
返回给定所有集合的交集并存储在 destination 中
SREM key member1 [member2]
移除集合中一个或多个成员
SUNION key1 [key2]
返回所有给定集合的并集
5.有序集合
ZADD key score1 member1 [score2 member2]
向有序集合添加一个或多个成员,或者更新已存在成员的分数
ZREVRANK key member
返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序
五.python使用redis
依赖
>: pip3 install redis
import redis r = redis.Redis(host='127.0.0.1', port=6379, db=1) r.setex('name', 'Bob', 60) print(r.get('name'))
连接池使用
import redis pool = redis.ConnectionPool(host='127.0.0.1', port=6379, db=1) r = redis.Redis(connection_pool=pool) r.setex('name', 'Bob', 60) print(r.get('name'))
六.Django使用redis
依赖
>: pip3 install django-redis
settings配置
CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://127.0.0.1:6379/7", # 连入指定数据库 "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", "CONNECTION_POOL_KWARGS": {"max_connections": 100} # 最大连接数 } } }
通过缓存使用
from django.core.cache import cache cache.set(键, 值, 过期时间) print(cache.get(键))
比如t_cache.py:
import os, django os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'luffyapi.settings.dev') django.setup() from apps.home import models, serializers banner_query = models.Banner.objects.all() banner_ser = serializers.BannerModelSerializer(banner_query, many=True) banner_data = banner_ser.data # banner_data虽然可以直接返回给前台(Response还做了二次处理) # 因为banner_data还不是字符串 print(banner_data) # 报错:原生redis无法直接操作Django的序列化数据 # import redis # r7 = redis.Redis(db=7) # r7.setex('banner_data', 60, banner_data) from django.core.cache import cache cache.set('banner_data', banner_data, 60) print(cache.get('banner_data')) # 不混用,缓存配置了redis,存取都用 cache # import redis # r = redis.Redis(db=7) # print(r.get('banner_data'))
测试: