8分钟

任务 5 配置基于CVM的Web项目

任务目的

经过 任务3 的操作。学员已经掌握了使用MyQR生成个性二维码的主要流程。但是上面的实验仅能在本地机器上进行操作,且无法分享给他人进行尝试。

如果希望制作一个简单的应用,将二维码的生成工作在网页上实现,并且可以供连接互联网的所有用户访问,可以参考这一步的任务操作:构建一个Flask项目,并配置在云服务器CVM上运行。

完成这一步的操作后,用户可以直接通过互联网访问生成二维码的应用页面,实现图片二维码的生成和下载操作。

任务步骤

1.登录CVM

打开浏览器,进入 CVM实例控制台 页面,选择已经配置好的CentOS云服务器实例,进行登录。

注:关于登录的具体操作,可以参考 任务1 中的 “登录CVM” 一节。

2.创建项目目录,复制项目代码

首先需要清楚项目的完整目录,以及目录中各文件和文件夹的作用。下面展示的是整个Flask项目的完整目录,以及项目中各文件和目录的作用说明。

.
├── app.py              # 主程序,项目启动文件
├── static              # 用于存放生成图片的静态文件目录
│     ├── export        # 存放导出的图片文件
│     └── origin        # 存放原始的图片文件
└── templates           # 存放模板文件的目录
       └── index.html   # 展示程序主页的模板文件

接下来将会通过具体的操作构建一个同样的目录,并填充文件内部的功能代码。

(1)创建一个项目目录,指定一个项目名。本例中定义一个名为qr_export的项目目录,并跳转到此目录中。

mkdir qr_export && cd qr_export

(2)创建static目录,以及它的两个子目录static/originstatic/export,分别用于存放原始图片和导出图片:

mkdir -p static/origin static/export

注:静态文件一般指项目中的CSS、js和图片文件。静态文件一般放到一个单独的目录中,以方便管理。此例中对应的静态文件目录为static目录,这也是Web项目常用的静态文件目录。

(3)创建项目文件app.py

vim app.py

此时将进入Vim终端交互界面,界面展示效果如下:

4-5-1 Vim终端界面展示

接下来需要在文件内粘贴代码。

英文输入状态下按I键(此处大小写均可)进入编辑模式,接下来复制下方的代码,在浏览器中通过 “右键-粘贴” 将完整的代码粘贴到文件中。

# flask模块用于实现简单的web服务项目
from flask import Flask, render_template, request, flash
# 导入COS相关模块,用于将二维码存入腾讯云对象存储COS
from qcloud_cos import CosConfig
from qcloud_cos import CosS3Client
import sys
# MyQR模块用于实现二维码的制作功能
from MyQR import myqr
# time模块用于生成文件中的时间戳
import time
# 导入配置文件
from config import *

app = Flask(__name__)

# 定义一个secret_key,内容可以随意指定,用于混入消息中进行加密
app.secret_key = "secret_key"   # 用户的 SecretKey,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参考https://cloud.tencent.com/document/product/598/37140

# ---实现主页展示功能---
@app.route('/')
def index():
    # 展示渲染后的主页模板文件
    return render_template('index.html')
# ---实现主页展示功能---

