ThinkJS入門+例項(實現認證許可權等基本功能)

mnn發表於2018-10-14

這是一篇關於ThinkJS框架的文章,因為網上對於該框架的介紹非常少,所以在這裡通俗講解一下自己對該框架基本的認識並且提供了一個練習基本功能的專案。

ThinkJS入門+例項(實現認證許可權等基本功能)

因為這是基於Node.js的框架,所以先帶新手入門一點Node.js的知識。

Node.js簡述

Node.js:簡單的說Node.js就是執行在服務端的JavaScript。

Node.js安裝配置 (介紹在window和Linux上的安裝Node.js的方法)

Node.js官方文件

官方文件介紹:

  • Node.js是一個基於Chrome V8引擎的JavaScript執行環境。
  • Node.js使用了一個事件驅動、非阻塞式I/O的模型,使其輕量又高效。。事件驅動與非同步IO
  • Node.js的包管理器npm,是全球最大的開源庫生態系統。

認識包管理器npm(npm已經在安裝Node.js的時候安裝好了)

當我們在Node.js上開發時,會用到很多別人寫的JavaScript程式碼。如果我們需要使用別人寫的某個包,每次都根據名稱搜尋一下官方文件,下載程式碼,解壓,再使用,非常繁瑣。於是一個集中管理的工具應運而生:大家都把自己開發的模組打包後放到npm官網上,如果要使用,直接通過npm安裝就可以直接使用,不管程式碼存在哪,應該從哪下載。

更重要的是,如果我們要使用模組A,而模組A又依賴於模組B,模組B又依賴於其他的模組,那麼npm可以根據依賴關係,把所有依賴的包都下載下來並管理起來。否則,靠我們自己手動管理,肯定是麻煩又容易出錯。

第一個Node程式

瞭解Node.js應用的幾個組成部分:

  1. 引入required模組:我們可以使用require指令來載入Node.js模組。
  2. 建立伺服器 :伺服器可以監聽客戶端的請求,類似於Apache,Nginx等伺服器。
  3. 接收請求和響應請求:伺服器很容易建立,客戶端可以使用瀏覽器或終端傳送http請求,伺服器接收請求後返回相應資料。

建立Node.js應用

步驟一:引入required模組

使用require指令載入http模組,並將例項化的HTTP賦值給變數http,例項如下:

var http = require('http');
複製程式碼

步驟二:建立伺服器

接下來我們使用 http.createServer() 方法建立伺服器,並使用listen方法繫結8888埠。函式通過requestresponse引數來接收和響應資料。例項如下:

var http = require('http');  //請求Node.js自帶的http模組,並且把它賦值給http變數
http.createServer(function (request, response) {  //呼叫http模組提供的模組
    // 傳送 HTTP 頭部 
    // HTTP 狀態值: 200 : OK
    // 內容型別: text/plain
    response.writeHead(200, {'Content-Type': 'text/plain'});

    // 傳送響應資料 "Hello World"
    response.end('Hello World\n');
}).listen(8888);

// 終端列印如下資訊
console.log('Server running at http://127.0.0.1:8888/');
複製程式碼

Node.js後端框架

Express和Koa(典型框架)

Express:輕量靈活的的node.js 框架,可以快速的搭建應用,使用廣泛。Express官方文件

Koa:由Express原班人馬打造,致力於成為web應用和API開發領域中的一個更小、更富有表現力、更健壯的基石。通過利用async函式,koa幫你丟棄回撥函式,並有力的增強錯誤處理。Koa官方文件

Express和Koa是node.js最基礎的兩個後端框架。因為構建一個app仍需要些很多腳手架程式碼,於是在他們基礎上出現了很多其他框架來減少編寫這類程式碼。(例如:ThinkJS,egg.js等)

ThinkJS

介紹:ThinkJS是一款面向未來開發的Node.js框架,整合了大量的專案最佳實踐,讓企業級開發變得簡單、高效。從3.0開始,框架底層基於Koa2.x實現,相容Koa的所有功能。

特性:

  • 基於Koa2.x,相容middleware
  • 核心小巧,支援ExtendAdapter等外掛方式
  • 效能優異,單元測試覆蓋程度高
  • 內建自動編譯、自動更新機制、方便快速開發
  • 使用更優雅的async/await處理非同步問題、不再支援*/yield方式

快速入門

