從零開始打造 Mock 平臺 - 功能模組篇

臨水照影發表於2018-02-07

前言

二月初想想這個月還得搗鼓一篇文章,也沒啥好的想法那還是記錄一下畢設的一些思路吧。

重要功能

一些擴充套件的重要功能將在這裡一點點從零開始進行思考。

專案匯入匯出功能

專案匯入匯出的構想是在設定特色功能時候想到的,主要是用於不同伺服器上如果部署了平臺,如果想要自己私下部署測試,那麼重新建立專案,然後再一個個配置介面路徑,配置返回資料是一件很麻煩的事情。為了以後用(自)戶(己)用的順暢,想了一鍵各種版本的功能,其中就包括專案的匯入匯出。

因此在設計資料結構的時候,我將最終返回結果資料設計成如下形式


{
  project:{
  	 projectId:,
  	 ...
    interfaceList: []
  }
}

複製程式碼

也就是前端本地快取的資料就可以直接匯出。

當然匯出可以把一些資訊先過濾處理一遍,比如專案Id ,介面 Id。

於是匯出可以為一個純 Json 文字。

然後倒入時候需要驗證 Json 格式,其實就是需要做個遍歷,看看該有的屬性有沒有,沒有的話就報錯不進行資料匯入。

而且匯入的時候需要做個分類,是匯入到專案示例還是個人/團隊專案。

常見的是打包下載 zip。這個大概需要後端處理,因此先找找有沒有前端處理的。

搜了一下方案,再結合 github 上一些專案的原始碼,可以簡單的寫出一個匯出模組


export function exportFile(data: string, filename: string, type: string) {
    var typeList = {
      json: 'application/json;charset=utf-8',
      markdown: 'text/markdown;charset=utf-8',
      doc: 'application/mswordcharset=utf-8',
    }
    // 建立隱藏的可下載連結 
    var eleLink = document.createElement('a');
    eleLink.download = filename;
    eleLink.style.display = 'none'; // 字元內容轉變成blob地址 
    var blob ;
    blob= new Blob(['\uFEFF' + data],{type: typeList[type]});

    eleLink.href = URL.createObjectURL(blob); 
    document.body.appendChild(eleLink);
    eleLink.click();
    document.body.removeChild(eleLink);
}

複製程式碼

呼叫方式很簡單,就是傳入三個引數:

exportFile(JSON.stringify(this.state.currentProjectData), 'default.md', 'markdwon')

imgn

當然我們現在獲得的資料是沒有進行處理的,我們需要對資料做個過濾,剔除一些關鍵資訊以及沒必要的資料。

假設現在的資料是這樣的


{
  "_id": "project001",
  "projectName": "演示專案 - REST介面示例超長字串測試asd123",
  "projectUrl": "/project001",
  "projectDesc": "專案描述",
  "version": "v1.0",
  "transferUrl": "http://haoqiao.me/api/project",
  "status": "transfer",
  "type": "demo",
  "teamMember": [
    {
      "_id": "user001",
      "username": "2333",
      "role": "前端工程師",
      "avatar": "https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png"
    },
    {
      "_id": "user002",
      "username": "宋青樹",
      "role": "後端工程師",
      "avatar": "https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png"
    }
  ],
  "interfaceList": [
    {
      "_id": "interface001",
      "interfaceName": "獲取",
      "url": "/getAll",
      "method": "get",
      "desc": "介面描述",
      "mode": "{data: 1 || 2}"
    },
    {
      "_id": "interface002",
      "interfaceName": "增加",
      "url": "/add",
      "method": "post",
      "desc": "介面描述",
      "mode": "{data: 1 || 2}"
    },
    {
      "_id": "interface003",
      "interfaceName": "刪除",
      "url": "/delete",
      "method": "delete",
      "desc": "介面描述",
      "mode": "{data: 1 || 2}"
    },
    {
      "_id": "interface004",
      "interfaceName": "更新",
      "url": "/update",
      "method": "put",
      "desc": "介面描述",
      "mode": "{data: 1 || 2}"
    }
  ]
}

複製程式碼

我們需要將裡面所有的 _id(欄位), teamMember(陣列) 去除。這裡大家想想如果是自己要怎麼處理?

其實非常簡單,只要你對原生的 JSON.stringify 比較熟悉,你就知道它的完整定義如下

JSON.stringify(value, replacer?, space?)