# ---实现二维码导出功能---
@app.route("/export", methods=["POST"])
def export():
    """上传文件函数,用于执行文件上传功能"""
    # 判断请求类型,只对POST请求的操作进行响应
    if request.method == "POST":
        # 获取表单中的 url_str 对应的内容
        url_str = request.form.get('url_str')
        # 如果 url_str 为空,默认使用腾讯云首页
        if not url_str:
            url_str = "https://cloud.tencent.com/"
        # 获取提交的文件对象,此文件通过模板中的表单提交
        upload_file = request.files.get('file')
        # 创建用于区分文件的13位时间戳,此时间戳会拼接在每个文件名的尾部
        time_str = str(int(round(time.time() * 1000)))

        # 如果没有获取到上传文件
        if not upload_file:
            # 拼接导出路径,文件名添加时间戳避免读取缓存
            export_name = "qrcode_{}.png".format(time_str)
            export_path = "./static/export/{}".format(export_name)
            # 生成不含图片的普通二维码
            try:
                myqr.run(url_str, save_name=export_path)
            # 如果生成过程中报错,展示错误信息,并返回主页
            except Exception as e:
                flash("Error: {}".format(e))
                return index()
            # 定义展示图片的标识 show_photo 为True,模板页面将会展示二维码
            show_photo = True

        # 如果上传了图片,保存图片并生成图片二维码
        else:
            # 获取文件名和文件后缀,用于进行判断和文件名生成
            file_name = upload_file.filename
            # 获取文件名的头部,作为生成的文件名头部
            file_header = file_name.split(".")[0]
            # 获取文件的格式尾缀,用于对文件格式进行判断
            file_type = file_name[-4:]
            print(file_type)
            # 如果文件后缀不符,展示提示信息,并跳转到主页
            if file_type not in [".bmp", ".jpg", ".png", ".gif"]:
                # flash可以在模板文件中闪现推送提示信息
                flash("文件格式有误,支持的文件格式为:bmp, jpg, png, gif")
                # 不进行转换操作,直接返回主页
                return index()

            # 定义上传图片的原始路径,用于存储上传的原始路径图片
            save_path = "./static/origin/{}".format(upload_file.filename)
            # 保存上传的原始图片
            upload_file.save(save_path)
            # 拼接导出文件的文件名头部,尾缀将在稍后进行判断
            export_header = "{}_{}".format(file_header, time_str)
            # 拼接导出路径,文件名添加时间戳避免读取缓存
            if file_type in [".bmp", ".jpg", ".png"]:
                export_name = export_header + ".png"
                export_path = "./static/export/{}".format(export_name)
            elif file_type == ".gif":
                export_name = export_header + ".gif"
                export_path = "./static/export/{}".format(export_name)
            # 生成包含图片的二维码(此处定义为生成彩色二维码)
            try:
                myqr.run(url_str,
                picture=save_path,
                colorized=True,
                save_name=export_path)
            # 如果生成过程中报错,展示错误信息,并返回主页
            except Exception as e:
                flash("Error: {}".format(e))
                return index()
            # 定义展示图片的标识 show_photo 为True,模板页面将会展示二维码
            show_photo = True

        # 完成上传后,重新渲染模板页面
        return render_template('index.html', export_path=export_path, show_photo=show_photo, export_name=export_name)
# ---实现二维码导出功能---

# ---实现二维码图片的下载功能---
@app.route("/download/<file_name>")
def download(file_name):
    download_type = request.args.get("type")
    if download_type == "local":
        from flask import Response  # 导入Response模块
        with open("./static/export/{}".format(file_name), "rb") as f:
            fp = f.read()
            response = Response(fp, content_type='application/octet-stream')
            # file_name需要进行编码转换,否则中文文件无法正常下载
            response.headers["Content-disposition"] = 'attachment; filename={}'.format(file_name.encode("utf-8").decode("latin-1"))
            return response
    elif download_type == "cos":
        global secret_id, secret_key, region
        # 执行功能——上传图片到COS存储桶
        secret_id = secret_id    # 用户的 SecretId,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参考https://cloud.tencent.com/document/product/598/37140
        secret_key = secret_key  # 用户的 SecretKey,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参考https://cloud.tencent.com/document/product/598/37140
        region = region
        config = CosConfig(Region=region, SecretId=secret_id,
                        SecretKey=secret_key)  # 用户的 SecretKey,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参考https://cloud.tencent.com/document/product/598/37140
        client = CosS3Client(config)
        with open("./static/export/{}".format(file_name), "rb") as fp:
            try:
                response = client.put_object(
                    Bucket=bucket_name,
                    Body=fp,
                    Key=file_name,
                    StorageClass='STANDARD',
                    EnableMD5=False
                )
            except Exception as e:
                # 上传文件失败时触发
                flash("Error: {}".format(e))
            else:
                flash("添加成功!COS图片链接为: https://{}.cos.{}.myqcloud.com/{}".format(bucket_name, region, file_name))
        export_path = "/static/export/{}".format(file_name)
        export_name = file_name
        show_photo = True
        return render_template('index.html', export_path=export_path, show_photo=show_photo, export_name=export_name)
# ---实现二维码图片的下载功能---

if __name__ == "__main__":
    # 定义监听host为0.0.0.0,表示此服务可以被外部网络访问
    # 默认监听端口为5000
    app.run(host="0.0.0.0")

项目核心功能说明:

此项目中主要包含三个视图函数,分别用于执行主页展示、生成二维码和下载功能,下面依次对这三个视图函数进行简单介绍:

视图函数名

url地址

实现功能

index

/

执行页面中所有内容的展示功能

export

/export

执行二维码的生成功能,会根据用户输入进行二维码处理

download

/download/<文件名>

执行下载已生成二维码的,将二维码图片添加到COS并生成访问链接的功能

完成粘贴后,需要保存文件,按下ESC键切换到命令模式,此模式下可以向编辑器中输入命令。在英文模式下输入:wq,即可保存文件,并退出Vim编辑器。

4-5-2 保存文件并退出Vim

(4)创建参数配置文件config.py

操作类似上方创建项目文件app.py的流程。

