使用NodeJs將swaggerV2文檔數(shù)據(jù)轉(zhuǎn)成docx

結(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ㄒ)/~,只能放到后面來了

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容