接口缓存、定时更新、异步发送短信

发布时间 2023-07-04 22:34:15作者: 星空看海

一、接口缓存

# 所有接口都可以改造,尤其是查询所有的这种接口,如果加入缓存,会极大的提高查询速度

# 首页轮播图接口:获取轮播图数据,加缓存--->咱们只是以它为例

# 增加接口缓存
from django.core.cache import cache
class BannerView(GenericViewSet, ListModelMixin):
    queryset = Banner.objects.all().filter(is_delete=False, is_show=True).order_by('orders')[:settings.BANNER_COUNT]
    serializer_class = BannerSerializer

    def list(self, request, *args, **kwargs):
        '''
          1 先去缓存中查一下有没有数据
          2 如果有,直接返回,不走父类的list了(list在走数据库)
          3 如果没有,走父类list,查询数据库
          4 把返回的数据,放到缓存中
        '''

        data = cache.get('home_banner_list')
        if not data:  # 缓存中没有
            print('走了数据库')
            res = super().list(request, *args, **kwargs)  # 查询数据库
            # 返回的数据,放到缓存中
            data = res.data.get('data')  # {code:100,msg:成功,data:[{},{}]}
            cache.set('home_banner_list', data)
        return APIResponse(data=data)


    
# 公司里可能会这么写
	-写一个查询所有带缓存的基类
    -写个装饰器,只要一配置,就自动带缓存
    
    
# 双写一致性问题:缓存数据和数据库数据不一致了
	-写入数据库,删除缓存
    -写入数据库,更新缓存
    -定时更新缓存

二、双写一致性之定时更新

# 一旦加入缓存,就会出现数据不一致的请请求
# 双写一致性问题
	-1 改数据,删缓存
    -2 改数据,改缓存
    -3 定时更新 --- 接口的数据对于实时性要求不高
    
    
# 首页轮播图存在双写一致性问题这个问题
	-第一种方法,以现在的技术水平,做不到 :改数据删缓存(--后期学习,通过信号机制改动)
	-能选择的就是定时更新
    	-轮播图接口--->实时性要求没有那么高
             

celery的定时任务

定时更新轮播图任务:
    1.查询数据库,拿到所有轮播图数据
    2.序列化后,放到缓存中
启动worker、beta两条命令

代码

celery_task/home_task.py

from home.models import Banner
from django.conf import settings
from home.serializer import BannerSerializer
from django.core.cache import cache


@app.task
def update_banner():
    # 1.查询数据库,拿到所有轮播图数据
    banner_list = Banner.objects.filter(is_delete=False, is_show=True).order_by('orders')[:settings.BANNER_COUNT]
    # 2.序列化后
    ser = BannerSerializer(instance=banner_list, many=True)
    # 前端显示缓存中的路径格式是:/media/banner/banner1.png"
    # 如果在视图类中,做序列化,因为视图类中有request对象,所以像图片这种,会自动加前面地址
    # 在这里没有request对象,需要手动拼
    for item in ser.data:  # [{}, {}, {}]
        item['image'] = settings.BACKEND_URL + item['image']
    # 3.放到缓存中
    cache.set('home_banner_list', ser.data)
    return True

celery_task/celery.py

# 定时配置
from datetime import timedelta

app.conf.beat_schedule = {
    'update_banner': {
        'task': 'celery_task.home_task.update_banner',  # 任务关联的函数
        'schedule': timedelta(seconds=5),  # 时间间隔是由公司来定制的可以,间隔一天,或间隔七天
        'args': (),
    },
}

启动worker、beta

(luffy) E:\python project\luffy_api>celery -A celery worker -l info -P eventlet
(luffy) E:\python project\luffy_api>celery -A celery_task beat -l info

三、异步发送短信

步骤

1.在celery_task中写发送短信的任务
2.需要导入短信模块,要记得导入django配置
3.在视图函数中提交异步任务delay(参数)

