一.频率限制短信接口
settings/dev.py:
# drf配置
REST_FRAMEWORK = {
# Throttling
'DEFAULT_THROTTLE_RATES': {
'user': None,
'anon': None,
'sms': '1/min'
},
}
apps/user/throttles.py:
from rest_framework.throttling import SimpleRateThrottle
class SMSRateThrottle(SimpleRateThrottle):
scope = 'sms'
def get_cache_key(self, request, view):
# 针对手机号进行限制
mobile = request.data.get('mobile') or request.query_params.get('mobile')
if not mobile:
return None
return self.cache_format % {
'scope': self.scope,
'ident': mobile
}
user/views.py:
from libs.txm import get_code, send_sms
from django.core.cache import cache
from .throttles import SMSRateThrottle
from . import models, serializers
from settings.const import SMS_CODE_EXC
class SMSAPIView(APIView):
throttle_classes = [SMSRateThrottle]
def post(self, request, *args, **kwargs):
# 拿到前端的手机号
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, SMS_CODE_EXC // 60)
# 失败:响应前台发送失败
if not result:
return APIResponse(1, '短信发送失败')
# 成功:缓存验证码(校验备用),响应前台发送成功
cache.set('%s_code' % mobile, code, SMS_CODE_EXC)
# print(cache.get('%s_code' % mobile))
return APIResponse(0, '短信发送成功')
二.手机号验证接口
前台手机号校验register.vue:
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({
url: this.$settings.base_url + '/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: "对不起!手机号码已经被注册!",
duration: 1000
});
return false;
} else {
this.$message({
message: "期待您加入我们!",
duration: 1000,
// onClose: () => {
// console.log('信息框关闭了')
// }
});
}
}).catch(error => {
let data = error.response.data;
this.$message({
message: data.message
})
})
},
...
后台手机号校验:
user/urls
from django.urls import path
from . import views
urlpatterns = [
path('sms/', views.SMSAPIView.as_view()),
path('mobile/', views.MobileAPIView.as_view()),
]
user/views.py
class MobileAPIView(APIView):
def get(self, request, *args, **kwargs):
# 获取手机号
mobile = request.query_params.get('mobile')
if not mobile:
return APIResponse(1, '手机号必须提供')
# 校验手机号是否合法
if not re.match(r'^1[3-9][0-9]{9}$', mobile):
return APIResponse(1, '手机号有误')
# 校验手机号是否存在
try:
models.User.objects.get(mobile=mobile)
return APIResponse(1, '手机号已注册')
except:
return APIResponse(0, '手机号未注册')
三.注册接口实现(用户名是手机号)
settings/const.py:
# 验证码过期时间(s),替换过期时间 SMS_CODE_EXC = 300000
前台发送短信修订register.vue:
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.base_url + '/user/sms/',
method: 'post',
data: {
mobile: this.mobile
}
}).then(response => {
this.$message({
message: response.data.msg,
});
// 修改短信的发送状态
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,
})
});
},
后台注册接口:
user/ url.py
from django.urls import path
from . import views
urlpatterns = [
path('sms/', views.SMSAPIView.as_view()),
path('mobile/', views.MobileAPIView.as_view()),
path('register/', views.RegisterAPIView.as_view()),
path('login/', views.LoginAPIView.as_view()),
path('login/mobile/', views.LoginMobileAPIView.as_view()),
]
views.py
class RegisterAPIView(APIView):
def post(self, request, *args, **kwargs):
# 将请求数据 手机密码验证码 => 账号手机密码验证码
request_data = request.data
request_data['username'] = request_data.get('mobile')
# 校验
user_ser = serializers.UserModelSerializer(data=request_data)
if user_ser.is_valid():
user_ser.save()
return APIResponse(0, '注册成功', results=user_ser.data)
else:
return APIResponse(1, '注册失败', results=user_ser.errors)
序列化类user/serializers.py
from rest_framework import serializers
from . import models
from django.core.cache import cache
import re
class UserModelSerializer(serializers.ModelSerializer):
code = serializers.CharField(write_only=True, min_length=4, max_length=4)
class Meta:
model = models.User
fields = ('username', 'password', 'mobile', 'code')
extra_kwargs = {
'password': {
'write_only': True
}
}
def validate_mobile(self, value):
if not re.match(r'^1[3-9][0-9]{9}$', value):
raise serializers.ValidationError('手机号有误')
return value
def validate(self, attrs):
code = attrs.pop('code')
mobile = attrs.get('mobile')
old_code = cache.get('%s_code' % mobile)
if code != old_code:
raise serializers.ValidationError({'验证码': '验证码有误'})
return attrs
def create(self, validated_data):
return models.User.objects.create_user(**validated_data) #重写create方法,密码为密文
注.手机号注册不能相同,models.py中将mobile字段加一个unique=true
注册前台register.vue:
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.base_url + '/user/register/',
method: 'post',
data: {
mobile: this.mobile,
password: this.password,
code: this.sms
}
}).then(response => {
let status = response.data.status;
let msg = response.data.msg;
if (status == 0) {
this.$message({
message: msg,
duration: 1000,
onClose: () => {
this.$router.push('/login')
}
});
} else {
// 实际根据错误信息,提示错误的具体输入框
this.mobile = '';
this.password = '';
this.sms = '';
this.$message({
message: msg,
duration: 1000,
});
}
}).catch(error => {
this.$message({
message: error.response.data.result
});
})
}
},
四.登录接口的实现
两种登录路由:
from django.urls import path
from . import views
urlpatterns = [
path('sms/', views.SMSAPIView.as_view()),
path('mobile/', views.MobileAPIView.as_view()),
path('register/', views.RegisterAPIView.as_view()),
path('login/', views.LoginAPIView.as_view()),
path('login/mobile/', views.LoginMobileAPIView.as_view()),
]
user/views
首先安装jwt: pip install djangorestframework-jwt
from rest_framework_jwt.serializers import jwt_encode_handler, jwt_payload_handler
class LoginAPIView(APIView):
authentication_classes = ()
permission_classes = ()
def post(self, request, *args, **kwargs):
# username可能携带的不止是用户名,可能还是用户的其它唯一标识 手机号 邮箱
username = request.data.get('username')
password = request.data.get('password')
if not (username and password):
return APIResponse(1, '账号密码必须')
# 如果username匹配上手机号正则 => 可能是手机登录
if re.match(r'^1[3-9][0-9]{9}$', username):
try:
# 手动通过 user 签发 jwt-token
user = models.User.objects.get(mobile=username)
except:
return APIResponse(1, '该手机未注册')
# 邮箱登录 等
# 账号登录
else:
try:
# 手动通过 user 签发 jwt-token
user = models.User.objects.get(username=username)
except:
return APIResponse(1, '该账号未注册')
# 获得用户后,校验密码并签发token
if not user.check_password(password):
return APIResponse(1, '密码错误')
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
return APIResponse(0, 'ok', results={
'username': user.username,
'mobile': user.mobile,
'token': token
})
class LoginMobileAPIView(APIView):
authentication_classes = ()
permission_classes = ()
def post(self, request, *args, **kwargs):
# 手机号获取并校验
mobile = request.data.get('mobile')
code = request.data.get('code')
if not (mobile and code):
return APIResponse(1, '手机号与验证码必须')
# 验证码获取并校验
old_code = cache.get('%s_code' % mobile)
# 验证码实际开发只可以使用一次
# cache.set('%s_code' % mobile, '0000', 1)
if code != old_code:
return APIResponse(1, '验证码错误')
# 通过手机号获取用户
try:
user = models.User.objects.get(mobile=mobile)
except:
return APIResponse(1, '该账号未注册')
# 签发token
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
return APIResponse(0, 'ok', results={
'username': user.username,
'mobile': user.mobile,
'token': token
})
登录前台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: "获取验证码",
}
},
created() {
// 拦截已登录情况
let token = this.$cookies.get('token');
if (token) {
this.$message({
message: '无需重复登录',
duration: 1000,
onClose: () => {
this.$router.go(-1)
}
})
}
},
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.base_url + '/user/login/',
method: 'post',
data: {
'username': this.username,
'password': this.password
}
}).then((response) => {
// 判断用户是否要记住密码
if (this.remember) { // 记住密码,永存
sessionStorage.clear();
localStorage.token = response.data.results.token;
localStorage.username = response.data.results.username;
localStorage.mobile = response.data.results.mobile;
} else { /// 没记住密码,暂存
localStorage.clear();
sessionStorage.token = response.data.results.token;
sessionStorage.username = response.data.results.username;
sessionStorage.mobile = response.data.results.mobile;
}
if (response.data.status == 0) {
let token = response.data.results.token;
this.$cookies.set('token', token, 24 * 60 * 60);
let username = response.data.results.username;
this.$cookies.set('username', username, 24 * 60 * 60);
this.$alert("欢迎回来!", "登录成功!", {
confirmButtonText: '确定',
callback: () => {
// 登录成功去主页
this.$router.push("/");
}
})
} else {
this.username = '';
this.password = '';
}
}).catch(() => {
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.base_url + '/user/sms/',
method: 'post',
data: {
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.base_url + '/user/login/mobile/',
method: 'post',
data: {
mobile: this.mobile,
code: this.sms
}
}).then(response => {
let _this = this;
let status = response.data.status;
let msg = response.data.msg;
if (status == 0) {
// cookie存储token,保存登录状态
let token = response.data.results.token;
_this.$cookies.set('token', token, 24 * 60 * 60);
let username = response.data.results.username;
_this.$cookies.set('username', username, 24 * 60 * 60);
_this.$message({
message: '登录成功',
duration: 1000,
onClose() {
// 保存登录状态
sessionStorage.token = token;
sessionStorage.username = response.data.results.username;
sessionStorage.mobile = response.data.results.mobile;
// 跳转到主页
_this.$router.push('/');
}
});
}
else {
_this.mobile = '';
_this.sms = '';
_this.$message({
message: msg,
});
}
}).catch(error => {
this.mobile = '';
this.sms = '';
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>
五.前台注销
后台token过期时间配置dev.py:
# jwt配置
import datetime
JWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
}
cookie在登录成功后已保存
header.vue:
<template>
<div class="header-box">
<div class="header">
<div class="content">
<div class="logo full-left">
<router-link to="/"><img @click="jump('/')" src="@/assets/img/logo.svg" alt=""></router-link>
</div>
<ul class="nav full-left">
<li><span @click="jump('/course')" :class="this_nav=='/course'?'this':''">免费课</span></li>
<li><span @click="jump('/light-course')" :class="this_nav=='/light-course'?'this':''">轻课</span></li>
<li><span>学位课</span></li>
<li><span>题库</span></li>
<li><span>老男孩教育</span></li>
</ul>
<div class="login-bar full-right">
<div class="shop-cart full-left">
<img src="@/assets/img/cart.svg" alt="">
<span><router-link to="/cart">购物车</router-link></span>
</div>
<div class="login-box full-left">
<div v-if="!is_login">
<router-link to="/login">登录</router-link>
|
<router-link to="/register">注册</router-link>
</div>
<div v-else>
<router-link to="/user">{{ username }}</router-link>
|
<span @click="logoutAction">注销</span>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "Header",
data() {
return {
this_nav: "",
is_login: false,
username: '',
}
},
created() {
this.this_nav = localStorage.this_nav;
this.is_login = this.$cookies.get('token');
this.username = this.$cookies.get('username');
},
methods: {
jump(location) {
localStorage.this_nav = location;
// vue-router除了提供router-link标签跳转页面以外,还提供了 js跳转的方式
this.$router.push(location);
},
logoutAction() {
this.is_login = false;
this.username = '';
this.$cookies.remove('token');
this.$cookies.remove('username');
}
}
}
</script>
六.接口缓存
home/views.py
from rest_framework.generics import ListAPIView
from rest_framework.response import Response
from . import models, serializers
"""
class BannerListAPIView(ListAPIView):
queryset = models.Banner.objects.filter(is_show=True, is_delete=False).order_by('-orders')
serializer_class = serializers.BannerModelSerializer
"""
# 接口缓存
from django.core.cache import cache
class BannerListAPIView(ListAPIView):
def get(self, request, *args, **kwargs):
banner_list_data = cache.get('api_banner_list_data')
if not banner_list_data:
print('查询了数据库')
banner_query = models.Banner.objects.filter(is_show=True, is_delete=False).order_by('-orders')
banner_list_data = serializers.BannerModelSerializer(banner_query, many=True).data
# 建立接口缓存
cache.set('api_banner_list_data', banner_list_data)
return Response(banner_list_data)
不走ListAPIView的Response,返回给前台的数据不带前缀,所以banner.vue需加前缀才能访问:
<template>
<div class="banner">
<el-carousel height="520px" :interval="3000" arrow="always">
<!--<el-carousel-item>-->
<!--<img src="@/assets/img/banner1.png" alt="">-->
<!--</el-carousel-item>-->
<!--<el-carousel-item>-->
<!--<img src="@/assets/img/banner2.png" alt="">-->
<!--</el-carousel-item>-->
<!--<el-carousel-item>-->
<!--<img src="@/assets/img/banner3.png" alt="">-->
<!--</el-carousel-item>-->
<el-carousel-item v-for="banner in banner_list" :key="banner.image">
<!--<router-link :to="banner.link">-->
<!--<img :src="banner.image" alt="">-->
<!--</router-link>-->
<a :href="banner.link">
<img :src="$settings.base_url + banner.image" alt="">
</a>
</el-carousel-item>
</el-carousel>
</div>
</template>
访问量过多,缓存也无法减缓数据库的访问压力,需要借助celery
七. Celery
官方
Celery 官网:http://www.celeryproject.org/
Celery 官方文档英文版:http://docs.celeryproject.org/en/latest/index.html
Celery 官方文档中文版:http://docs.jinkan.org/docs/celery/
Celery架构
Celery的架构由三部分组成,消息中间件(message broker)、任务执行单元(worker)和 任务执行结果存储(task result store)组成。
消息中间件
Celery本身不提供消息服务,但是可以方便的和第三方提供的消息中间件集成。包括,RabbitMQ, Redis等等
任务执行单元
Worker是Celery提供的任务执行的单元,worker并发的运行在分布式的系统节点中。
任务结果存储
Task result store用来存储Worker执行的任务的结果,Celery支持以不同方式存储任务的结果,包括AMQP, redis等
使用场景
异步任务:将耗时操作任务提交给Celery去异步执行,比如发送短信/邮件、消息推送、音视频处理等等
定时任务:定时执行某件事情,比如每天数据统计
Celery的安装配置
pip install celery
消息中间件:RabbitMQ/Redis
app=Celery('任务名', broker='xxx', backend='xxx')
包架构封装
project ├── celery_task # celery包 │ ├── __init__.py # 包文件 │ ├── celery.py # celery连接和配置相关文件,且名字必须叫celery.py │ └── tasks.py # 所有任务函数 ├── add_task.py # 添加任务 └── get_result.py # 获取结果
安装: pip install celery
celery.py:
# 1)创建app + 任务 # 2)启动celery(app)服务: # 非windows # 命令:celery worker -A celery_task -l info # windows: # pip3 install eventlet # celery worker -A celery_task -l info -P eventlet # 3)添加任务:手动添加,要自定义添加任务的脚本,右键执行脚本 # 4)获取结果:手动获取,要自定义获取任务的脚本,右键执行脚本
代码:
from celery import Celery """ broker=存储tasks的仓库 backend=存储results的仓库 include=[任务文件们] """ broker = 'redis://127.0.0.1:6379/11' backend = 'redis://127.0.0.1:6379/12' include = ['celery_task_1.task1', 'celery_task_1.task2'] app = Celery(broker=broker, backend=backend, include=include) # 启动celery服务的命令: # 前提:一定要进入celery_task所属的文件夹(上一层) # celery worker -A celery_task_1 -l info -P eventlet
基本使用(可写多个task文件,也可把多个函数写在一个task.py文件中):
tasks.py
from .celery import app
import time, random
# 任务就是一个个的功能函数,功能函数的返回值就是任务的执行结果
@app.task
def add(n, m):
res = n + m
# 模拟不固定耗时操作
time.sleep(random.randint(1, 5))
print(res)
return res
add_task.py:
# 手动添加任务的文件,该文件不是必须的(可以自动添加任务)
# 该文件也不属于 celery task 包的一部分,就是用来实现手动添加任务的
# 将add添加到broker仓库,celery会去异步执行
from celery_task import tasks
# 添加立即执行任务
t1 = tasks.add.delay(10, 20)
t2 = tasks.low.delay(100, 50)
print(t1.id)
# 添加延迟任务
from datetime import datetime, timedelta
def eta_second(second):
ctime = datetime.now()
utc_ctime = datetime.utcfromtimestamp(ctime.timestamp())
time_delay = timedelta(seconds=second)
return utc_ctime + time_delay
tasks.low.apply_async(args=(200, 50), eta=eta_second(10))
get_result.py:
from celery_task_1.celery import app
from celery.result import AsyncResult
id = '4e249f2d-559a-4a3e-8b43-d498b3d6355e'
if __name__ == '__main__':
async = AsyncResult(id=id, app=app)
if async.successful():
result = async.get()
print(result)
elif async.failed():
print('任务失败')
elif async.status == 'PENDING':
print('任务等待中被执行')
elif async.status == 'RETRY':
print('任务异常后正在重试')
elif async.status == 'STARTED':
print('任务已经开始被执行')
自动添加(没有add_task.py文件):
celery.py
from celery import Celery
broker = 'redis://127.0.0.1:6379/11'
backend = 'redis://127.0.0.1:6379/12'
include = ['celery_task.tasks',]
app = Celery(broker=broker, backend=backend, include=include)
# 启动celery服务的命令:
# 前提:一定要进入celery_task所属的文件夹
# celery worker -A celery_task -l info -P eventlet
# 自动添加任务
# 时区
app.conf.timezone = 'Asia/Shanghai'
# 是否使用UTC
app.conf.enable_utc = False
# 任务的定时配置
from datetime import timedelta
from celery.schedules import crontab
app.conf.beat_schedule = {
'low-task': {
'task': 'celery_task.tasks.low',
'schedule': timedelta(seconds=3),
# 'schedule': crontab(hour=8, day_of_week=1), # 每周一早八点
'args': (300, 150),
},
'my-add-task': {
'task': 'celery_task.tasks.add',
'schedule': timedelta(seconds=6),
'args': (300, 150),
}
}
# 启动 添加任务 服务的命令,也要进入celery_task所属文件夹
# celery beat -A celery_task -l info
tasks.py:
from .celery import app
@app.task
def add(n, m):
res = n + m
print(res)
return res
@app.task
def low(n, m):
res = n - m
print(res)
return res
get_result.py同上
celery异步处理django任务(把celery_task放在大luffy下):
celery.py
# 启动django依赖
# 将celery服务框架放在项目根目录下
# import sys
# sys.path.append(r'C:\Users\oldboy\Desktop\luffy\luffyapi')
import os, django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'luffyapi.settings.dev')
django.setup()
from celery import Celery
broker = 'redis://127.0.0.1:6379/11'
backend = 'redis://127.0.0.1:6379/12'
include = ['celery_task.tasks',]
app = Celery(broker=broker, backend=backend, include=include)
# 启动celery服务的命令:
# 前提:一定要进入celery_task所属的文件夹
# celery worker -A celery_task -l info -P eventlet
# 自动添加任务
# 时区
app.conf.timezone = 'Asia/Shanghai'
# 是否使用UTC
app.conf.enable_utc = False
# 任务的定时配置
from datetime import timedelta
from celery.schedules import crontab
app.conf.beat_schedule = {
'django-task': {
'task': 'celery_task.tasks.django_task',
'schedule': timedelta(seconds=10),
# 'schedule': crontab(hour=8, day_of_week=1), # 每周一早八点
'args': (),
},
}
# 启动 添加任务 服务的命令
# celery beat -A celery_task -l info
tasks.py:
from .celery import app
from django.core.cache import cache
from apps.home import models, serializers
@app.task
def django_task():
banner_query = models.Banner.objects.filter(is_show=True, is_delete=False).order_by('-orders')
banner_list_data = serializers.BannerModelSerializer(banner_query, many=True).data
# 建立接口缓存
cache.set('api_banner_list_data', banner_list_data)
return '轮播图缓存更新完毕'