藉助ThinkJS提供的腳手架,可以快速的建立一個專案。為了可以使用更多的ES6特性,框架要求node.js的版本至少是6.x,建議使用LTS版本。

安裝ThinkJS 命令

npm install -g think-cli
複製程式碼

安裝完成後,系統中會有thinkjs命令(可以通過thinkjs-v檢視think-cli版本號)

建立專案


thinkjs new demo  //建立名為demo的專案
npm install   //安裝依賴
npm start  //執行專案
複製程式碼

專案結構

預設建立的專案結構如下:

|--- development.js   //開發環境下的入口檔案
|--- nginx.conf  //nginx 配置檔案
|--- package.json
|--- pm2.json //pm2 配置檔案
|--- production.js //生產環境下的入口檔案
|--- README.md
|--- src
| |--- bootstrap  //啟動自動執行目錄 
| | |--- master.js //Master 程式下自動執行
| | |--- worker.js //Worker 程式下自動執行
| |--- config  //配置檔案目錄
| | |--- adapter.js  // adapter 配置檔案 
| | |--- config.js  // 預設配置檔案 
| | |--- config.production.js  //生產環境下的預設配置檔案,和 config.js 合併 
| | |--- extend.js  //extend 配置檔案 
| | |--- middleware.js //middleware 配置檔案 
| | |--- router.js //自定義路由配置檔案
| |--- controller  //控制器目錄 
| | |--- base.js
| | |--- index.js
| |--- logic //logic 目錄
| | |--- index.js
| |--- model //模型目錄
| | |--- index.js
|--- view  //模板目錄
| |--- index_index.html
複製程式碼

基礎功能

Config(配置)

實際專案中,肯定需要各種配置,包括:框架需要的配置以及專案自定義的配置。ThinkJS將所有的配置都統一管理,檔案都放在src/config/目錄下,並根據不同的功能劃分為不同的配置檔案。

  • config.js 通用的一些配置
  • adapter.js adapter配置 (資料庫的配置)
  • router.js自定義路由配置
  • middleware.js middleware配置
  • validator.js 資料校驗配置
  • extend.js extend 配置

配置格式

// src/config.js

module.exports = {
  port: 1234,
  redis: {
    host: '192.168.1.2',
    port: 2456,
    password: ''
  }
}
複製程式碼

配置值即可以是一個簡單的字串,也可以是一個複雜的物件,具體是什麼型別根據具體的需求來決定。

使用配置

框架提供了在不同環境下不同的方式快速獲取配置:

  • 在ctx(上下文)中,可以通過ctx.config(key)來獲取配置
  • 在controller中,可以通過controller.config(key)來獲取配置
  • 其他情況下,可以通過think.config(key)來獲取配置

實際上,ctx.configcontroller.config是基於think.config包裝的一種更方便的獲取配置的方式。

Adapter(介面卡)

Adapter是用來解決一類功能的多種實現,這些實現提供一套相同的介面,類似設計模式裡的工廠模式。如:支援多種資料庫,支援多種模板引擎等。通過這種方式,可以很方便地在不同的型別中進行切換。Adapter一般配合Extend一起使用。

框架預設提供了很多Adapter,如:View、Model、Cache、Session、Websocket,專案中也可以根據需要進行擴充套件,也可以引入第三方的Adapter。

Adapter配置

Adapter的配置檔案為src/config/adapter.js,格式如下:

const nunjucks = require('think-view-nunjucks');
const ejs = require('think-view-ejs');
const path = require('path');

exports.view = {
  type: 'nunjucks', // 預設的模板引擎為 nunjucks
  common: { //通用配置
    viewPath: path.join(think.ROOT_PATH, 'view'),
    sep: '_',
    extname: '.html'
  },
  nunjucks: { // nunjucks 的具體配置
    handle: nunjucks
  },
  ejs: { // ejs 的具體配置
    handle: ejs,
    viewPath: path.join(think.ROOT_PATH, 'view/ejs/'),
  }
}

exports.cache = {
  ...
}
複製程式碼

Adapter 配置支援執行環境,可以根據不同的執行環境設定不同的配置,如:在開發環境和生產環境的資料庫一般都是不一樣的,這時候可以通過 adapter.development.jsadapter.production.js 存放有差異的配置,系統啟動後會讀取對應的執行環境配置和預設配置進行合併。

Adapter配置解析

