环境搭建

虚拟环境pycharm连接

SSH 连接远程环境

教程:

image-20210127200156966

设置

image-20210127212257509

image-20210127204107813

image-20210127204115047

image-20210127204332900

image-20210127204338389

连接虚拟环境

选择虚拟环境的解释器:/root/workspaces/flask/bin/python

博客迁移

打包依赖

(flask) C:\Users\q2723\PycharmProjects\flaskProject9>pip freeze >requirements.txt

建虚拟环境

mkvirtualenv myblog
(myblog) [root@izbp1i7e0dqxcb89vkdgc3z ~]# which python
/root/workspaces/myblog/bin/python
[root@izbp1i7e0dqxcb89vkdgc3z tmp]# mkdir myproject
[root@izbp1i7e0dqxcb89vkdgc3z tmp]# cd myproject/
[root@izbp1i7e0dqxcb89vkdgc3z myproject]# mkdir flask_blog
[root@izbp1i7e0dqxcb89vkdgc3z myproject]# pwd
/tmp/myproject

设置 ssh 配置

image-20210127213501190image-20210127213503853

image-20210127213658469

他会自动同步

安装依赖

(myblog) [root@izbp1i7e0dqxcb89vkdgc3z flask_blog]# pip install -r requirements.txt 
(myblog) [root@izbp1i7e0dqxcb89vkdgc3z flask_blog]# pip list
Package          Version
---------------- ---------
alembic          1.4.3
antiorm          1.2.1
certifi          2020.12.5
chardet          4.0.0
.........
配置数据库

可以删除原来的 migration 文件夹

原来的项目没有设置 host

可以在启动的时候,加 -h

python app.py runserver -h 0.0.0.0

使用navicat迁移数据库(可选)

image-20210127220204364

需要提前在服务器建好同名数据库

我的阿里云服务器已经安装mysql5.7

[root@izbp1i7e0dqxcb89vkdgc3z ~]# cd /usr/local/mysql
[root@izbp1i7e0dqxcb89vkdgc3z mysql]# mysql
-bash: mysql: command not found
[root@izbp1i7e0dqxcb89vkdgc3z mysql]# alias mysql=/usr/local/mysql/bin/mysql
[root@izbp1i7e0dqxcb89vkdgc3z mysql]# mysql
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)
[root@izbp1i7e0dqxcb89vkdgc3z mysql]# mysql -u root -p
Enter password: 

[root@izbp1i7e0dqxcb89vkdgc3z mysql]# sudo grep mysql_root_passwd /root/env.txt
mysql_root_passwd:xxxxxxxxx

mysql> create database flask;
Query OK, 1 row affected (0.00 sec)

#因为原来model文件article content数据类型不对
用mysql 语句修改
mysql> alter table article modify column content blob
    -> ;
Query OK, 2 rows affected (0.06 sec)
Records: 2  Duplicates: 0  Warnings: 0

允许远程连接mysql

mysql> use mysql;
Database changed

mysql> select host from user where user='root';
+-----------+
| host      |
+-----------+
| localhost |
+-----------+
1 row in set (0.00 sec)

mysql> SET SQL_SAFE_UPDATES = 0;
Query OK, 0 rows affected (0.00 sec)

mysql> update user set host = '%' where user ='root';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> SET SQL_SAFE_UPDATES = 1;
Query OK, 0 rows affected (0.00 sec)

mysql> flush privileges;
Query OK, 0 rows affected (0.00 sec)

需要更改 settings.py 文件

nohup命令说明:

用途:不挂断地运行命令。

语法:nohup Command [ Arg … ] [ & ]

不建议使用

Nginx

image-20210128092658005

Nginx 是一个高性能的 HTTP 和方向代理服务器,也是一个 IMAP/POP3/SMTP 服务器

Nginx 也是一个轻量级服务器(Tomcat 属于重量级,Java 一般部署在 Tomcat)

Apache、Tomcat、Nginx

Tomcat:应用(Java)服务器,只是一个 Servlet 容器,可以认为是 Apache 的扩展,可以独立于 Apache 运行

Apache:

Nginx:

image-20210128095205120

  • 俄罗斯人编写轻量级 HTTP 服务器,发音‘engine X’
  • 抗并发,nginx 处理请求是异步非阻塞
  • 高度模块化
  • 反向代理,接受外网请求,转发给内网服务器,再将结果返回给外网请求的客户端
  • 负载均衡,通过方向代理服务器来优化网站的负载

作者:嘶吼

这三者统一的功能是都有网络代理服务,我想侧重于先说一下nginx。然后因为apache和tomcat都是以由apache组织开发,所以我会从几个方面来看看nginx与apache的部分区别。

Nginx

