Flask项目之手机端租房网站的实战开发(十)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_41782425/article/details/86512594

说明:该篇博客是博主一字一码编写的,实属不易,请尊重原创,谢谢大家!

接着上一篇博客继续往下写 :https://blog.csdn.net/qq_41782425/article/details/86488529

目录

一丶区县信息前端编写

二丶发布新房源后端接口编写

三丶发布新房源前端编写

四丶测试接口

五丶celery的使用

六丶测试celery

七丶celery以目录形式创建


一丶区县信息前端编写

1.分析,当用户进入发布新房源,页面加载完毕即向后端发送请求拿取数据库中的区域信息数据,显示在页面上所在区县栏

2.所以需在此页面newhouse.js中进行如下编写

//当页面加载完成时,想后端获取区县信息
$.get("/api/v1.0/areas", function (resp) {
    if (resp.errno == "0"){
        var areas = resp.data;
        for (i=0; i<areas.length; i++){
            var area = areas[i]
            $("#area-id").append('<option value="'+area.aid+'">'+area.aname+'</option>')
        }
    }else {
        alert(resp.errmsg)
    }
}, "json")

3.当通过js向后端获取区域信息全部对象,通过循环遍历获取每个对象,将每个对象中的aid以及aname通过append方式追加到id为area_id的select标签下,所以最后需要在newhouse.html中将原有的option标签删除

<select class="form-control" id="area-id" name="area_id">
  <!--<option value="1">锦江区</option>-->
  <!--<option value="2">青羊区</option>-->
  <!--<option value="3">金牛区</option>-->
  <!--<option value="4">武侯区</option>-->
  <!--<option value="5">成华区</option>-->
  <!--<option value="6">龙泉驿区</option>-->
  <!--<option value="7">青白江区</option>-->
  <!--<option value="8">新都区</option>-->
  <!--<option value="9">温江区</option>-->
  <!--<option value="10">郫都区</option>-->
  <!--<option value="11">双流区</option>-->
  <!--<option value="12">高新区</option>-->
  <!--<option value="13">天府新区</option>-->
  <!--<option value="14">新津县</option>-->
  <!--<option value="15">大邑县</option>-->
  <!--<option value="16">金堂县</option>-->
</select>

4.测试,重新启动程序,清除页面缓存,查看发布房源页面中的所有区县栏是否有数据,此时显示的区县数据,就是从后端数据库获取的

 5.查看网页Network,从api/v1.0/area接口获取的数据

6.当前端页面需要从后端拿取很多数据时候,如果用上面的方式通过jquery想后端获取数据,再填充到标签中,这样就太麻烦了,所以这里需要在前端去引入一个js模板,来帮助我们更快的完成页面数据的填充

  • step1 比如在房间信息详情页面,需要很多数据

7.将发布新房源页面中的所在区县栏使用前端js模板进行改写

  • step1 在newhouse.html中引入template.js
<script src="/static/js/template.js"></script>
  • step2 在newhouse.html中定义模板
<script type="text/html" id="areas-template">
    {{ each areas as area }}
    <option value="{{area.aid}}">{{area.aname}}</option>
    {{ /each }}
</script>   
  •  step3 在newhouse.js中使用js模板
var html_text =template("areas-template", {areas:areas});
$("#area-id").html(html_text)
  • step4 测试,清除缓存,刷新网页,查看结果使用js模板成功

二丶发布新房源后端接口编写

1.流程分析,在发布房源前端文件newhouse.html中将该页面下的上传图片栏隐藏了#form-house-image,当用户完成先关填写后,该上传房屋图片表单才会显示出来

  • step1 在浏览器开发端进行显示

  •  step2 查看发布房源界面

2.在house.py中进行房屋信息接口编写

  • step1 创建视图
@api.route("/houses/info", methods=["POST"])
@login_required
def save_house_info():
    """
    保存房屋信息
    :return: 
    """
    pass
  • step2 获取数据
user_id = g.user_id
house_data = request.get_json()
  • step3 从获取的data数据中拿取必填字段的值,facility配套设施可能啥都没有,前端就会传个空列表
title = house_data.get("title")  # 房屋名称标题
price = house_data.get("price")  # 房屋单价
area_id = house_data.get("area_id")  # 房屋所属城区的编号
address = house_data.get("address")  # 房屋地址
room_count = house_data.get("room_count")  # 房屋包含的房间数目
acreage = house_data.get("acreage")  # 房屋面积
unit = house_data.get("unit")  # 房屋布局(几室几厅)
capacity = house_data.get("capacity")  # 房屋容纳人数
beds = house_data.get("beds")  # 房屋卧床数目
deposit = house_data.get("deposit")  # 押金
min_days = house_data.get("min_days")  # 最小入住天数
max_days = house_data.get("max_days")  # 最大入住天数
  • step4 校验参数
