用户注册
-
选用用通讯进行短信验证码发送
-
几个注意点:
- 图片验证码最好设置只能使用一次,即在从redis中取出后,要删除其数据,防止用户多次使用同一验证码
- 短信验证码要设置发送间隔时间
-
密码加密时,采用的是sha256加密,但不能直接对密码进行加密,因为密码相同的话,其加密后的值就相同,有暴露的风险,所以在给密码加密时还需在读取到的密码中加一个盐值(salt)(即一串随机的字符串),然后在对其进行加密
- 对密码操作,可以使用werkzeug.security模块中的generate_password_hash, check_password_hash两个方法,它们可以帮助我们对密码进行加密和校验,只需把用户输入的密码参数传入即可
-
@property装饰器
-
该装饰器会把所装饰的函数变成一个属性,属性名就为函数名,我们可以在User类中定义两个方法来定义两个方法,用这个装器,简化视图中对密码的操作处理
# 加上property装饰器后,会把函数变为属性,属性名即为函数名 @property def password(self): """读取属性的函数行为""" # print(user.password) # 读取属性时被调用 # 函数的返回值会作为属性值 # return "xxxx" raise AttributeError("这个属性只能设置,不能读取") # 使用这个装饰器, 对应设置属性操作 @password.setter def password(self, value): """ 设置属性 user.passord = "xxxxx" :param value: 设置属性时的数据 value就是"xxxxx", 原始的明文密码 :return: """ self.password_hash = generate_password_hash(value)
- 注意的是:两个函数名需一致,就算不要读取的属性,也必须定义出来,设置的属性的函数的装饰器是
函数名.setter
- 注意的是:两个函数名需一致,就算不要读取的属性,也必须定义出来,设置的属性的函数的装饰器是
-
-
接收保存完数据,需要保存登陆状态到session中
# 保存登录状态到session中 session["name"] = mobile session["mobile"] = mobile session["user_id"] = user.id
-
操作数据库存储数据时,只要其中有一条数据保存出现问题,该次注册所有的数据都音rollback()
用户登陆
-
用户名与密码可以一起校验,校验失败时返回
“用户名或密码错误”
,以防止用户测试数据库中的用户信息 -
校验密码时,可以在User类中加一个方法来校验密码的正确性,在视图中直接调用,传入用户所输入的密码
def check_password(self, passwd): """ 检验密码的正确性 :param passwd: 用户登录时填写的原始密码 :return: 如果正确,返回True, 否则返回False """ return check_password_hash(self.password_hash, passwd)
-
限制用户的IP登陆密码错误的次数:可以用redis缓存存储
-
redis中有个incr方法,该方法如果redis中无该条记录,就自动创建并把其值加1,有该记录就将值加1
redis_store.incr("access_num_%s" % user_ip) # 设置该条数据的缓存时间,即用户错误次数够后多少时间可以在次登陆 redis_store.expire("access_num_%s" % user_ip, constants.LOGIN_ERROR_FORBID_TIME)
-
-
检查登陆状态
-
因为登陆后会把用户信息存入session中,所以直接尝试从session获取用户信息,看有无数据
name = session.get("name")
-
-
用户退出
-
直接清楚session中的用户数据即可
session.clear()
-
登陆装饰器
-
某些页面需要用户登陆后才能访问,可以在视图函数一开始来检测用户的登陆状态,但多个函数都写显得有点麻烦,这时可以把检测用户登陆状态的代码放入装饰器,用装饰器来实现
-
装饰器装饰的函数视图中可能会用到user_id,而在装饰器中已经获取后user_id,我们可以用g对象来存储数据
g.user_id = user_id
,然后在视图中想要获取时,直接g.user_id
即可 -
定义装饰器时,最好给内层函数在加一个装饰器
@functools.wraps()
,它可以确保该装饰器所装饰的函数的属性不会被改变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: # 将user_id保存到g对象中,在视图函数中可以通过g对象获取保存的数据 g.user_id = user_id return view_func(*args, **kwargs) else: # 如果未登录,返回未登录的信息 return jsonify(errno=RET.SESSIONERR, errmsg="用户未登录") return wrapper
用户信息
- 图片存储
- 存储到本地的问题:①磁盘满的问题 ②备份问题 ③多机存储问题 ④图片内容一样,名不一样,会存储多次,浪费空间 ⑤名字一样,内容不一样,后存储的会覆盖前一张内容
- 解决方案:
- 自己搭建文件存储系统
- FastDFS:快速分布式文件存储系统,一般用于电商存放图片
- HDFS :Hadoop分布式文件系统,它可以存储各种文件
- 选择第三方服务:如七牛云之类的
- 自己搭建文件存储系统
房屋模块
-
当获取城区信息时,得到的是一个字典对象,而我们传递给前端的是json格式的数据,字典对象是不支持转换成JSON的,所以我们还需把字典对象转成字典才行
-
这时可以把对象转换成字典的代码封装成一个方法放入房屋类中,在视图中直接调用就好
def to_dict(self): """将对象转换为字典数据""" user_dict = { "user_id": self.id, "name": self.name, "mobile": self.mobile, "avatar": constants.QINIU_URL_DOMAIN + self.avatar_url if self.avatar_url else "", "create_time": self.create_time.strftime("%Y-%m-%d %H:%M:%S") } return user_dict ############视图函数中######## area_dict_li = [] # 将对象转换为字典 for area in area_li: area_dict_li.append(area.to_dict()) # 将数据转换为json字符串 resp_dict = dict(errno=RET.OK, errmsg="OK", data=area_dict_li) resp_json = json.dumps(resp_dict)
-
缓存机制:首页的房屋列表和城区信息是会被用户频繁访问的,每次访问都要从数据库中读取的话,会比较慢,这时就可以在第一次查询时,把查询结果保存到redis 中,再次访问时,先查询redis中有无数据,若没有数据的话,在去查询数据库中的资料
- 但其缺点也是比较明显的,因为redis和数据库是不同步的,所以无法实时的更新缓存数据,唯有redis该数据的有效期过了,或者更新数据库时,删除redis数据,才会重新的读取数据库更新缓存
房屋的搜索
-
时间的处理:要确保接受到的时间数据格式正确,还有得判断下结束时间要大于起始时间
datetime.strptime
模块:把字符串转到时间datetime.strftime
模块:把时间转到字符串
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="日期参数有误")
-
查询处理
-
可以定义一个过滤条件的列表容器,然后将个各条件塞进去,然后在查询数据库
# 过滤条件的参数列表容器 filter_params = [] # 时间条件 conflict_orders = None 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="数据库异常") 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)) # 区域条件 if area_id: filter_params.append(House.area_id == area_id) # 查询数据库 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()) # 处理分页 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="数据库异常") # 获取页面数据 house_li = page_obj.items houses = [] for house in house_li: houses.append(house.to_basic_dict()) # 获取总页数 total_page = page_obj.pages resp_dict = dict(errno=RET.OK, errmsg="OK", data={"total_page": total_page, "houses": houses, "current_page": page}) resp_json = json.dumps(resp_dict)
-
house_query.paginate()
:paginate()返回查询后的对象- 三个参数:page(当前的页数),per_page(每页的数据量),error_out(错误输出)
-
在往
filter_params.append(House.area_id == area_id)
时,加进去的其实不是False和True,它加进去的是sql的查询语句 -
==
实则为一个魔法方法,上面的相当于House.area_id.__eq__(area_id)
,但该对象重写了__qe__
方法,所以返回的是一条语句
-
redis的pipeline
-
相当于一个管道,当多条redis语句要执行时,可能会有的成功,有的失败,这时就可以创建一个管道,一次性执行多条语句
# redis_store.hset(redis_key, page, resp_json) # redis_store.expire(redis_key, constants.HOUES_LIST_PAGE_REDIS_CACHE_EXPIRES) # 创建redis管道对象,可以一次执行多个语句 pipeline = redis_store.pipeline() # 开启多个语句的记录 pipeline.multi() pipeline.hset(redis_key, page, resp_json) pipeline.expire(redis_key, constants.HOUES_LIST_PAGE_REDIS_CACHE_EXPIRES) # 执行语句 pipeline.execute()
-
订单支付采用支付宝,其操作跟Django项目中的差不多