nginx是由一位来自俄罗斯的程序员Igor Sysoe所编写的十分轻量级的HTTP服务器。nginx,它的发音为“engine X”。是一个高性能基于HTTP和反向代理的服务器,当只有静态资源的时候,就可以使用nginx来做服务器,现在很流行的动静分离(普遍情况下,是需要动态资源和静态资源分开,将静态资源部署在nginx上,当如果是静态资源的请求来时,就直接到nginx配置的静态资源目录下面获取资源;如果是动态资源的请求,nginx利用反向代理的原理,把请求转发给后台应用去处理,从而实现动静分离)就能通过nginx来实现。

· 具备高性能反向代理服务并属于轻量级的web服务器

反向代理

简单说就是利用代理服务器来接受互联网上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给互联网上请求连接的客户端,此时的代理服务器对外表现就属于一个反向代理服务器。

· 良好的扩展性,可以通过模块方式来进行功能扩展

· 较为灵活的负载均衡策略

轮询(默认)算法:每个请求按时间顺序逐一分配到不同的后端服务器,如果后端某台服务器宕机,则自动剔除故障机器,使用户访问不受到影响,个人觉得这个是最好的方法,但也有成本就是消耗机器的成本!实际情况中也验证了这一点,移动段的token!

· 抗并发的能力很强

Apache

Apache HTTP服务器是一个模块化的服务器,可以运行在几乎所有广泛使用的计算机平台上。其属于应用服务器。Apache支持支持模块多,性能稳定,Apache本身是静态解析,适合静态HTML、图片等,但可以通过扩展脚本、模块等支持动态页面等。

Tomcat

是应用(Java)服务器,它只是一个Servlet(JSP也翻译成Servlet)容器,可以认为是Apache的扩展,但是可以独立于Apache运行。

如果请求是静态网页则由Apache处理,并将结果返回;如果是动态请求,Apache会将解析工作转发给Tomcat处理,Tomcat处理后将结果通过Apache返回。这样可以达到分工合作,实现负载均衡,提高系统的性能。

Apache和Nginx的部分功能相比较

异步能力:apache中也有异步模块支持异步功能,不过是阻塞性异步,而nginx是非阻塞性异步。

抗并发:nginx因为软件体积小,消耗主机资源少,抗并发能力是apache的3倍以上。

重量等级:apache配置相对nginx复杂,自身不支持动态页面。

漏洞缺陷:apache相对nginx的bug会少很多。

Nginx 以轻量的优点在慢慢取带重量级的Apache ,现在已经是主流。

正反向代理

img

  • 正向代理:翻墙就是正向代理
  • 反向代理:服务器位于用户与目标服务器之间,但是对于用户而言,反向代理服务器就相当于目标服务器,即用户直接访问反向代理服务器就可以获得目标服务器的资源。同时,用户不需要知道目标服务器的地址,也无须在用户端作任何设定
安装

image-20210128100926851

https://my.oschina.net/yueshengwujie/blog/3099219

#查找安装路径,默认都是这个路径
[root@izbp1i7e0dqxcb89vkdgc3z nginx-1.18.0]# whereis nginx
nginx: /usr/local/nginx
  • nginx 端口80

    /usr/local/nginx

    开机自启

  • apache 端口 8080

    cd /usr/local/apache

    开机自启

systemctl问题解决
# 问题
[root@izbp1i7e0dqxcb89vkdgc3z sbin]# systemctl restart ./nginx
Failed to restart .-nginx.service: Unit not found.

# 解决方法
在/etc/init.d/目录下新建文件,文件名为nginx;或者用命令在根目录下执"行:# vim /etc/init.d/nginx (注意vim旁边有一个空格),随后插入代码:

#!/bin/sh
# nginx - this script starts and stops the nginx daemin
#
# chkconfig:   - 85 15
# description:  Nginx is an HTTP(S) server, HTTP(S) reverse \
#               proxy and IMAP/POP3 proxy server
# processname: nginx
# config:      /usr/local/nginx/conf/nginx.conf
# pidfile:     /usr/local/nginx/logs/nginx.pid
# Source function library.
. /etc/rc.d/init.d/functions
# Source networking configuration.
. /etc/sysconfig/network
# Check that networking is up.
[ "$NETWORKING" = "no" ] && exit 0
nginx="/usr/local/nginx/sbin/nginx"
prog=$(basename $nginx)
NGINX_CONF_FILE="/usr/local/nginx/conf/nginx.conf"
lockfile=/var/lock/subsys/nginx
start() {
[ -x $nginx ] || exit 5
[ -f $NGINX_CONF_FILE ] || exit 6
echo -n $"Starting $prog: "
daemon $nginx -c $NGINX_CONF_FILE
retval=$?
echo
[ $retval -eq 0 ] && touch $lockfile
return $retval
}
stop() {
echo -n $"Stopping $prog: "
killproc $prog -QUIT
retval=$?
echo
[ $retval -eq 0 ] && rm -f $lockfile
return $retval
}
restart() {
configtest || return $?
stop
start
}
reload() {
configtest || return $?
echo -n $"Reloading $prog: "
killproc $nginx -HUP
RETVAL=$?
echo
}
force_reload() {
restart
}
configtest() {
$nginx -t -c $NGINX_CONF_FILE
}
rh_status() {
status $prog
}
rh_status_q() {
rh_status >/dev/null 2>&1
}
case "$1" in
start)
rh_status_q && exit 0
$1
;;
stop)
rh_status_q || exit 0
$1
;;
restart|configtest)
$1
;;
reload)
rh_status_q || exit 7
$1
;;
force-reload)
force_reload
;;
status)
rh_status
;;
condrestart|try-restart)
rh_status_q || exit 0
;;
*)
echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload|configtest}"