if not all([title, price, area_id, address, room_count, acreage, unit,capacity, beds, deposit, min_days, max_days]):
    return jsonify(errno=RET.PARAMERR, errmsg="参数不完整")
  • step5 判断用户输入的房屋单价和押金是否为正确参数,通过存入数据库字段单位分,如果用户输入的值不能转换为float和int类型,说明参数错误
try:
    price = int(float(price)*100)
    deposit = int(float(deposit)*100)
except Exception as e:
    current_app.logger.error(e)
    return jsonify(errno=RET.PARAMERR, errmsg="参数错误")
  • step6 判断区县id是否存在
try:
    area = Area.query.get(area_id)
except Exception as e:
    current_app.logger.error(e)
    return jsonify(errno=RET.DBERR, errmsg="数据库异常")
  • step7 如果在数据库中查询不到区域信息,表示区县信息有误
if area is None:
    return jsonify(errno=RET.NODATA, errmsg="区县信息有误")
  • step8 保存房屋信息到数据库
house = House(
    user_id = user_id,
    title = title,
    price = price,
    area_id = area_id,
    address = address,
    room_count = room_count,
    acreage = acreage,
    unit = unit,
    capacity = capacity,
    beds = beds,
    deposit = deposit,
    min_days = min_days,
    max_days = max_days
)
 
  • step9 获取设备设施数据id值
facility_ids = house_data.get("facility")
  • step10 对获取设备设施字段的值进行判断,下判断这个值存不存在,当用户勾选设备设施时,举例facility_ids值为[2,4]
if facility_ids:
    # 通过Facility类中的id值使用in_方法查询其中的id
    # select * from ih_facility_info where id in facility_ids;
    try:
        facilities = Facility.query.filter(Facility.id.in_(facility_ids)).all()
    except Exception as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.DBERR, errmsg="数据库异常") 
  • step11 判断查询的每个facility对象是否存在,存在则保存设施数据
if facilities:
    house.facilities = facilities
    try:
        db.session.add(house)
        db.session.commit()
    except Exception as e:
        current_app.logger.error(e)
        db.session.rollback()
        return jsonify(errno=RET.DBERR, errmsg="保存数据异常")
  • step12 返回正确响应内容
return jsonify(errno=RET.OK, errmsg="OK", data={"house_id":house.id})

3.上传房屋图片接口编写

  • step1 定义视图
@api.route("/houses/image", methods=["POST"])
@login_required
def save_house_image():
    """保存房屋图片"""
    pass
  • step2 获取图片
image_file = request.files.get("house_image")
  • step3 获取图片id
house_id = request.form.get("house_id")
  • step4 校验参数
if not all([image_file, house_id]):
    return jsonify(errno=RET.PARAMERR, errmsg="参数错误")
  • step5 判断房屋是否在,存在才上传到七牛,如果不存在就不上传
try:
    house = House.query.get(house_id)
except Exception as e:
    current_app.logger.error(e)
    return jsonify(errno=RET.DBERR, errmsg="数据库异常")
  • step6 house对象不存在则返回错误信息
if house is None:
    return jsonify(errno=RET.NODATA, errmsg="房屋不存在")
  • step7 获取图片二进制数据
image_data = image_file.read()
  • step8 将图片保存到七牛中
try:
    file_name = storage(image_data)
except Exception as e:
    current_app.logger.error(e)
    return jsonify(errno=RET.THIRDERR, errmsg="上传图片失败")
  • step9 保存图片信息到数据库中
house_image = HouseImage(house_id=house_id, url=file_name)
db.session.add(house_image)
  • step10 当house对象中的index_image_url不存在时,设置网站主页房屋图片
if not house.index_image_url:
    house.index_image_url = file_name
    db.session.add(house)
  • step11 提交到数据库
try:
    db.session.commit()
except Exception as e:
    current_app.logger.error(e)
    db.session.rollback()
    return jsonify(errno=RET.DBERR, errmsg="保存图片异常")
  • step12 拼接图片完整url,最后返回正确响应数据
image_url = constants.QINIU_URL_DOMAIN + file_name
return jsonify(errno=RET.OK, errmsg="OK", data={"image_url":image_url})

