🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# 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)功能,之后的进阶就需要更加深入的学习了。 ![](https://user-gold-cdn.xitu.io/2018/10/14/16671a6999ee80a7?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) 项目源码:[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,之后项目会进行更新)