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

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

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

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

目录

一丶房源列表页后端编写

二丶房源列表页缓存处理

三丶redis中pipeline(管道)的使用

四丶房源列表页前端编写

五丶搜索条件测试


一丶房源列表页后端编写

1.搜索条件分析:用户在主页页面中点击搜索按钮,即跳转到房源列表页面,需要注意的是,第一用户在不选择任何搜索条件下,点击搜索按钮跳转到的房源列表页,此房源页显示的房屋列表应该是采用默认排序,将所有的房屋信息显示出来;第二用户还可以只选择某一个搜索条件(比如区县或者入住日期);第三就是用户选择区县以及入住日期,那么在房源列表页则根据用户的搜索条件进行显示

扫描二维码关注公众号,回复: 5885656 查看本文章

2.入住日期分析:最开始在项目中创建数据库模型类models.py文件中,在House房屋类并未定义入住时间,而是在Order订单类中定义了预定起始以及结束时间,因为在一个房屋可以被多次入住,所以放到House类中显然不合适,而放在Order订单类中 ,是因为一旦订单中出现了该房屋的入住时间,那么代表该房屋其余时间都是可以被用户搜索到并预定下单

3.接口逻辑编写

  • step1 定义视图函数,需要构建请求地址
# /api/v1.0/houses?sd=2019-01-23&ed=2019-01-31&aid=3&sk=new&p=1
@api.route("/houses")
def get_house_list():
    """房源页房屋列表信息"""
    pass
  • step2 获取请求参数
start_date = request.args.get("sd") # 用户入住日期
end_date = request.args.get("ed") # 用户离开日期
area_id = request.args.get("aid") # 入住区县
sort_key = request.args.get("sk", "new") # 排序关键字,当未选择排序条件时,默认按最新排序,这个new关键字根据前端定义走的
page = request.args.get("p") # 页数
  • step3 处理日期 用户可能选择入住日期或者是离开日期,所以要一一进行判断
try:
    if start_date:
        start_date = datetime.strptime(start_date, "%Y-%m-%d")

    if end_date:
        end_date = datetime.strptime(end_date, "%Y-%m-%d")
    # 当用户两者都选择情况下,需要进行判断,入住日期肯定是小于或等于离开日期的
    if start_date and end_date:
        assert start_date <= end_date
except Exception as e:
    current_app.logger.error(e)
    return jsonify(errno=RET.PARAMERR, errmsg="日期参数有误")
  • step4 判断区县id
if area_id:
    try:
        area = Area.query.get(area_id)
    except Exception as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.PARAMERR, errmsg="区县参数有误")
  • step5 处理页数
try:
    page = int(page)
except Exception as e:
    # 如果出现异常则使page=1
    page = 1
    current_app.logger.error(e)
  • step6 定义过滤条件的参数列表容器以及存放冲突订单对象
filter_params = []
conflict_orders = None
  • step7 在数据库中查询订单表中的冲突订单,这里稍微比较绕,就是以什么条件来筛选订单中冲突的订单,其实简单一句就是用户不管选择的入住日期或者是离开日期又或者是入住以及离开日期,这三种情况中任一情况的日期都不能在Order表中订单起始日期begin_date与end_date结束日期这范围内,即作出以下逻辑判断
try:
    if start_date and end_date:
        # 查询冲突的订单所有对象
        conflict_orders = Order.query.filter(Order.begin_date <= end_date, Order.end_date >= start_date).all()
    elif start_date:
        # 用户只选择入住日期
        conflict_orders = Order.query.filter(Order.end_date >= start_date).all()
    elif end_date:
        # 用户只选择离开日期
        conflict_orders = Order.query.filter(Order.begin_date <= end_date).all()
except Exception as e:
    current_app.logger.error(e)
    return jsonify(errno=RET.DBERR, errmsg="数据库异常")
  • step8 当获取的冲突订单对象存在时,获取冲突房屋的id,如果冲突的房屋id不为空,则向查询参数中添加条件
if conflict_orders:
    # 从订单中获取冲突的房屋id
    conflict_house_ids = [order.house_id for order in conflict_orders] # 使用列表生成式进行简写操作

    # 如果冲突的房屋id不为空,向查询参数中添加条件
    if conflict_house_ids:
        filter_params.append(House.id.notin_(conflict_house_ids))
  • step9 当区县id存在时,向列表中添加条件,这个条件House.area_id == area_id返回的不是True或者是False而是返回的是SQLALchemy表达式,所以才能在filter方法中进行过滤
if area_id:
    filter_params.append(House.area_id == area_id)
  • step10 根据过滤参数列表,查询数据库,并进行条件排序
if sort_key == "booking":  # 入住做多
    house_query = House.query.filter(*filter_params).order_by(House.order_count.desc())
