Flask项目笔记_2

文章目录

      • 用户注册
      • 用户登陆
      • 登陆装饰器
      • 用户信息
      • 房屋模块
      • 房屋的搜索
      • redis的pipeline

用户注册

  • 选用用通讯进行短信验证码发送

  • 几个注意点:

    • 图片验证码最好设置只能使用一次,即在从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的查询语句[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cI6BOg4I-1571491574777)(https://FXHao.github.io/images/posts/flask项目/图4.png)]

    • ==实则为一个魔法方法,上面的相当于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项目中的差不多

你可能感兴趣的:(Flask项目,Flask,python)