# ThinkJS入門+實例(實現認證權限等基本功能)
這是一篇關于ThinkJS框架的文章,因為網上對于該框架的介紹非常少,所以在這里通俗講解一下自己對該框架基本的認識并且提供了一個練習基本功能的項目。
因為這是基于Node.js的框架,所以先帶新手入門一點Node.js的知識。
# Node.js簡述
Node.js:簡單的說Node.js就是運行在服務端的JavaScript。
[Node.js安裝配置](https://link.juejin.im/?target=http%3A%2F%2Fwww.runoob.com%2Fnodejs%2Fnodejs-tutorial.html)(介紹在window和Linux上的安裝Node.js的方法)
[Node.js官方文檔](https://link.juejin.im/?target=http%3A%2F%2Fnodejs.cn%2F)
官方文檔介紹:
* Node.js是一個基于Chrome V8引擎的JavaScript運行環境。
* Node.js使用了一個**事件驅動、非阻塞式I/O的模型**,使其輕量又高效。[。事件驅動與異步IO](https://link.juejin.im/?target=https%3A%2F%2Fblog.csdn.net%2Fm0_37886429%2Farticle%2Fdetails%2F78292300)
* 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端口。函數通過`request`,`response`參數來接收和響應數據。實例如下:
~~~
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官方文檔](https://link.juejin.im/?target=http%3A%2F%2Fexpressjs.jser.us%2F)
**Koa**:由Express原班人馬打造,致力于成為web應用和API開發領域中的一個更小、更富有表現力、更健壯的基石。通過利用async函數,koa幫你丟棄回調函數,并有力的增強錯誤處理。[Koa官方文檔](https://link.juejin.im/?target=https%3A%2F%2Fkoa.bootcss.com%2F)
Express和Koa是node.js最基礎的兩個后端框架。因為構建一個app仍需要些很多腳手架代碼,于是在他們基礎上出現了很多其他框架來減少編寫這類代碼。(例如:ThinkJS,egg.js等)
# ThinkJS
介紹:ThinkJS是一款面向未來開發的Node.js框架,整合了大量的項目最佳實踐,讓企業級開發變得簡單、高效。從3.0開始,框架底層基于Koa2.x實現,兼容Koa的所有功能。
特性:
* 基于Koa2.x,兼容`middleware`
* 內核小巧,支持`Extend`、`Adapter`等插件方式
* 性能優異,單元測試覆蓋程度高
* 內置自動編譯、自動更新機制、方便快速開發
* 使用更優雅的`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.config`和`controller.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.js`和`adapter.production.js`存放有差異的配置,系統啟動后會讀取對應的運行環境配置和默認配置進行合并。
Adapter配置解析
Adapter 配置存儲了所有類型下的詳細配置,具體使用時需要對其解析,選擇對應的一種進行使用。比如上面的配置文件中,配置了`nunjucks`和`ejs`二種模板引擎的詳細配置,但具體使用時一種場景下肯定只會用其一種模板引擎。當然,配置解析并不需要使用者在項目中具體調用,一般都是在插件對應的方法里已經處理。
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 里引入了擴展機制,方便對框架進行擴展。支持的擴展類型為:`think`、`application`、`context`、`request`、`response`、`controller`、`logic`和`service`。
框架內置的很多功能也是擴展來實現的,如:`Session`、`Cache`。
**Context(上下文)**
Context是Koa中處理用戶請求中的一個對象,貫穿整個請求生命周期。一般在`middleware`、`controller`、`logic`中使用,簡稱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`參數,其中`ctx`為`context`的簡寫,是當前請求生命周期的一個對象,存儲了當前請求的一些相關信息,`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)/,
},
}
]
復制代碼
~~~
配置項為項目中要使用的中間件列表,每一項支持`handle`,`enable`,`options`,`match`等屬性。
`handle`
中間件的處理函數,可以使用系統內置的,也可以是外部導入的,也可以是項目里的中間件。
`enable`
是否開啟當前的中間件。
`options`
傳遞給中間件的配置項,格式為一個對象,中間件里獲取到這個配置。
`match`
匹配特定的規則后才執行該中間件,支持兩種方式,一種是路徑匹配,一種是自定義函數匹配。
框架內置的中間件
框架內置了幾個中間件,可以通過字符串的方式直接引用。
* `meta`顯示一些meta信息。如:發送ThinkJS版本號,接口的處理時間等
* `resource`處理靜態資源,生產環境建議關閉,直接用webserver處理即可
* `trace`處理報錯,開發環境將詳細的報錯信息顯示處理,也可以自定義顯示錯誤頁面
* `payload`處理表單提交和文件上傳,類似于`koa-bodyparser`等`middleware`
* `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`的問題,為了解決這個問題相繼出現了`event`、`Promise`、`Generator``function`、`Async 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 functions`以`Promise`為基礎。如果`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…](https://link.juejin.im/?target=https%3A%2F%2Fthinkjs.org%2Fzh-cn%2Fdoc%2F3.0%2Frelation_model.html)
## 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.Mongo`、`think.mongo`、`ctx.mongo`和`controller.mongo`方法,其中`think.Mongo`為 Mongo 模型的基類文件,其他為實例化 Mongo 模型的方法,`ctx.mongo`和`controller.mongo`是`think.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…](https://link.juejin.im/?target=https%3A%2F%2Fthinkjs.org%2Fzh-cn%2Fdoc%2F3.0%2Fmongo.html)
## think對象
框架中內置`think`全局對象,方便在項目中隨時隨地使用。
API
[thinkjs.org/zh-cn/doc/3…](https://link.juejin.im/?target=https%3A%2F%2Fthinkjs.org%2Fzh-cn%2Fdoc%2F3.0%2Fthink.html)
啟動自定義
當通過`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.service`、`controller.service`,這些方法都是`think.service`的快捷方式。
~~~
//controller
think.service('user').find(111)
復制代碼
~~~
項目啟動時,會掃描項目下所有的`services`文件,并存放到`think.app.services`對象下,實例化時會從該對象上查找對應的類文件,如果找不到則報錯。
以上就是對該框架的基本認識,如果是新入手該框架,那么了解了src下的基本配置,包括如何添加數據庫的適配器(adapter)同時擴展模型(extend),之后在model層進行數據庫的操作,controller層進行前后臺交互便可以實現接口(api)功能,之后的進階就需要更加深入的學習了。

項目源碼:[github.com/mfnn/thinkj…](https://link.juejin.im/?target=https%3A%2F%2Fgithub.com%2Fmfnn%2Fthinkjs)
注意:該項目使用的是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,之后項目會進行更新)
- 內容介紹
- EcmaScript基礎
- 快速入門
- 常量與變量
- 字符串
- 函數的基本概念
- 條件判斷
- 數組
- 循環
- while循環
- for循環
- 函數基礎
- 對象
- 對象的方法
- 函數
- 變量作用域
- 箭頭函數
- 閉包
- 高階函數
- map/reduce
- filter
- sort
- Promise
- 基本對象
- Arguments 對象
- 剩余參數
- Map和Set
- Json基礎
- RegExp
- Date
- async
- callback
- promise基礎
- promise-api
- promise鏈
- async-await
- 項目實踐
- 標簽系統
- 遠程API請求
- 面向對象編程
- 創建對象
- 原型繼承
- 項目實踐
- Classes
- 構造函數
- extends
- static
- 項目實踐
- 模塊
- import
- export
- 項目實踐
- 第三方擴展庫
- immutable
- Vue快速入門
- 理解MVVM
- Vue中的MVVM模型
- Webpack+Vue快速入門
- 模板語法
- 計算屬性和偵聽器
- Class 與 Style 綁定
- 條件渲染
- 列表渲染
- 事件處理
- 表單輸入綁定
- 組件基礎
- 組件注冊
- Prop
- 自定義事件
- 插槽
- 混入
- 過濾器
- 項目實踐
- 標簽編輯
- 移動客戶端開發
- uni-app基礎
- 快速入門程序
- 單頁程序
- 底部Tab導航
- Vue語法基礎
- 模版語法
- 計算屬性與偵聽器
- Class與Style綁定
- 樣式與布局
- Box模型
- Flex布局
- 內置指令
- 基本指令
- v-model與表單
- 條件渲染指令
- 列表渲染指令v-for
- 事件與自定義屬性
- 生命周期
- 項目實踐
- 學生實驗
- 貝店商品列表
- 加載更多數據
- 詳情頁面
- 自定義組件
- 內置組件
- 表單組件
- 技術專題
- 狀態管理vuex
- Flyio
- Mockjs
- SCSS
- 條件編譯
- 常用功能實現
- 上拉加載更多數據
- 數據加載綜合案例
- Teaset UI組件庫
- Teaset設計
- Teaset使用基礎
- ts-tag
- ts-badge
- ts-button
- ta-banner
- ts-list
- ts-icon
- ts-load-more
- ts-segmented-control
- 代碼模版
- 項目實踐
- 標簽組件
- 失物招領客戶端原型
- 發布頁面
- 檢索頁面
- 詳情頁面
- 服務端開發技術
- 服務端開發環境配置
- Koajs快速入門
- 快速入門
- 常用Koa中間件介紹
- 文件上傳
- RestfulApi
- 一個復雜的RESTful例子
- 使用Mockjs生成模擬數據
- Thinkjs快速入門
- MVC模式
- Thinkjs介紹
- 快速入門
- RESTful服務
- RBAC案例
- 關聯模型
- 應用開發框架
- 服務端開發
- PC端管理界面開發
- 移動端開發
- 項目實踐
- 失物招領項目
- 移動客戶端UI設計
- 服務端設計
- 數據庫設計
- Event(事件)
- 客戶端設計
- 事件列表頁面
- 發布頁面
- 事件詳情頁面
- API設計
- image
- event
- 微信公眾號開發
- ui設計規范