基于GitBook框架搭建技术文档平台
源寶導讀:為了向用戶更好的傳遞ERP開放平臺的價值與技術知識,我們基于GitBook框架搭建了一個文檔中心站點,本文將介紹此站點的設計與實現過程。
一、項目架構圖
? ? 因為文檔會涉及到很多的產品線,所以目前主要是通過拉取各個產品線的文檔倉庫,進行合并編譯生成方式構建處理,整個的架構和執行流程如下:
1.1、收集階段
? ? 首先產品需要按照約定好的目錄格式建立文檔倉庫,然后再新增markdown文件書寫文檔內容,最后提交發布到遠程。在服務器端會間隔一段時間周期性的獲取遠程倉庫文檔,然后進行后續的編譯與生成。
1.2、 編譯階段
? ? 當用戶將更新的文檔推送到遠程倉庫后,服務器拉取最新的倉庫文檔,并且啟動編譯命令,首先調用gitbook build命令來編譯文檔,每次執行前都會安裝最新的主題插件,保證生成的文檔都是最新的,執行完畢后將在對應目錄下生成編譯后的html靜態文件。完畢后再會執行npm run build-api來處理平臺組件api以及后端接口事件的編譯,完畢后會生成可被重復使用的單個接口文件,目的是便于后續查詢更加快速。
1.3、 生成階段
? ?上面數據編譯完畢后,會生成相對比較友好的數據格式,此時就需要對這些生成的文件重新生成ElasticSearch的索引,生成索引的目的是為了后續文檔搜索調用ElasticSearch的webApi來對文章進行過濾,獲取搜索結果。索引完畢后,可以安裝elasticsearch-head插件,完畢后可以通過瀏覽器打開頁面在網頁上查看生成的結果。
1.4、 發布階段
? ? 當文檔和索引生成完畢之后,最后會將文檔生成的靜態html文件拷貝到開放平臺站點static/docs目錄中,同時重新啟動站點,至此完成站點文檔的更新。
二、技術棧
Gitbook
? ? GitBook是一款文檔編輯工具,目前主要用來編寫文檔,同時他還有一些很好的特性能夠滿足當前的需求,如:
支持Git,一個分布式的文檔編輯工具。可多人共同編寫文檔。
可以通過命令一鍵打包成靜態站點部署到web服務器。
由于采用的是md格式文件存儲,現在有許多比較成熟的可視化編輯器,對于寫作人員非常友好。
可通過插件模式,來自定義主題風格,滿足定制化訴求,具體插件使用可參考http://gitbook.hushuang.me/plugins/create.html。
Nuxt
? ? Nuxt是一個基于Vue.js的通用應用框架,預設了利用Vue.js開發服務端渲染的應用所需要的各種配置。可以將html在服務端渲染,合成完整的html文件再輸出到瀏覽器。目前平臺的主要使用的是vue開發應用,所以上手比較容易,內部易于溝通交流。
JsDoc
? ? JSDoc是一個根據javascript文件中注釋信息,生成JavaScript應用程序或庫、模塊的API文檔的工具。平臺內部的組件庫以及相關工具庫接口都是按照JsDoc規范書寫的,集成方便,可維護性高,也是業內主流的方式之一。
ElasticSearch
? ? Elasticsearch是一個開源的分布式、RESTful 風格的搜索和數據分析引擎,他提供了許多的webApi接口,對于前端開發使用很友好,同時相關的文檔也比較豐富,學習起來不會很盲目,同時部署也比較方便。
? ? 項目中還是用其他相關技術,如nodejs、vue、grunt…在此不一一介紹了。
三、生成文檔
? ? 通過一系列的編譯處理,最終將用戶書寫的文檔生成為內部可以持續使用的數據或者文件,具體如下:
3.1、創建文檔倉庫
? ? 首先產品需要建立好自己的文檔倉庫,然后按照Gitbook的書寫規范(http://gitbook.hushuang.me/structure.html)編寫自己的業務文檔,最后推送到倉庫即可,倉庫目錄結構如下:
SLXT // 產品目錄名稱src // 書寫markdown文件的目錄hello-world.md // 文檔markdown文件book.json // gitbook配置文件,通常不會調整package.json // 依賴包文件README.md // 幫助指引文件SUMMARY.md // 文檔目錄md文件3.2、合并文檔倉庫
? ? 開放平臺需要將所有的產品文檔倉庫拉取到本地,并按照一定的策略進行合并處理,最后在編譯生成為靜態文件進行同步更新,目前主要合并兩塊內容:
3.2.1、合并文檔目錄
? ? 各個文檔倉庫按照約定好的目錄結構在倉庫根目錄下書寫正確的SUMMARY.md目錄文件,結構如下:
#?Summary * 成本系統* 標準擴展接口* [合同登記](/src/biao-zhun-kuo-zhan-jie-kou/he-tong-deng-ji.md)* [新增合同](/src/biao-zhun-kuo-zhan-jie-kou/he-tong-deng-ji/xin-zeng-he-tong.md)* [編輯合同](/src/biao-zhun-kuo-zhan-jie-kou/he-tong-deng-ji/bian-ji-he-tong.md)* [合同審批](/src/biao-zhun-kuo-zhan-jie-kou/he-tong-deng-ji/he-tong-shen-pi.md)* [合同審批中修改](/src/biao-zhun-kuo-zhan-jie-kou/he-tong-deng-ji/he-tong-shen-he-zhong-bian-ji.md)* [設計變更](/src/biao-zhun-kuo-zhan-jie-kou/she-ji-bian-geng.md)...? ? 由于開放平臺的文檔目錄都是由平臺管理的,所以在平臺的文檔目錄下面會約定好各個產品文檔對應的目錄位置,如:
# Summary* 平臺文檔* 基礎知識* [開發基礎知識](/src/zi-yuan-yu-gui-fan/tong-yong-ji-shu.md)* DevOps概念及Git基本操作* [Git基本操作](https://open.mingyuanyun.com/docs/rdc/kuai-su-ru-men/git-ji-ben-cao-zuo.html)* 平臺整體介紹* [開放平臺技術白皮書](/src/ping-tai-zheng-ti-jie-shao/kai-fang-ping-tai-bai-pi-shu.md)....* [集成平臺](mip)* [研發協同平臺](rdc)* [流程中心](wd_lczx)* [工作流](gzl) * 業務文檔* [售樓系統](slxt)* [成本系統](cbxt)....? ? 上面類似[集成平臺](mip)這樣的標識會在編譯的時候替換為產品文檔目錄SUMMARY.md中的內容,并將產品目錄內的/src路徑替換成實際產品的簡稱,如/src -> /cbxt
3.2.2、合并文檔文件
? ? 合并完目錄后,我們還需要將產品的文檔全部拷貝到臨時的文檔目錄中,暫且稱為docs目錄,同時還需要將通用的gitbook的book.json以及剛剛合并的SUMMAYR.md拷貝進去,合并后的臨時docs目錄如下:
? ? 最后通過執行gitbook generate生成靜態的html文檔。
? ? 通過這種方式就可以有效的解決合并產品文檔的問題,也滿足了平臺管理整個開放平臺文檔目錄的訴求。
3.3、索引文檔中的內容
? ? 現在有了大量的產品文檔,在實際使用的時候,用戶需要根據關鍵詞去搜索文檔,基于這個場景,引入了開源的ElasticSearch搜索引擎,目前主要有兩個步驟:
3.3.1、識別并索引靜態文檔中的特殊標記內容
? ? 借助于cheerio第三方庫,可以很容易的從已生成的文檔html文檔中拿到需要的標簽內容,如:
const cheerio = require('cheerio') ... // 讀取靜態html文件內容 const fileContent = fs.readFileSync(options.filePath, 'utf8') if (!fileContent) {return Promise.resolve() } let $ = cheerio.load('<div id="_content_">' + fileContent + '</div>') // 匹配第一個h1的標簽內容 const title = $('h1').first().text() || '' // 匹配blockquote標簽內容 const description = $('blockquote').text() || '' return {title,description }? ? 提取到h1以及blockquote中的內容后,可以給后續ElasticSearch生成索引使用。
3.3.2、批量生成文檔搜索索引
? ? 獲取到文檔提取的特征內容后,調用ElasticSearch創建索引的接口,完成對文檔的索引,代碼片段如下:
ElasticSearchClientInstance.index({index: options['index'],type: options['type'],body: {title: '前端數值計算', // 文章標題system: '建模平臺', // 所屬系統url: '', // 生成的url地址description: '日常開發中,我們經常遇到浮點數計算不準確問題,為此平臺提供了mapnumber數值計算庫,用來解決數值計算不準確的問題', // 文檔描述,盡量貼近關鍵詞,便于搜索content: '...', // 文章內容summary: '...', // 文章摘要},},function(error) {if (!error) {console.log(title + '生成成功')}resolve()} )? ? 當索引重新生成后,后面用戶在搜索文檔的時候,通過根據搜索關鍵詞調用ElasticSearch提供的搜索API接口,即可返回搜索后的結果數據集合,相關代碼如下:
function search ({ keyword, pageSize, pageIndex, index, system }) {let operator = 'and'if (keyword.match(/[\s +]/)) {operator = 'or'}// 查詢條件let query = {bool: {must: {multi_match: {...}},filter: {term: {system}}}}// 處理分頁const from = pageIndex * pageSize// 執行搜索const { hits } = ElasticSearchClientInstance.search({size: pageSize,from,index,body: {query}})... }? ? 最終用戶使用的時候,輸入關鍵詞即可完成搜索,如圖:
四、創建文檔主題插件
? ? GitBook插件是在NPM上發布的遵循定義的約定的節點包,所以可以很好的滿足我們多個產品文檔同時應用同一個主題包的訴求,下面簡單說明如何創建以及發布Gitbook插件。
? ? 注意:GitBook從3.0版本開始支持自定義主題。
4.1、主題插件參考目錄結構
? ? 開放平臺GitBook主題插件的目錄結構如下:
gitbook-plugin-theme-document_assets // 靜態資源文件目錄_layouts // 布局模板文件目錄index.js // 主題入口配置文件package.json // 包描述文件README.md // 項目說明文件4.2、定義插件package.json
? ? 標準的npm包描述文件,注意如果是主題插件包名稱需要以gitbook-plugin-theme作為前綴命名,結構如下:
{"name": "gitbook-plugin-theme-doc","dependencies": {},"description": "文檔描述","license": "ISC","main": "index.js","readmeFilename": "README.md","engines": {"gitbook": ">=3.0.0"},"scripts": {"test": "echo \"Error: no test specified\" && exit 1"},"version": "1.0.0" }4.3、主題插件運行時入口
? ? 主要是定義主題插件編譯時的配置信息,下面簡單介紹常用的配置:
// index.js module.exports = {// 站點配置website: {assets: './_assets/', // 插件靜態資源目錄地址js: [ // 站點引用的js文件,相對assets目錄'xxxx.js'],css: ['xxxx.css' // 站點引用的css文件,相對assets目錄]},// 后面還有關于插入自定義邏輯或增強插件的鉤子以及塊,這里不做詳細介紹,具體可參閱http://gitbook.hushuang.me/plugins/create.html... }4.4、調整主題布局
? ? 有些場景下需要對生成的模板進行個性化調整,如調整導航菜單的結構,那么這個時候可以進入./_layouts/website/page.html模板進行相關的調整,gitbook內置了很多的上下文變量來提供給模板使用,如需要訪問導航數據并對導航模板結構進行處理,可參考如下處理:
... <ul class="doc-header-nav"><!--循環輸出導航-->{% for mainNav in book.navigation %}<!--如果配置了鏈接輸出A標簽,否則輸出SPAN標簽-->{% if mainNav.url %}<a href="{{mainNav.url}}" target="{{ mainNav.target}}">{{ mainNav.text }}</a>{% else %}<span>{{ mainNav.text }}</span>{% endif %}<!--如果是個菜單導航,那么輸出子級-->{% if mainNav.children %}<span class="doc_trangle"></span><ul class="doc-header-nav__sub">{% for subNav in mainNav.children %}<li><a href="{{subNav.url}}" target="{{ subNav.target}}">{{ subNav.text }}</a></li>{% endfor %}</ul>{% endif %}</li>{% endfor %} </ul> ...? ? 具體的模板變量可參考gitbook變量(http://gitbook.hushuang.me/ templating/variables.html),同理對于文檔目錄的個性化處理也可以參考這個方式去做。
4.5、發布主題
? ? 通過以上步驟創建了自定義主題,這個時候就可以發布主題了,主題以theme-前綴插件方式發布。例如,主題doc將從theme-doc插件加載,然后從gitbook-plugin-theme-doc NPM包加載。
4.6、使用自定義主題插件
? ? 發布主題后,在創建gitbook的項目中,通過更改book.json中的plugins來引入自定義的主題插件,如下:
// book.json"title": "開放平臺文檔","plugins": ["theme-doc" // 增加theme-doc的插件依賴],"pluginsConfig": {},....? ? 每次執行gitbook install命令后會自動安裝主題插件,并引用到當前文檔項目中。
五、自定義主頁
? ? 門戶站點使用Nuxtjs開發,得益于Nuxtjs的機制,可以很容易的實現產品定制化主頁的開發訴求,而且開發上手容易,成本不高,下面以MIP主頁為例簡單說明一下開發步驟:
5.1、新增主頁頁面
? ? 拉取開發平臺倉庫后,在site目錄下面新增產品主頁目錄及文件,如圖:
? ? 這里新增了mip的文件夾,同時在文件夾中新建了index.vue文件,因為Nuxtjs的默認路由是根據目錄結構定義好的,所以剛剛新增的產品文件夾最終會生成/mip路由,訪問http://localhost: 3000/mip就能顯示新增的主頁。
5.2、調整主頁布局樣式
? ? Nuxtjs是基于vue的后端渲染引擎,所以整個模板語法都是和Vue保持一致的,并且也是支持單文件模式,示例如下:
// ~ site/mip/index.vue<template><!--在這里可以直接寫頁面的布局--><div class="page-mip">MIP主頁</div> </template> <script> // 此處寫頁面的邏輯 export default {name: 'PageMip',data () {return {...}},computed: {...},methods: {...} } </script> <style> /*** 頁面的樣式 */ .page-mip{... } </style>? ? 注意:因為Nuxtjs是默認根據pages里面的結構生成路由的,所以pages里面都應該是頁面維度的組件,不能把頁面拆分為多個細粒度的模塊進行組合,否則會生成不符合預期的路由,遇到這種情況可以在sites下面創建components解決此問題。
5.3、使用靜態資源
? ? 頁面中會使用大量的圖片或者其它的媒體資源,這個時候可以通過將資源文件放到sites/static/目錄下,通過絕對路徑的方式訪問,如:
? ? 在模板中可以通過以下方式使用:
<template><img src="/images/conact_us/conact_us_1.svg"> </template> <style> .page-mip{background-image: url('/images/conact_us/conact_us_1.svg'); } </style>? ? 更多關于Nuxtjs的使用文檔,可參考Nuxtjs官方中文文檔(https://zh.nuxtjs.org/guide/ #nuxt-js-%E6%98%AF%E4%BB%80%E4%B9%88-)。
六、API生成機制
? ? 組件api文檔采用的是現在流程的jsDoc生成,它通過識別代碼中約定好的注釋生成可被后續重復使用的json數據,最后通過編譯生成html展現給用戶閱讀。
6.1、約定注釋規范
? ? 在寫組件或者工具庫的api時候都會帶上符合jsDoc規范的注釋,下面列舉一些常用的注釋標記:
@class:標明函數是一個構造器函數,意味著需要使用 new 關鍵字來返回一個實例 @function:作為對象的一個函數類型成員 @event:描述一個可觸發的事件 @param:指定要描述參數的名稱。還可以包含參數的數據類型 @public:標記是否是公開的 @returns:記錄一個函數的返回值實際使用示例如下:
/*** 格式化數字* @public* @function* @param {Number} val 要格式化的數字* @param {String} format 格式字符串,`#,###.000`:保留三位小數,`#,###`:沒有小位數* @example* utility.formatNumber(123123123.123, '#,###.000') // 返回值:123,123,123.123* utility.formatNumber(123123123.123, '#,###.00') // 返回值:123,123,123.12* @return {String} 轉換后的字符串*/function formatNumber () {...}? ? 想了解更多jsDoc的使用規范,可以參考jsDoc中文文檔(https://www.html.cn/doc/ jsdoc/tags-example.html)。
6.2、編譯生成API數據
? ? 借助jsDoc提供的編譯庫,可以遍歷項目中指定的文件進行解析,最終會得到api.json的文件。文件的內容結構大致如下:
[{"description": "<p>設置模式</p>", // 接口描述"kind": "function", // 接口類型,可以為class、var、function"longname": "AddressSelect#setMode", // 接口完整名稱"name": "setMode", // 接口名稱"memberof": "AddressSelect", // 接口所屬父類"params": [{ // 接口參數"type": {"names": ["Number"]},"description": "<p>要設置的模式值</p>","name": "mode"}],"examples": ["//設置編輯模式\rAddressSelect.setMode(2);\rAddressSelect.getMode(); //return 2;\r//設置查看模式\rAddressSelect.setMode(3);\rAddressSelect.getMode(); //return 3;"] // 接口示例 }, ...? ? 生成好數據后,接下來可以對數據進行進一步處理。
6.3、生成細粒度的API模塊文件
? ? jsDoc生成的api.json文件數據尺寸非常大,在實際使用的時候需要根據當前api所屬的類拆分為小的模塊文件,當請求某個組件的api接口列表的時候,后臺服務只需要根據請求的參數從api目錄中獲取到對應的文件,同時將數據返回給前端,生成的api數據文件如下:
? ? 通過拆分大文件為小文件方式,減輕實際請求中的解析性能問題,加快前端用戶使用的體驗。
6.4、API應用頁面展示
? ? 根據上述步驟,現在有了比較合理的api數據文件,此時在前端界面中會顯示api的菜單列,如圖:
? ? 當用戶點擊接口時,會將當前用戶點擊的接口信息發送給后端請求,后端請求根據生成的api文件目錄結構,獲取到對應的接口數據返回給前端,前端再來渲染出來。
? ? 注意:目前僅平臺支持組件api文檔,產品暫不支持。
七、如何跨應用組件公用
? ? 文檔項目目前存在門戶站點以及文檔站點兩個獨立的應用,其中文章反饋不僅在業務文檔中使用了,同時在api文檔中也需要,這個時候通過vue cli自動的組件lib打包方式,可以將通用的組件發布為內部的npm包,供兩個應用使用,在vue cli中的配置如下:
// vue.config.js module.exports = {// 修改 src 為 examples,可以對組件進行測試pages: {index: {entry: 'examples/main.js',template: 'public/index.html',filename: 'index.html'}},// 強制內聯CSScss: {extract: false},... }// package.json {"name": "doc-platform-component",..."scripts": {"serve": "vue-cli-service serve","build": "vue-cli-service build","lint": "vue-cli-service lint","lib": "vue-cli-service build --target lib --name docComponent --dest lib packages/index.js" // 指定打包方式為lib方式},... }? ? 后面執行npm run build生成組件lib文件,結果如圖:
? ? 編譯完畢后,在通過npm publish 發布內部組件包,供其他應用使用。
? ? 關于編譯配置具體的使用方式參見構建目標-庫(https://cli.vuejs.org/zh/guide/build-targets.html#%E5%BC%82%E6%AD%A5-web-components-%E7%BB%84%E4%BB%B6),這種方式的好處是,配置簡單,上手容易,且為官方提供的方式,不需要額外的依賴項,對于簡單的應用維護成本也低。
八、收集用戶反饋
? ? 文檔的持續優化和更新需要用戶的及時反饋,在每篇文檔中提供了用戶提交反饋的表單,同時這個組件也是跨應用的通用組件,最終效果如圖:
? ? 用戶點擊提交后,實際上是在jira系統中針對開發平臺項目登記了一條反饋,此時管理人員通過登錄jira系統,即可對這個反饋進行相關的處理,在實現層面只需要如下方式即可接入jira系統提供的api:
// 生成請求的token const token = 'xxxxxbbbbbcccccddddd==' // 調用jira開放的新增issue接口 const request({method: 'POST',url: 'https://jira.mingyuanyun.com/rest/api/2/issue',headers: {'cache-control': 'no-cache','Authorization': `Basic ${token}`,'Content-Type': 'application/json'},body: JSON.stringify({fields: {project: {id: 'xxxx'},issuetype: {id: 'xxxx'},components: [{id: 'xxxx'}],customfield_10204: 'xxxx',summary: `反饋標題`,description: '反饋內容'}}) })? ? 注意:使用jira的接口,需要生成好token,并且寫入請求的headers里面。
總結
? ? 使用Gitbook以及nuxtjs方式打包構建生成靜態站點,通過git來管理文檔的提交以及用戶權限,目前來看主要的優勢是開發成本比較低,對于前端開發也比較友好,很多的知識點也再可接受的范圍內,后續擴展功能相對比較容易,比如后續增加的幾個產品頁面都是基于nuxt良好的機制很快的開發完畢的。
? ? 隨著文檔的數量越來越多,每次全量打包編譯導致整個構建環節時間逐漸增加,也是后面需要優化的環節,可以通過拉取git變更集,分析出需要重新編譯的文件進行增量更新,提升這塊的性能。
? ? 在文檔搜索這塊由于前期對ElasticSearch這塊經驗的不足,有些文章并不能很好的被精準搜索到,后續可以通過以下幾個方式進行一定優化,如:
加強對文章的書寫規范要求,文章的描述和子標題中應盡可能的多帶有和當前文章貼近的語義關鍵詞。
針對文章不同的片段在過濾時增加不同級別的權重,可以為主標題權重最高、子標題其次、再就是描述中的關鍵詞,最后是文章內容,通過增加不同的權重設置可以對搜素結果進行按權重排序。
針對中文可以安裝中文的分詞插件,優化中文分詞規則,使搜索結果更友好。
? ? 未來在文檔這塊可以采取線上編寫以及發布更新方式,這樣的好處是用戶不需要在本地安裝一些開發工具來編寫以及預覽文檔了,同時所有用戶的體驗也是一致性,當然最主要的是文檔的更新也是實時的,不需要再定時去構建了,相信隨著后面的不斷優化,文檔這塊能越來越好。
------ END ------
作者簡介
楊同學:?研發工程師,目前負責ERP建模平臺的設計與開發工作。
也許您還想看
從案例角度解析建模平臺動態規則引擎
WEB頁面前端性能診斷方法與實踐
TypeScript+vue使用與遷移經驗總結
Web頁面適配移動端方案研究
前端異步對象的原理與使用方法
總結
以上是生活随笔為你收集整理的基于GitBook框架搭建技术文档平台的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Abp vNext 二进制大对象系统(B
- 下一篇: 用Blazor技术封装G2Plot实现C