使用Vue和thrift建立前后端交互的demo


初識thrift

thrift 是 facebook 於2007年開發的一款跨平台 RPC(Remote Procedure Call) 軟件框架,
它可以在多種平台上進行無縫交互,數據傳輸使用二進制的方式,比XML和JSON體積更小,適合於內網的之間的數據進行交互。

thrift 結構

(參見https://www.ibm.com/developerworks/cn/java/j-lo-apachethrift/)

thrift 是由傳輸層、協議層和業務組成。用戶選定的傳輸層類型、協議層類型之后,只需要關注業務代碼即可,無需關注底層實現。

當生成了一套協議后,由客戶端和和服務端根據協議文件生成 thrift 的接口庫,
接口庫會提供定義的service方法,直接調用方法,遠程服務端會返回數據。

thrift類型

接口定義地址;
http://thrift.apache.org/docs/types

基本類型:

  • bool 對應js中boolean
  • byte 8位無符號整數
  • i16 16位無符號整數
  • i32 32位無符號整數
  • i64 64位無符號整數
  • double 64位符點型數字
  • string utf-8類型的字符串
  • binary 一系列基於string類型的編碼的字符串

與Js對應的對象類型,

  • struct 結構體,可使用基本類型,強類型,需要定義不同的名稱
    示例:
struct ListItem {
  1: i32 id,
  2: string content = '',
  3: Status status = 1,
  4: string author,
  5: i32 textLength
}

容器類型,是不能直接在外層定義的,需要在 struct 中定義或者在 service 中定義,
主要包括:
與Js的數組對應的類型:

  • list 一系列的數組元素

與Js中set對應的類型:

  • set 相當於不重復的數組

與Js中Map對應的類型

  • map 類似於字典模式

其他類型;
異常類型

  • exception

可調用接口

  • service
    示例:
service Todo {
  list<ListItem> getTodoList(),
  i32 getTotalLength(1: string author),
  i8 postTodo(1: PostItem item)
  ListItem doneArtical(1: i32 id)
  ListItem deleteArtical(1: i32 id)
}

使用thrift實現網頁和nodejs服務端進行交互的實踐

安裝環境

為方便操作,使用vue進行html的開發。首先,需要安裝thrift環境(在mac環境下,其他環境請參考http://thrift.apache.org/tutorial/):

brew install thrift

同時安裝vue開發的環境,vue/cli,用於直接對單個文件進行開發(其實是為了省事,不想搭webpack環境)。

npm install -g @vue/cli
npm install -g @vue/cli-service-global

新建接口文件

接口文件是我們根據 thrift 定義的類型進行書寫。其中除service類型外,其他定義都相當於定義成全局的類型,要注意名字的唯一性,service 是供我們調用的類型,就是接口。

創建的thrift文件如下:


enum Status {
  NORMAL = 1,
  DONE = 2,
  DELETED = 3
}

struct PostItem {
  1: string content = '',
  2: string author,
}

exception CodeError {
  1: i32 code = 0,
  2: string message = ''
}

struct ListItem {
  1: i32 id,
  2: string content = '',
  3: Status status = 1,
  4: string author,
  5: i32 textLength
}

service Todo {
  list<ListItem> getTodoList(),
  i32 getTotalLength(1: string author),
  i8 postTodo(1: PostItem item)
  ListItem doneArtical(1: i32 id)
  ListItem deleteArtical(1: i32 id)
}

Todo就是我們需要使用的類。

生成接口庫文件

thrift -r --gen js:node todo.thrift && thrift -r --gen js todo.thrift

js:node 是供 Nodejs 調用的庫文件,js 是瀏覽器環境的文件(貌似是需要使用grunt進行打包,反正我是用不習慣,只是個示例,直接中在html通過腳本引入了)。生成的文件保存在gen-js 和 gen/nodejs 兩個文件夾下,一個是類型文件,一個是接口文件。

建立 server 端代碼

由於瀏覽器和后台交互目前只支持 ajax 的方式,所以我們的服務端是需要搭建http服務器的。
使用 thrift 的 createWebServer即可(注意不要使用示例中的createServer,那個創建的是socket服務,不是Http服務)。同時設置好傳輸協議為json格式,傳輸層類型為buffer模式。為接口中的每個 service 添加實現方式。

const thrift = require('thrift')

const Todo = require('./gen-nodejs/Todo')
const tTypes = require('./gen-nodejs/todo_types')

const data = []
let gid = 0

const actions = {
  getTodoList () {
    return data
  },
  getTotalLength () {
    return data.length
  },
  postTodo (item) {
    const result = new tTypes.ListItem({
      content: item.content,
      author: item.author,
      status: tTypes.Status.NORMAL,
      textLength: item.content.length,
      id: ++gid
    })
    data.push(result)
    return 0
  },
  doneArtical (id) {
    const result = data.find(item => item.id === id)
    if (!result) {
      throw new tTypes.CodeError({code: 1, message: '請選擇條目!'})
    }
    result.status = tTypes.Status.DONE
    return result
  },
  deleteArtical (id) {
    const index = data.findIndex(item => item.id === id)
    const result = data[index]
    if (!~result) {
      throw new tTypes.CodeError({code: 1, message: '請選擇條目!'})
    }
    data.splice(index, 1)
    return result
  }
}

const serverOptions = {
  // 靜態文件服務器路徑
  files: '.',
  // 設置跨域請求
  cors: {
    '*': true
  },
  services: {
    // 設置service
    '/': {
      // 傳輸層類型為buffer模式
      transport: thrift.TBufferedTransport,
      // 協議類型為json格式
      protocol: thrift.TJSONProtocol,
      processor: Todo,
      handler: actions,
    }
  }
}

const server = thrift.createWebServer(serverOptions)

server.listen(7878, () => {
  console.log(`監聽端口:${7878}`)
})

創建瀏覽器端代碼

瀏覽器代碼就是寫網頁了。為了使用 vue 的 serve 功能,網頁的名稱需要設置為 App.vue,同時添加個自定義的 html 文件,添加引入 thrift 庫腳本:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <title>新增文章</title>
  <meta name="viewport" id="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
  <link rel="shortcut icon" href="/favicon.ico">
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-status-bar-style" content="black">
  <meta name="format-detection" content="telephone=no">
</head>

<body>
  <div id="app"></div>
  <script type='text/javascript' src='http://localhost:7878/thrift-bundle.js'></script>
</body>

</html>

vue文件內容為:

<template>
  <div>
    <section class="artical-list">
      <ul>
        <li
          v-for="(item, index) in list"
          :key="index">
          <p>{{item.content}}</p>
          <p>作者: {{item.author}}, 當前狀態:{{item.status | status}}</p>
          <button @click="doneArtical(item)">設置為已閱</button>
          <button @click="deleteArtical(item)">刪除</button>
        </li>
      </ul>
    </section>

    <section class="form-data">
      <textarea name="artical" v-model="artical" cols="30" rows="10"></textarea>
      <input type="text" name="author" v-model="author"/>
      <button @click="postArtical">提交</button>
    </section>
  
  </div>
</template>

<script>
/* eslint-disable */
export default {
  data () {
    return {
      list: [],
      artical: '',
      author: '',
    }
  },
  created () {
    this.init()
  },
  filters: {
    status (value) {
      const status = ['無', '正常', '已閱', '刪除']
      return status[value]
    },
  },
  methods: {
    init () {
      const transport = new Thrift.Transport('http://localhost:7878')
      const protocol = new Thrift.Protocol(transport)
      const client = new TodoClient(protocol)
      this.client = client
      this.getList()
    },
    getList () {
      this.client.getTodoList((result) => {
        this.list = result
      })
    },
    postArtical () {
      const result = new PostItem()
      result.content = this.artical
      result.author = this.author

      this.client.postTodo(result, (result) => {
        this.getList()
      })
    },
    doneArtical (item) {
      this.client.doneArtical(item.id, (result) => {
        if (result instanceof Thrift.TApplicationException) {
          alert(result.message)
          return
        }
        this.getList()
      })
    },
    deleteArtical (item) {
      this.client.deleteArtical(item.id, (result) => {
        if (result instanceof Thrift.TApplicationException) {
          alert(result.message)
          return
        }
        this.getList()
      })
    },
  },
}
</script>

主要思路是在初始化先創建接口的實例,設置 transport 的請求地址,然后使用我們定義的 service,
綁定實例在this上。每次要操作時直接調用實例的方法即可。看起來是不是和我們寫普通封裝好的axios一樣?

運行

為方便使用,我們使用 nodemon 進行服務器開發自動重啟,我們在 npm 包中添加以下腳本:

"scripts": {
  "start": "vue serve & node server.js",
  "dev": "vue serve & npm run compile && nodemon server.js",
  "compile": "npm run gen && npm run concat",
  "gen": "thrift -r --gen js:node todo.thrift && thrift -r --gen js todo.thrift",
  "concat": "concat -o thrift-bundle.js ./thrift.js ./gen-js/*.js"
},

這樣,我們使用 npm start 啟動已經構建好的服務,使用 npm run dev 進行開發,
使用 npm run compile 在改動了 thrift 接口文件后進行重新編譯。

這樣我們的網頁就做好了:

總結

搭建一個簡單的 thrift 項目還是很容易的,所有的代碼已經放在我的github上https://github.com/wenlonghuo/code-test/tree/master/004_thrift
其他原理和總結有待后續挖掘。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM