爱家租房项目②

08-登录

01_登录后端逻辑编写

@api.route('/session', methods=['POST'])
def login():
    """用户登录"""
    # 1. 获取参数
    req_dict = request.get_json()
    mobile = req_dict.get('mobile')
    password = req_dict.get('password')

    # 2. 检验参数
    if not all([mobile, password]):
        return jsonify(errno=RET.PARAMERR, errmsg='参数缺失')

    if not re.match(r'1[345789]\d{9}', mobile):
        return jsonify(errno=RET.PARAMERR, errmsg='手机号码格式不正确')

    # 检查用户重试登录次数, 限制ip, 而不是手机
    user_ip = request.remote_addr
    try:
        access_nums = redis_store.get(f'access_num_{user_ip}')
    except Exception as e:
        current_app.logger.error(e)
    else:
        if access_nums is not None and \
            int(access_nums) >= constants.LOGIN_ERROR_MAX_TIMES:
            return jsonify(errno=RET.IPERR, errmsg='登录次数过多,请稍后重试')

    # 3.查询用户是否存在
    try:
        user = User.query.filter_by(mobile=mobile).first()
    except Exception as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.DBERR, errmsg='获取用户信息失败')

    if user is None or not check_password_hash(user.password_hash, password):
        try:
            redis_store.incr(f'access_num_{user_ip}')
            redis_store.expire(
                f'access_num_{user_ip}', 
                constants.LOGIN_ERROR_FORBID_TIME
            )
        except Exception as e:
            current_app.logger.error(e)
        return jsonify(errno=RET.USERERR, errmsg='用户名或密码错误')

    # 5. 保存到session
    session['name'] = user.name
    session['mobile'] = mobile
    session['user_id'] = user.id

    return jsonify(errno=RET.OK, errmsg='登录成功')

02_登录测试与前端代码

function getCookie(name) {
    var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
    return r ? r[1] : undefined;
}

$(document).ready(function () {
    $("#mobile").focus(function () {
        $("#mobile-err").hide();
    });
    $("#password").focus(function () {
        $("#password-err").hide();
    });
    $(".form-login").submit(function (e) {
        e.preventDefault();
        mobile = $("#mobile").val();
        passwd = $("#password").val();
        if (!mobile) {
            $("#mobile-err span").html("请填写正确的手机号!");
            $("#mobile-err").show();
            return;
        }
        if (!passwd) {
            $("#password-err span").html("请填写密码!");
            $("#password-err").show();
            return;
        }

        var req_data = {
            mobile: mobile,
            password: passwd
        }
        var req_json = JSON.stringify(req_data)
        $.ajax({
            type: "post",
            url: "/api/v1.0/session",
            data: req_json,
            dataType: "json",
            contentType: "application/json",
            headers: {
                'X-CSRFToken': getCookie('csrf_token')
            },
            success: function (rsp) {
                if (rsp.errno == 0) {
                    location.href = '/'
                } else {
                    // alert(rsp.errmsg)
                    $('#password-err span').html(rsp.errmsg)
                    $('#password-err').show()
                }
            }
        });
    });
})

03_检查登录状态与退出代码

@api.route('/session', methods=['GET'])
def check_login():
    """检查用户登录状态"""
    name = session.get('name')
    if name is None:
        return jsonify(errno=RET.SESSIONERR, errmsg='false')
    return jsonify(errno=RET.OK, errmsg='true', data={'name': name})


@api.route('/session', methods=['DELETE'])
def logout():
    """用户登出"""
    csrf_token = session['csrf_token']
    session.clear()
    session['csrf_token'] = csrf_token
    return jsonify(errno=RET.OK, errmsg='OK')

前端

function getCookie (name) {
    var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
    return r ? r[ 1 ] : undefined;
};

function logout () {
    $.ajax({
        type: "delete",
        url: "/api/v1.0/session",
        dataType: "json",
        headers: {
            'X-CSRFToken': getCookie("csrf_token")
        },
        success: function (rsp) {
            if ('0' == rsp.errno) {
                location.href = "/";
            }
        }
    });
}

$(document).ready(function () {
    $.ajax({
        type: "get",
        url: "/api/v1.0/user",
        dataType: "json",
        success: function (rsp) {
            if ('0' == rsp.errno) {
                user = rsp.data.user
                $('#user-avatar').attr('src', user.avatar_url)
                $('#user-name').html(user.name)
                $('#user-mobile').html(user.mobile)
            } else if ('4101' == rsp.errno) {
                location.href = '/login.html'
            } else {
                alert(rsp.errmsg)
            }
        }
    });
});

