Flask搭建生物学数据库全流程
今年3月开学,我们组几个人坐在休息室拉家常。我说我去年可6了,从0开始做了一个可以预测基因编辑的网页。于是他们说,为啥做基因编辑,我们这几个做RNA结构的为啥不做个RNA结构的数据库呢?于是RSVdb计划就开始了。目前数据库已经完成,功能极其炫酷,我也从一个弱鸡全栈工程师硬是被活活逼成了凑活能上的全栈工程师。在此把建站全流程和技术给大家分享一下。
- 什么是Flask,怎么学
- 后端
- 前端
20210911更新
刚部署了新的服务器,还是觉得flask+uwsgi+nginx的组合还是不错的;
SQLite数据库很方便,但是效率太低,建议换成mySQL或者mariaDB,推荐后者;
前端现在流行用框架,不再是直接写js或者jquery。
我用的是vue.js(主要是上手快),还有大佬们会推荐react框架,都可以试试;
个人感觉用框架之后就回不去jquery了...
最近我会再更新一个博客,下文就不更新了
什么是Flask,怎么学
我们底层用Flask架构。Flask是一款轻量级的web框架,Python写的,也是基于Python的WSGI做的。所以很多大佬都在讨论那些说Flask好的人是不是大部分都在说WSGI做的好=-=大佬的世界我不懂。
我个人体会,这个架构号称轻量级,大概就是我用U盘一拷贝就可以把整个网站拷走,部署也很简单。并且因为都是用Python写的,所以我个人也比较熟悉,处理数据、调用本地命令都可以用Python完成也比较方便。总的来说就是,这个web架构不需要php,如果用SQLalchemy的话就不要MySQL,一切都比较方便:),不妨试试。
怎么学嘛?我是看李辉写的博客和书一路走过来的,几乎没看啥其他课程(当然也看来很多其他博客和百度到的资料)。可以看他的首页李辉的博客 http://greyli.com/ ,他写的书也很不错,《Flask 入门教程》和《Flask Web 开发实战》,后者实验室有一本,对我帮助很大。真的不想做广告,但是他写的这个确实是我看到的最好的。
之后可以参考Flask的官方教程(搜Flask就能看到),这个教程的快速入门的前半部分写的不错,我还跟着做了好多。之后的解释也很详细,然鹅对于当时初学的我确实看不懂,所以当时我就开始看李辉写的东西了。
后端
突然不知道该从哪说起了=-=,先来看张图,我们生产环境中后端的大概流程,也就是Flask+Uwsgi+Nginx作为后端。生产环境就是发布的环境,平时测试叫测试环境,只需要Flask。