elif sort_key == "price-inc": # 价格低-高
    house_query = House.query.filter(*filter_params).order_by(House.price.asc())
elif sort_key == "price-des": # 价格高-低
    house_query = House.query.filter(*filter_params).order_by(House.price.desc())
else:
    # 如果用户什么都没选择,则按照最新排序(数据库字段创建时间)
    house_query = House.query.filter(*filter_params).order_by(House.create_time.desc())
  • step11 分页处理 paginate方法需传递三个参数,page:分页页数 per_page:每页显示多少条数据 error_out: 错误输出
try:
    page_obj = house_query.paginate(page=page, per_page=constants.HOUSE_LIST_PAGE_CAPACITY, error_out=False)
except Exception as e:
    current_app.logger.error(e)
    return jsonify(errno=RET.DBERR, errmsg="数据库异常")
  • step12 获取分页页面数据
houses = []
house_list = page_obj.items
for house in house_list:
    houses.append(house.to_basic_dict())
  • step13 获取总页数,并返回正确响应数据
total_page = page_obj.pages
return jsonify(errno=RET.OK, errmsg="OK", data={"houses":houses, "total_page":total_page, "current_page":page})

4.测试此接口

  • step1 在Postman工具中向127.0.0.1:5000/api/v1.0/houses接口发送请求,注意该请求未携带任何参数,后端接口逻辑分页设定每页显示2条数据,总页数为4页,说明数据库中房屋数据为7条或者8条

  • step2 查看数据库房屋信息表,房屋数据为7条,后端分页逻辑正确

  • step3 为了进行搜索条件测试,博主这里进行发布一套房源,详情如下

  •  step4 点击发布房源,并上传图片,此时再查看数据库则显示刚发布的房屋信息了

  • step5 向127.0.0.1:5000/api/v1.0/houses接口发送请求,并携带搜索条件参数,按价格从低到高进行筛选

  • step6 向127.0.0.1:5000/api/v1.0/houses接口发送请求,并携带参数在第3页价格从低到高,因博主之前测试,除了今天发布的房源不同于其他7条数据,之前的7条数据价格都是一样的,所以这里走个过程,但逻辑是没问题的

  • step7 当发送请求参数中不存在的页数5时,则后端返回一个空列表

 二丶房源列表页缓存处理

1.分析,为什么添加缓存处理,因为可能会出现不同的用户在进行房源条件搜索时,出现条件相同,所以需要进行页面数据缓存处理

2.具体实现

  • step1 需要将页面数据以hash类型存到redis数据库
"house_入住_离开_区域id_排序条件": hash
{    
     "page": "{}"
     "1": "{}",
     "2": "{}",
}
  • step2 将响应数据构建成json格式数据
resp_dict = dict(errno=RET.OK, errmsg="OK", data={"houses":houses, "total_page":total_page, "current_page":page})
resp_json = json.dumps(resp_dict)
  • step3 设置redis数据库的key
redis_key = "house_%s_%s_%s_%s" % (start_date, end_date, area_id, sort_key)
  • step4 设置缓存数据
try:
    redis_store.hset(redis_key, page, resp_json) # 将数据存入redis数据
    redis_store.expire(redis_key, constants.HOUES_LIST_PAGE_REDIS_CACHE_EXPIRES) # 设置有效期
except Exception as e:
    current_app.logger.error(e)
  • step5 返回正确响应数据
return resp_json, 200, {"Content-Type": "application/json"}
  • step6 设置获取参数的默认值,因为需要在数据刚开始需要去数据库获取缓存数据,所以给参数设置默认值,防止查询出错
start_date = request.args.get("sd", "") # 用户入住日期
end_date = request.args.get("ed", "") # 用户离开日期
area_id = request.args.get("aid", "") # 入住区县
  • step7 在接口函数处理完请求完数据时,此时需在数据库从获取缓存数据,使用hget方法获取存入数据的hash类型数据,page为hash数据中的键,当查询获取的json响应数据不为空时,则将该数据进行返回,如果出现异常或者查询数据不存在时,则进入mysql数据库数据查询,设置缓存,以及返回正确响应逻辑操作
redis_key = "house_%s_%s_%s_%s" % (start_date, end_date, area_id, sort_key)

try:
    resp_json = redis_store.hget(redis_key, page)
except Exception as e:
    current_app.logger.error(e)
else:
    if resp_json:
        return resp_json, 200, {"Content-Type":"application/json"}

