Flask 之旅 (一)

背景

接触 python 两周有余了。越来越喜欢这门语言,因为做什么都足够快,不像 java 那么笨重。虽然它还是那么容易出错。但是构建一个并不复杂的小工具的时候,它显的效率的多。就像海盗一样:快,狠,准。这里我就不比较 java 和 python 的优劣了,这个话题太大了,以我的水平也怕评论不好。我们就各自使用自己认为最适合项目的语言就好了。刚学 python 的第一周先是学习了一下基本语法,环境,IDE(好在有几年的 java 底子,学的比较快),然后用 pytest 和 allure 做了测试的基础架子,准备测试我们提供的 python 版本的 SDK。之后用 python 重写了一遍环境管理的那一堆 shell 脚本,比 shell 少些了很多代码,之后扩展也好,维护也好都方便了很多 (后悔这么晚才学 python 中~~~)。上周末在家开始学习 django 和 flask, 考虑到底选什么作为建站的框架。后来还是选了 flask,因为够小,够灵活。我暂时的需要可能不需要数据库之类的东西。所以也就没选什么都包办了的 django。于是我拿我们环境管理的需求练手。之前是通过一堆脚本控制的,现在我做一个 web,可以通过在页面上点点点就能管理好这些环境。我想一边练习一边记录其中的步骤,给像我一样刚接触的人一个快速上手的指引吧。由于都是业余时间做,所以更新的会比较慢。

Flask

这里介绍一下 Flask 给像我这种刚刚接触的人把。它给我的感觉就是个十分轻量级的,轻量级到令人发指的 web 框架。用过 Flask 之后才知道 springMVC 到底有多重。它使用起来也很简单,不需要任何 web 容器。 直接按下面的方式写一小段代码就可以了

复制代码
from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello World!'

if __name__ == '__main__':
    app.run(host='0.0.0.0', debug=True, port=9537)

当你运行这个脚本后,就可以在浏览器访问了。是不是够简单。同样它也支持像 django 那么大而全的功能,不过这些功能并没有被集成到 Flask 里面。 而是在你需要的时候通过安装相应的模块来实现。例如支持数据库的 orm 框架:Flask-SQLAlchemy 支持数据库迁移,升级等功能的模块:Flask_Migrate。 支持页面表单管理的模块:Flask-WTF。 这些都是需要的时候再安装就可以了。

学习资料

贴一下我找到的学习资料

Flask 官方文档的中文翻译版本:欢迎使用 Flask --- Flask 0.10.1 文档

Flask 所有扩展模块的文档:Extensions --- Flask Documentation (3.0.x)

一个我觉得比较好的博客:数据库 --- flask mega-tutorial 1.1 documentation

开始

OK,我们首先安装 Flask 吧,直接用 pip 安装就好了。暂时我们的功能还很简单,不需要其他的扩展模块。先贴一个图片看看我们的项目结构。

​编辑

我从上到下开始介绍吧

  • env_info 是我们所有环境的配置文件,暂时我没有用数据库来保存这些信息。里面都是 ini 文件,详情不表了。
  • lib 是我放置控制 docker 的脚本的地方。如大家所见,都是些 compile,deploy 什么的
  • web 是我们 Flask 的根目录
  • static 是我们放置静态文件的地方,例如 css,js,图片什么的。注:这是 Flask 的默认设置
  • templates 是我们所有模板的文件,大家可以先理解为所有的 html 文件都放在这里。注:这是 Flask 的默认设置

首先我们先在 web 目录的init.py 文件里写一下我们 Flask 的配置:

复制代码
# coding=utf-8

from flask import Flask

app = Flask(__name__)
# 如果想要使用Flask-WTF的表单,需要一个config文件
app.config.from_object('config')

接着创建一个 urls.py 文件,用来编写我们的路由和启动命令。就如我们上面的例子一样,我们这样写

复制代码
if __name__ == '__main__':
    app.run(host='0.0.0.0', debug=True, port=9537)

之后运行 urls.py 就算是我们的启动脚本了。然后写一个首页的路由方法。如下:

复制代码
@app.route('/')
@app.route('/index', methods=['GET', 'POST'])
def index():
    configs = list_all_config()
    return render_template('index.html', configs=configs)