Adapter 配置儲存了所有型別下的詳細配置,具體使用時需要對其解析,選擇對應的一種進行使用。比如上面的配置檔案中,配置了nunjucksejs 二種模板引擎的詳細配置,但具體使用時一種場景下肯定只會用其一種模板引擎。當然,配置解析並不需要使用者在專案中具體呼叫,一般都是在外掛對應的方法裡已經處理。

Adapter使用

Adapter 都是一類功能的不同實現,一般是不能獨立使用的,而是配合對應的擴充套件一起使用。如:view Adapter(think-view-nunjucks、think-view-ejs)配合 think-view擴充套件進行使用。

資料庫:(model Adapter配合think-mongo擴充套件進行使用)

model adapter

/**
 * model adapter config
 * @type {Object}
 */
exports.model = {
  type: 'mongo', // 預設使用的型別,呼叫時可以指定引數切換
  common: { // 通用配置
    logConnect: true, // 是否列印資料庫連線資訊
    logger: msg => think.logger.info(msg) // 列印資訊的 logger
  },
  mongo: {
    host: '127.0.0.1',
    port: 27017,
    user: '',
    password: '',
    database: 'manannan', // 資料庫名稱
    options: ''
  }
};
複製程式碼

extend

const view = require('think-view');
const model = require('think-model');
const cache = require('think-cache');
const session = require('think-session');
const mongo = require('think-mongo');

module.exports = [
  view, // make application support view
  model(think.app),  ////將 think.app 傳遞給 model 擴充套件
  mongo(think.app),
  cache,
  session
];
複製程式碼

Extend(擴充套件)

雖然框架內建了很多功能,但在實際專案開發中,提供的功能還是遠遠不夠的。3.0 裡引入了擴充套件機制,方便對框架進行擴充套件。支援的擴充套件型別為:thinkapplicationcontextrequestresponsecontrollerlogicservice

框架內建的很多功能也是擴充套件來實現的,如:SessionCache

Context(上下文)

Context是Koa中處理使用者請求中的一個物件,貫穿整個請求生命週期。一般在middlewarecontrollerlogic中使用,簡稱ctx。

框架裡繼承了該物件,並通過 Extend 機制擴充套件了很多非常有用的屬性和方法。

例如:

ctx.state

在中介軟體之間傳遞資訊以及將資訊傳送給模板時,推薦的名稱空間。避免直接在 ctx 上加屬性,這樣可能會覆蓋掉已有的屬性,導致出現奇怪的問題。

ctx.state.user = await User.find(id);
複製程式碼

這樣後續在 controller 裡可以通過 this.ctx.state.user 來獲取對應的值。

module.exports = class extends think.Controller {
  indexAction() {
    const user = this.ctx.state.user;
  }
}
複製程式碼

ctx.header

獲取所有的 header 資訊,等同於 ctx.request.header

const headers = ctx.headers;
複製程式碼

ctx.headers

獲取所有的 header 資訊,等同於ctx.header

ctx.url

獲取請求地址。

Middleware(中介軟體)

Middleware稱之為中介軟體,是Koa中一個非常重要的概念,利用中介軟體,可以很方便的處理使用者的請求。

中介軟體格式為一個高階函式,外部的函式接收一個 options 引數,這樣方便中介軟體提供一些配置資訊,用來開啟/關閉一些功能。執行後返回另一個函式,這個函式接收 ctx, next 引數,其中 ctxcontext 的簡寫,是當前請求生命週期的一個物件,儲存了當前請求的一些相關資訊,next 為呼叫後續的中介軟體,返回值是 Promise,這樣可以很方便的處理後置邏輯。(執行過程是個洋蔥模型)

配置格式

為了方便管理和使用中介軟體,框架使用的配置檔案來管理中介軟體,配置檔案為src/config/middleware.js

const path = require('path')
const isDev = think.env === 'development'

module.exports = [
  {
    handle: 'meta', // 中介軟體處理函式
    options: {   // 當前中介軟體需要的配置
      logRequest: isDev,
      sendResponseTime: isDev,
    },
  },
  {
    handle: 'resource',
    enable: isDev, // 是否開啟當前中介軟體
    options: {
      root: path.join(think.ROOT_PATH, 'www'),
      publicPath: /^\/(static|favicon\.ico)/,
    },
  }
]
複製程式碼

配置項為專案中要使用的中介軟體列表,每一項支援handleenableoptionsmatch等屬性。

handle

