文章目录
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)
}
}
});