# flaskBlog **Repository Path**: zevsee/flask-blog ## Basic Information - **Project Name**: flaskBlog - **Description**: flask博客 - **Primary Language**: Python - **License**: Not specified - **Default Branch**: dev - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-08-16 - **Last Updated**: 2021-08-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # flask 博客 地址 [YouTube 最火的 Python Flask 入门教程\_哔哩哔哩\_bilibili](https://www.bilibili.com/video/BV1K4411j7Dp) [bootstrap](https://v4.bootcss.com/docs/) [flask](https://dormousehole.readthedocs.io/en/latest/quickstart.html) [源码](https://github.com/CoreyMSchafer/code_snippets/tree/master/Python/Flask_Blog) [规范化样式](https://www.bootcdn.cn/normalize/) [专栏](https://zhuanlan.zhihu.com/p/53970826) [登录与权限控制](https://blog.51cto.com/wenguonideshou/1982232) [Flask 之旅 (四):登录与权限控制](https://testerhome.com/topics/7875) ## 虚拟环境 如果没有,安装 apt install python3-pip apt install python3-venv 3.9 版本不需要安装 venv 进入项目目录,执行 python3 -m venv venv 激活虚拟环境:venv\Scripts\activate 安装模块:pip install Flask 退出环境:exit 删除环境:rm venv ## 启动项目 方式 1: python .\run.py 方式 2: bash: `export FLASK_APP=run.py` cmd: `set FLASK_APP=run.py` ps: `$env:FLASK_APP='run.py'` 运行: `flask run` ## 模板引擎 jinja 不需要安装 新建 templates 目录,from flask 导入 render_template ## 表单校验 wtf 需要安装,命令: `pip install flask-wtf` 单独安装: `pip install wtforms[email]` 生成密钥 ```python >>> import secrets >>> secrets.token_hex(16) '56ad155e3bbdbce585ec6ebce8fdd149' >>> ``` ## 数据库 ### 使用 安装:`pip install flask-sqlalchemy` 创建 ```python >>> from flaskBlog import db >>> db.create_all() >>> from flaskBlog import User, Post >>> user_1 = User(username='Alice', email='A@blog.com',password='asdf') >>> db.session.add(user_1) >>> user_2 = User(username='Bob', email='B@blog.com',password='asdf') >>> db.session.add(user_2) >>> db.session.commit() >>> User.query.all() >>> User.query.first() User('Alice', 'A@blog.com', 'default.jpg') >>> User.query.filter_by(username='Bob').all() [User('Bob', 'B@blog.com', 'default.jpg')] >>> user = User.query.filter_by(username='Bob').first() >>> user.id 2 >>> post_1 = Post(title='Blog 1', content='First Post content!', user_id=user.id) >>> post_2 = Post(title='Blog 2', content='Second Post content!', user_id=user.id) >>> db.session.add(post_1) >>> db.session.add(post_2) >>> db.session.commit() >>> user.posts >>> post = Post.query.first() >>> post Post('Blog 1', '2021-08-17 06:31:17.671336') >>> post.user_id 2 >>> post.author User('Bob', 'B@blog.com', 'default.jpg') >>> db.drop_all() ``` ### 重构 [重构](https://www.bilibili.com/video/BV1K4411j7Dp?p=5) 之前的结构分离 model 会存在导包冲突问题,所以进行项目结构重构 ## 用户认证 安装:`pip install flask-bcrypt` 演示 ```python >>> from flask_bcrypt import Bcrypt >>> b = Bcrypt() >>> b.generate_password_hash('testing') b'$2b$12$G3DktUx4.G3TaJow4xR38OeU52GSWuUPh5Sypw8B2UUFnu8Djkt6u' >>> b.generate_password_hash('testing') b'$2b$12$AyzBvACXTk5RzWoZKZc4TuDtTlvGq1uaROk4tzVTt5LDP5QyH47vW' >>> b.generate_password_hash('testing').decode('utf-8') '$2b$12$IAhkUVOuC9WWvRwmFvITFelwf4SIvNdiQwMqSVHhqwFvVLjybzl1m' >>> hash = b.generate_password_hash('testing').decode('utf-8') >>> b.check_password_hash(hash, 'test') False >>> b.check_password_hash(hash, 'testing') True ``` 安装 login: `pip install flask-login` 管理登录用户信息 ```python @login_manager.user_loader def load_user(user_id): # 创建用户加载回调函数,接受用户 ID 作为参数 user = User.query.get(int(user_id)) # 用 ID 作为 User 模型的主键查询对应的用户 return user # 返回用户对象 ``` Flask-Login 提供了一个 current_user 变量,注册这个函数的目的是,当程序运行后,如果用户已登录, current_user 变量的值会是当前用户的用户模型类记录。 另一个步骤是让存储用户的 User 模型类继承 Flask-Login 提供的 UserMixin 类: ```python from flask_login import UserMixin class User(db.Model, UserMixin): # ... ``` 继承这个类会让 User 类拥有几个用于判断认证状态的属性和方法,其中最常用的是 is_authenticated 属性:如果当前用户已经登录,那么 current_user.is_authenticated 会返回 True, 否则返回 False。有了 current_user 变量和这几个验证方法和属性,我们可以很轻松的判断当前用户的认证状态。 ## 完善用户信息 增加 account 表单,需要上传文件,文件重命名 安装 Pillow,调整文件大小 `pip install Pillow` 压缩图片减少存储空间 ## crud 帖子 ## 分页 ```python posts = Post.query.paginate() posts.per_page #每页多少 posts.page #当前页 posts.items #当前页的数据 posts = Post.query.paginate(page=2) #第二页 posts = Post.query.paginate(per_page=5) #每页5 posts.total posts = Post.query.paginate(page=2, per_page=3) posts.iter_pages() #多少页 ``` ## 发送邮件 生成 token ```python from itsdangerous import TimedJSONWebSignatureSerializer as Serializer def get_reset_token(self, expires_sec=1800): s = Serializer(app.config['SECRET_KEY'], expires_sec) return s.dumps({'user_id': self.id}).decode('utf-8') #类的静态方法 @staticmethod def verify_reset_token(token): s = Serializer(app.config['SECRET_KEY']) try: user_id = s.loads(token)['user_id'] except: return None return User.query.get(user_id) ``` 安装 email 模块 `pip install flask-mail` 设置环境变量,窗口关闭后消失,这里的邮箱要和代码中的 sender 一致(新浪邮箱) ```powershell $env:EMAIL_USER='donot_reply@sina.com' # 密码为授权码 $env:EMAIL_PASS='************' ``` 用户在登录页面点击忘记密码->到 reset_request 函数(此页面输入邮箱)->检查是否已登录,是则跳转 home,否则->根据输入的邮箱查找用户->邮箱通过验证,发送重置密码连接至邮箱-> 邮箱中的连接到 reset_token 函数(此页面重置密码)->用户未登录,且验证 token 通过,且密码校验通过->修改密码,返回登录页面 ## 蓝图重构 使用蓝图添加新页面更容易, 蓝图必须为包,有**init** 注意修改 url_for 的路径 分离配置,创建 config.py ```bash # SECRET_KEY写入bash_profile export SECRET_KEY='56ad155e3bbdbce585ec6ebce8fdd149' export SQLALCHEMY_DATABASE_URI='sqlite:///site.db' ``` ```python # config.py获取 class Config: SECRET_KEY = os.environ.get('SECRET_KEY') SQLALCHEMY_DATABASE_URI = os.environ.get('SQLALCHEMY_DATABASE_URI') ``` ## 定义错误页面 创建蓝图,注册 ## 部署 1. 打包环境 `pip freeze > requirements.txt` 2. 复制项目文件到服务器 3. 服务器安装虚拟环境并进入 4. `pip install -r requirements.txt` 5. 创建配置文件 touch /etc/config.json 6. 写入内容:包含敏感信息 ```json { "SECRET_KEY": "56ad155e3bbdbce585ec6ebce8fdd149", "SQLALCHEMY_DATABASE_URI": "sqlite:///site.db", "MAIL_USERNAME": "EMAIL_USER", "MAIL_PASSWORD": "EMAIL_PASS" } ``` 7. 修改 config.py ```python with open('D:/workingspace/Projects/config.json', 'r', encoding='UTF-8') as config_file: config = json.load(config_file) class Config: SECRET_KEY = config.get('SECRET_KEY') ``` 8. 避免 debug 模式,使用 flask 运行:`export FLASK_APP=run.py` win: `$env:FLASK_APP='run.py'` 9. 允许外部(ip 地址)访问:`flask run --host=0.0.0.0`,测试 ok 10. 安装 nginx `apt install nginx` 11. pip install gunicorn #WSGI HTTP 服务器,[gunicorn](https://zhuanlan.zhihu.com/p/102716258) 并发比 flask 好 12. `rm /etc/nginx/site-enable/default` 13. `vim /etc/nginx/site-enable/flaskBlog` ``` server { listen 80; server_name 123.123.123.123; location /static { alias /home/joker/flaskBlog/flaskBlog/static; } location / { proxy_pass http://localhost:8000; #gunicorn的端口为8000 include /etc/nginx/proxy_params; proxy_redirect off; } } ``` 14. `ufw allow http/tcp ; systemctl restart nginx` 15. `gunicorn -w 3 run:app` 参数 3 = (2 x num_cores) + 1 num_cores = `nproc --all` 表示 3 个 worker run 为文件名去掉后缀,app 为变量 16. 安装 supervisor `pip install supervisor` 17. `vi /etc/supervisor/conf.d/flaskBlog.conf` ``` [program:flaskBlog] directory=/home/YOUR_USER/YOUR_PROJECT command=/home/YOUR_USER/YOUR_PROJECT/venv/bin/gunicorn -w 3 run:app user=YOUR_USER autostart=true autorestart=true stopasgroup=true killasgroup=true stderr_logfile=/var/log/flaskblog/flaskblog.err.log stdout_logfile=/var/log/flaskblog/flaskblog.out.log ``` 18. `supervisorctl reload` 19. 如果有图片无法上传问题,`vi /etc/nginx/nginx.conf` 找到 keepalive_timeout 65; 在下面添加: `client_max_body_size 5M;` 20. `systemctl restart nginx` 流量->nginx->gnuicorn->python supervisor 保证 gunicorn 启动 ## 用户列表 删除 user 时,需要先删除 user 的 posts,否则报错 为什么直接使用日志(logging.info)没有输出?`logger = logging.getLogger('flaskBlog.app')` 这里'flaskBlog.app'名字不对也没有输出.? 1. logging 的默认等级是 Warning,所以 logging.info 没有输出, logging.warning 是有输出的 2. `logger = logging.getLogger('flaskBlog')` 没有名为 flaskBlog 的 logger,所以会创建.其参数为默认,默认等级 warning,所以 info 没有输出,warning 有 3. `logger = logging.getLogger('flaskBlog.app')` 拿到的是 flask 已经配置好的 logger,其等级是 info,所以 info 有输出 [日志 1](https://segmentfault.com/a/1190000018087099?utm_source=tag-newest) [日志 2](https://www.cnblogs.com/oklizz/p/11385847.html) flask 的 logger: flask 的 logger 就是一个标准的 Python logging,它的命名是 flask(包的名字).app。我们既可以使用 app.logger,也可以自己定义一个 logger。那么如何使用 app.logger 呢? 有两种方式: - 直接调用 ```python import logging logger = logging.getLogger('flaskBlog.app')#注意这里的名字 logger.info('flask.app') ``` - 使用 Flask 提供的接口(推荐) ```python from flask import current_app current_app.logger.info('logged by current_app from main') ```