04_登录验证装饰器

更多装饰器的介绍

09-个人信息

01_图片存储服务的引入

在这里插入图片描述

02_七牛上传图片的使用

七牛云存储
七牛云 python SDK
在这里插入图片描述
与python 交互

# -*- coding: utf-8 -*-
# flake8: noqa

import qiniu.config
from qiniu import Auth, etag, put_data, put_file

access_key = ''
secret_key = ''


def storage(file_data):
    """
    存储图片到七牛服务器
    :param file_data: 文件二进制码
    :return:
    """
    q = Auth(access_key, secret_key)

    bucket_name = 'ihome-python-why'

    # # 上传文件到七牛后, 七牛将文件名和文件大小回调给业务服务器。
    # policy = {
    #     'callbackUrl': 'http://your.domain.com/callback.php',
    #     'callbackBody': 'filename=$(fname)&filesize=$(fsize)'
    # }

    token = q.upload_token(bucket_name, None, 3600)

    # localfile = '../static/images/home01.jpg'
    # ret, info = put_file(token, None, localfile)

    ret, info = put_data(token, None, file_data)

    if info.status_code == 200:
        return ret.get('key')
    else:
        raise Exception('上传图片失败')


if __name__ == "__main__":
    with open('../static/images/home01.jpg', 'rb') as f:
        file_data = f.read()
        storage(file_data)

03_保存头像后端编写

@api.route('/user/avatar', methods=['POST'])
@login_required
def set_user_avatar():
    """
    设置用户头像
    参数: 图片(表单格式), 用户id
    """
    # 1. 获取参数
    user_id = g.user_id
    image_file = request.files.get('avatar')

    if user_id is None:
        return jsonify(errno=RET.SESSIONERR, errmsg='用户未登录')

    if image_file is None:
        return jsonify(errno=RET.PARAMERR, errmsg='没有选择图片')
    # 2. 上传图片
    try:
        image_data = image_file.read()
        file_name = storage(image_data)
    except Exception as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.THIRDERR, errmsg='上传头像失败')

    # 3. 更新数据库用户信息
    try:
        User.query.filter_by(id=user_id).update({'avatar_url': file_name})
        db.session.commit()
    except Exception as e:
        db.session.rollback()
        current_app.logger.error(e)
        return jsonify(errno=RET.DBERR, errmsg='保存图片信息失败')

    # 4. 设置用户头像 avatar_url
    return jsonify(
        errno=RET.OK,
        errmsg='上传头像成功',
        data={'avatar_url': constants.QINIU_URL_DOMAIN+file_name}
    )

04_上传图片的前端代码

/**
     * 设置用户头像
     */
    $('#form-avatar').submit(function (e) {
        // 阻止表单默认行为
        e.preventDefault();
        // 利用jquery.from.min.js提供的ajaxSubmit异步提交
        $(this).ajaxSubmit({
            url: '/api/v1.0/user/avatar',
            method: 'post',
            dataType: 'json',
            headers: {
                'X-CSRFToken': getCookie('csrf_token')
            },
            success: function (rsp) {
                if ('0' == rsp.errno) {
                    $('.error-msg').hide()
                    $("#user-avatar").attr('src', rsp.data.avatar_url)
                    showSuccessMsg()
                } else if ('4001' == rsp.errno) {
                    $('.error-msg b').html(rsp.errmsg).parent().show()
                } else if ('4101' == rsp.errno) {
                    location.href = '/login.html'
                }
            }
        })
    });

10-城区信息

01_城区信息后端编写

02_缓存机制的介绍

在这里插入图片描述

@api.route('/areas', methods=['GET'])
def get_areas():
    """查询全部区域
    由于区域访问频繁,但是更新不频繁,所以可以放入redis缓存
    """
    try:
        rsp_json = redis_store.get('area_info')
    except Exception as e:
        current_app.logger.error(e)
    else:
        if rsp_json is not None:
            current_app.logger.info('hit area into redis')
            return rsp_json, 200, {'Content-Type': 'application/json'}

    try:
        areas = Area.query.all()
    except Exception as e:
        current_app.logger.err(e)
        return jsonify(errno=RET.DBERR, errmsg='数据库查询异常')

    areas_list = []
    for area in areas:
        areas_list.append(area.to_dict())

    # 将数据转换成json字符串
    rsp_dict = dict(errno=RET.OK, errmsg='OK', data=areas_list)
    rsp_json = json.dumps(rsp_dict)

    try:
        redis_store.setex(
            'area_info',
            constants.AREA_INFO_REDIS_CACHE_EXPIRES,
            rsp_json
        )
    except Exception as e:
        current_app.logger.error(e)

    return rsp_json, 200, {'Content-Type': 'application/json'}