视图函数user/views.py

	@action(methods=['GET'], detail=False)
    # 发送短信接口
    def send_sms(self, request, *args, **kwargs):
        # 前端需要把要发送的手机号传入 在地址栏中
        mobile = request.query_params.get('mobile', None)
        # 判断手机号是否合法
        if not re.match(r'^1[3-9][0-9]{9}$', mobile):
            raise APIException('手机号不合法')

        code = get_code()  # 把code存起来,放到缓存中,目前在内存,后期换别的
        cache.set('send_sms_code_%s' % mobile, code)
        # 想要从缓存中取
        # cache.get('send_sms_code_%s' % mobile)
        if mobile:
            # 手机号存在,开启线程(target是线程执行任务),异步发送信息
            # thread = Thread(target=send_sms_by_mobile, args=[mobile, code])
            # thread.start()

            # 使用celery异步发送短信
            res = send_sms_task.delay(mobile, code)
            print(res)  # res是id号,要做个任务表记录下id号
            return APIResponse(msg='短信已发送')
        raise APIException('手机号没有携带')

任务celery_task/user_task.py

from .celery import app
from libs.send_tx_sms import send_sms_by_mobile


@app.task
def send_sms_task(mobile, code):
    res = send_sms_by_mobile(mobile, code)
    if res:
        return '%s的短信发送成功' % mobile
    else:
        return '%s的短信发送失败' % mobile

四、异步秒杀逻辑前后端

4.1 前端 Sckill.vue

<template>
    <div class="skill">
        <el-col :span="24">
            <div class="grid-content bg-purple"><h2>go语言从入门到放弃</h2></div>
        </el-col>
        <el-button type="danger" @click="handleSkill">秒杀</el-button>
    </div>
</template>

<script>
export default {
    name: "Skill",
    data() {
        return {
            task_id: '',
            t: null,
        }
    },
    methods: {
        handleSkill() {
            this.$axios.post(`${this.$settings.BASE_URL}`, {
                name: "性感帽子",  // 向后端发送商品信息跟用户信息
            }).then(res => {
                    if (res.data.code === 100) {
                        alert('您正在排队')
                        // 加载图标
                        const loading = this.$loading({
                            lock: true,
                            text: 'Loading',
                            spinner: 'el-icon-loading',
                            background: 'rgba(0, 0, 0, 0.7)'
                        });
                        setTimeout(() => {
                            loading.close();
                        }, 2000);
                        // 获取id号
                        this.task_id = res.data.task_id

                        // 起定时任务
                        this.t = setInterval(() => {
                            this.$axios.get(`${this.$settings.BASE_URL}user/skill/?task_id=${this.task_id}`).then(res => {
                                // 100是成功,102是失败
                                if (res.data.code === 100 || res.data.code === 101) {
                                    this.$message(res.data.msg)
                                    // 销毁定时器
                                    clearInterval(this.t)
                                    this.t = null
                                } else {
                                    console.log('过会再查')
                                }
                            })
                        }, 3000)
                    }
                }
            )
        }
    },
}
</script>

<style scoped>
.bg-purple {
    background: #d3dce6;
}

.grid-content {
    border-radius: 4px;
    min-height: 36px;
}
</style>

4.2 后端

视图类

from celery_task.user_task import skill_goods
from celery_task.celery import app
from celery.result import AsyncResult


class Skill(APIView):
    def post(self, request, *args, **kwargs):
        name = request.data.get('name')
        # 提交秒杀异步任务
        res = skill_goods.delay(name)
        return APIResponse(task_id=str(res))

    def get(self, request, *args, **kwargs):
        task_id = request.query_params.get('task_id')
        asy = AsyncResult(id=task_id, app=app)
        if asy.successful():  # 正常执行完成
            result = asy.get()  # 任务返回的结果
            if result:
                return APIResponse(code=100, msg=('秒杀成功'))
            else:
                return APIResponse(code=101, msg=('秒杀失败'))
        elif asy.status == 'STARTED':
            print('任务已经开始被执行')
            return APIResponse(code=103, msg='还在排队')
        else:
            return APIResponse(code=102, msg='没成功')

路由

urlpatterns = [
    path('sckill/', SckillView.as_view()),
]

任务:celery_task/user_task.py

@app.task
def skill_goods(name):
    # 逻辑是:开启事务 ---> 扣减内存 ---> 生成订单
    import time
    time.sleep(6)
    res = random.choice([100, 102])
    if res == 100:
        print('%s被秒杀成功了' % name)
        return True
    else:
        print('%s被秒杀失败了' % name)
        return False
    
# 在celery_task的路径下,启动worker
celery -A celery_task worker -l info -P eventlet