中介軟體的處理函式,可以使用系統內建的,也可以是外部匯入的,也可以是專案裡的中介軟體。

enable

是否開啟當前的中介軟體。

options

傳遞給中介軟體的配置項,格式為一個物件,中介軟體裡獲取到這個配置。

match

匹配特定的規則後才執行該中介軟體,支援兩種方式,一種是路徑匹配,一種是自定義函式匹配。

框架內建的中介軟體

框架內建了幾個中介軟體,可以通過字串的方式直接引用。

  • meta 顯示一些meta資訊。如:傳送ThinkJS版本號,介面的處理時間等
  • resource 處理靜態資源,生產環境建議關閉,直接用webserver處理即可
  • trace 處理報錯,開發環境將詳細的報錯資訊顯示處理,也可以自定義顯示錯誤頁面
  • payload 處理表單提交和檔案上傳,類似於koa-bodyparsermiddleware
  • router 路由解析,包含自定義路由解析
  • logic logic呼叫,資料校驗
  • controller controller和action

專案中自定義的中介軟體

有時候專案中根據一些特定需要新增中介軟體,那麼可以放在src/middleware目錄下,然後就可以直接通過字串的方式引用。

// middleware/log.js

const defaultOptions = {
  consoleExecTime: true // 是否列印執行時間的配置
}
module.exports = (options = {}) => {
  // 合併傳遞進來的配置
  options = Object.assign({}, defaultOptions, options);
  return (ctx, next) => {
    if(!options.consoleExecTime) {
      return next(); // 如果不需要列印執行時間,直接呼叫後續執行邏輯
    }
    const startTime = Date.now();
    let err = null;
    // 呼叫 next 統計後續執行邏輯的所有時間
    return next().catch(e => {
      err = e; // 這裡先將錯誤儲存在一個錯誤物件上,方便統計出錯情況下的執行時間
    }).then(() => {
      const endTime = Date.now();
      console.log(`request exec time: ${endTime - startTime}ms`);
      if(err) return Promise.reject(err); // 如果後續執行邏輯有錯誤,則將錯誤返回
    })
  }
}
複製程式碼

用法:在/src/config/middleware.js

module.exports = [
  {
    handle: 'log', // 中介軟體處理函式
    options: {   // 當前中介軟體需要的配置
      consoleExecTime: true,
    },
  }
]
複製程式碼

引入外部的中介軟體

引入外部的中介軟體非常簡單,只需要require進來即可。

const cors = require('koa2-cors');
module.exports = [
  ...,
  {
    handle: cors,
    option: {
      origin: '*'
    }
  },
  ...
]
複製程式碼

Controller(控制器)

MVC模型中,控制器是使用者請求的邏輯處理部分。比如:將使用者相關的操作都放在user.js裡,每一個操作就是裡面的一個Action。

建立controller

專案裡的controller需要繼承think.Controller類,這樣能使用一些內建的方法。當然專案中可以建立一些通用的基類,然後實際的controller都繼承自這個基類。

專案建立時會自動建立一個名為base.js的基類,其他的controller繼承該類即可。

Action執行

Action執行是通過中介軟體think-controller來完成的,通過ctx.action值在controller尋找xxxAction的方法名並呼叫,且呼叫相關的魔術方法,具體順序為:

  • 例項化 Controller 類,傳入 ctx物件

  • 如果方法 __before 存在則呼叫,如果返回值為false,則停止繼續執行

  • 如果方法xxxAction 存在則執行,如果返回值為 false,則停止繼續執行

  • 如果方法 xxxAction 不存在但 __call 方法存在,則呼叫 __call,如果返回值為 false,則停止繼續執行

  • 如果方法 __after 存在則執行前置操作__before

  • 如果方法 __after 存在則執行

前置操作 __before

專案中,有時候需要在一個統一的地方做一些操作,如:判斷是否已經登入,如果沒有登入就不能繼續後面行為。此種情況下,可以通過內建的 __before 來實現。

__before是在呼叫具體的 Action 之前呼叫的,這樣就可以在其中做一些處理。

如果類繼承需要呼叫父級的 __before 方法的話,可以通過 super.__before 來完成。

後置操作 __after

後置操作 __after__before 對應,只是在具體的 Action 執行之後執行,如果具體的 Action 執行返回了 false,那麼 __after 不再執行。

Logic