replacer 是一個過濾函式或則一個陣列包含要被 stringify 的屬性名。如果沒有定義,預設所有屬性都被 stringify

可以做一個遍歷器,遍歷Json裡的屬性名。然後內部做個剔除。

比如這樣


filterData = (json: any) =>{
    console.log(json)
    let expectArr = ['_id', 'teamMember']
    let filterArr = []
    let result = ''
    for( let key in json){
      if (expectArr.indexOf(key) === -1){
      filterArr.push(key)
    }
    result = JSON.stringify(this.state.currentProjectData, filterArr)
    console.log(result)
    
    return result

  }

複製程式碼

但這樣只能拿到第一層的屬性名,如何拿到巢狀的陣列裡的Json的屬性呢?

我們只需要再做個判斷就可以了


filterData = (json: any) =>{
    console.log(json)
    let expectArr = ['_id', 'teamMember']
    let filterArr = []
    let result = ''
    for( let key in json){
      if (expectArr.indexOf(key) === -1){
        filterArr.push(key)
        // 如果是巢狀陣列,而且陣列內有資料
        if(Object.prototype.toString.call(json[key]) == "[object Array]" && json[key].length > 0){
          for( let item in json[key][0]){
            // 同樣對裡面的json資料進行屬性欄位過濾
            if (expectArr.indexOf(item) === -1){
              filterArr.push(item)
            }
          }
        }
      }
    }
    result = JSON.stringify(this.state.currentProjectData, filterArr)
    console.log(result)
    
    return result

  }

複製程式碼

這樣就把該有的屬性篩選出來了。然後做個轉換就能過濾只剩需要的資料。

資料清理後就變成如下格式


{
  "_id": "project001",
  "projectName": "演示專案 - REST介面示例超長字串測試asd123",
  "projectUrl": "/project001",
  "projectDesc": "專案描述",
  "version": "v1.0",
  "transferUrl": "http://haoqiao.me/api/project",
  "status": "transfer",
  "type": "demo",
  "teamMember": [
    {
      "_id": "user001",
      "username": "2333",
      "role": "前端工程師",
      "avatar": "https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png"
    },
    {
      "_id": "user002",
      "username": "宋青樹",
      "role": "後端工程師",
      "avatar": "https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png"
    }
  ],
  "interfaceList": [
    {
      "_id": "interface001",
      "interfaceName": "獲取",
      "url": "/getAll",
      "method": "get",
      "desc": "介面描述",
      "mode": "{data: 1 || 2}"
    },
    {
      "_id": "interface002",
      "interfaceName": "增加",
      "url": "/add",
      "method": "post",
      "desc": "介面描述",
      "mode": "{data: 1 || 2}"
    },
    {
      "_id": "interface003",
      "interfaceName": "刪除",
      "url": "/delete",
      "method": "delete",
      "desc": "介面描述",
      "mode": "{data: 1 || 2}"
    },
    {
      "_id": "interface004",
      "interfaceName": "更新",
      "url": "/update",
      "method": "put",
      "desc": "介面描述",
      "mode": "{data: 1 || 2}"
    }
  ]
}

複製程式碼

之後是要考慮匯入專案,我首先想到點選上傳 JSON 格式檔案然後讀取裡面的內容,然後驗證資料再讓後臺將其匯入到指定表中。

這裡面發生了一些事情,比如我肯定不希望使用者真的把json檔案上傳到伺服器上,我覺得這玩意前端肯定能解析和解決,但是我們肯定還是需要一個上傳的按鈕和UI互動。

這裡我直接用了 antdupload 元件,它裡面有個方法就是 beforeUpload , 只要我們在這個函式裡直接返回 false 那麼是不會真正觸發上傳動作的,但是我們又可以拿到本地的 File,可以用 HTML5 的新方法 FileReader 來幫助讀取內容。

我們的元件可以這麼修改


const uploadProps = {
      name: 'file',
      action: '',
      showUploadList: false,
      beforeUpload: (file: any) => {
        const isJSON = file.type === 'application/json';
        if (!isJSON) {
          Message.error('只允許上傳JSON格式檔案!');
        }
        const isLt2M = file.size / 1024 / 1024 < 2;
        if (!isLt2M) {
          Message.error('JSON檔案大小必須小於 2MB!');
        }
        var reader = new FileReader(); // 讀取操作都是由FileReader完成的
        var that = this
        reader.readAsText(file);
        reader.onload = function(){//讀取完畢從中取值
          const json = this.result
          if(isJson(json) && that.state.uploadJsonData.length === 0){
            that.setState({
              uploadProject:true,
              uploadJsonData: json
            })
            Message.success('Json檔案上傳識別成功!');
          }
        }
        return false;
      }},
      
      onChange: (info: any) => {
      },
    };
    
   <Dragger {...uploadProps}>
      <p className="ant-upload-drag-icon">
      <Icon type="inbox" />
      </p>
      <p className="ant-upload-text">點選上傳JSON檔案或者拖拽上傳JSON檔案</p>
              
  </Dragger>

