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

這是一篇關於ThinkJS框架的文章,由於網上對於該框架的介紹很是少,因此在這裏通俗講解一下本身對該框架基本的認識而且提供了一個練習基本功能的項目。 php

由於這是基於Node.js的框架,因此先帶新手入門一點Node.js的知識。css

Node.js簡述

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

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

Node.js官方文檔mysql

官方文檔介紹:nginx

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

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

當咱們在Node.js上開發時,會用到不少別人寫的JavaScript代碼。若是咱們須要使用別人寫的某個包,每次都根據名稱搜索一下官方文檔,下載代碼,解壓,再使用,很是繁瑣。因而一個集中管理的工具應運而生:你們都把本身開發的模塊打包後放到npm官網上,若是要使用,直接經過npm安裝就能夠直接使用,無論代碼存在哪,應該從哪下載。github

更重要的是,若是咱們要使用模塊A,而模塊A又依賴於模塊B,模塊B又依賴於其餘的模塊,那麼npm能夠根據依賴關係,把全部依賴的包都下載下來並管理起來。不然,靠咱們本身手動管理,確定是麻煩又容易出錯。web

第一個Node程序

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

  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)功能,以後的進階就須要更加深刻的學習了。

項目源碼: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,以後項目會進行更新)

相關文章
相關標籤/搜索