使用Vue和thrift創建先後端交互的demo

初識thrift

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

thrift 結構

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

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

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

thrift類型

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

基本類型:webpack

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

與Js對應的對象類型,ios

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

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

  • list 一系列的數組元素

與Js中set對應的類型:github

  • set 至關於不重複的數組

與Js中Map對應的類型web

  • 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。 其餘原理和總結有待後續挖掘。

相關文章
相關標籤/搜索