Flask环境的部署
分为测试和正式发布。上图是正式发布的时候,平时测试相对简单。
Flask的安装在虚拟环境中的,我用的是pipenv。
pip install pipenv
pipenv install
#运行的时候
pipenv shell
虚拟环境启动之后,安装Flask和相关软件
pipenv install flask python-dotenv flask-sqlalchemy
dotenv是用来配置环境变量的。一般环境变量中会储存App的名字,数据库密码等,不用这个似乎也可以。
SQLalchemy是Flask默认的数据库管理。我觉得这个数据库的优势和MySQL相比就是,如果你的数据库不复杂,并且用户只是读取不录入,SQLalchemy的效果就挺好的。并且,用Python操作的,也比较方便。
安装配置好后,每次运行只需要进入虚拟环境,之后
flask run
#或者
python run.py
#run.py下文提到
就可以在本地环境的5000端口,也就是127.0.0.1:5000访问到你的网站了。当然,你一个空文件夹是跑不起来的=-=,至少写个run.py
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello World!'
if __name__ == '__main__':
app.run()
其他的不多说了,大家好好看教程了。
Flask网站目录文件存放
我再说一下Flask文件的存放。似乎网上有两种风格,第一种为:
#以下的中午名字仅为解释,实际似乎不能用中文名
-一个文件夹/
--webname/(你网站app的名字,我们的叫taolab)
---template(存放网页模板html)/
----网站1/
-----index.html
----网站2/
---static(存放静态文件js、css、img等)/
----网站1/
-----js/
-----css/
----网站2/
---blueprints(蓝本文件)/
----__ini__.py (把blueprints作为一个python包,这里面不用写啥)
----网站1.py (网站1的路由写在这里)
----网站2.py
---__ini__.py (启动网站的设置,比如导入蓝本,网站设置,启动数据库等)
---command.py (可以不写。Flask可以自定义一些命令,方便运维和管理)
---setting.py (网站和数据库的设置,再比如是否启用测试环境还是生成环境等等)
---model.py (定义数据库模型)
---extensions.py (引入的flask扩展,比如SQLalchemy)
#和网站文件夹同一目录下
--wsgi.py (把网站认为是一个python包,导入包,启动网站)
--.flaskenv (环境变量)
这个简单说就是,每个子网站都作为子文件夹存放在static,template等里面。还有一种就是,每个子网站一个文件夹,里面放static,template等。我现在用的是上面这种。
什么是蓝本
上面提到的蓝本,我简单说一下。其实在我看来就是把子网站的路由分开了。也就是理论上所有网站都可以写在一起,比如在/intro域名跳转到个人简介,在/blog页面跳转到个人博客(我说的不是子网页而是子网站),这两个可以写在一起的,也就是:
@app.route("/intro/")#子网站1,intro,个人简介(假设)
@app.route("/intro/index") #注册两个路由,也就是无论访问intro还是,intro/index都可以到主页页面
def showIntroIndex():
render_template('intro/index.html')
@app.route("/intro/labintro")#假设intro有个子页面,叫labintro
def showIntroIndex():
render_template('intro/labintro.html')
@app.route("/blog/")#子网站2,blog
@app.route("/blog/index")
def showBlogIndex():
render_template('blog/index.html')
@app.route("/blog/photoblog")#假设blog有个子页面,叫photoblog
def showIntroIndex():
render_template('intro/photo.html')
可以写到一起,然鹅问题就是,如果网站很多比如我这次做的在主网站中有5个子网站,每个子网站有5-10不等的子页面,这么写就很混乱(总的来说就是,大佬说这么写虽然方便很混乱,高手都用Blueprint,所以我也用)。所以分开了就在主网页中注册蓝本:
from app.blueprints.intro import intro #蓝本名叫intro.py
from app.blueprints.blog import blog #蓝本名叫intro.py
app.register_blueprint(intro, url_prefix='/intro')
app.register_blueprint(blog, url_prefix='/blog')
之后建立蓝本文件夹,里面有Intro.py, blog.py,把路由分开
from flask import render_template, Blueprint
intro = Blueprint("intro", __name__)
@intro.route("/")
@intro.route("/index")
def showIntroIndex():
render_template('intro/index.html')
这么弄,看起来整齐多了=-=。
ORM数据库
数据库管理我们用的是SQLalchemy。他是一种ORM数据库。ORM和其他数据库有啥区别自行百度。
这个数据库定义挺简单的,就用python写个类就行了:
class Species(rdb.Model):
#__tablename__ ='species'
#上面的表名不用自己定义就默认为把class中的名字小写了
id=rdb.Column(rdb.Integer, primary_key=True)
name=rdb.Column(rdb.String(40),unique=True, index=True) #unique为唯一,index为索引
gename=rdb.Column(rdb.String(20),index=True)
geo=rdb.Column(rdb.String(15))
这样一个叫species的表就定义好了。
查询的话就是:
tquery=Species.query.filter_by(name="Yeast").all()
#或者功能更强大的filter
tquery=Species.query.filter(Species.name=="Yeast", Species.gename.ilike("%ea%")).first()
#上面的ilike就是包含“ea”这两个字符。all()就是所有查询结果,first()就是第一个查询结果。
增删改查自行搜索;定义表和表间的一对多,多对一等关系,自行查看SQLalchemy的官方文档。
上线的一些部署和经验
其他后端的东西需要跟着教程走一遍都会了。建议自己先建个小网站,比如李辉做的初学者示例,一个显示电影名的watchlist github链接。
之后到了上线过程。我用的是学校网教中心的服务器,所以外围的安全不需要我们操心。内网我们都是堡垒机登陆之后操作,也更安全。
如果自己上线到服务器,就不能flask run了。需要用uWSGI和NGINX。uWSGI监听本地端口,操控链接flask,Nginx负责和外网通信处理请求,为啥要弄这两步?其实只用uWSGI理论也可以,然而NGINX是经过众多网站考验的,稳定性没得说,所以这俩配合比较好。当然,最后log也是双份的,定时清理就好了。遇到攻击nginx可以设置黑名单什么的。
uWSGI配置
[uwsgi]
socket = 127.0.0.1:xxxx
#(写上负责通讯的本地端口)
chdir = 网站目录
home = 虚拟环境目录
#这两个目录写绝对路径
#虚拟环境地址需要根据pipenv install之后的地址更改
callable = app
#wsgi文件中app的名字
wsgi-file = wsgi.py
#(你的wsgi文件)!!注意是相对路径
processes = 2
threads = 2
stats = 127.0.0.1:xxxx
#负责监听log的端口
daemonize = uwsgi.log
#储存网站log的文件名
WSGI.py的配置
import os
from dotenv import load_dotenv
dotenv_path = os.path.join(os.path.dirname(__file__), '.env')
#一般如果安装dotenv,会默认搜索.flaskenv里面的信息
#但是生成环境需要明确的定义,因此手动载入env
if os.path.exists(dotenv_path):
load_dotenv(dotenv_path)
from taolab import create_app
app = create_app('production')
env中的信息
FLASK_ENV=production
FLASK_APP=app
#上面写app名
SECRET_KEY='pJ********'
#数据库的密钥最好放在这里
nginx配置
location / {
include uwsgi_params;
uwsgi_pass 127.0.0.1:xxxxx;
#和上面的uwsgi端口要写成一样的
#下面这3行似乎可以有可无,因为你include了uwsgi的配置
uwsgi_param UWSGI_PYHOME /www/wwwroot/taolab.nwsuaf.edu.cn/taolab;
uwsgi_param UWSGI_CHDIR /www/wwwroot/taolab.nwsuaf.edu.cn/taolab;
uwsgi_param UWSGI_SCRIPT wsgi:app;
}
就是这些吧~
前端
前端其实没啥说的=-=,因为就是HTML5+CSS+JS嘛。所以,我说说Jinja2模板和前后端的通讯吧。
Jinja模板
是flask内置的模板渲染语言。“渲染”这次词,非常神乎其神,其实就是生成html代码。Jinja2有自己的一套语法,可以内置到html中,这样如果这个页面被访问了,首先jinja2读这个页面,遇到jinja2的语法相关代码就运行,把相应的html代码生成并替换到相应位置,之后再给用户。我举个例子啊:
<!--
这个是我们生成按钮组,也就是一串botton的jinja2代码。
也就是,我们不需要写一大堆<button></button>什么的了,
用jinja2的循环for语句就可以把所有物种名称全部生成
spin是后端传来的物种参数,也就是显示哪个物种,所以用jinja2的if判断以下,加个active的类就可以高亮
jinja2的语法主要就是
\{\% 语句 \%\}
\{\{ 变量 \}\}
因为github.io用的也是jinja2模板,所以后面的我用\注释了,实际没有这些\
-->
<div class="btn-group-vertical" role="group">
<button class="btn btn-default" disabled="disabled">Species</button>
\{\% for sp in spes \%\}
\{\% if sp==spin \%\}
<button type="button" class="btn btn-default spbutt active">{{sp}}</button>
\{\% else \%\}
<button type="button" class="btn btn-default spbutt">{{sp}}</button>
\{\% endif \%\}
\{\% endfor \%\}
</div>
flask可以给jinja2传参数,传变量,比如我现在要把物种做成变量传给模板
@rsvdb.context_processor
def imspecies():
spes = ["Arabidopsis", "Drosophila", "E.coli",
"Human", "Mouse", "Rice", "Yeast", "Zebrafish"]
return dict(spes=spes)
一般传数据就是在flask的render_template()中带参数,就传过去了。
前后端通信
刚才提到了Jinja2传数据,可以render_template()也就定义常量,用context_processor()。然鹅,我觉得这个方法传数据不好,和flash消息一样不好=-=。因为每次传完需要将页面重定向,也就是刷新,才能显示新数据展示的页面。可能针对不同需求可能有用吧,比如服务器提交之后页面不断刷新获得最新状态什么的(然鹅这个似乎用的是AJAX的长链接=-=)。总之,我觉得每次换点数据就刷新页面还是太夸张了。所以我用Jinja2模板主要是第一次把模板渲染好,之后通信都采用AJAX异步通讯完成。
AJAX是异步通讯的实现方法之一,吧。大概逻辑就算给后台发点请求,后端收到之后处理请求,再返回前端,直接更新在页面上,无需重新刷新。这样就方便多了:)
AJAX使用JS实现的,我举个jQuery的例子(jQuery写的代码少:)):
function showGenomeInfo(genomename) {
$.post("/rsvdb/browse/", { "med": "genome", "label": genomename }, function (data) {
//返回数据为json格式,js可以直接用,假设传的是{"genomeinfo":'R62'}这种
$("#genomeinfo").text(data.genomeinfo);
// 收回数据之后更新表格中的一些值
}, "json");
};
后台的python需要用到Flask里面的request,举个栗子:
from flask import request,jsonify
@rsvdb.route("/browse/", methods=['GET','POST'])
def showDetail():
if (request.method == 'POST'): #如果请求为POST
if (request.values['med'] == 'geoname'): #判断值里面有没有叫med的,它的值是不是genome
tgenomeinfo=getGenomeInfo(request.values['label'])#调用某个方法获得genome的信息
return jsonify(tgenomeinfo)#以json的格式返回数据
这样的话,用户体验会比较好。
前端的其他杂事
就是,不要重复造轮子啦。那些大佬的前端UI做的那么好,jQuery插件那么好,直接用就好了啊。有需求先Github一下,看看有没有轮子,不用总是手撸轮子。