我们来解释一下吧。 @app.route, 就是我们的路由方法,可以配置多个, 也就是多个 url 都可以绑定到这一个方法上。 methods 参数设定了方法类型。 可以看到方法里面我们取了所有的配置信息 (就是 env_info 中所有的配置信息)。我们希望将所有的配置信息都展示在首页上。 所以取出信息以后,我们使用 render_template 这个渲染模板的方法,将 index.html 进行渲染并把 configs 传递到模板中 (在 Flask 中页面就是模板)。 好了,现在我们去看看这个 index.html 里是怎么写的吧。

复制代码
<div class="row clearfix">
        {% for config in configs %}
            <div class="col-md-4 column">
                <h2>
                    {{ config.name_prefix }}
                </h2>
                <p>
                </p>
                <p>
                    <a class="btn" href="#">查看详情 >></a>
                    <a class="btn" href="/restart/{{ config.name_prefix }}">部署环境 >></a>
                </p>
            </div>
        {% endfor %}
    </div>

这里来看一下 Flask 内置的 Jinjia2 的语法吧。流程控制语句使用{% content %} 来表示, 直接把元素输出到页面上使用{{% element %}}。 上面的代码就是说,我循环一下传递进来的 configs 列表。把每个配置的名字都展示到页面上,然后每个配置都有两个链接,分别是查看配置的详情和部署环境。这个是后话了。 现在如果我们启动 web 服务,使用浏览器访问就会看到所有的配置都显示在了我们的主页上。 但是还有点不够,那就是我们的页面实在太难看了。 没有任何的布局。 但是我还是个前端的小白,让我重头学一遍 css,js 什么的太费时间了。 所以我们可以取巧一下,偷懒一下。来给我们的 web 换个装

bootstrap

换装工具就是 bootstrap 了。 下载地址如下: Bootstrap · The most popular HTML, CSS, and JS library in the world.

我们要把下载下来的文件解压,然后放到 static 目录下,如图:

​编辑

然后我们在 index.html 加入引用

复制代码
<meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!-- 引入 Bootstrap -->
    <link href="http://cdn.static.runoob.com/libs/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">

    <!-- HTML5 Shim 和 Respond.js 用于让 IE8 支持 HTML5元素和媒体查询 -->
    <!-- 注意: 如果通过 file://  引入 Respond.js 文件,则该文件无法起效果 -->
    <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
    <script src="https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>

接着我们使用在线的页面生成工具:http://www.runoob.com/try/bootstrap/layoutit/#

​编辑

我们用这个工具拖拽好自己的布局之后。就可以点击那个下载。把 HTML 代码弄到我们的页面里。 然后我们就有了如下的页面了

​编辑

好吧,我不具备什么审美天赋。也没下什么大工夫。看着顺眼就行。

最关键的功能

好了,到了最关键的功能了,那就是部署一个环境。 所以在我们的首页中才会有这样一个链接:

复制代码
<a class="btn" href="/restart/{{ config.name_prefix }}">重新启动 >></a>

现在我们需要为这个链接创造另一个路由方法。如下:

复制代码
@app.route('/restart/<name>', methods=['GET', 'POST'])
def restart(name):
    workspace = os.path.dirname(os.path.abspath(os.path.dirname(__file__)))
    path = workspace + '/boot.sh'
    config = workspace + '/env_info/' + name + '.ini'
    command = 'bash %s %s' % (path, config)
    result = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    jobs = Jobs.jobs
    if name in jobs.keys():
        jobs[name].kill()
        jobs.pop(name)
    jobs[name] = result
    return render_template('restart.html', name=name)

这里我们看到了一个用法/restart/。 这个<>中的 name 就是页面向我们传递的参数。 所以在 restart 方法里我们才可以使用 name 这个参数。我们根据 name 找到相应的配置文件并使用 subprocess 模块进行调用。由于我们希望这个步骤是异步的,不能再部署过程中页面一直处于卡死的状态。所以使用 Popen 这个函数来调用。 我们把标准输出和标准错误都重定向到管道中。把这个调用后返回的对象保存在一个全局的 dict 中。方便之后取 log 的时候用。 那么现在我们可以在页面上点击链接进行部署了。但是有一个问题,就是我们不知道部署当前是失败了还是成功了。 我们希望能实时的在页面上看到部署的日志, 我们需求页面能实时的显示最新的 log 而不必刷新页面

Jquery Ajax

