一、 html页面生成图片的技术背景
将html页面生成图片,目前经过本人验证的,有两种方式:
1、纯前端通过html2canvas生成图片,
2、将前端的dom传回Django后端,通过wkhtmltoimage生成图片
但是以上两种方式,都有一个前提,就是需要用户先打开页面(即页面必须先在前端渲染完成),然后要么是通过setimeout自动延迟生成图片,要么是用户通过点击按钮,触发生成图片的请求。
二、发送图片邮件的需求
需求:
在后端,做一个定时任务,定时发送日报周报邮件,邮件的正文是图片。图片中包含图片、饼图、柱状图和表格。
思考:
1、做定时自动任务,就不能要求用户先打开页面,那上面两种生成图片的方式就不可行。
2、虽然以上方式不可行,但是html页面在后端生成图片的技术还可以用,只不过要换一种思路。
3、解决办法就是,在后端生成html页面,并在后端将html页面生成图片。
实现步骤:
- 先用html实现一个精美的海报页面。在vue架构中,该html页面不能使用原有的组件,比如element等vue组件,因为使用这些组件,不好控制css,而且在生成图片时,会有一些奇怪的兼容问题。其次就是,所有的css都手写,和html放在一起。
- 将生成的html页面,制作成模板。第一、将css文件和图片素材单独放到后端的静态文件夹中,模板只需要通过链接引用。第二、html页面要将参数填入部分空出来,制作成变量,方便python通过format来填入数据。
- 通过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.饼图和柱状图的插入方式
- 用pyecharts生成饼图或柱状图
- 通过snapshot-phantomjs将饼图或柱状图渲染成图片,放到对应的静态文件夹中
- 模板直接通过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模板的背景色
柱状图
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)