最近在公司里得到了一项任务,人事给了一个 飞书的职位表格 ,要求将里面的信息更新到官网上面去。其中涉及了已有岗位的信息更新,删除部分岗位和添加部分岗位。这种工作虽然简单,但是很琐碎,很麻烦。
三十个岗位的 岗位列表 和 岗位详情 花了我一个早上去 更新及核对 。主要的工作量都在于 表格的数据结构和代码中的数据结构不同需要进行一个修改 以及 更新后数据后网站展示情况的核对。
作为一个程序员当然得想办法优化一下整体的流程,正好本身就知道飞书是有开放api的,于是就开始琢磨着能不能基于飞书表格将内容自动化生成一下,这样人事那边只需要 维护飞书表格 即可。
说锤就锤,首先要了解的是飞书的开放api是如何调用的,这里可以直接参考 飞书开放平台 中对应功能的介绍,还是很清晰的。
如果想要获取一个多维表格中的数据主要的工作是以下两步:
开放平台提供了3种不同类型的访问凭证,用于验证调用方身份、确保调用方具有执行操作所需要的权限:
app_access_token:应用:授权凭证,开放平台可据此识别调用方的应用身份,应用可以访问应用自身相关的信息,不归属到具体的企业或者用户,比如获取当前登录应用的用户身份。tenant_access_token:
租户:授权凭证,使用该access token,应用将代表公司或者团队执行对应的操作,比如获取一个通讯录用户的信息。user_access_token:
用户:授权凭证,使用该access token,应用将代表用户执行对应的操作,比如通过API 创建一篇云文档或者一个日程。
因为我们的网站是使用 next.js 写的,服务端渲染并没有涉及到前后端交互,因此我没有使用 axios 而是直接使用 request 这个库进行请求发送。事实上飞书的开放api为了保障安全也只允许 服务端 进行调用。
实现的代码如下:
const { promisify } = require("util")
const request = promisify(require("request"))
async function updateCareer() {
// 请求获取token
const tokenRes = await request({
url: "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal",
body: {
app_id: APP_ID,
app_secret: APP_SECRET
},
json: true,
method: "POST",
headers: {
"content-type": "application/json",
},
})
// 请求获取表格数据
const tableRes = await request({
url: "https://open.feishu.cn/open-apis/bitable/v1/apps/bascnuEd4NBfjyptIQKl6I75J3e/tables/tbl22HQor1gcwe2A/records",
method: "GET",
headers: {
"Content-Type": "application/json; charset=utf-8",
"Authorization": "Bearer " + tokenRes.body.tenant_access_token
},
})
}
在上面的代码中,我首先引入了 promisify 这个方法使 request 可以实现 promise化 的使用,更加清晰的进行异步编程,然后就是分别调用两个接口获取表格的数据。
我们先发送请求获取 tenant_access_token ,之后将 tenant_access_token 拼接后作为请求头的 Authorization 发送给获取表格数据的接口,接口的url中需要拼接上我们的 表格id。
在获取到表格的数据后,我们需要进行一定的格式化后再将其生成为代码,我们网站的岗位列表页面如下图:
这样的一个列表是基于一个json文件生成的,其中有两个列表分别对应中文和英文,每一张卡片对应的数据结构如下:
{
"zh-CN": [
{
"title": "产品运营经理",
"href": "/careers/product-marketing-manager",
"type": "技术中心",
"description": " 学习研究公司的 SaaS 产品 APISEVEN Cloud;"
}],
"en-US": [
{
"title": "Product Marketing Manager",
"href": "/careers/product-marketing-manager",
"type": "Technology Center",
"description": " Learn about our SaaS product APISEVEN Cloud;"
}]
}
而咱们从表格中取出的数据结构如下:
{
id: 'recwb6xrfK',
record_id: 'recwb6xrfK',
fields: {
'Job Responsibilities': 'xxx',
'部门': '运营部',
'岗位说明最后修改日期': 1635523200000,
'任职要求': 'xxxx',
'岗位状态': 'OPEN',
'岗位名称': '技术文档 实习生\nTechnical Writer(Internship)'
},
fileName: 'technical-writer(internship)'
},
因此需要一个方法将数据进行格式化,这里的实现与表格具体内容关联性较强,不展开介绍,下面是我写的一个格式化的方法:
function formatCareerList(tableData) {
const careerList = {
"zh-CN": [],
"en-US": []
}
tableData.forEach((item, index) => {
const [zhTitle, enTitle] = item.fields["岗位名称"].split("\n")
const fileName = (enTitle || zhTitle).replace(/ |\/|\\/ig, "-").toLowerCase()
tableData[index].fileName = fileName
const href = "/careers/" + fileName
const typeMap = {
"技术中心": "Technology Center",
"运营部": "Operation Department",
"职能部": "Functional Department",
"销售部": "Sales Department",
"Global Team": "Global Team"
}
const zhType = item.fields["部门"]
const enType = typeMap[zhType]
const zhDesc = item.fields["岗位职责"].split("\n")[0].substr(2)
const enDesc = item.fields["Job Responsibilities"].split("\n")[0].substr(2)
careerList["zh-CN"].push({
title: zhTitle,
href,
type: zhType,
description: zhDesc
})
careerList["en-US"].push({
title: enTitle || "",
href,
type: enType,
description: enDesc
})
})
return careerList
}
主要的操作就是解构赋值或者对象进行一个数据的映射,实现一个数据的格式化,并且生成中文和英文的两个对象数组。
在岗位列表生成后,还需要生成 岗位介绍详情 ,也就是列表中 href 字段跳转过去的那个页面。当前我们的岗位介绍是通过 markdown文档 渲染生成的,格式大致如下:
---
title: job name
date: xxx
---
### 岗位职责
xxx
### 任职要求
xxx
### 附加信息
xxx
### 联系方式
xxx
因此我写了两个生成中文内容和英文内容两个单独的方法,利用 模板字符串 传入参数生成md文档的内容,使用模板字符串可以支持我们编辑可以换行的文本,而且在文本中嵌入变量也特别方便,下面展示一下英文版模板生成的方法:
// enTemplate.js
module.exports = function enTemplate(fields) {
const date = new Date()
return `---
title: ${fields["岗位名称"].split("\n")[1]}
date: ${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}:00
---
### Job Responsibilities
${fields["Job Responsibilities"]}
### Job Requirements
${fields["Job Requirements"]}
## Additional
We"re a remote work company with employees in 6 cities across China, and we do all of our collaboration through GitHub, Slack and Google Docs.
## Contact
[[email protected]](mailto:[email protected])
`}
内容生成后,我们需要将内容写到文件中并创建md文件和对应的json列表:
fs.writeFileSync(path.join(__dirname, '../data/career.json'), JSON.stringify(careerList, null, 2), 'utf8')
tableData.forEach(item => {
fs.writeFileSync(path.join(__dirname, '../_posts/careers/en-US/', item.fileName + '.md'), enTemplate(item.fields), 'utf8')
fs.writeFileSync(path.join(__dirname, '../_posts/careers/zh-CN/', item.fileName + '.md'), zhTemplate(item.fields), 'utf8')
})
在上面的代码中我使用了 writeFileSync 这个方法创建文件,第一个参数传入创建文件的位置和名称,如果文件已存在则会 覆盖 掉,第三个参数传入 文件内容,第三个参数传入编码方式。通过遍历表格数据的方式传入参数,调用前面写的方法创建多个md文档。
在生成列表的时候,为了json字符串的可读性我传入了第三个参数用于 指定缩进用的空白字符串 ,用于美化输出(pretty-print);如果参数是个数字,它代表有多少的空格;上限为10。
下图为最终生成的md文档:
最后再为我们这个生成文档的脚本新增一个启动命令,在 package.json 中 scripts 新增一个字段:
//package.json
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start --port=80",
"postbuild": "next-sitemap",
"career":"node career-api/app.js"
}
下次启动时只需要执行 npm run career
即可。
当前需要更新岗位信息的话还需要开发人员进入项目中执行指定命令,如果想要更加轻松的话我们可以在 github action 中配置定时自动执行,亦或者使用 next.js 的服务将这个更新岗位的接口暴露出去,让人事那边想要更新时直接调用接口就好了~
通过飞书开放api可以实现大量的自动化功能,将维护某些简单功能的精力从技术人员转移到非技术人员,降低开发成本。通过我两个小时的开发就可以避免之后每次修改都需要花半天的时间去改和核对,这才是 程序员的价值 嘛~