还记得我们之前编写了一个register接口吗?可那只是个空壳,直接返回了注册成功的响应体。
这不禁让我想起以前在某快递网站上,点击催单按钮,对方直接弹出催单成功
的 alert 提示。但我打开了他的 html 代码,发现只要点击这个按钮就会弹出提示,没有任何后端的交互,这里我就不点名这个韵达快递了哈。只能说真的很秀~
cookie 和 session
传统的 web 软件,需要存储用户的登录信息。cookie 和 session 是比较常见的模式,当然也是比较古老的模式。其中 cookie 主要依赖于客户端,可以简单的说是浏览器。可以说用户的登录信息,存储在浏览器的 localstorage 中。而 session,顾名思义,它是将用户存储于服务器中。更好的解释请自行百度。。
token
token 从广义说的话,是令牌的意思。我这里是 json web token(JWT) 的简写。简单的说,就是一种通过存储用户信息 (以 base64 编码存储) 然后需要的时候将之解析的方法。这样就达到你这个用户所有信息都被存储为一个字符串而且也看不到具体内容的目的。感兴趣的同学可以去看看~~
Python 自带了 jwt,现在咱们来熟悉一下相关的方法。
先给出我在pity/middleware/Jwt.py
中的实现
import hashlib
from datetime import timedelta, datetime
import jwt
from jwt.exceptions import ExpiredSignatureError
EXPIRED_HOUR = 3
class UserToken(object):
key = 'pityToken'
salt = 'pity'
@staticmethod
def get_token(data):
new_data = dict({"exp": datetime.utcnow() + timedelta(hours=EXPIRED_HOUR)}, **data)
return jwt.encode(new_data, key=UserToken.key).decode()
@staticmethod
def parse_token(token):
try:
return jwt.decode(token, key=UserToken.key)
except ExpiredSignatureError:
raise Exception("token已过期, 请重新登录")
@staticmethod
def add_salt(password):
m = hashlib.md5()
m.update(password + UserToken.salt)
return m.hexdigest()
UserToken 类有 3 个方法,第一个方法呢,就是把用户信息压缩成一串字符串,并附带3 小时的过期时间。
第二个就是解析 token 为之前的用户信息。
第二个方法 add_salt 是为了能够用 md5 码去存储用户密码,一旦有邪恶势力拿到数据库密码的话,不至于会暴露用户的密码,add_salt就是加盐的意思,如果你要反破解 md5 码,嘿嘿,我这里给你加了一层你想象不到的东西,所以相对来说还是比较安全的呢。
仔细想一下,我们的用户如果需要注册的话,第一步是先判断这个账号是否已经注册过了。好,那我们来编写第一个方法: register_user。
我们新建dao/auth/UserDao.py
from sqlalchemy import or_
from app.middleware.Jwt import UserToken
from app.models import db
from app.models.user import User
from app.utils.logger import Log
class UserDao(object):
log = Log("UserDao")
@staticmethod
def register_user(username, name, password, email):
"""
:param username: 用户名
:param name: 姓名
:param password: 密码
:param email: 邮箱
:return:
"""
try:
users = User.query.filter(or_(User.username == username, User.email == email)).all()
if users is not None:
raise Exception("用户名或邮箱已存在")
# 注册的时候给密码加盐
pwd = UserToken.add_salt(password)
user = User(username, name, pwd, email)
db.session.add(user)
db.session.commit()
except Exception as e:
UserDao.log.error(f"用户注册失败: {str(e)}")
return str(e)
return None
注意,User.query.filter 这行代码的意思是,找出所有 username 或 email 已经存在的用户,如果有,则抛出异常,没有则直接通过 orm 插入这行数据。
在 orm 的世界里,我们实例化的 user 对象就是数据表中的一行数据,这样理解会否更清晰一点呢?
可以看到这个方法很空洞,我们需要做什么呢。
首先我们是不是得接收传入的用户名/密码/邮箱等注册信息,对参数进行一些关键校验,然后调用刚才的核心方法。如果不出错的话,咱们的登录方法就写好了~
Flask 通过 request.get_json() 可以直接获取到传入的 json 数据,并返回一个 dict 对象。
PS: 这么写比较丑陋,暂时先忍忍,后续我们再进行改造。
只是导入了 UserDao 模块并调用了 register 方法,如果 err 不为空,就返回 code=110, 意思是报警了 (报错了)
说明一下,code=101 是参数错误,110 是异常错误, 0 是正常返回。
注意,这不是什么官方的规定,是我一时兴起哈哈,以后都会按照这个约定来。
完整代码:
启动服务
可以看到,出现了循环引用的问题,原来我们在 app/init.py 里面引入了 controller,在 controller 里面又引入了 model,在 model 里面又引入了 app/init.py 里面的 pity 对象,所以出现了循环引用。
解决的方法很简单:我们把注册蓝图这种脏活累活都放到 run.py,他是最外层,没有人引入 run.py 的东西。
改动后的 app/init.py:
from flask import Flask
from config import Config
pity = Flask(__name__)
pity.config.from_object(Config)
run.py:
from datetime import datetime
from app import pity
from app.utils.logger import Log
from app.controllers.auth.user import auth
# 注册蓝图
pity.register_blueprint(auth)
@pity.route('/')
def hello_world():
log = Log("hello world")
log.info("有人访问了你的网站了")
now = datetime.now().strftime("%Y-%M-%d %H:%M:%S")
print(now)
return now
if __name__ == "__main__":
pity.run("0.0.0.0", threaded=True, port="7777")
打开 postman 测试一下:
不对劲的事情又发生了,好气呀!但是咱们不慌,仔细看一下这个报错,说的是 405http 方法不允许,找到我们写的接口:
原来是这里出了问题,我们稍稍改动一下,将请求方式改成post即可。
重启服务try again
奇怪了,明明没有注册过这个用户,却提示了这个,首先我们检查一下数据库:
发现数据库也没问题,那么我们使出大招,断点疗法:
此处点一下会出现一个红色小圆形,再次请求:
原来如此,我们的判断除了问题,返回的是个空数组,当然不是 None 了,所以咱们需要修改下校验方式:
重启后继续尝试:
这个我看出来了,这一定是 md5 那块出了问题,仔细找下原因吧。
(太特么自信了,导致前面的图懒得改,只能边写边修改了,以后就不会这样直播写了,写好了直接上代码即可。)
看报错的意思是,unicode 必须 hash 成 bytes,多大点事: 直接利用 api:
重启后继续尝试:
真不容易,咱们检查下数据库:
时间不早了,今天的内容就到这了,打烊了打烊了,疯狂写 bug!!!
后端代码地址: https://github.com/wuranxu/pity
前端代码地址: https://github.com/wuranxu/pityWeb
觉得有用的话可以帮忙点个 Star 哦 QAQ