我这个前端水平就只能想到这样一个方案了,现在我们去现在一个 Jquery.js 吧。 同样放到 static 目录下。

​编辑

然后我们再搞一个 log 页面, 在 head 标签中我们引入 Jquery。

复制代码
<script type=text/javascript src="{{ url_for('static', filename='jquery.js') }}"></script>

然后我们加入下面的一段 JS:

复制代码
<script type=text/javascript>
    $SCRIPT_ROOT = {{ request.script_root|tojson|safe }};
</script>


<script type=text/javascript>
    $(function () {
        for (var i = 1; i < 10000; i++) {
            $.get($SCRIPT_ROOT + '/log/{{ name }}', function (data) {
                $("#log").append(data);
            })
        }
    });
</script>


<dev id="log"></dev>

我来解释一下上面这段 JS。 .get 方法就是动态执行一个请求的方法,后面跟上 url。 ("#log") 是 JS 的选择器,用来定位页面元素,意思是找到 id 为 log 的元素并执行 append 的操作。所有的代码的意思就是循环不停的调用这个 get 方法,并将返回值作为 data 传递给之后的 function,这个 function 用来定位元素并把日志追加到页面上。 那么现在,我们唯一缺的就是再写这么一个路由方法实时读取日志了。如下:

复制代码
@app.route('/log/<name>')
def update_log(name):
    # time.sleep(1)
    job = Jobs.jobs[name]
    # error = job.stderr.readline()
    out = job.stdout.readline()
    return '<p>'+out+'</p>'

看过之前 restart 方法的代码,我们知道所有的 Popen 返回的结果对象都放到了一个全局的 dict 里。 现在我们根据页面 AJax 传递 name 把它找出来。然后读取一行日志信息返回。OK,万事大吉。 我不会截动态图~~ 大家见谅。。。

唠叨两句

我知道前端的发展现在已经很厉害了,Jquery 这种玩意都不知道过时了多久,我们公司的前端都已经在用 React.js。可我是没那么多时间去学了。反正我们做的都是给项目内部用的,所以我想不用纠结什么前沿技术了,够用就好。目前我就做了这么多。 大概实现了最主要的几个功能:显示配置,部署环境,实时获取 log。之后加上创建配置和修改配置的功能后就基本上达到可用状态了。额,想想在老东家的时候用 springMVC 为了达到可用状态可是比这多花了不少的时间。所以我现在很喜欢 python,在构建这些工具的时候效率更高,更快。 有个小段子是我最开始请教我司写 SDK 的小伙子的时候,问他你为什么选择用 python,他回答说:为了多活两年。 恩,现在我有点理解他为什么这么说了。 之后我会慢慢的完善这个小网站,就当是练手了,然后我会慢慢的把过程都记录下来发到社区里,一个是对我的学习和工作有个记录,再一个也希望对一些同学有所帮助。毕竟在工作中做测试平台,测试工具什么的可能都要封装成一个 web。由于这纯是我的个人意愿,组织上并没有给我在工作中安排时间去做。 所以我都是抽空搞一搞,进度可能有点慢。希望之后能成熟到给一些非技术人员,例如产品,售前的同学去搭建一个他们需要的环境,而不再需要懂技术的人跑去给他们运行脚本部署。恩~ 保持学习

相关推荐
她说彩礼65万4 小时前
Asp.net core 模型绑定
后端·asp.net
Anova.YJ4 小时前
AI Notebook
人工智能·python·机器学习
用户34232323763174 小时前
BLE 传感器节点实战
后端
疯狂成瘾者4 小时前
Java 常见集合方法
java·windows·python
web守墓人4 小时前
【python】uv解决依赖安装缓慢的问题
开发语言·python·uv
asdzx674 小时前
使用 Python 精准操控 Word 字体:获取与替换方案
python·c#·word
胡耀超4 小时前
固件逆向:从一坨二进制,到一个可以被解释的系统
python·逆向工程·硬件安全·二进制分析·电子数据取证·嵌入式安全·固件逆向
fliter4 小时前
Go 1.26 新增 `bytes.Buffer.Peek`:只看数据,不移动读取位置
后端
羑悻4 小时前
我受够了 Coding Agent 的“假聪明,真串行”,于是做了 Meta-Orchestrator!
后端
神奇小汤圆4 小时前
字节二面挂了!被问“订单超时自动取消”
后端