三丶发布新房源前端编写

1.在newhouse.js中进行提交房屋信息表单事件编写

$("#form-house-info").submit(function (e) {
})
  • step1 阻止表单默认行为
e.preventDefault();
  • step2 处理表单数据
var data = {};
$("#form-house-info").serializeArray().map(function (x) { data[x.name] = x.value });
  • step3 收集用户勾选的设备id
var facility = [];
$(":checked[name=facility]").each(function (index, x) {facility[index] = $(x).val()});
  • step4 将收集到的用户勾选id存到data数据库里的facility属性中
data.facility = facility;
  • step5 通过ajax方式向后端接口发送请求
$.ajax({
    url:"/api/v1.0/houses/info",
    type:"post",
    contentType:"application/json",
    data:JSON.stringify(data),
    dataType:"json",
    headers:{
        "X-CSRFToken": getCookie("csrf_token")
    },
    success: function (resp) {
        if (resp.errno == "4101"){
            location.href = "/login.html"
        } else if (resp.errno == "0"){
            // 将设备设施表单隐藏
            $("#form-house-info").hide();

            // 将上传房屋图片表单显示
            $("#form-house-image").show();

            // 设置上传房屋图片表单中的house_id
            $("#house-id").val(resp.data.house_id);
        }else {
            alert(resp.errmsg)
        }
    }
},"json")

2.在newhouse.js中进行上传房屋图片表单事件编写

$("#form-house-image").submit(function (e) {
})
  • step1 阻止表单默认行为
e.preventDefault();
  • step2 利用jquery.form.min.js插件通过的ajaxSubmit对表单进行异步提交,当后端返回正确响应时, 往class 为 house-image-cons div标签下去添加img标签,该div下的img标签为用户上传图片的标签,上传一个图片则在此div下追加一个img标签
$(this).ajaxSubmit({
    url:"/api/v1.0/houses/image",
    type:"post",
    dataType:"json",
    headers:{
        "X-CSRFToken": getCookie("csrf_token")
    },
    success:function (resp) {
        if(resp.errno == "4101"){
            location.href = "/login.html"
        }else if (resp.errno == "0"){
            // 往class 为 house-image-cons div标签下去添加img标签
            $(".house-image-cons").append('<img src="' + resp.data.image_url+'">')
        }else {
            alert(resp.errmsg);
        }
    }
})

四丶测试接口

1.运行项目,清除浏览器缓存,刷新网页,进入发布房源,博主这里使用的FS截图工具进行滚动窗口截图

2. 填写房屋信息

3.填写完数据后,点击发布房源信息,逻辑成功则显示上传房屋图片界面

查看数据库ih_house_info房屋信息表,数据显示为刚填写的表单勾选数据,此时并没有上传图片所以index_image_url为空

4.点击选择文件,选择桌面上的图片后,再点击上传,则显示在页面中

 5.依次上传三张图片后

查看此时数据库ih_house_image表信息,在上图上传的三张图片是house_id为6的房屋例子,所以在下标显示house_id为6的图片url链接为三个

6.同时查看七牛云上的存储空间

五丶celery的使用

1.问题,我们在做网站后端程序开发时,会碰到这样的需求:用户需要在我们的网站填写注册信息,我们发给用户一封注册激活邮件到用户邮箱,如果由于各种原因,这封邮件发送所需时间较长,那么客户端将会等待很久,造成不好的用户体验.

2.解决,celery适用异步处理问题,当发送邮件、或者文件上传, 图像处理等等一些比较耗时的操作,我们可将其异步执行,这样用户不需要等待很久,提高用户体验。 celery的特点是:

  • 简单,易于使用和维护,有丰富的文档。
  • 高效,单个celery进程每分钟可以处理数百万个任务。
  • 灵活,celery中几乎每个部分都可以自定义扩展。

3.Task Queue

celery通过消息进行通信,通常使用一个叫Broker(中间人)来协client(任务的发出者)和worker(任务的处理者). clients发出消息到队列中,broker将队列中的信息派发给worker来处理。

  一个celery系统可以包含很多的worker和broker,可增强横向扩展性和高可用性能

 4.在项目ihome目录下创建一个tasks包,用于处理项目中的需要的异步任务,在这个包下创建单一文件来处理对应的异步任务,这种方式是对于所有的web框架来说,都是万能的,像django中使用的djcelery包是别人针对于django框架封装好的包

  • step1 在task包下创建一个task_sms.py文件,在这个文件中去编写对于发送短信的任务

  • step2 定义celery对象
