# 业务上的需求

在网页中配置完 word 报告的指标后,点击生成报告。后端响应生成报告请求,开始异步执行报告导出程序。报告内容中有部分是图表,需要借助前端渲染完后返回下载链接。

# 实现逻辑

基于 egg.js 框架编写 node 服务,以供报告导出程序请求。请求为 post 请求,请求时在消息体中携带图片渲染所需数据。最终数据经由 puppeteer page 实例中的 evaluate 方法传递到 Echart 图片渲染页。

# 代码实现

代码主要分为两部分:Node 接口部分,Echarts 图片渲染部分。

# Node 接口

先说 Node 接口,一直都说 Node 的掌握是一个前端进阶的槛,现在看确实如此。自己调用自己接口的感觉很奇妙,和早之前写.Net 的感觉完全不一样。

由于在实现这部分功能之前对 Egg.js 没有更多的了解(一无所知),所以这里会着重分享代码层面如何实现,而如何作为整个项目结构中的一部分出现此处不讨论。

# Router

首先需要在 router.js 文件中添加接口地址,router.post('/example/my/demo', controller.todo.addTodoItem)。前一个参数是接口地址,后一个参数是访问该接口时调用的方法(Controller)。而一个 Controller 一般包含多个方法,即 todo 是一个 Controller 文件,addTodoItem 是如下文所说的承担执行动作的方法。

Router 主要用来描述请求 URL 和具体承担执行动作的 Controller 的对应关系。

# Controller

编写 Controller 类时,首先需要将其继承于 egg.controller 。

const Controller =  require('egg').Controller
class todoController extends Controller {
    // code
}
1
2
3
4

定义的 Controller 类,会在每一个请求访问到 server 时实例化一个全新的对象,而在继承的 todoController 中会有一些需要用到的属性挂载到 this 上。诸如this.ctx this.service this.logger。在上下文就可以拿到 post 请求中携带的参数。

在 todoController 中可以对参数进行校验,校验通过后调用 service 进行业务处理。

在 todoController 中的一般性代码如下:

try {
    if (paramsCheck) {
        // call service
        ctx.body = {
            status: 0,
            message: '获取成功',
            data:
        }
    } else {
        // 参数并不完整
    }
} catch (e) {
    // service 部分出现问题
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# Service

以下为service中的主要代码,注释都在里头了。


url = targetUrl(
  `${config.webUrl}/exam/report/chart`,
  {
    userId: data.userId,
    userName: data.userName,
    userType: data.userType
  }
)
// target为截图页面URL,自行配置。
page.goto(url)

// 进入页面后通过evaluate方法向页面注入请求传来的数据
// 通过window这个公共桥梁,可以使得两端能够通信
page.evaluate(score => {
  // eslint-disable-next-line no-undef
  window.chartData = score
  // if (data.token) {
  //   // eslint-disable-next-line no-undef
  //   window.token = data.token
  // }
  setTimeout(() => {
    // eslint-disable-next-line no-undef
    window.startRender()
  }, 1000) 
  // 为了解决一个怪异的问题,延迟执行页面端的渲染
  // startRender 是页面端提供的渲染方法,等待注入数据后,开始渲染页面
}, data)

// 等待页面渲染完成 #render-finish
// 持续监听目标页面的相关DOM元素的display情况,如果未true,则可以认定渲染完成
page.waitForSelector('#renderFinish', {visible: true, timeout: 1000 * 60})

// 获取渲染完之后,上传至OSS的URL
const ossUrl = page.evaluate(() => {
  // eslint-disable-next-line no-undef
  return window.ossUrl
})

page.close()
browser.disconnect()

logger.info('word报告图片渲染完成')
return ossUrl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

前端页面就相对简单了,就只是个工具人,一切都被Node端安排的明明白白。

一笔写于: 9/11/2020, 2:57:23 PM
扫码添加我的微信
个人
个人号
公众号
公众号