04_缓存数据同步的问题

redis设置有效期

try:
        redis_store.setex(
            'area_info',
            constants.AREA_INFO_REDIS_CACHE_EXPIRES,
            rsp_json
        )
    except Exception as e:
        current_app.logger.error(e)

05_用户模块修改用户名_获取个人资料_实名认证代码说明

用户模块修改用户名 后端接口

@api.route('/user/name', methods=['PUT'])
@login_required
def set_user_name():
    """设置用户名称"""
    # 1. 校验参数完整性
    user_id = g.user_id

    if user_id is None:
        return jsonify(errno=RET.SESSIONERR, errmsg='用户未登录')

    name = request.get_json().get('name')

    # try:
    #     user = User.query.get(user_id)
    # except Exception as e:
    #     pass
    if name is None:
        return jsonify(errno=RET.PARAMERR, errmsg='请填写用户名')

    # 2. 保存用户数据
    try:
        # 2) 查询的 BaseQuery 对象的 update 方法进行保存
        User.query.filter_by(id=user_id).update({'name': name})
        db.session.commit()
    except IntegrityError as e:
        db.session.rollback()
        current_app.logger.error(e)
        return jsonify(errno=RET.DATAEXIST, errmsg='用户名已存在,请重新设置')
    except Exception as e:
        db.session.rollback()
        current_app.logger.error(e)
        return jsonify(errno=RET.DBERR, errmsg='保存用户名失败')
    session['name'] = name
    g.name = name
    return jsonify(errno=RET.OK, errmsg='保存成功', data={'name': name})


@api.route('/user', methods=['GET'])
@login_required
def get_user_profile():
    """获取用户信息"""
    user_id = g.user_id

    if user_id is None:
        return jsonify(errno=RET.SESSIONERR, errmsg='用户未登录')

    try:
        user = User.query.get(user_id)
    except Exception as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.DBERR, errmsg='获取用户信息失败')

    if user.avatar_url is None:
        avatar_url = ''
    else:
        avatar_url = constants.QINIU_URL_DOMAIN + user.avatar_url

    user_info = {
        "name": user.name,
        "mobile": user.mobile,
        "avatar_url": avatar_url,
        "real_name": user.real_name,
        "id_card": user.id_card
    }

    return jsonify(
        errno=RET.OK,
        errmsg='获取用户信息成功',
        data={'user': user_info}
    )

获取、 设置用户名称 前端js