當在Action裡處理使用者的請求時,經常要先獲取使用者提交過來的資料,然後對其校驗,如果校驗沒問題後才能進行後續的操作;當引數校驗完成後,有時候還需要進行許可權判斷等,這些都判斷無誤後才能進行真正的邏輯處理。如果將這些程式碼都放在一個Action裡,勢必讓Action的程式碼非常複雜且冗長。

為了解決這個問題,ThinkJS在控制器前面增加了一層Logic,Logic裡的Action和控制器裡的Action一一對應,系統在呼叫控制器裡的Action之前會自動呼叫Logic裡的Action。

Router(路由)

當使用者訪問一個地址時,需要有一個對應的邏輯進行處理。傳統的處理方式下,一個請求對應的檔案,如訪問是/user/about.php,那麼就會在專案對應的目錄下有/user/about.php這個實體檔案。這種方式雖然能解決問題,但會導致檔案很多,同時可能很多檔案邏輯功能其實比較簡單。

在現在的MVC開發模型裡,一般都是通過路由來解決此類問題。解決方式為:先將使用者的所有請求對映到一個入口檔案(如:index.php),然後框架解析當前請求的地址,根據配置或者約定解析出對應要執行的功能,最後去呼叫然後響應使用者的請求。

由於Node.js是自己啟動HTTP(S)服務的,所以已經將使用者的請求彙總到一個入口檔案了,這樣處理路由對映就更簡單了。

在ThinkJS中,當使用者訪問一個URL時,最後是通過controller裡具體的action來響應的。所以就需要解析出URL對應的controller和action,這個解析工作是通過think-router模組來實現的。

路由配置

think-router是一個middleware,專案建立時已經預設加到配置檔案src/config/middleware.js裡了。

路徑預處理

當使用者訪問服務時,通過ctx.url屬性,可以得到初始的pathname,但是為了方便後續通過 pathname解析出controller和action,需要對pathname進行預處理。比如去除URL中.html字尾等操作,最後得到真正後續所需解析的pathname。預設的路由解析規則為/controller/action.

對於ThinkJS中的controller,可以不用寫router,也可以自定義router。

pathname 子集控制器 controller action 備註
/ index index controllller、action為配置的預設值
/user user index action為配置的預設值
/user/login user login
/console/user/login console/user login 有子集控制器console/user
/console/user/login/aaa/b console/user login 剩餘的aaa/b不在解析

自定義路由規則

雖然預設的路由解析方式能夠滿足需求,但有時候會導致URL看起來不夠優雅,我們更希望URL比較簡短,這樣會更利於記憶和傳播。框架提供了自定義路由來處理這種需求。

自定義路由規則配置檔案為src/config/router.js,路由規則為二維陣列。

非同步處理

Node.js 使用了一個事件驅動、非阻塞式 I/O 的模型,很多介面都是非同步的,如:檔案操作、網路請求。雖然提供了檔案操作的同步介面,但這些介面是阻塞式的,非特殊情況下不要使用它。

對於非同步介面,官方的 API 都是 callback 形式的,但是這種方式在業務邏輯複雜後,很容易出現callback hell 的問題,為了解決這個問題相繼出現了eventPromiseGenerator functionAsync function等解決方案,ThinkJS使用async function方案來解決非同步問題。

Async functions

Async functions 使用async/await語法定義函式,如:

async function fn() {
  const value = await getFromApi();
  doSomethimgWithValue();
}
複製程式碼
  • await 時必須要有 async,但有 async 不一定非要有 await
  • Async functions 可以是普通函式的方式,也可以是 Arrow functions 的方式
  • await 後面需要接 Promise,如果不是 Promise,則不會等待處理
  • 返回值肯定為 Promise

返回值和 await 後面接的表示式均為 Promise,也就是說 Async functionsPromise 為基礎。如果 await 後面的表示式返回值不是 Promise,那麼需要通過一些方式將其包裝為 Promise

模型/資料庫

關係型資料庫(MYSQL)

在專案開發中,經常需要運算元據庫(如:增刪改查等功能),手工拼寫 SQL 語句非常麻煩,同時還要注意 SQL 注入等安全問題。為此框架提供了模型功能,方便運算元據庫。

擴充套件模型功能

框架預設沒有提供模型的功能,需要載入對應的擴充套件才能支援,對應的模組為 think-model。修改擴充套件的配置檔案 src/config/extend.js,新增如下的配置:

