
結(jié)果示例
前言
最近公司一個(gè)項(xiàng)目需要提供一份技術(shù)文檔,其中需要整理相關(guān)的接口文檔。作為一個(gè)后端程序員,為了方便前端測(cè)試我們都會(huì)提供一個(gè)在線的接口文檔供前端測(cè)試,例如swagger就是比較常用的一種,我用于生成這個(gè)文檔的數(shù)據(jù)就是基于swagger提供的json數(shù)據(jù)。我最早的思路是打算輸入json文件的地址或者在線url自動(dòng)去加載數(shù)據(jù),然后再根據(jù)swagger的版本的不同去使用不同的解析方式,但是最后因?yàn)闀r(shí)間原因,只實(shí)現(xiàn)了對(duì)swagger V2的本地?cái)?shù)據(jù)的解析。
準(zhǔn)備工作
swagger V2數(shù)據(jù)
node 環(huán)境
officegen office生成庫(kù)
程序源碼
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數(shù)據(jù) 如果是文件就讀取文件,如果是鏈接就讀取抓取接口數(shù)據(jù)
* @param {String}} link
* @param {Boolean} isFile
*/
function readApi(link, isFile) {
let dataStr = fs.readFileSync(link).toString()
let dataObj = JSON.parse(dataStr)
return dataObj;
}
/**
* 解析Api數(shù)據(jù)
* @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)
}
}
}
/**
* 解析參數(shù)數(shù)組對(duì)象
* @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("版本號(hào) ", { 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 => {
// 創(chuàng)建一個(gè)段
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("請(qǐng)求方法 ", { bold: true, font_size: 11 })
pObj.addText(api.method.toUpperCase())
var table = [
[
{
val: "屬性名"
},
{
val: "類型"
},
{
val: "是否必選"
},
{
val: "參數(shù)位置"
},
{
val: "參數(shù)描述"
}
]
]
let params = api.params
if (params) {
pObj = docx.createP()
pObj.addText("請(qǐng)求參數(shù)", { 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)
}
/**
* 運(yùn)行函數(shù)
*/
(_=>{
let dataObj = readApi(apiFilePath,true)
resolveApi(dataObj)
})()
總結(jié)
因?yàn)闀r(shí)間匆忙的關(guān)系,并沒有對(duì)代碼做很好的review之后就馬上做了這份筆記,其中存在許多問題以及可以優(yōu)化的地方,/(ㄒoㄒ)/~,只能放到后面來了