exit 2
esac

# 接下来就依次操作以下命令:
cd /etc/init.d

chmod 755 /etc/init.d/nginx

chkconfig --add nginx
nginx控制

这样解决之后可以通过系统管理

systemctl status nginx 查看nginx状态

systemctl start nginx 启动

systemctl stop nginx 关闭

systemctl enable nginx 设置开机自启

systemctl disable nginx 禁止开机自启

或者进入 /usr/local/nginx 目录通过文件控制

image-20210128154944387

image-20210128155832300

配置文件

image-20210128155907840

image-20210128160422011

image-20210128160507684

image-20210128160631833

image-20210128160825995

image-20210128160910960

其他:image-20210128161042323

image-20210128161117473

image-20210128161129944

需要改 user 和 localtion

#进入nginx配置文件目录,找到nginx的配置文件nginx.conf
cd /usr/local/nginx/conf/
#直接修改
vi nginx.conf
#nginx.conf
[root@izbp1i7e0dqxcb89vkdgc3z conf]# cat nginx.conf
user  root;
worker_processes  1;
# 需要改user为当前登录用户
# 主进程

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    # 支持文件类型
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;
    # 日志格式,保存位置

    sendfile        on;
    #tcp_nopush     on;
    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    server {
        listen       80;
        server_name  localhost;

        #charset koi8-r;
        #access_log  logs/host.access.log  main;

        location / {
            root   html;
            index  index.html index.htm;
        }
        # 网站根目录

        #error_page  404              /404.html;
[root@izbp1i7e0dqxcb89vkdgc3z conf]# cat mime.types

types {
    text/html                                        html htm shtml;
    text/css                                         css;
    text/xml                                         xml;
    image/gif                                        gif;
    image/jpeg                                       jpeg jpg;
    application/javascript                           js;
    application/atom+xml                             atom;
    application/rss+xml                              rss;
......

uWSGI

image-20210128163405605

WSGI

  • WEB 服务网关接口
  • 不同的框架遵循的协议标准规范
  • 表述 web server 如何于 web application 通信的规范

uwsgi

  • 和 WSGI 一样是一种通信协议,是 uWSGI 服务器的独占协议
  • 用于定义传输信息的类型
  • 每一个 uwsgi packet 的前 4byte 为传输信息类型的描述,速度较快

uWSGI

  • uWSGI是一个Web服务器,它实现了WSGI协议、uwsgi、http等协议
  • Nginx中HttpUwsgiModule的作用是与uWSGI服务器进行交换
  • WSGI是一种Web服务器网关接口
  • 它是一个Web服务器(如nginx,uWSGI等服务器)与web应用(如用Flask框架写的程序)通信的一种规范

Django,Flask 等框架都有自己的就简单 WSGI server,一般用于服务器调试,生产环境使用其他 WSGI server

nginx作用

image-20210128163640224

image-20210128164128767

配置 uWSGI

安装

(myblog) [root@izbp1i7e0dqxcb89vkdgc3z ~]# pip install uwsgi

配置

工程目录下创建 uwsgi.ini

(myblog) [root@izbp1i7e0dqxcb89vkdgc3z flask_blog]# touch uwsgi.ini

uwsgi.ini 文件

[uwsgi]

#指定IP端口  // 直接外部访问
#使用nginx连接时,使用
#socket = 0.0.0.0:2333
#直接作为web服务器时,使用
http = 0.0.0.0:80

#项目目录
chdir = /tmp/myproject/flask_blog

#适用于flask项目部署
wsgi-file = app.py
#router
callable = app

#进程个数
processes = 5
pidfile = uwsgi.pid

#启用线程
enable-threads = true

#启用主进程
master = true

#设置日志目录
daemonize = uwsgi.log


#####################
简单项目可以只用以上部分
#指定项目的application
module = web.wsgi:application

#uwsgi启动用户名和用户组
uid = www
gid = www

#指定Socket路径  //内部访问  #权限和nginx一样
socket = /www/wwwroot/web/script/uwsgi.sock
#socket = :8080

#socket权限设置
chmod-socket = 755


#自动移除unix Socket 和 Pid 文件 当服务停止的时候
vacuum = true

#序列化接受的内容,如果可能的话
thunder-lock = true

#设置自动中断时间
harakiri = 30

#设置缓冲
post-buffering = 4096

#设置静态文件
#static-map = /static=//www/wwwroot/mysite/static

使用 uWSGI 服务器

启动:		uwsgi --ini uwsgi.ini
停止:		uwsgi --stop uwsgi.pid


#(myblog) [root@izbp1i7e0dqxcb89vkdgc3z flask_blog]# uwsgi --ini uwsgi.ini
[uWSGI] getting INI configuration from uwsgi.ini

(myblog) [root@izbp1i7e0dqxcb89vkdgc3z flask_blog]# ps -aux |grep uwsgi
root      2403 15.3  2.2 263304 41456 ?        S    19:38   0:00 uwsgi --ini uwsgi.ini
root      2406  0.0  1.9 263304 36468 ?        S    19:38   0:00 uwsgi --ini uwsgi.ini
root      2407  0.0  1.9 263304 36468 ?        S    19:38   0:00 uwsgi --ini uwsgi.ini
root      2408  0.0  1.9 263304 36468 ?        S    19:38   0:00 uwsgi --ini uwsgi.ini
root      2409  0.0  1.9 263304 36468 ?        S    19:38   0:00 uwsgi --ini uwsgi.ini
root      2410  0.0  1.9 263304 36468 ?        S    19:38   0:00 uwsgi --ini uwsgi.ini
root      2412  0.0  0.0 112812   972 pts/0    R+   19:38   0:00 grep --color=auto uwsgi


#当前文件夹内就会生成
uwsgi.log
uwsgi.pid
(myblog) [root@izbp1i7e0dqxcb89vkdgc3z flask_blog]# cat uwsgi.pid
28374
用于停止服务时使用

问题解决

#(myblog) [root@izbp1i7e0dqxcb89vkdgc3z flask_blog]# curl 127.0.0.1
curl: (7) Failed connect to 127.0.0.1:80; Connection refused

#(myblog) [root@izbp1i7e0dqxcb89vkdgc3z flask_blog]# netstat -anptl
Active Internet connections (servers and established)

tcp        0      0 172.17.28.202:45708     100.100.30.26:80        ESTABLISHED 1166/AliYunDun      
# 就是你端口没有服务的话,阿里云盾会自动占用

(myblog) [root@izbp1i7e0dqxcb89vkdgc3z flask_blog]# uwsgi --ini uwsgi.ini
[uWSGI] getting INI configuration from uwsgi.ini
#启动成功

动静分离

nginx.conf 拷贝到当前项目下

http{
		include      /usr/local/nginx/conf/mime.types;
........
        root /tmp/myproject/flask_blog;
        location / {
            include /usr/local/nginx/conf/uwsgi_params;
            uwsgi_pass localhost:2333;
        }
        # 动态 连接 uwsgi

        location /static{
        alias /root/myproject/flask_blog/static;
        }
        #静态
.........        
}
[root@izbp1i7e0dqxcb89vkdgc3z sbin]# nginx -c /tmp/myproject/flask_blog/nginx.conf
#启动用绝对路径

只使用 systemctl 无法指定 conf 文件

uwsgi.ini

#关闭http = 0.0.0.0:80
socket = 0.0.0.0:2333

ls

(myblog) [root@izbp1i7e0dqxcb89vkdgc3z flask_blog]# ls
app.py  ext         nginx-1.18.0.tar.gz  nginx.conf   requirements.txt  static     uwsgi2.ini  uwsgi.log
apps    migrations  nginx2.conf          __pycache__  settings.py       templates  uwsgi.ini   uwsgi.pid

Redis

使用 Redis 对数据进行缓存

安装

centos7

  1. 安装gcc依赖

    由于 redis 是用 C 语言开发,安装之前必先确认是否安装 gcc 环境(gcc -v),如果没有安装,执行以下命令进行安装

    [root@localhost local]# yum install -y gcc 
    
  2. 下载并解压安装包

    [root@localhost local]# wget http://download.redis.io/releases/redis-5.0.3.tar.gz
    [root@localhost local]# tar -zxvf redis-5.0.3.tar.gz
    
   
3. cd切换到redis解压目录下,执行编译

   ````bash
   [root@localhost local]# cd redis-5.0.3

[root@localhost redis-5.0.3]# make

  1. 安装并指定安装目录

    [root@localhost redis-5.0.3]# make install PREFIX=/usr/local/redis
    
  2. 启动服务

    5.1前台启动

    [root@localhost redis-5.0.3]# cd /usr/local/redis/bin/
    [root@localhost bin]# ./redis-server
    
   
5.2后台启动
   
从 redis 的源码目录中复制 redis.conf 到 redis 的安装目录
   
​````bash
   [root@localhost bin]# cp /usr/local/redis-5.0.3/redis.conf /usr/local/redis/bin/

修改 redis.conf 文件,把 daemonize no 改为 daemonize yes

   [root@localhost bin]# vi redis.conf

img

后台启动

   [root@localhost bin]# ./redis-server redis.conf

img

  1. 设置开机启动

    添加开机启动服务

    [root@localhost bin]# vi /etc/systemd/system/redis.service
    

    复制粘贴以下内容:

    [Unit]
    Description=redis-server
    After=network.target
    
    [Service]
    Type=forking
    ExecStart=/usr/local/redis/bin/redis-server /usr/local/redis/bin/redis.conf
    PrivateTmp=true
    
    [Install]
    WantedBy=multi-user.target
    

    注意:ExecStart配置成自己的路径

    设置开机启动

    [root@localhost bin]# systemctl daemon-reload
    [root@localhost bin]# systemctl start redis.service
    [root@localhost bin]# systemctl enable redis.service
    

    创建 redis 命令软链接

    [root@localhost ~]# ln -s /usr/local/redis/bin/redis-cli /usr/bin/redis
    

    测试 redis

    img

服务操作命令
systemctl start redis.service  #启动redis服务

systemctl stop redis.service  #停止redis服务

systemctl restart redis.service  #重新启动服务

systemctl status redis.service  #查看服务当前状态

systemctl enable redis.service  #设置开机自启动

systemctl disable redis.service  #停止开机自启动
Redis 基础知识

https://www.runoob.com/redis/redis-tutorial.html

五种数据类型

  • string 字符串
  • hash 哈希(键值对)
  • list 列表
  • set 集合
  • zset 有序集合(常用于排序)
SET name value

GET value

虚拟环境安装 redis 插件

(myblog) [root@izbp1i7e0dqxcb89vkdgc3z ~]# pip install redis
Flask-caching

为了尽量减少缓存穿透,同时减少web的响应时间,可以针对那些需要一定时间才能获取结果的函数和那些不需要频繁更新的视图函数提供缓存服务,可以在一定的时间内直接返回结果而不是每次都需要计算或者从数据库中查找

(myblog) [root@izbp1i7e0dqxcb89vkdgc3z ~]# pip install flask-caching

引入项目

ext/__init__.py

from flask_caching import Cache

cache = Cache()

apps/__init__.py

......
from ext import db, bootstrap, cache

config = {
    'CACHE_TYPE': 'redis',
    'CACHE_REDIS_HOST': '127.0.0.1',
    'CACHE_REDIS_PORT': 6379
}
# 配置 redis 数据库

def create_app():
    app = Flask(__name__, template_folder='../templates', static_folder='../static')
    app.config.from_object(settings.ProductionConfig)
    cache.init_app(app=app, config=config)
    # 初始化缓存文件
......

缓存键值对

设置:

cache.set(key,value,timeout=second)
cache.set_many([key,value],[key,value].......)

获取:

cache.get(key)
cache.get_many(key1,key2......)

删除:

cache.delete(key)
cache.delete_many(key1,key2......)
cache.clear()

手机验证码就可以使用 缓存键值对的形式 保存校验

缓存视图函数

@user_bp.route('/', endpoint='index')
@cache.cached(timeout=50)
def index():
    ......
    
# @cache.cached(timeout=50)
timeout 设置过期时间

直接加到对应路由上,适用于某一页面内容较多刷新速度较慢时

与Flask-caching类似的插件:Flask-cache(较少用)

WTForms

开发文档:http://www.pythondoc.com/flask-wtf/

Flask-WTF是集成WTForms,并带有 CSRF 令牌的安全表单和全局的 CSRF 保护功能,在建立表单所创建的类都是继承 Flask_wtf 中的 FlaskForm,而 FlaskForm 是继承 WTForms 中的 forms

功能

  • 集成 wtforms。
  • 带有 csrf 令牌的安全表单。
  • 全局的 csrf 保护。
  • 支持验证码(Recaptcha)。
  • 与 Flask-Uploads 一起支持文件上传。
  • 国际化集成。

安装

[root@izbp1i7e0dqxcb89vkdgc3z ~]# pip install Flask-WTF
表单

使用方式类似于数据库

只提供表单所以要加 form 标签

使用 wtform 必须设置 SECRET_KEY

标准表单字段

TextField 		代表<input type ='text'> HTML表单元素
IntegerField	用于显示整数的TextField
TextAreaField 	代表<testarea> html表单元素
PasswordField 	代表<input type ='password'> HTML表单元素
SubmitField 	表示<input type ='submit'>表单元素
SelectField 	表示选择表单元素
StringField
PasswordField
DecimalField
BooleanField
DatetimeField
......

常用验证器

DataRequired 	检查输入栏是否为空
mail 			检查字段中的文本是否遵循电子邮件ID约定
IPAddress 		验证输入字段中的IP地址
Length 			验证输入字段中字符串的长度是否在给定范围内
NumberRange 	在给定范围内的输入字段中验证一个数字
URL 			验证输入字段中输入的URL
EqualTO


# 添加方法 
name = TextField("Name Of Student",[validators.Required("Please enter your name.")])
使用
  1. 引入 CSRF
# __init__.py
from flask_wtf import CSRFProtect

def create_app():
    ......
    CSRFProtect.init_app(app=app)
    ......
  1. 定义 Form.py
from flask_wtf import Form, validators
from wtforms import StringField, PasswordField
from wtforms.validators import DataRequired, Length


class UserForm(Form):
    username = StringField('name', validators=[DataRequired()])
    password = PasswordField('password', validators=[Length(min=6, max=20, message='长度必须在6~20位之间')])

    # 此表有 username和password两个字段,类似于sqlalchemy
  1. 使用

视图函数

@article_bp1.route('form', methods=["GET", "POST"], endpoint='form')
def form():
    userform = UserForm()
    # 进行校验
    if userform.validate_on_submit():
        return 'ok'
    return render_template('article/form.html', userform=userform)

模板中

<form method="POST" action="{{ url_for('article.form')}}">
    {{ userform.csrf_token }}
    {#  防止csrf,必须设置secret_key  #}
    {{ userform.username }}{% if userform.username.errors %}{{ userform.username.errors.0 }}{% endif %}
    {#  如果有报错则输出报错的message, .0表示只输出内容  #}
    {{ userform.password }}{% if userform.password.errors %}{{ userform.password.errors.0 }}{% endif %}
    <input type="submit" value="Go">
</form>

#如果模板中存在表单,你不需要做任何事情。与之前一样:
<form method="post" action="/">
    {{ form.csrf_token }}
</form>

# 但是如果模板中没有表单,你仍然需要一个 CSRF 令牌:

<form method="post" action="/">
    <input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
</form>

image-20210130093544251

自定义验证
# form.py
class UserForm(FlaskForm):
    username = StringField(label='用户名', validators=[DataRequired(), Length(min=6, max=20, message='长度必须在6~20位之间')])
    password = PasswordField(label='密码', validators=[DataRequired(), Length(min=6, max=20, message='长度必须在6~20位之间')])
    confirm_password = PasswordField('确认密码', validators=[DataRequired(), Length(min=6, max=20, message='长度必须在6~20位之间'),EqualTo('password', '密码不一致')])
    phone = StringField('手机号', validators=[DataRequired(), Length(min=11, max=11, message='长度必须是11位')])
    email = EmailField('邮箱', validators=[DataRequired()])

    def validate_username(self, data):
        # validata_(与上边定义的字段名相同)
        if self.username.data[0].isdigit():
            raise ValidationError('用户名不能以数字开头')

    def validate_phone(self, data):
        phone = data.data
        # 如果匹配上就返回一个对象,没匹配就返回 None
        if not re.search(r'^1[356789]\d{9}$', phone):
            raise ValidationError('手机号格式不正确')
            # 注意使用 raise 关键字而非 return 

image-20210130093744991

form.html

<form method="POST" action="{{ url_for('article.form')}}">
    {{ userform.csrf_token }}
    {#  防止csrf,必须设置secret_key  #}
    {{ userform.username.label }}:{{ userform.username }}{% if userform.username.errors %}{{ userform.username.errors.0 }}{% endif %}<br>
    {#  如果有报错则输出报错的message, .0表示只输出内容  #}
    {{ userform.password.label }}:{{ userform.password }}{% if userform.password.errors %}{{ userform.password.errors.0 }}{% endif %}<br>
    {{ userform.confirm_password.label }}:{{ userform.confirm_password }}{% if userform.confirm_password.errors %}{{ userform.confirm_password.errors.0 }}{% endif %}<br>
    {{ userform.phone.label }}:{{ userform.phone }}{% if userform.phone.errors %}{{ userform.phone.errors.0 }}{% endif %}<br>
    {{ userform.email.label }}:{{ userform.email }}{% if userform.email.errors %}{{ userform.email.errors.0 }}{% endif %}<br>

    <input type="submit" value="Go">
文件上传

FileField

接受一个 FileStorage

  1. 定义form

    使用 FileField,如果要指明上传类型需要使用:FileAllowed([‘jpg’,‘png’])

  2. 模板中的使用和其它类型字段一致,但是必须在 form 上面加:enctype=“multipart/form-data”

  3. 视图函数中如果验证成功

    file=uform.data

验证码
  1. 使用 Flask-WTF 内置验证码

recaptcha

使用 Google 验证器,国内不太友好

  1. pillow

安装

pip install -i https://pypi.douban.com/simple/  pillow

使用

utils/captcha.py

import os
import random
from PIL import Image, ImageFont, ImageDraw


# 生成随机颜色,返回 rgb 元组
def get_random_color():
    return (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))


# 生成验证码图片
def generate_image(length):
    s = 'qazwPLMTRDXZSENBHUJCFhnujmikol098IYGNBHUJIYGV12NBHUJIYGVCF365478WAQ'
    size = (130, 50)
    # 创建画布
    im = Image.new('RGB', size, color=get_random_color())
    # 创建字体
    font = ImageFont.truetype('simfang.ttf', size=30)
    print(os.path.curdir)
    # 创建 ImageDraw 对象
    draw = ImageDraw.Draw(im)
    # 绘制验证码
    code = ''
    for i in range(length):
        c = random.choice(s)
        code += c
        draw.text((5 + random.randint(2, 15) + 20 * i, random.randint(2, 7)), text=c, fill=get_random_color(),
                  font=font)

    # 绘制干扰线
    for i in range(8):
        x1 = random.randint(0, 130)
        y1 = random.randint(0, 50 / 2)
        x2 = random.randint(0, 130)
        y2 = random.randint(50 / 2, 50)

        draw.line(((x1, y1), (x2, y2)), fill=get_random_color())
    return im, code

user/view.py

@user_bp.route('/image')
def get_image():
    im, code = generate_image(4)
    session['valid'] = code
    # 将 image 对象转换成二进制
    buffer = BytesIO()
    im.save(buffer, 'JPEG')
    buf_bytes = buffer.getvalue()
    response = make_response(buf_bytes)
    response.headers['Content-Type'] = 'image/jpg'
    return response

注意:字体文件,部署服务器使也需要设置,可以使用命令查找

[root@izbp1i7e0dqxcb89vkdgc3z sbin]# find  / -name *.ttf 
/usr/share/fonts/dejavu/DejaVuSansCondensed-BoldOblique.ttf
/usr/share/fonts/dejavu/DejaVuSans-ExtraLight.ttf
/usr/share/fonts/dejavu/DejaVuSansCondensed.ttf
/usr/share/fonts/dejavu/DejaVuSans-BoldOblique.ttf
.....

bootstrap结合flask-wtf

{% extends 'base.html' %}
{% import 'bootstrap/wtf.html' as wtf %}


{% block mycontent %}

    <form action="{{ url_for('user.form02') }}" method="post" enctype="multipart/form-data">
    {{ wtf.quick_form(uform,button_map={'submit_button':'primary'},horizontal_columns=('sm',1,1)) }}
    </form>

{% endblock %}

在大多数flask教程中,都会介绍使用WTForms和bootstrap,使用起来确实比较方便。方法大致如下:

from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired

class LoginForm(FlaskForm):
    name = StringField('姓名', validators=[DataRequired()])
    password = PasswordField('密码', validators=[DataRequired()])
    submitfield = SubmitField('提交')
12345678

在模版中各表单项分开渲染:

<form id="loginform" action="/login" method="post">
	{{ form.hidden_tag() }}
	{{ form.name.label }}{{ form.name }}
	{{ form.password.label }}{{ form.password }}
	{{ form.submitfield() }}
</form>
123456

或者使用quick_form()渲染:

{% import 'bootstrap/wtf.html' as wtf %}
{{ wtf.quick_form(form) }}
12

对于WTForms大概也是如此介绍,一般情况下也能用。但是如果我想把表单的检验放在前端呢?如何下手?渲染时若使用第一种方法,那还可以在<form>中设置id、action之类的;但若使用的是quick_form()呢?你想修改一点什么东西都无从下手。百度中也很少有人提及这个问题,查了不少资料,总算摸出点头绪来,解决了自己的问题。

在各种Field的定义中,可以加入一个render_kw。这是一个字典,平常HTML各种定义都可以放在里边。比如:

class LoginForm(FlaskForm):
    name = StringField('姓名', render_kw={"id":"name", "placeholder":"请输入用户名"})
    password = PasswordField('密码', render_kw={"id":"pwd", "placeholder":"请输入密码"})
    submitfield = SubmitField('提交', render_kw={"type": "button", "onclick":"alert('提交')"})
1234

这样子就可以在点击提交按钮后调用自定义的js函数进行检验,若满足条件再submit()。(上例中只是使用alert()弹出一个窗口。注意:在SubmitField中,需要加上"type": "button",要不然就算js函数中检验未通过不想submit(),它最终依然还是会自己提交表单。----其实加上"type": "button"的SubmitField,已经可以当作普通的button来使用了。)但是在前端中调用submit()需要用到form对象,而获取form对象需要定义form的id,再使用var form = document.getElementById("loginform");。但quick_form(form)渲染时是没有id的。这就需要在quick_form()中加入一些参数:

{{ wtf.quick_form(form, id='loginform', action='/login', form_type='horizontal', horizontal_columns=('lg',4,4)) }}

id和action意义很明显了。form_type是指定按什么样的方式渲染form的,horizontal_columns是指定一行几列,这两参数都与bootstrap有关,在原代码中看不太懂,大家修改做下测试,一般能满足到你的要求的。

闪现

message flash

Flask 使用闪现系统向用户反馈信息

flash 内容默认存储到 session 中,所以要提前设置 SECRET_KEY

index.html

{% extends 'base.html' %}

{% block middle %}

    {% with messages = get_flashed_messages() %}
        <ul>
            {% for message in messages %}
                <li>
                    {% if message %}
                        {{ message }}
                    {% endif %}
                </li>
            {% endfor %}
        </ul>
    {% endwith %}

{% endblock %}

user/view.py

@user_bp.route('/login', methods=['get', 'post'], endpoint='login')
def login():
    if request.method == "POST":
        username = request.form.get('username')
        if username == 'admin':
            flash('验证成功!1')
            flash('验证成功!2')
            flash('验证成功!3')
            return render_template('user/index.html')
    else:
        return render_template('user/login.html')
分类闪现

三种类型

  • message(未指定默认为 message)
  • error
  • warning
  • info

接受列表元组

@user_bp.route('/login', methods=['get', 'post'], endpoint='login')
def login():
    if request.method == "POST":
        username = request.form.get('username')
        if username == 'admin':
            flash('验证成功!1', 'info')
            flash('验证成功!2', 'warning')
            flash('验证成功!3', 'error')
            return render_template('user/index.html')

index.html

{% block middle %}

    {% with messages = get_flashed_messages(with_categories=True) %}
        <ul>
            {% for category,message in messages %}
                <li class="{{ category }}">
                    {% if message %}
                        {{ message }}
                    {% endif %}
                </li>
            {% endfor %}
        </ul>
    {% endwith %}

{% endblock %}
  1. 在一个请求结束的时候添加 flash

  2. 在当前请求中渲染获取或者是下一个请求中可以获取,其他不可以

    使用 redirect 依然可以接收到闪现的信息,但第三次请求就接受不到了

    flash('验证成功!1', 'info')
    return redirect(url_for('user.index'))
    
  3. 获取闪现内容

    get_flash_messages(whit_categories=True)
    
过滤闪现消息

可选,有针对性地获取对应类型的闪现消息

get_flash_messages(category_filter=['error'])

日志记录

python logging模块

级别排序:CRITICAL > ERROR > WARNING > INFO > DEBUG

import logging  # 引入logging模块
logging.basicConfig(level=logging.DEBUG,format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s')  
# logging.basicConfig函数对日志的输出格式及方式做相关配置
# 由于日志基本配置中级别设置为DEBUG,所以一下打印信息将会全部显示在控制台上

日志总结

uwsgi --> uwsgi.log

  1. 使用 app 自带

    app.logger.info('')
    app.logger.debug('')
    app.logger.warning('')
    app.logger.error('')
    
  2. 通过 logging 进行创建

    import logging
    logger = logging.getLogger('name')
    # 默认 flask 的名字叫 app
    

保存到文件

  1. logging.basicConfig(level=logging.DEBUG,format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s')  
    
  2. import logging
    
    logger = logging.getLogger(__name__)
    logger.setLevel(level=logging.INFO)
    handler = logging.FileHandler('log.txt')
    handler.setLevel(logging.INFO)
    formatter = logging.Formatter('%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s')
    handler.setFormatter(formatter)
    logger.addHandler(handler)
    
  3. 使用 logger.info('message')
    

获取闪现内容

get_flash_messages(whit_categories=True)
过滤闪现消息

可选,有针对性地获取对应类型的闪现消息

get_flash_messages(category_filter=['error'])

日志记录

python logging模块

级别排序:CRITICAL > ERROR > WARNING > INFO > DEBUG

import logging  # 引入logging模块
logging.basicConfig(level=logging.DEBUG,format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s')  
# logging.basicConfig函数对日志的输出格式及方式做相关配置
# 由于日志基本配置中级别设置为DEBUG,所以一下打印信息将会全部显示在控制台上

日志总结

uwsgi --> uwsgi.log

  1. 使用 app 自带

    app.logger.info('')
    app.logger.debug('')
    app.logger.warning('')
    app.logger.error('')
    
  2. 通过 logging 进行创建

    import logging
    logger = logging.getLogger('name')
    # 默认 flask 的名字叫 app
    

保存到文件

  1. logging.basicConfig(level=logging.DEBUG,format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s')  
    
  2. import logging
    
    logger = logging.getLogger(__name__)
    logger.setLevel(level=logging.INFO)
    handler = logging.FileHandler('log.txt')
    handler.setLevel(logging.INFO)
    formatter = logging.Formatter('%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s')
    handler.setFormatter(formatter)
    logger.addHandler(handler)
    
  3. 使用 logger.info('message')
    
Logo

GitCode 天启AI是一款由 GitCode 团队打造的智能助手,基于先进的LLM(大语言模型)与多智能体 Agent 技术构建,致力于为用户提供高效、智能、多模态的创作与开发支持。它不仅支持自然语言对话,还具备处理文件、生成 PPT、撰写分析报告、开发 Web 应用等多项能力,真正做到“一句话,让 Al帮你完成复杂任务”。

更多推荐