const model = require('think-model');
module.exports = [
  model(think.app) // 讓框架支援模型的功能
];
複製程式碼

配置資料庫

模型由於要支援多種資料庫,所以配置檔案的格式為 Adapter 的方式,檔案路徑為 src/config/adapter.js

Mysql

Mysql 的 Adapter 為 think-model-mysql,底層基於 mysql 庫實現,使用連線池的方式連線資料庫,預設連線數為 1。

const mysql = require('think-model-mysql');
exports.model = {
  type: 'mysql',
  mysql: {
    handle: mysql, // Adapter handle
    user: 'root', // 使用者名稱
    password: '', // 密碼
    database: '', // 資料庫
    host: '127.0.0.1', // host
    port: 3306, // 埠
    connectionLimit: 1, // 連線池的連線個數,預設為 1
    prefix: '', // 資料表字首,如果一個資料庫裡有多個專案,那專案之間的資料表可以通過字首來區分
  }
複製程式碼

建立模型檔案

模型檔案放在 src/model/ 目錄下,繼承模型基類 think.Model,檔案格式為:

// src/model/user.js
module.exports = class extends think.Model {
  getList() {
    return this.field('name').select();
  }
}
複製程式碼

例項化模型

專案啟動時,會掃描專案下的所有模型檔案,掃描後會將所有的模型類存放在think.app.models 物件上,例項化時會從這個物件上查詢,如果找不到則例項化模型基類 think.Model。

CRUD 操作

think.Model 基類提供了豐富的方法進行 CRUD 操作,下面來一一介紹。

thinkjs.org/zh-cn/doc/3…

MongoDB

有時候關聯式資料庫並不能滿足專案的需求,需要 MongoDB 來儲存資料。框架提供了think-mongo 擴充套件來支援 MongoDB,該模組是基於mongodb 實現的。

配置MongoDB資料庫

MongoDB 的資料庫配置複用了關聯式資料庫模型的配置,為 adapter 配置,放在 model 下。檔案路徑為 `src/config/adapter.js`

exports.model = {
  type: 'mongo', // 預設使用的型別,呼叫時可以指定引數切換
  common: { // 通用配置
    logConnect: true, // 是否列印資料庫連線資訊
    logger: msg => think.logger.info(msg) // 列印資訊的 logger
  },
  mongo: {
    host: '127.0.0.1',
    port: 27017,
    user: '',
    password: '',
    database: '', // 資料庫名稱
    options: {
      replicaSet: 'mgset-3074013',
      authSource: 'admin'
    }
  }
}
複製程式碼

擴充套件MongoDB功能

修改擴充套件的配置檔案src/config/extend.js,新增如下的配置:

const mongo = require('think-mongo');

module.exports = [
  mongo(think.app) // 讓框架支援模型的功能
]
複製程式碼

新增完擴充套件後,會注入 think.Mongothink.mongoctx.mongocontroller.mongo 方法,其中 think.Mongo 為 Mongo 模型的基類檔案,其他為例項化 Mongo 模型的方法,ctx.mongocontroller.mongothink.mongo 方法的包裝。

建立模型檔案

模型檔案放在 src/model/ 目錄下,繼承模型基類 think.Mongo,檔案格式為:

// src/model/user.js
module.exports = class extends think.Mongo {
  getList() {
    return this.field('name').select();
  }
}
複製程式碼

例項化模型

專案啟動時,會掃描專案下的所有模型檔案(目錄為 src/model/),掃描後會將所有的模型類存放在 think.app.models 物件上,例項化時會從這個物件上查詢,如果找不到則例項化模型基類 think.Mongo

API

thinkjs.org/zh-cn/doc/3…

think物件

框架中內建 think 全域性物件,方便在專案中隨時隨地使用。

API

thinkjs.org/zh-cn/doc/3…

啟動自定義

當通過 npm start或者node production.js 來啟動專案時,雖然可以在這些入口檔案裡新增其他的邏輯程式碼,但並不推薦這麼做。系統給出了其他啟動自定義的入口。

bootstrap

系統啟動時會載入 src/boostrap/ 目錄下的檔案,具體為:

  • Master 程式下時載入 src/bootstrap/master.js
  • Worker 程式下時載入 src/bootstrap/worker.js

所以可以將一些需要在系統啟動時就需要執行的邏輯放在對應的檔案裡執行。

Service / 服務

Service 檔案存放在 src/service/目錄下,檔案內容格式如下:

//  src/service/user.js
module.exports = class extends think.Service {
  find(id){
     return {username:'123',id:id} 
  }
}
複製程式碼

Service 都繼承 think.Service基類,但該基類不提供任何方法,可以通過 Extend 進行擴充套件。

例項化Service類

可以通過 think.service 方法例項化Service 類,在控制器、ctx 也有對應的 service 方法,如:ctx.servicecontroller.service,這些方法都是 think.service 的快捷方式。

//controller

think.service('user').find(111)
複製程式碼

專案啟動時,會掃描專案下所有的 services 檔案,並存放到 think.app.services 物件下,例項化時會從該物件上查詢對應的類檔案,如果找不到則報錯。

以上就是對該框架的基本認識,如果是新入手該框架,那麼瞭解了src下的基本配置,包括如何新增資料庫的介面卡(adapter)同時擴充套件模型(extend),之後在model層進行資料庫的操作,controller層進行前後臺互動便可以實現介面(api)功能,之後的進階就需要更加深入的學習了。

ThinkJS入門+例項(實現認證許可權等基本功能)

專案原始碼:github.com/mfnn/thinkj…

注意:該專案使用的是mongoDB資料庫。 專案基本功能介紹:

1.獲取前臺請求頭(token),實現使用者身份驗證

//   controller/base.js
const jwt = require('jsonwebtoken');
const Token = require('../logic/token');

module.exports = class extends think.Controller {
    async __before() {
        if (this.ctx.config('allowUrls').indexOf(this.ctx.url) === -1) {
            if (!this.ctx.request.header.authorization) {
                this.fail(401, '沒有認證');
                return false;
            } else {
                let payload = null;
                const authorization = this.ctx.request.header.authorization;
                const secret = this.ctx.config('secret');
                try {
                    payload = jwt.verify(authorization, secret); 
                     // 該驗證函式在logic/token
                    await Token.verify(authorization);
                    this.ctx.state.user_id = payload._id;
                } catch (error) {
                    this.fail(error.code, error.message);
                    return false;
                }
            }
        }
    }
};
複製程式碼

2.設定token,存入redis,設定過期時間

//controller/user.js
 // 使用者登入
    async loginUserAction() {
        const user = await this.mongo('user').loginUser(this.post('account'), this.post('password'));
        if (think.isEmpty(user)) {
            this.fail(403, '登陸失敗,使用者名稱或密碼錯誤');
        } else {
            let payload = {_id: user._id, account: user.account, password: user.password};
            let token = jwt.sign(payload, think.config('secret'), {expiresIn: 60 * 60 * 24 * 30});
            redis.set(token, payload._id.toString());
            redis.expire(token, token_expire);
            return this.success({token}, '使用者登陸成功');
        }
    }
複製程式碼

3.實現wamp實時推送訊息

//controller/wamp.js
const autobahn = require('autobahn');
const wampConfig = require('../config/config').wamp;
const options = wampConfig.options;

module.exports = {
    roomInfo: (args) => {
        const connection = new autobahn.Connection(options);
        console.log("連線資訊",connection);
        connection.onopen = function (session) {
            session.publish(wampConfig.definedTopic, [args]);
            console.log("wamp釋出的主題是:" + wampConfig.definedTopic);
            console.log(args);
        };
        console.log("end======");
        connection.open();
    }
};
複製程式碼
 //使用
    /**
     * @param {any} user
     * @returns
     * 新增房屋資訊後推送wamp確認訊息
     */
    async addRoomWamp(roomInfo) {
        let sum = 0;
        const rooms = await this.model('room').add(roomInfo);
        if(!(think.isEmpty(rooms))){
            const data = {sum: "lalal"};
            wamp.roomInfo(data);
        }
    }

複製程式碼

4.身份許可權驗證

  //獲取所有房源資訊
    async getAllRoomsAction() {
        const userInfo = await this.mongo('user').findUserDetailInfo(this.ctx.state.user_id);
        console.log("userInfo", userInfo);
        if (!(think.isEmpty(userInfo)) && userInfo.role === 'admin') {
            this.success(await this.mongo('room').getAllRooms());
        } else {
            this.fail("沒有許可權訪問");
        }

    }
複製程式碼

(實現方式是進項使用者角色判斷,可以使用acl,之後專案會進行更新)

ThinkJS入門+例項(實現認證許可權等基本功能)

相關文章