$(document).ready(function () {
    /**
     * 获取用户名
     */
    $.get("/api/v1.0/user", function (rsp) {
            console.log(rsp)
            if (rsp.errno == '0') {
                $("#user-avatar").attr('src', rsp.data.user.avatar_url)
                $("#user-name").val(rsp.data.user.name)
            } else if ('4001' == rsp.errno) {
                $('.error-msg b').html(rsp.errmsg).parent().show()
            } else if ('4101' == rsp.errno) {
                location.href = '/login.html'
            }
        },
        "json"
    );

 /**
     * 设置用户名称
     */
    $("#form-name").submit(function (e) {
        e.preventDefault();
        var name = $('#user-name').val();
        if (!name) {
            $('.error-msg b').html('请填写用户名').parent().show();
            return;
        }
        var req_data = {
            'name': name
        };
        var req_json = JSON.stringify(req_data);
        $.ajax({
            type: "put",
            url: "/api/v1.0/user/name",
            data: req_json,
            contentType: "application/json",
            dataType: "json",
            headers: {
                'X-CSRFToken': getCookie('csrf_token')
            },
            success: function (rsp) {
                if ('0' == rsp.errno) {
                    $('.error-msg').hide()
                    showSuccessMsg()
                } else if ('4001' == rsp.errno) {
                    $('.error-msg b').html(rsp.errmsg).parent().show()
                } else if ('4101' == rsp.errno) {
                    location.href = '/login.html'
                }
            }
        });
    });

设置、查询用户实名认证 后端接口

@api.route('/user/auth', methods=['PUT'])
@login_required
def user_auth():
    """用户实名认证, 只能更改一次"""
    req_json = request.get_json()
    user_id = g.user_id
    real_name = req_json.get('real_name')
    id_card = req_json.get('id_card')

    if not all([real_name, id_card]):
        return jsonify(errno=RET.PARAMERR, errmsg='参数不完整')

    if user_id is None:
        return jsonify(errno=RET.SESSIONERR, errmsg='获取用户信息失败')

    try:
        User.query.filter_by(id=user_id, real_name=None, id_card=None).update(
            {'real_name': real_name, 'id_card': id_card})
        db.session.commit()
    except Exception as e:
        db.session.rollback()
        current_app.logger.error(e)
        return jsonify(errno=RET.DBERR, errmsg='数据库操作失败')

    data = {'real_name': real_name, 'id_card': id_card}
    return jsonify(errno=RET.OK, errmsg='实名认证成功', data=data)


@api.route('/user/auth', methods=['GET'])
@login_required
def get_user_auth():
    """查询用户认证信息"""
    user_id = g.user_id

    try:
        user = User.query.get(user_id)
    except Exception as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.DBERR, errmsg='数据库操作失败')

    real_name = user.real_name
    id_card = user.id_card

    if not all([real_name, id_card]):
        return jsonify(errno=RET.DATAERR, errmsg='未进行实名认证')

    return jsonify(
        errno=RET.OK,
        errmsg='获取认证信息成功',
        data={
            'real_name': real_name,
            'id_card': id_card
        }
    )

加载、设置用户认证信息 前端js

function showSuccessMsg() {
    $('.popup_con').fadeIn('fast', function () {
        setTimeout(function () {
            $('.popup_con').fadeOut('fast', function () {});
        }, 1000)
    });
}

function getCookie(name) {
    var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
    return r ? r[1] : undefined;
}

$(document).ready(function () {
    // 初始加载用户认证信息
    get_user_auth();

    $('#form-auth').submit(function (e) {
        e.preventDefault();
        set_user_auth();
    });
});

/**
 * 获取用户实名认证信息
 */
function get_user_auth() {
    $.get("/api/v1.0/user", function (rsp) {
            if ('0' == rsp.errno) {
                if (rsp.data.user.real_name && rsp.data.user.id_card) {
                    $('#real-name').val(rsp.data.user.real_name).prop('disabled', 'disabled')
                    $('#id-card').val(rsp.data.user.id_card).prop('disabled', 'disabled')
                    $('.error-msg').hide()
                    $('#form-auth>input[type=submit]').hide()
                }
            } else if ('4101' == rsp.errno) {
                location.href = '/login.html'
            } else {
                alert(rsp.data.errmsg)
            }
        },
        "json"
    );
    
}

/**
 * 设置用户实名认证信息
 */
function set_user_auth() {
    var real_name = $('#real-name').val()
    var id_card = $('#id-card').val()

    if (!real_name) {
        $('.error-msg b').html('请填写真是姓名')
    }

    if (!id_card) {
        $('.error-msg b').html('请填写身份证号码')
    }

    var data = {
        real_name: real_name,
        id_card: id_card
    }

    $.ajax({
        type: "put",
        url: "/api/v1.0/user/auth",
        data: JSON.stringify(data),
        contentType: "application/json",
        dataType: "json",
        headers: {
            'X-CSRFToken': getCookie('csrf_token')
        },
        success: function (rsp) {
            if ('0' == rsp.errno) {
                if (rsp.data.real_name && rsp.data.id_card) {
                    $('#real-name').val(rsp.data.real_name).prop('disabled', 'disabled')
                    $('#id-card').val(rsp.data.id_card).prop('disabled', 'disabled')
                    $('.error-msg').hide()
                    $('#form-auth>input[type=submit]').hide()
                    showSuccessMsg()
                }
            } else if ('4101' == rsp.errno) {
                location.href = '/login.html'
            } else {
                alert(rsp.data.errmsg)
            }
        }
    });
}

06_城区信息前端编写与前端模板的使用

    // 查询所有区域
    $.get("/api/v1.0/areas",
        function (rsp) {
            console.log(rsp)
            if ('0' == rsp.errno) {
                var areas = rsp.data
                // $.each(areas, function (index, area) { 
                //      $('#area-id').append(
                //          '<option value="' + area.aid + '">' + area.aname + '</option>'
                //      );
                // });

                // art template 使用js模板
                var html = template('areas-tmpl', { areas: areas })
                // 插入到对应html区域
                $('#area-id').html(html);
            } else {
                alert(rsp.errmsg)
            }
        },
        "json"
    );

11-发布房源

01_保存房屋基本信息数据后端编写

@api.route('/house/info', methods=['POST'])
@login_required
def save_house_info():
    """保存新发布的房源信息, 包括该房源的设备信息"""
    # 1. 获取房源基本信息参数
    user_id = g.user_id
    house_data = request.get_json()

    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')

    # 2. 校验参数
    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='参数不完整')

    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='参数错误')

    # 3. 检查区域id是否存在
    try:
        area = Area.query.get(area_id)
    except Exception as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.DBERR, errmsg='数据库异常')

    if area is None:
        return jsonify(errno=RET.NODATA, errmsg='区域信息错误')

    house = House(
        user_id=user_id,
        area_id=area_id,
        title=title,
        price=price,
        address=address,
        room_count=room_count,
        acreage=acreage,
        unit=unit,
        capacity=capacity,
        beds=beds,
        deposit=deposit,
        min_days=min_days,
        max_days=max_days
    )

    # 4. 校验是否有设备,若有设备,设备id是否存在
    facility_ids = house_data.get('facilities')
    if 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='保存数据异常')

        if facilities:
            house.facilities = facilities

    # 5. 保存房源信息到数据库
    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='保存数据异常')

    return jsonify(errno=RET.OK, errmsg='OK', data={"house_id": house.id})

