一、 html页面生成图片的技术背景

将html页面生成图片,目前经过本人验证的,有两种方式:

1、纯前端通过html2canvas生成图片,
2、将前端的dom传回Django后端,通过wkhtmltoimage生成图片
但是以上两种方式,都有一个前提,就是需要用户先打开页面(即页面必须先在前端渲染完成),然后要么是通过setimeout自动延迟生成图片,要么是用户通过点击按钮,触发生成图片的请求。

二、发送图片邮件的需求

需求:

在后端,做一个定时任务,定时发送日报周报邮件,邮件的正文是图片。图片中包含图片、饼图、柱状图和表格。

思考:

1、做定时自动任务,就不能要求用户先打开页面,那上面两种生成图片的方式就不可行。
2、虽然以上方式不可行,但是html页面在后端生成图片的技术还可以用,只不过要换一种思路。
3、解决办法就是,在后端生成html页面,并在后端将html页面生成图片。

实现步骤:

  1. 先用html实现一个精美的海报页面。在vue架构中,该html页面不能使用原有的组件,比如element等vue组件,因为使用这些组件,不好控制css,而且在生成图片时,会有一些奇怪的兼容问题。其次就是,所有的css都手写,和html放在一起。
  2. 将生成的html页面,制作成模板。第一、将css文件和图片素材单独放到后端的静态文件夹中,模板只需要通过链接引用。第二、html页面要将参数填入部分空出来,制作成变量,方便python通过format来填入数据。
  3. 通过pyecharts+snapshot-phantomjs生成饼图和柱状图的图片。然后将这些图片插入html模板中。

三、代码实现

1.html模板文件(xxx.html):

注:{XXX}模板中的这些标记,都是后端需要填入数据的位置

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
  <title>日报</title>
  <link rel="stylesheet" href="{report_css}">
</head>

<body>
  <div id='app'>
    <div class="day-report">
      <div class="dr-header">
        <img src="{report_header_img}" alt="标题图片" class="drh-img">
        <div class="drh-title">
          <p class="drh-small-title">嘉为蓝鲸WeOps</p>
          <p class="drh-large-title">应用状态管理日报</p>
          <span class="drh-date-title">{report_header_date}</span>
        </div>
      </div>
      <div class="dr-header-blue"></div>
      <div class="dr-content">
        <div class="drc-ex drc-ex-situation">
          <p class="drc-ex-title"> 异常概况 </p>
          <div class="dr-exs-list">
            {exception_situation}
          </div>
        </div>
        <div class="drc-ex drc-ex-type">
          <p class="drc-ex-title"> 异常类型分布 </p>
          <img id="drc-ext-pie" class="drc-ext-pie" src="{exception_type_pie}" alt="异常类型分布">
        </div>
        <div class="drc-ex drc-ex-time">
          <p class="drc-ex-title"> 异常时间分布 </p>
          <img class="drc-ext-bar mb-50" src="{exception_week_bar}" alt="异常时间分布">
          <img class="drc-ext-bar" src="{exception_time_bar}" alt="异常时间分布">
        </div>
        <div class="drc-ex drc-ex-detail">
          <p class="drc-ex-title"> 异常清单 </p>
          <table class="drc-table">
            {exception_detail_table}
          </table>
        </div>
        <div class="drc-ex drc-ex-biz">
          <p class="drc-ex-title"> 业务异常数top </p>
          <table class="drc-table">
            {exception_biz_table}
          </table>
        </div>
        <div class="drc-ex drc-ex-inst">
          <p class="drc-ex-title"> 实例异常数top </p>
          <table class="drc-table">
            {exception_inst_table}
          </table>
        </div>
      </div>
      <div class="dr-footer">
        <img src="{report_footer_logo}" alt="logo">
        <p>研运至简 无限可为</p>
      </div>
    </div>
  </div>
</body>

</html>
2.后端填入模板的核心代码