3.测试

  • step1 打开Postman测试工具向127.0.0.1:5000/api/v1.0/houses发送数据,不携带任何参数,此时在工具中返回了正确响应以及数据,第一个箭头表示当前页码,第二个箭头表示总页数

  • step2 查看redis数据库,因为向接口发送请求时,为携带任何参数,而当初在接口函数中获取请求参数时,设置了默认值为空字符串,所以这里看的key为house____new,通过hget方式获取house____new键page为1的数据

 

  • step3 在Postman工具中设置请求参数p=2,,即获取第二页的数据

  • step4 此时在redis数据库中通过hgetall house____new,查看该键所有数据,此时显示数据出现了第一页和第二页数据

 

  • step5 房屋信息数据一共只有8条数据,每页显示2条数据,则总页数为4页,当发送请求参数p=5时,那么编写的逻辑也会把这个不存在的数据存到redis数据库

  • step6 经查看redis数据库发现,获取到的第5页空数据,也存到了数据库,这可是我不想要的

 

4.修改代码逻辑,在设置数据缓存的时候进行判断如果current_page <= total_page,然后才设置数据缓存

if page <= total_page:
    # 设置缓存数据
    try:
        redis_store.hset(redis_key, page, resp_json) # 将数据存入redis数据
        redis_store.expire(redis_key, constants.HOUES_LIST_PAGE_REDIS_CACHE_EXPIRES) # 设置有效期
    except Exception as e:
        current_app.logger.error(e)

 三丶redis中pipeline(管道)的使用

1.为什么使用pipeline,因为我们在设置缓存数据时,使用hash类型进行数据的存储,而不是像之前写的接口的数据缓存那样使用的是str类型存储,所以没有一步到位的存入数据以及有效期的设置放到一块,hash没有setex方法,设想如果将数据存入到redis数据库成功,而expire设置有效期时未成功,那么此数据就变成了永久有效,即使用pipeline是可取的

2.具体实现如下

# 创建redis pipeline 管道对象,可以一次性执行多条语句
pipeline = redis_store.pipeline()
# 开启多个语句的记录
pipeline.multi()
# 使用管道对象管理多条语句
pipeline.hset(redis_key, page, resp_json) # 将数据存入redis数据
pipeline.expire(redis_key, constants.HOUES_LIST_PAGE_REDIS_CACHE_EXPIRES) # 设置有效期
# 执行语句
pipeline.execute()

四丶房源列表页前端编写

1.在房源页面search.js中进行编写

  • step1 构建js代码中所用到的全局变量
var cur_page = 1; // 当前页
var next_page = 1; // 下一页
var total_page = 1;  // 总页数
var house_data_querying = true; // 是否正在向后台获取数据
  • step2 定义decodeQuery函数用于解析url中的查询字符串
function decodeQuery(){
    var search = decodeURI(document.location.search);
    return search.replace(/(^\?)/, '').split('&').reduce(function(result, item){
        values = item.split('=');
        result[values[0]] = values[1];
        return result;
    }, {});
}
  • step3 定义updateFilterDateDisplay函数用于更新用户点选的筛选条件
function updateFilterDateDisplay() {
    var startDate = $("#start-date").val();
    var endDate = $("#end-date").val();
    var $filterDateTitle = $(".filter-title-bar>.filter-title").eq(0).children("span").eq(0);
    if (startDate) {
        var text = startDate.substr(5) + "/" + endDate.substr(5);
        $filterDateTitle.html(text);
    } else {
        $filterDateTitle.html("入住日期");
    }
}
  • step4 定义updateHouseData函数用于更新房源列表信息
function updateHouseData(action) {
    var areaId = $(".filter-area>li.active").attr("area-id");
    if (undefined == areaId) areaId = "";
    var startDate = $("#start-date").val();
    var endDate = $("#end-date").val();
    var sortKey = $(".filter-sort>li.active").attr("sort-key");
    var params = {
        aid:areaId,
        sd:startDate,
        ed:endDate,
        sk:sortKey,
        p:next_page
    };
    $.get("/api/v1.0/houses", params, function(resp){
        house_data_querying = false;
        if ("0" == resp.errno) {
            if (0 == resp.data.total_page) {
                $(".house-list").html("暂时没有符合您查询的房屋信息。");
            } else {
                total_page = resp.data.total_page;
                if ("renew" == action) {
                    cur_page = 1;
                    $(".house-list").html(template("house-list-tmpl", {houses:resp.data.houses}));
                } else {
                    cur_page = next_page;
                    $(".house-list").append(template("house-list-tmpl", {houses: resp.data.houses}));
                }
            }
        }
    })
}
  • step5 在页面加载时获取筛选条件中的城市区域信息