02_保存房屋图片后端接口编写

@api.route('/house/image', methods=['POST'])
@login_required
def save_house_image():
    """保存房屋图片"""
    # 1. 获取图片
    image_file = request.files.get('house_image')
    house_id = request.form.get('house_id')
    print(image_file, house_id)
    # 2. 校验参数
    if not all([house_id, image_file]):
        return jsonify(errno=RET.PARAMERR, errmsg='参数不完整')

    try:
        house = House.query.get(house_id)
    except Exception as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.DBERR, errmsg='数据库异常')

    if house is None:
        return jsonify(errno=RET.NODATA, errmsg='房屋信息错误')

    # 3. 上传图片
    image_data = image_file.read()
    try:
        file_name = storage(image_data)
    except Exception as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.THIRDERR, errmsg='上传图片失败')

    # 4. 保存url到数据库
    house_image = HouseImage(
        house_id=house.id,
        url=file_name
    )
    db.session.add(house_image)

    # 处理房屋主图片
    if not house.index_image_url:
        house.index_image_url = file_name
        # 如果不是主图片,house表没有变动,不需要添加到session
        db.session.add(house)

    try:
        db.session.commit()
    except Exception as e:
        current_app.logger.err(e)
        db.session.rollback()
        return jsonify(errno=RET.DBERR, errmsg='保存图片数据异常')

    image_url = constants.QINIU_URL_DOMAIN + file_name
    return jsonify(errno=RET.OK, errmsg='OK', data={'image_url': image_url})

03_保存房屋基本信息前端代码

newhouse.js

/* 提交房源基本信息 */
    $('#form-house-info').submit(function (e) {
        e.preventDefault();
        var data = {}
        $('#form-house-info').serializeArray().map(function (x) { data[ x.name ] = x.value })

        var facilities = []
        $(':checked[name=facility]').map(function () { facilities.push($(this).val()) })

        data.facilities = facilities

        $.ajax({
            type: "post",
            url: "/api/v1.0/house/info",
            data: JSON.stringify(data),
            contentType: "application/json",
            dataType: "json",
            headers: {
                'X-CSRFToken': getCookie('csrf_token')
            },
            success: function (rsp) {
                if ('0' == rsp.errno) {
                    // 用户未登录
                    $('#form-house-info').hide()
                    // 显示图片表单
                    $('#form-house-image').show()
                    // 设置图片表单
                    $('#house-id').val(rsp.data.house_id)
                } else if ('4101' == rsp.errno) {
                    location.href = '/login.html'
                } else {
                    alert(rsp.errmsg)
                }
            }
        });

    });

03_保存房屋基本信息前端代码

/* 上传房屋图像 */
    $('#form-house-image').submit(function (e) {
        e.preventDefault()
        $(this).ajaxSubmit({
            type: "post",
            url: "/api/v1.0/house/image",
            dataType: "json",
            cache: false,
            headers: {
                'X-CSRFToken': getCookie('csrf_token')
            },
            success: function (rsp) {
                if ('0' == rsp.errno) {
                    $('.house-image-cons').append('<img src="' + rsp.data.image_url + '" />')
                    $('#form-house-image .form-group').empty().html(
                        '<label for="house-image">选择图片</label>' +
                        '<input type="file" accept="image/*" name="house_image" id="house-image">'
                    )
                } else if ('4101' == rsp.errno) {
                    location.href = '/login.html'
                } else {
                    alert(rsp.errmsg)
                }
            }
        });
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值