
结果示例
前言
最近公司一个项目需要提供一份技术文档,其中需要整理相关的接口文档。作为一个后端程序员,为了方便前端测试我们都会提供一个在线的接口文档供前端测试,例如swagger就是比较常用的一种,我用于生成这个文档的数据就是基于swagger提供的json数据。我最早的思路是打算输入json文件的地址或者在线url自动去加载数据,然后再根据swagger的版本的不同去使用不同的解析方式,但是最后因为时间原因,只实现了对swagger V2的本地数据的解析。
准备工作
swagger V2数据
node 环境
officegen office生成库
程序源码
const fs = require('fs')
const officegen = require('officegen')
// api path
const apiFilePath = "D:\\project\\nodejs\\swagger2doc\\api.json"
// controller Name
let _tag2Desc = new Map()
let _modelVo = new Map()
let _apis = []
/**
* 读取api数据 如果是文件就读取文件,如果是链接就读取抓取接口数据
* @param {String}} link
* @param {Boolean} isFile
*/
function readApi(link, isFile) {
let dataStr = fs.readFileSync(link).toString()
let dataObj = JSON.parse(dataStr)
return dataObj;
}
/**
* 解析Api数据
* @param {Object} api
*/
function resolveApi(api) {
let apiInfo = api.info
let tags = api.tags
let paths = api.paths
let definitions = api.definitions
// 聚合controller Name
tags.forEach(tag => {
_tag2Desc.set(tag.name, tag.description)
});
// 聚合model
for (const voName in definitions) {
if (definitions.hasOwnProperty(voName)) {
const vo = definitions[voName];
_modelVo.set(voName, vo)
}
}
// 解析paths
for (let path in paths) {
let _commonApiPath = path
let apis = paths[path]
resolvePaths(_commonApiPath, apis)
}
// fs.writeFileSync("apiattr.json",JSON.stringify(_apis))
genDoc(apiInfo, _apis)
}
/**
* 解析接口信息
* @param {String} path
* @param {Object} apis
*/
function resolvePaths(path, apis) {
let basePath = path
for (const method in apis) {
if (apis.hasOwnProperty(method)) {
const api = apis[method];
let _api = {
controller: api.tags[0],
path: basePath,
tag: _tag2Desc.get(api.tags[0]),
desc: api.summary,
method: method,
params: resolveParam(api.parameters)
}
_apis.push(_api)
}
}
}
/**
* 解析参数数组对象
* @param {Object[]} params
*/
function resolveParam(params) {
if (!params) {
return null
}
let _params = []
let _param = {}
params.forEach(param => {
let position = param.in
_param = {}
if (position == 'query') {
_param = {
name: param.name,
position: param.in,
desc: param.description,
required: param.required,
type: param.type
}
_params.push(_param)
} else if (position == "formData") {
_param = {
name: param.name,
position: param.in,
desc: param.description,
required: param.required,
type: param.type
}
_params.push(_param)
} else if (position == "path") {
_param = {
name: param.name,
position: param.in,
desc: param.description,
required: param.required,
type: param.type
}
_params.push(_param)
} else if (position == "body") {
let voName = param.schema.originalRef
let vo = _modelVo.get(voName)
let requiredAttr = vo.required
let properties = vo.properties
for (const attrName in properties) {
if (properties.hasOwnProperty(attrName)) {
const attr = properties[attrName];
let required = (requiredAttr && requiredAttr.indexOf(attrName) >= 0);
let type = attr.type
let desc = attr.description
_param = {
name: attrName,
position: position,
desc: desc,
required: required,
type: type
}
_params.push(_param)
}
}
} else {
console.log("未知类型", param)
}
})
return _params
}
/**
* 生成文档doc
* @param {Object}} baseInfo
* @param {Object[]} apis
*/
function genDoc(baseInfo, apis) {
let docx = officegen('docx')
// Officegen calling this function after finishing to generate the docx document:
docx.on('finalize', function (written) {
console.log(
'Finish to create a Microsoft Word document.'
)
})
// Officegen calling this function to report errors:
docx.on('error', function (err) {
console.log(err)
})
let pObj = docx.createP();
pObj.addText(baseInfo.title, { bold: true, font_size: 20 })
pObj = docx.createP();
pObj.addText("版本号 ", { bold: true, font_size: 11 })
pObj.addText(baseInfo.version)
pObj = docx.createP();
pObj.addText("描述 ", { bold: true, font_size: 11 })
pObj.addText(baseInfo.description)
var tableStyle = {
tableAlign: "center",
spacingLineRule: 'atLeast', // default is atLeast
indent: 100, // table indent, default is 0
fixedLayout: false, // default is false
borders: true, // default is false. if true, default border size is 4
borderSize: 2, // To use this option, the 'borders' must set as true, default is 4
columns: [{ width: 600 }, { width: 600 }, { width: 600 }, { width: 600 }, { width: 600 }], // Table logical columns
}
apis.forEach(api => {
// 创建一个段
pObj = docx.createP()
pObj.addText(api.desc, { bold: true, font_size: 18 })
pObj = docx.createP()
pObj.addText("所属模块 ", { bold: true, font_size: 11 })
pObj.addText(api.tag)
pObj = docx.createP()
pObj.addText("所属控制器 ", { bold: true, font_size: 11 })
pObj.addText(api.controller)
pObj = docx.createP()
pObj.addText("Path ", { bold: true, font_size: 11 })
pObj.addText(api.path)
pObj = docx.createP()
pObj.addText("请求方法 ", { bold: true, font_size: 11 })
pObj.addText(api.method.toUpperCase())
var table = [
[
{
val: "属性名"
},
{
val: "类型"
},
{
val: "是否必选"
},
{
val: "参数位置"
},
{
val: "参数描述"
}
]
]
let params = api.params
if (params) {
pObj = docx.createP()
pObj.addText("请求参数", { bold: true, font_size: 14 })
params.forEach(param => {
table.push([param.name, param.type, param.required ? '必选' : '可选', param.position, param.desc])
})
docx.createTable(table, tableStyle)
}
pObj = docx.createP()
pObj.addHorizontalLine()
})
let out = fs.createWriteStream('doc.docx')
docx.generate(out)
}
/**
* 运行函数
*/
(_=>{
let dataObj = readApi(apiFilePath,true)
resolveApi(dataObj)
})()
总结
因为时间匆忙的关系,并没有对代码做很好的review之后就马上做了这份笔记,其中存在许多问题以及可以优化的地方,/(ㄒoㄒ)/~,只能放到后面来了