说明:base_url是指后端服务的网址+静态文件,指向服务器的静态文件存放地址。比如(http://127.0.0.1/static/)

读取html文件,转换成字符串

def get_report_template():
    with open(REPORT_TEMPLATE, encoding='utf-8') as file:
        file_content = file.read()
    return file_content

通过format方法,将参数填入html模板字符串中

report_content = get_report_template()
        report_params = {
            "report_css": base_url + 'report/report.css',
            "report_header_img": base_url + "report/report-header.jpg",
            "report_header_date": "12月27日",
            "exception_situation": report_situation,
            "exception_type_pie": base_url + "report/exception_type_pie.png",
            "exception_week_bar": base_url + "report/exception_week_bar.png",
            "exception_time_bar": base_url + "report/exception_time_bar.png",
            "exception_detail_table": except_detail_table,
            "exception_biz_table": exception_biz_table,
            "exception_inst_table": exception_inst_table,
            "report_footer_logo": base_url + "report/logo.png"
        }
        report_content = report_content.format(**report_params)

通过wkhtmltoimage,将html字符串模板生成图片

def download_img(html_string, img_name):
    if settings.DEBUG:
        config = imgkit.config(wkhtmltoimage=r'E:\wkhtmltopdf\bin\wkhtmltoimage.exe')
    else:
        config = imgkit.config(wkhtmltoimage='/usr/local/bin/wkhtmltoimage')
    img_file = imgkit.from_string(html_string, False, config=config)
    return download_file(img_file, img_name)

最后是发送邮件

3.饼图和柱状图的插入方式
  1. 用pyecharts生成饼图或柱状图
  2. 通过snapshot-phantomjs将饼图或柱状图渲染成图片,放到对应的静态文件夹中
  3. 模板直接通过img引入生成的图片

饼图

def get_exception_type_pie():
    pie_url = os.path.join(BASE_DIR, 'static/report/exception_type_pie.png')
    pie_data = [["告警异常", 10], ["巡检异常", 4], ["基线异常", 3], ["备份异常", 2], ["其他异常", 2]]
    pie_colors = ['#ff761b', '#2873c4', '#ffbd00', '#7f7f7f', '#ffffff']
    pie = Pie(init_opts=opts.InitOpts(width=" 660px", height="420px"))
    pie.add("", pie_data, radius=['40%', '80%'])
    pie.set_colors(pie_colors)
    pie.set_global_opts(
        legend_opts=opts.LegendOpts(
            orient='vertical',
            legend_icon='circle',
            pos_right=10,
            pos_bottom=50,
            textstyle_opts=opts.TextStyleOpts(
                color="#ffffff",
                font_size=13,
                padding=[5, 0]
            )
        )
    )
    pie.set_series_opts(
        label_opts=opts.LabelOpts(
            color="#ffffff",
            font_size=13,
            formatter="{b}: {c}"
        )
    )
    make_snapshot(snapshot, pie.render(), pie_url)

生成的图片是没有背景色的,蓝色背景是html模板的背景色

html5 保存布局为图片到本地 html页面保存成图片_周报日报


柱状图

def get_exception_time_bar():
    bar_url = os.path.join(BASE_DIR, 'static/report/exception_time_bar.png')
    bar_x = ['昨日遗留', '0:00-1:00', '1:00-2:00', '2:00-3:00', '3:00-4:00', '4:00-5:00', '5:00-6:00',
             '6:00-7:00', '7:00-8:00', '8:00-9:00', '9:00-10:00', '10:00-11:00', '11:00-12:00', '12:00-13:00',
             '13:00-14:00', '14:00-15:00', '15:00-16:00', '16:00-17:00', '17:00-18:00', '18:00-19:00',
             '19:00-20:00', '20:00-21:00', '21:00-22:00', '23:00-24:00']
    bar_y = [4, 1, 1, 3, 4, 3, 3, 2, 1, 4, 1, 1, 3, 4, 3, 0, 2, 1, 3, 4, 3, 0, 2, 1]
    create_bar(bar_url, bar_x, bar_y, 15)


def get_exception_week_bar():
    bar_url = os.path.join(BASE_DIR, 'static/report/exception_week_bar.png')
    bar_x = ['上周遗留', '星期天', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']
    bar_y = [4, 1, 1, 3, 4, 3, 3, 2]
    create_bar(bar_url, bar_x, bar_y, 30)


def create_bar(bar_url, bar_x, bar_y, bar_width):
    bar = Bar()
    bar.add_xaxis(bar_x)
    bar.add_yaxis(
        "",
        bar_y,
        itemstyle_opts=opts.ItemStyleOpts(color="#0068f8"),
        bar_width=bar_width
    )
    bar.set_global_opts(
        xaxis_opts=opts.AxisOpts(
            axistick_opts=opts.AxisTickOpts(
                is_align_with_label=True
            ),
            axislabel_opts=opts.LabelOpts(
                color="#ffffff",
                font_size=13
            )
        ),
        yaxis_opts=opts.AxisOpts(
            axislabel_opts=opts.LabelOpts(
                color="#ffffff",
                font_size=13
            ),
            splitline_opts=opts.SplitLineOpts(
                is_show=True,
                linestyle_opts=opts.LineStyleOpts(width=1, opacity=0.5, type_='solid', color='#ffffff')
            )
        )
    )
    bar.set_series_opts(label_opts=opts.LabelOpts(is_show=False))
    grid = Grid(init_opts=opts.InitOpts(width=" 660px", height="240px"))
    grid.add(bar, grid_opts=opts.GridOpts(pos_left='3%', pos_right='4%', pos_top='3%', pos_bottom='3%',
                                          is_contain_label=True))
    make_snapshot(snapshot, grid.render(), bar_url)

html5 保存布局为图片到本地 html页面保存成图片_生成图片_02