定义登录装饰器
utils\commons.py
from werkzeug.routing import BaseConverter
from flask import session, jsonify, g
from ihome.utils.response_code import RET
import functools
def login_required(view_func):
@functools.wraps(view_func)
def wrapper(*args, **kwargs):
user_id = session.get("user_id")
if user_id is not None:
g.user_id = user_id
return view_func(*args, **kwargs)
else:
return jsonify(errno=RET.SESSIONERR, errmsg="用户未登录")
return wrapper
redis文档
http://redis-py.readthedocs.io/en/latest/#indices-and-tables http://redisdoc.com/index.html
图片服务
1)保存到程序本地,扩容(磁盘满的问题)
2)备份的问题
3)多机存储的问题
4) 用户A 图片A
用户B 图片B
图片A 与 图片B 是同一张图片,会保存两次,浪费空间
5)用户A a.jpg
用户B a.jpg
同名图片,但不是同一张,后上传的会覆盖掉之前的内容
文件存储解决方案:
1) 自己搭建文件存储系统 FastDFS 快速分布式文件存储系统 HDFS Hadoop分布式文件系统
2) 选择第三方服务 七牛云存储 www.qiniu.com
封装七牛方法
utils\image_storage.py
from qiniu import Auth, put_data, etag, urlsafe_base64_encode
import qiniu.config
access_key = 'uzc59bVURbUbazey9vrexXKocNKBUN8NuLijk57N'
secret_key = '-9lenw28jU2REojvGkcsEPWk5Nm9V2HIVqb5Nkts'
def storage(file_data):
"""
上传文件到七牛
:param file_data: 要上传的文件数据
:return:
"""
q = Auth(access_key, secret_key)
bucket_name = 'ihome'
token = q.upload_token(bucket_name, None, 3600)
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("./1.png", "rb") as f:
file_data = f.read()
storage(file_data)
profile.py
from . import api
from ihome.utils.commons import login_required
from flask import g, current_app, jsonify, request
from ihome.utils.response_code import RET
from ihome.utils.image_storage import storage
from ihome.models import User
from ihome import db, constants
@api.route("/users/avatar", methods=["POST"])
@login_required
def set_user_avatar():
"""设置用户的头像
参数: 图片(多媒体表单格式) 用户id (g.user_id)
"""
user_id = g.user_id
image_file = request.files.get("avatar")
if image_file is None:
return jsonify(errno=RET.PARAMERR, errmsg="未上传图片")
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="上传图片失败")
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="保存图片信息失败")
avatar_url = constants.QINIU_URL_DOMAIN + file_name
return jsonify(errno=RET.OK, errmsg="保存成功", data={"avatar_url": avatar_url})
前端部分 profile.py
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 () {
$("#form-avatar").submit(function (e) {
e.preventDefault();
$(this).ajaxSubmit({
url: "/api/v1.0/users/avatar",
type: "post",
dataType: "json",
headers: {
"X-CSRFToken": getCookie("csrf_token")
},
success: function (resp) {
if (resp.errno == "0") {
var avatarUrl = resp.data.avatar_url;
$("#user-avatar").attr("src", avatarUrl);
} else {
alert(resp.errmsg);
}
}
})
})
})
城区数据下拉列表(缓存)
house.py
from . import api
from ihome.utils.commons import login_required
from flask import g, current_app, jsonify, request
from ihome.utils.response_code import RET
from ihome.models import Area
from ihome import db, constants, redis_store
import json
@api.route("/areas")
def get_area_info():
"""获取城区信息"""
try:
resp_json = redis_store.get("area_info")
except Exception as e:
current_app.logger.error(e)
else:
if resp_json is not None:
current_app.logger.info("hit redis area_info")
return resp_json, 200, {"Content-Type": "application/json"}
try:
area_li = Area.query.all()
except Exception as e:
current_app.logger.error(e)
return jsonify(errno=RET.DBERR, errmsg="数据库异常")
area_dict_li = []
for area in area_li:
area_dict_li.append(area.to_dict())
resp_dict = dict(errno=RET.OK, errmsg="OK", data=area_dict_li)
resp_json = json.dumps(resp_dict)
try:
redis_store.setex("area_info", constants.AREA_INFO_REDIS_CACHE_EXPIRES, resp_json)
except Exception as e:
current_app.logger.error(e)
return resp_json, 200, {"Content-Type": "application/json"}
前端部分 newhouse.js 前端js模板 art-template https://aui.github.io/art-template/zh-cn/index.html 1.下载template文件 2.html文件引入
<script src="/static/js/template.js"></script>
function getCookie(name) {
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
return r ? r[1] : undefined;
}
$(document).ready(function(){
// 向后端获取城区信息
$.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>');
// }
// 使用js模板
var html = template("areas-tmpl", {areas: areas})
$("#area-id").html(html);
} else {
alert(resp.errmsg);
}
}, "json");
$("#form-house-info").submit(function (e) {
e.preventDefault();
// 处理表单数据
var data = {};
$("#form-house-info").serializeArray().map(function(x) { data[x.name]=x.value });
// 收集设置id信息
var facility = [];
$(":checked[name=facility]").each(function(index, x){facility[index] = $(x).val()});
data.facility = facility;
// 向后端发送请求
$.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);
}
}
})
});
$("#form-house-image").submit(function (e) {
e.preventDefault();
$(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") {
$(".house-image-cons").append('<img src="' + resp.data.image_url +'">');
} else {
alert(resp.errmsg);
}
}
})
})
})
用户认证相关
profile.py
@api.route("/users/name", methods=["PUT"])
@login_required
def change_user_name():
"""修改用户名"""
user_id = g.user_id
req_data = request.get_json()
if not req_data:
return jsonify(errno=RET.PARAMERR, errmsg="参数不完整")
name = req_data.get("name")
if not name:
return jsonify(errno=RET.PARAMERR, errmsg="名字不能为空")
try:
User.query.filter_by(id=user_id).update({"name": name})
db.session.commit()
except Exception as e:
current_app.logger.error(e)
db.session.rollback()
return jsonify(errno=RET.DBERR, errmsg="设置用户错误")
session["name"] = name
return jsonify(errno=RET.OK, errmsg="OK", data={"name": name})
@api.route("/user", methods=["GET"])
@login_required
def get_user_profile():
"""获取个人信息"""
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="获取用户信息失败")
if user is None:
return jsonify(errno=RET.NODATA, errmsg="无效操作")
return jsonify(errno=RET.OK, errmsg="OK", data=user.to_dict())
@api.route("/users/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="获取用户实名信息失败")
if user is None:
return jsonify(errno=RET.NODATA, errmsg="无效操作")
return jsonify(errno=RET.OK, errmsg="OK", data=user.auth_to_dict())
@api.route("/users/auth", methods=["POST"])
@login_required
def set_user_auth():
"""保存实名认证信息"""
user_id = g.user_id
req_data = request.get_json()
if not req_data:
return jsonify(errno=RET.PARAMERR, errmsg="参数错误")
real_name = req_data.get("real_name")
id_card = req_data.get("id_card")
if not all([real_name, id_card]):
return jsonify(errno=RET.PARAMERR, 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:
current_app.logger.error(e)
db.session.rollback()
return jsonify(errno=RET.DBERR, errmsg="保存用户实名信息失败")
return jsonify(errno=RET.OK, errmsg="OK")
发布房源
house.py
from . import api
from ihome.utils.commons import login_required
from flask import g, current_app, jsonify, request
from ihome.utils.response_code import RET
from ihome.models import Area, House, Facility, HouseImage
from ihome import db, constants, redis_store
from ihome.utils.commons import login_required
from ihome.utils.image_storage import storage
import json
@api.route("/areas")
def get_area_info():
"""获取城区信息"""
try:
resp_json = redis_store.get("area_info")
except Exception as e:
current_app.logger.error(e)
else:
if resp_json is not None:
current_app.logger.info("hit redis area_info")
return resp_json, 200, {"Content-Type": "application/json"}
try:
area_li = Area.query.all()
except Exception as e:
current_app.logger.error(e)
return jsonify(errno=RET.DBERR, errmsg="数据库异常")
area_dict_li = []
for area in area_li:
area_dict_li.append(area.to_dict())
resp_dict = dict(errno=RET.OK, errmsg="OK", data=area_dict_li)
resp_json = json.dumps(resp_dict)
try:
redis_store.setex("area_info", constants.AREA_INFO_REDIS_CACHE_EXPIRES, resp_json)
except Exception as e:
current_app.logger.error(e)
return resp_json, 200, {"Content-Type": "application/json"}
@api.route("/houses/info", methods=["POST"])
@login_required
def save_house_info():
"""保存房屋的基本信息
前端发送过来的json数据
{
"title":"",
"price":"",
"area_id":"1",
"address":"",
"room_count":"",
"acreage":"",
"unit":"",
"capacity":"",
"beds":"",
"deposit":"",
"min_days":"",
"max_days":"",
"facility":["7","8"]
}
"""
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")
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="参数错误")
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
)
facility_ids = house_data.get("facility")
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
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})
@api.route("/houses/image", methods=["POST"])
@login_required
def save_house_image():
"""保存房屋的图片
参数 图片 房屋的id
"""
image_file = request.files.get("house_image")
house_id = request.form.get("house_id")
if not all([image_file, house_id]):
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="房屋不存在")
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="保存图片失败")
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
db.session.add(house)
try:
db.session.commit()
except Exception as e:
current_app.logger.error(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})
map 函数
li1 = [1, 2, 3, 4]
li2 = [2, 3]
def add(num1, num2):
return num1+num2
ret = map(add, li1, li2)
def add_self(num):
return num+2
print(list(ret))
celery 基本使用
from celery import Celery
from ihome.libs.yuntongxun.sms import CCP
celery_app = Celery("ihome", broker="redis://127.0.0.1:6379/1")
@celery_app.task
def send_sms(to, datas, temp_id):
"""发送短信的异步任务"""
ccp = CCP()
ccp.send_template_sms(to, datas, temp_id)
@api.route("/sms_codes/<re(r'1[34578]\d{9}'):mobile>")
def get_sms_code(mobile):
"""获取短信验证码"""
image_code = request.args.get("image_code")
image_code_id = request.args.get("image_code_id")
if not all([image_code_id, image_code]):
return jsonify(errno=RET.PARAMERR, errmsg="参数不完整")
try:
real_image_code = redis_store.get("image_code_%s" % image_code_id)
except Exception as e:
current_app.logger.error(e)
return jsonify(errno=RET.DBERR, errmsg="redis数据库异常")
if real_image_code is None:
return jsonify(errno=RET.NODATA, errmsg="图片验证码失效")
try:
redis_store.delete("image_code_%s" % image_code_id)
except Exception as e:
current_app.logger.error(e)
if real_image_code.lower() != image_code.lower():
return jsonify(errno=RET.DATAERR, errmsg="图片验证码错误")
try:
send_flag = redis_store.get("send_sms_code_%s" % mobile)
except Exception as e:
current_app.logger.error(e)
else:
if send_flag is not None:
return jsonify(errno=RET.REQERR, errmsg="请求过于频繁,请60秒后重试")
try:
user = User.query.filter_by(mobile=mobile).first()
except Exception as e:
current_app.logger.error(e)
else:
if user is not None:
return jsonify(errno=RET.DATAEXIST, errmsg="手机号已存在")
sms_code = "%06d" % random.randint(0, 999999)
try:
redis_store.setex("sms_code_%s" % mobile, constants.SMS_CODE_REDIS_EXPIRES, sms_code)
redis_store.setex("send_sms_code_%s" % mobile, constants.SEND_SMS_CODE_INTERVAL, 1)
except Exception as e:
current_app.logger.error(e)
return jsonify(errno=RET.DBERR, errmsg="保存短信验证码异常")
send_sms.delay(mobile, [sms_code, int(constants.SMS_CODE_REDIS_EXPIRES/60)], 1)
return jsonify(errno=RET.OK, errmsg="发送成功")
房屋管理
from . import api
from flask import g, current_app, jsonify, request, session
from ihome.utils.response_code import RET
from ihome.models import Area, House, Facility, HouseImage, User, Order
from ihome import db, constants, redis_store
from ihome.utils.commons import login_required
from ihome.utils.image_storage import storage
from datetime import datetime
import json
@api.route("/user/houses", methods=["GET"])
@login_required
def get_user_houses():
"""获取房东发布的房源信息条目"""
user_id = g.user_id
try:
user = User.query.get(user_id)
houses = user.houses
except Exception as e:
current_app.logger.error(e)
return jsonify(errno=RET.DBERR, errmsg="获取数据失败")
houses_list = []
if houses:
for house in houses:
houses_list.append(house.to_basic_dict())
return jsonify(errno=RET.OK, errmsg="OK", data={"houses": houses_list})
@api.route("/houses/index", methods=["GET"])
def get_house_index():
"""获取主页幻灯片展示的房屋基本信息"""
try:
ret = redis_store.get("home_page_data")
except Exception as e:
current_app.logger.error(e)
ret = None
if ret:
current_app.logger.info("hit house index info redis")
return '{"errno":0, "errmsg":"OK", "data":%s}' % ret, 200, {"Content-Type": "application/json"}
else:
try:
houses = House.query.order_by(House.order_count.desc()).limit(constants.HOME_PAGE_MAX_HOUSES)
except Exception as e:
current_app.logger.error(e)
return jsonify(errno=RET.DBERR, errmsg="查询数据失败")
if not houses:
return jsonify(errno=RET.NODATA, errmsg="查询无数据")
houses_list = []
for house in houses:
if not house.index_image_url:
continue
houses_list.append(house.to_basic_dict())
json_houses = json.dumps(houses_list)
try:
redis_store.setex("home_page_data", constants.HOME_PAGE_DATA_REDIS_EXPIRES, json_houses)
except Exception as e:
current_app.logger.error(e)
return '{"errno":0, "errmsg":"OK", "data":%s}' % json_houses, 200, {"Content-Type": "application/json"}
@api.route("/houses/index", methods=["GET"])
def get_house_index():
"""获取主页幻灯片展示的房屋基本信息"""
try:
ret = redis_store.get("home_page_data")
except Exception as e:
current_app.logger.error(e)
ret = None
if ret:
current_app.logger.info("hit house index info redis")
return '{"errno":0, "errmsg":"OK", "data":%s}' % ret, 200, {"Content-Type": "application/json"}
else:
try:
houses = House.query.order_by(House.order_count.desc()).limit(constants.HOME_PAGE_MAX_HOUSES)
except Exception as e:
current_app.logger.error(e)
return jsonify(errno=RET.DBERR, errmsg="查询数据失败")
if not houses:
return jsonify(errno=RET.NODATA, errmsg="查询无数据")
houses_list = []
for house in houses:
if not house.index_image_url:
continue
houses_list.append(house.to_basic_dict())
json_houses = json.dumps(houses_list)
try:
redis_store.setex("home_page_data", constants.HOME_PAGE_DATA_REDIS_EXPIRES, json_houses)
except Exception as e:
current_app.logger.error(e)
return '{"errno":0, "errmsg":"OK", "data":%s}' % json_houses, 200, {"Content-Type": "application/json"}
@api.route("/houses/<int:house_id>", methods=["GET"])
def get_house_detail(house_id):
"""获取房屋详情"""
user_id = session.get("user_id", "-1")
if not house_id:
return jsonify(errno=RET.PARAMERR, errmsg="参数确实")
try:
ret = redis_store.get("house_info_%s" % house_id)
except Exception as e:
current_app.logger.error(e)
ret = None
if ret:
current_app.logger.info("hit house info redis")
return '{"errno":"0", "errmsg":"OK", "data":{"user_id":%s, "house":%s}}' % (user_id, ret), \
200, {"Content-Type": "application/json"}
try:
house = House.query.get(house_id)
except Exception as e:
current_app.logger.error(e)
return jsonify(errno=RET.DBERR, errmsg="查询数据失败")
if not house:
return jsonify(errno=RET.NODATA, errmsg="房屋不存在")
try:
house_data = house.to_full_dict()
except Exception as e:
current_app.logger.error(e)
return jsonify(errno=RET.DATAERR, errmsg="数据出错")
json_house = json.dumps(house_data)
try:
redis_store.setex("house_info_%s" % house_id, constants.HOUSE_DETAIL_REDIS_EXPIRE_SECOND, json_house)
except Exception as e:
current_app.logger.error(e)
resp = '{"errno":"0", "errmsg":"OK", "data":{"user_id":%s, "house":%s}}' % (user_id, json_house), \
200, {"Content-Type": "application/json"}
return resp
前端部分 detail.js
function hrefBack() {
history.go(-1);
}
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;
}, {});
}
$(document).ready(function(){
var queryData = decodeQuery();
var houseId = queryData["id"];
$.get("/api/v1.0/houses/" + houseId, function(resp){
if ("0" == resp.errno) {
$(".swiper-container").html(template("house-image-tmpl", {img_urls:resp.data.house.img_urls, price:resp.data.house.price}));
$(".detail-con").html(template("house-detail-tmpl", {house:resp.data.house}));
if (resp.data.user_id != resp.data.house.user_id) {
$(".book-house").attr("href", "/booking.html?hid="+resp.data.house.hid);
$(".book-house").show();
}
var mySwiper = new Swiper ('.swiper-container', {
loop: true,
autoplay: 2000,
autoplayDisableOnInteraction: false,
pagination: '.swiper-pagination',
paginationType: 'fraction'
});
}
})
})
myhouse.js
$(document).ready(function(){
$.get("/api/v1.0/users/auth", function(resp){
if ("4101" == resp.errno) {
location.href = "/login.html";
} else if ("0" == resp.errno) {
if (!(resp.data.real_name && resp.data.id_card)) {
$(".auth-warn").show();
return;
}
$.get("/api/v1.0/user/houses", function(resp){
if ("0" == resp.errno) {
$("#houses-list").html(template("houses-list-tmpl", {houses:resp.data.houses}));
} else {
$("#houses-list").html(template("houses-list-tmpl", {houses:[]}));
}
});
}
});
})
|