$.get("/api/v1.0/areas", function(data){
        if ("0" == data.errno) {
            // 用户从首页跳转到这个搜索页面时可能选择了区县,所以尝试从url的查询字符串参数中提取用户选择的区县
            var areaId = queryData["aid"];
            // 如果提取到了区县id的数据
            if (areaId) {
                // 遍历从后端获取到的区县信息,添加到页面中
                for (var i=0; i<data.data.length; i++) {
                    // 对于从url查询字符串参数中拿到的区县,在页面中做高亮展示
                    // 后端获取到区县id是整型,从url参数中获取到的是字符串类型,所以将url参数中获取到的转换为整型,再进行对比
                    areaId = parseInt(areaId);
                    if (data.data[i].aid == areaId) {
                        $(".filter-area").append('<li area-id="'+ data.data[i].aid+'" class="active">'+ data.data[i].aname+'</li>');
                    } else {
                        $(".filter-area").append('<li area-id="'+ data.data[i].aid+'">'+ data.data[i].aname+'</li>');
                    }
                }
            } else {
                // 如果url参数中没有区县信息,不需要做额外处理,直接遍历展示到页面中
                for (var i=0; i<data.data.length; i++) {
                    $(".filter-area").append('<li area-id="'+ data.data[i].aid+'">'+ data.data[i].aname+'</li>');
                }
            }
  • step6 在页面添加好城区选项信息后,更新展示房屋列表信息
updateHouseData("renew");
  • step7  获取页面显示窗口的高度
var windowHeight = $(window).height();
  • step8 为窗口的滚动添加事件函数
window.onscroll=function(){
            // var a = document.documentElement.scrollTop==0? document.body.clientHeight : document.documentElement.clientHeight;
            var b = document.documentElement.scrollTop==0? document.body.scrollTop : document.documentElement.scrollTop;
            var c = document.documentElement.scrollTop==0? document.body.scrollHeight : document.documentElement.scrollHeight;
            // 如果滚动到接近窗口底部
            if(c-b<windowHeight+50){
                // 如果没有正在向后端发送查询房屋列表信息的请求
                if (!house_data_querying) {
                    // 将正在向后端查询房屋列表信息的标志设置为真,
                    house_data_querying = true;
                    // 如果当前页面数还没到达总页数
                    if(cur_page < total_page) {
                        // 将要查询的页数设置为当前页数加1
                        next_page = cur_page + 1;
                        // 向后端发送请求,查询下一页房屋数据
                        updateHouseData();
                    } else {
                        house_data_querying = false;
                    }
                }
            }
        }

2.在search.html中定义模板

<script id="house-list-tmpl" type="text/html">
            {{each houses as house}}
            <li class="house-item">
                <a href="/detail.html?id={{house.house_id}}"><img src="{{house.img_url}}"></a>
                <div class="house-desc">
                    <div class="landlord-pic"><img src="{{house.user_avatar}}"></div>
                    <div class="house-price">¥<span>{{(house.price/100.0).toFixed(0)}}</span>/晚</div>
                    <div class="house-intro">
                        <span class="house-title">{{house.title}}</span>
                        <em>出租{{house.room_count}}间 - {{house.order_count}}次入住 - {{house.address}}</em>
                    </div>
                </div>
            </li>
            {{/each}}
        </script>

五丶搜索条件测试

1.用户选择不同的区域信息,显示出对应区域下的房屋列表信息

  • step1 之前在测试接口的时候,导致有些房屋没有上传图片,所以这里在后台数据库进行补充

  • step2 查看当前18022222222账号的发布的房源列表

  • step3 查看主页中显示的房屋图片,此时应该是按最初开发需求那样在主页显示5张l房屋ogo图片

  • step4 为了方便主页搜索接口测试,所以这里使用18033333333(张三) 账号进行其他区县房源发布

  • step5 查看数据库房屋信息,一共有18套房屋包括(成都主城区)

  •  step6 在主页选择锦江区,搜索界面如下(右边没完全显示因为截图工具原因),每页显示2个房屋信息,当滚动鼠标时,触发js函数,向后端接口获取第二页数据,依次类推

  • step7 在如上搜索页面,选择青羊区以及金牛区,查看该区域下的房屋列表

 

  • step8 在如上搜索页面,选择武侯区以及成华区 ,查看该区域下的房屋列表

2.根据用户选择的入住以及离开日期,显示出对应时间下的房屋列表信息

  • step1 以锦江区为例进行测试(左边图为未选择时间条件,右边图为选择时间条件),可以发现当选择了入住时间以及离开时间后,所对应条件的房屋列表并未发生任何改变,原因是现在所有的房屋都是未被入住,所以在后端接口中未有冲突订单,即房源列表显示一样,此测试等待订单接口写完后,再进行测试

  • step2 当在页面滚动鼠标时,则向接口发送请求数据,期间根据用户鼠标滚动的浮动来决定p为多少

2.根据用户选择排序关键字,显示对应的房源列表信息

说明:首先默认排序为最新上线,这个不用进行测试,然后入住最多是按照订单量来排序,因订单接口还没写,所以也不用测试,这里只需要对价格进行排序

左图为按价格由低到高进行排序,右图为按价格由高到低进行排序

猜你喜欢

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