複製程式碼

來看下實際的互動效果

imgn

這樣我們就打通了專案的匯入匯出功能的互動。之後就是介面對接一下就行了。

專案克隆 / 介面克隆

這兩個功能其實很類似,主要是用於幫助使用者能夠複製已經存在的介面或者專案。 比如我已經之前建立了一套系統的介面,包括了增刪減改。我下一個系統和這套系統很類似,可能只需改幾個欄位就可以用了。 我們當然可以利用匯入和匯入功能,但是在系統內部我們最好有一鍵遷移的方式,那就是克隆。

克隆我們需要注意,首先是介面克隆,假設我們介面定義的格式如下:


_id(pin): "interface005"
interfaceName(pin): "註冊"
url(pin): "/reg"
method(pin): "post"
desc(pin): "介面描述"
mode(pin): "{data: 1 || 2}"

複製程式碼

然後我為了方便定義的專案 Model 裡面包含了介面 Model。

也就是我只需把 介面 Id,和 專案 Id 傳給後臺,讓後臺做一個查詢介面內容,然後新建介面把查詢到的內容插入到指定 Id 就可以了。

這很簡單。主要部分是 UI 這塊,不過通過對資料流的管理也是花時間就能解決的事情。如下圖:

imgn

之後是克隆專案這塊, 我們首先已經知道專案 Model 裡面包含了 介面 Model,因此我們克隆整個專案其實是需要將整個介面提取出來,團隊成員是需要剔除的,因為新克隆專案應該是隻有建立者,因此我們需要把 使用者 Id 也從前端傳過去,當然也可不傳,通過 Jwt 對 token 解析也能識別使用者資訊。

主要是後端拿到資訊之後它的思路應該是先查詢這個專案的資訊,然後提取部分資訊建立新專案, 然後遍歷原有專案的介面列表,批量建立介面。

個人資訊的更改

基本上中後臺的應用都會有個人資訊管理這項,有的用表單,有的拆分。

其餘資料都好搞定,無非是傳參的問題,前後端約定的問題。 當然比較麻煩的其實是頭像的更改。 假設你註冊的時候預設分配給一個使用者頭像,然後再個人資訊裡使用者想要更改。 這時候問題來了。更改頭像其實是一個互動問題,你肯定不能讓使用者一步步操作。而是一步到位,符合要求的圖片上傳之後,拿到上傳後的圖片地址。然後更改本地的資料。還需要在後臺自動更新資料。

imgn

這裡是用 redux 維護了一個本地的前端資料層,所有顯示的變更顯示操作需要對其進行更新。

然後是 reducer 裡監聽了動作, 定義為 UPDATE_LOCALXXX 的動作是用於提交資料後等待後臺處理完畢後返回成功,然後將本地的資料進行更新。

顯而易見這個動作是非同步操作。如果很多個類似操作需要管理我們程式碼寫起來肯定會很亂。因此我在技術評估階段引入了 rxjs

通過其特點時間線的管理,就很容易了。以下是簡單的示例程式碼


export const userUpdate = (action$: any) =>
  action$.ofType(UPDATE_USER)
    .mergeMap((action: any) => {
      return fetch.post(updateUser, action.data)
        // 登入驗證情況
        .map((response: any) => {
          console.log(response);
          if (response.state.code === 1) {
            updateUserSuccess(response.state.msg);
            return updateLocalUser(action.data);
          } else {
            console.log('token error')
            updateUserError(response.state.msg);
            return nothing();
          }
        })
        // 只有伺服器崩潰才捕捉錯誤
        .catch((e: any): any => {
          // console.log(e)
          return Observable.of(({ type: USER_LOGINERROR })).startWith(loadingError())
        })

    });


複製程式碼

結尾

還有一些功能需要等後端開發的時候再記錄思路,因此這部分先到這裡。

相關文章