首先创建参数配置文件:

vim config.py

按下I键进入编辑模式,复制下方的代码,粘贴到文件中:

bucket_name = "<你的bucket_name>"
region = "<你的region>"
secret_id = "<你的secret_id>"    # 用户的 SecretId,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参考https://cloud.tencent.com/document/product/598/37140
secret_key = "<你的secret_key>"  # 用户的 SecretKey,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参考https://cloud.tencent.com/document/product/598/37140

将代码中右侧的部分,替换为学员自己的存储桶信息和云API密钥信息。

注:具体的参数获取,可以参考 任务4 中 “获取存储桶信息” 和 “获取云API密钥” 这两节

完成参数的配置后,再次按下 ESC键切换到命令模式,并在英文模式下使用命令:wq保存文件并退出编辑器。

(5)创建模板文件目录

此处将创建名为templates的模板文件目录,并在创建完成后跳转到此目录中。

mkdir templates && cd templates

(6)创建模板文件index.html

首先创建模板文件:

vim index.html

按下I键进入编辑模式,复制下方的代码,粘贴到文件中。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>图片二维码生成系统</title>
    <!-- 导入jquery,用于作为bootstrap依赖环境 -->
    <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"
        integrity="sha256-pasqAKBDmFT4eHoN2ndd6lN370kFiGUFyTiUHWhU7k8="
        crossorigin="anonymous"></script>
    <!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
    <link rel="stylesheet"
        href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css"
        integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
        crossorigin="anonymous">
</head>
<body>
    <div class="container">
    <h1>图片二维码生成系统</h1>
    <hr />
    <form class="form-inline" action="/export" method="post"
        enctype="multipart/form-data">
        <div class="form-group url_con">
            <h4>请输入访问url:</h4>
            <input type="text" name="url_str">
        </div>
        <div class="form-group file_con">
            <h4>请选择要上传的图片:</h4>
            <input type="file" name="file">
        </div>
        <input type="submit" value="生成" class="btn btn-primary">
    </form>
    <hr />
    {% if show_photo %}
        <div class="hide_con">
            <div class="qrcode_con">
                <img class="qrcode" src="{{ export_path }}" alt="生成的二维码">
            </div>
            <div class="download_con">
                <a class="download_button" href="/download/{{ export_name }}?type=local">
                    <button class="download_button btn btn-success">下载</button>
                </a>
                <a class="download_button" href="/download/{{ export_name }}?type=cos">
                    <button class="download_button btn btn-info">添加到COS</button>
                </a>
            </div>
        </div>
    {% endif %}
    <!-- 用于展示提示信息 -->
    {% for message in get_flashed_messages() %}
    <strong style="color:red">{{ message }}</strong>
    {% endfor %}
    </div>

    <style type="text/css">
        .container{
            width: 50%;
            margin: 40px auto 0;
        }  
        h1 {
            text-align: center;
        }
        .url_con {
            width: 35%;
        }
        .file_con {
            width: 45%;
            margin-top: -2px;
        }
        .hide_con {
            text-align: center;
        }
        .qrcode_con {
            width: 100%;
        }
        .qrcode {
            display: inline-block;
            width: 200px;
            height: 200px;
            border: 1px solid #000;
        }  
        .download_con {
            width: 60%;
            margin: 10px 20%;
            display: flex;
        }
        .download_button {
            width: 55%;
        }
    </style>
</body>
</html>

完成粘贴后,再次按下 ESC键切换到命令模式,并在英文模式下使用命令:wq保存文件并退出编辑器。

至此完整的项目目录及文件内容均已完成配置,但目前所在的位置仍为模板文件目录templates下,通过命令cd ..跳转到上一级目录——项目的根目录中,方便在下一步进行运行项目的操作。

3.运行项目

(1)在命令行执行命令python3 app.py,运行成功后会展示如下提示信息:

 * Serving Flask app "app" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)

注意:提示信息中也有提到,这个服务器是一个用于开发的服务器,不要将它用于生产环境。 Flask自带服务器的处理能力极其有限,仅适用于测试使用。本例为简化步骤直接自带服务器用于进行测试。如果配置正式的生产环境,需要搭建一个专门的Web服务器。

(2)在浏览器中输入服务器的IP和项目端口,尝试访问项目页面。

注:IP可以在登录CVM的登录界面获取,Flask项目的默认端口为5000。 如CVM的IP为1.1.1.1,没有额外配置Flask端口,访问地址1.1.1.1:5000即可进入项目页面

如果访问成功,将会看到以下界面:

4-5-3 项目Web页面

接下来可以尝试进行二维码的生成操作,确认项目功能已经顺利实现。