app = Celery("ihome", broker="redis://127.0.0.1:6379/1")
  • step3 定义短信的异步任务
@app.task
def send_sms():
    """发送短信的异步任务"""
    pass
  • step4 导入之前定义好云通讯中封装的CCP类,并创建CCP的对象ccp
from ihome.libs.yuntongxun.SendSMS import CCP
  • step5 通过ccp对象调用sendTemplateSMS方法,同时调用send_sms方法时传递三个参数,从这三个参数拿sendTemplateSMS方法中即可
@app.task
def send_sms(to, datas, tempId):
    """发送短信的异步任务"""
    ccp = CCP()
    ccp.sendTemplateSMS(to, datas, tempId)
  • step6 回到api_1_0目录下的verify_code中,将之前写的get_sms_code方法中发送短信逻辑代码进行重新编写,跟之前写的逻辑不一样,之前是发送成功则返回发送成功msg,失败则返回发送失败msg,大多网站都是现在以下这种逻辑
# 使用celery异步发送短信
send_sms.delay(mobile, [sms_code, int(constants.SMS_CODE_REDIS_EXPIRES/60)], 1)
    
# 发送成功与否,用户自己去查看手机,大多网站都是这样做的
return jsonify(errno=RET.OK, errmsg="发送短信成功")

六丶测试celery

1.在终端中运行项目


 

2.另开一个终端执行celery -A ihome.tasks.task_sms worker -l info,-l代表开启日志 info级别,但是出现了报错

3.在百度上查询此报错信息,明白原来是celery4.0以上版本不支持windows

4.所以博主这里进行 pip install celery==3.1.7 进行3.x版本安装

5.在新开启的终端上,重新执行celery -A ihome.tasks.task_sms worker -l info,报错问题解决

6.进入网站注册界面http://127.0.0.1:5000/register.html,后进行注册,输入博主本人手机号,输入正确验证码,然后点击获取短信验证码后,回头查看celery终端,结果又报错了

7.这次报的错好像是跟redis有关,查看redis版本为3.0的

8.将redis版本缓存2.0的,再看看是否报错,pip install redis==2.10.5

9.先运行项目,再开启另一终端执行celery -A ihome.tasks.task_sms worker -l info

  • step1 在注册页面进行注册

  • step2 查看celery终端日志,显示七牛SDK发送短信成功日志

  • step3 查看博主手机上短信验证码为987427

  • step4 查看redis数据库1中的keys

七丶celery以目录形式创建

1.在ihome/tasks目录下创建main.py用于启动文件,再创建一个config.py用于作配置文件,然后在tasks目录下创建一个sms包,再该包下创建一个tasks.py文件或者是celery.py文件用于创建worker

2.拆分task_sms.py文件

  • step1 在启动文件main.py中编写以下代码
app = Celery("ihome", broker="redis://127.0.0.1:6379/1")
  • step2 将如下代码拷贝到tasks.py中
@app.task
def send_sms(to, datas, tempId):
    """发送短信的异步任务"""
    ccp = CCP()
    ccp.sendTemplateSMS(to, datas, tempId)
  • step3 将main.py中创建app对象中的配置文件拿到config.py中
BROKER_URL = "redis://127.0.0.1:6379/1"
CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379/2'
  • step4 回到main.py中进入如下编写
app = Celery("ihome")

# 引入配置信息
app.config_from_object(config)

# 自动搜索任务
app.autodiscover_tasks(["ihome.tasks.sms"])
  • step5 回到ihome/api_1_0/verify_code.py中,将之前写的代码进行修改,将不在从from ihome.tasks.task_sms去导入send_sms,而是从我们刚定义好的sms.tasks中去导入send_sms
from ihome.tasks.sms.tasks import send_sms

3.测试

  • step1 首先启动项目,再另起终端启动worker,结果又特么报错了

  • step2 这个错是因为操作系统原因导致的,在linux系统上运行则不会保错

原因因为windows操作系统的原因,在Windows中,多进程multiprocessing使用的是序列化pickle来在多进程之间转移数据,而socket对象是不能被序列化的,但是在linux操作系统上却没问题,因为在linux上多进程multiprocessing使用的是fork,所以在windows上可以改用多线程,而celery是使用多进程以及携程方式进行异步工作,博主又是在windows环境进行测试开发,所以这里先暂时这样,等开发完成部署在linux服务器即可

猜你喜欢

转载自blog.csdn.net/qq_41782425/article/details/86512594