hi, 欢迎回来,那么这一节呢,我们上来就先简单地尝试一下用 nest js 创建一个项目,它到底是怎样的一个体验。 那作为对比的话呢,我们先来回顾一下我们之前学到的 express, 它构建一个后端应用或者是接口的一个过程。 express 的 构建呢,其实非常的简单,可以看到它的官网给了这样一个简单的示意,我们只需要把这样一段代码复制一下,来到我们 自己的 ide 中啊,这里当前的课程呢,我之后都会以 vsco 的 为例,我新建一个文件夹 express app, 新建一个 js 文件 直接粘贴,然后需要注意这里的 express, 它是通过一块儿来引入的,所以呢,我们需要一个包管理器来安装这样一个依赖,所以呢,一般来说,我们都会选择打开终端,用我们的包管理器,不管是 npm 或者是 npm 来着手划一个项目,这里我用 npm init, 然后呢安装我们的 express, ok, 就 这么简单,那接下来我们直接使用 node 就 可以运行我们的 index js 了。好,那此时呢,我们可以另外打开一个终端,在这个终端中呢,我们去请求这样一个接口, c u r l, 这是一个命令行工具,那么我们的这样一个接口呢,它会默认在我们整个项目的根目录,然后端口呢是三千,所以呢,我们请求的地址就是 local host 的 端口三千,然后目录是根目录。好,我们来到 c u r l h d d p local host 的 三千啊,因为是根目录,所以呢,后面这个路径我们就不写了,直接回车, 可以看到返回的就是我们这里的 appget, 它的第二个参数,这里所定义的一个路由处理器,也就是这里的这个秘密函数,它返回的这个内容,它返回了一个 hello world, 看起来非常的简单,那我们的 nest 这次它它又是怎么实现的呢?我们来到刚才的终端啊,把我们的这个应用暂停应用掉。 那 nest js 呢?它创建的时候呢,有专门的工具,我们需要来到 nest js 的 官网,那这就是 nest js 的 官网,要认准它的网址 nest js 点 com, 我 们来到它的文档 documentation, 来到这里的 installation。 那么在安装或者说创建一个项目的时候呢,它推荐我们使用这样一个叫做 nest c l i 的 工具,那这个工具呢,用来给我们创建项目以及在整个 nest js 项目的开发过程中所需要我们 经常使用的这样一个工具。之后呢,我们会学习非常多的关于 ness 的 j s c l i 的 一些用法。所以呢,我们需要先在我们本地的电脑上通过我们的包管理器全局安装这样一个 ness 的 j s c l i。 注意是全局安装,那这里呢,我们只需要复制这个命令,把前面的 n p m 替换成你想要使用的包管理器就可以了。啊,这里我是通过 p n p m 已经安装过了。 那简单看一下它的版本, nest version, 现在的最新版应该是十一点零点一六,那这个工具该怎么使用呢?回到我们的官网,那使用的过程呢?其实非常的简单,它提示我们需要使用一个命令,叫做 nest new, 我们直接复制这个命令,或者是参照它上面所说的 next new, 后面加上我们这个项目的名字就可以了,那我们直接使用 next new 把这个终端呢稍微放大一点, next new, 啊,不对,我要来到上一个文件夹,不然的话和我们的 express app 会打架。 运行这个 c l i 命令之后呢,它会弹出一些简单的交互。首先呢是问我们我们这个项目名字叫什么,这里我们就写作 nest js demo, 选择我们的包管理器,可以看到它目前还没有把 boom 给加进去啊,我们选择屁眼片,这是我个人比较喜欢的一款 包管理器啊。然后呢就这么简单它就开始创建了,可以看到它创建了非常多的东西,包括我们的 package json, 还有各种各样的配置,比如这里的 type script 的 相应的配置。因为 nest 的 js 项目在创建的过程中呢,默认我们会使用 type script 而不是 javascript。 当然如果你想的话,也可以使用 javascript 来创建一个 nest 的 js 项目,但是我个人非常的不推荐这样做。那创建好了之后呢,我们只需要进入这个目录,然后呢通过包过滤器运行这个 start 命令,就可以直接运行我们的 nest 的 js 项目了。我们来看一下它创建的这个项目啊, 可以看到东西非常的多,我们把不必要的东西全部都给它删掉,这里的 excel, 还有这里的 excel 编辑。 然后呢来到 package jason, 我 们来看一下它的项目依赖啊,那么最核心的项目依赖其实就这么三个东西啊,这里的 nest just common, nest just core, 还有这里的 nest just platform express。 为什么是 nest js platform express 而不是单纯的 express 呢?这是因为我们的 nest js, 它对我们的 express 以及 fastify 做了一些封装,并不是单纯的引入了 express 这样一个编程库,而是在它的基础上呢,做了一些改动。然后下面这些开发依赖就不用说了,和 yes link 相关的我们暂时用不到,我们全部都可以给它删掉。 当然你不删也没关系啊,只是待会儿可能会有一些错误提示,会比较的麻烦,好全部删掉。然后它用到的测试工具呢,是 jest, 我们同样可以给它删掉,我们暂时也用不到,那么它运行我们的 type script 项目所用到的项目依赖是 t s node 啊,在之前我的 node js type script 教程中我说过啊, t s node 到现在的话呢,它的发展是已经 停滞了,对于新版本的 note j s 的 支持呢,不是特别好,但是呢,它又是目前为止第三方的 tab script 的 运行器中,对于装饰器来说支持最好的一个运行器。所以呢,我们暂时没有办法很好地去替代它, 就算我们想用 t s x 来替代它,也不是很现实。 ok, 那 么剩下的东西呢,我们就先不讲,讲太多的话也没什么意义啊,我们把这些 yes link 相关的东西删掉就可以了。然后呢,它的项目依赖它是自动给我们安装好的,我们来到终端 next to this demo, 我 重新安装一下,把 yesterday 相关的给它删除出去。那么运行的话很简单,我们看一下它的这个脚本啊,脚本的话其实就是和这里的 start 相关的这样四个,那么我们平时用的就是 start, 还有 start def, 那 么 startev 它的区别就是后面加了一个 watch, 也就是有这样一个热更新的概念,检测到我们的文件发生修改,它就自动进行更新。我们在部署的时候呢,它是需要运行 distance 目录下面的这样一个 main 文件的。那正常来说的话,我们在 通过 start 命令或者是 start dev 命令运行的时候呢,它其实都是会把所有的这样一些项目中,也就是 s r c 下面的这样一些贴纸文件给它翻译成 javascript 的 文件的。我们来试一下 p n p m start, 可以看到它在我们的项目目录下呢,创建了一个 disk 目录,创建的文件呢,基本上都是一些 type script 的 类型文件,还有一些 javascript 的 文件,那我们要运行的就是这样一个 may js ness js 项目的部署其实很简单,就是通过构建 把 type script 编辑成 javascript 之后呢,再去运行 disk 目录下面的 make js, 就 可以启动我们整个项目了,或者说部署我们整个项目了。那我们来看一下当前这个项目它创建出来到底实现了怎么怎样的一个功能啊?在 s a c 下面呢,就是我们这个 nest js 项目所主要 提供的这样一些文件。首先呢是 main t s, 那 么 main t s 的 这个写法呢?其实啊,它就是基于我们的 express 创建这个服务器的过程呢,它做了一层封装,封装了一个叫做 bootstrap 的 一个函数, 其实里面的内容可以看到和我们的 express 是 大差不差的啊。我们来到 express app, 来到这里的 index js, 再和我们的 main t s 做一下对比啊,可以看到大家其实都是 通过一个所谓的函数创建了一个 app, 或者说一个服务器对象实体,那这里呢,是通过 express, 那 nest 这里呢是通过一个 nest factory create 啊,这个方法看起来复杂一点,但是实际上调用的时候呢,大家都是 app listen 啊,这里也是一样, app listen, 甚至传的第一个参数啊,都是我们的端口,只是呢, nest 这里呢做了一层封装,所以呢,它们两个其实差不多啊,那实际上定义我们的接口,还有 我们的这个路由处理器的地方呢,在 express 这里呢,我们是通过这样一个 app 实例调用 get, 然后呢传入对应的路由以及对应的路由处理函数。那 nest 这是怎么做的呢?哎,我们回看一下 s r c, 可以 发现它这里有四个以 app 开头的文件, 那么为了让这四个文件有一定的区别,我推荐各位在 vsco 中设置一下你的文件图标主题,来到这里的齿轮, 找到这里的 things, 找到这里的 file icon thing, 推荐你去下载这样一个叫做 vsco 的 这样一个文件图标主题,这样的话,它就能够以一定的色彩差距来给你 体现出这样三种不同的类似 js 文件的这样一个差距。你可以直接在这里的扩展商店去搜索 vscoord icons, 然后把它安装下来,并设置成你的文件图标主题就可以了。不然的话,在我们之后的学习过程中呢,你可能会 非常难以去辨别这么三种不同的文件,有了图标主题之后呢,你就很好辨别了,那这里的四个文件呢,一个是测试文件,也就是这里的 specs 点 ts 或者是 test 点 ts, 它都会呈现出这样一个试管状的一个图标, 这就是一个测试文件,那测试呢,我们就不讲,我们之后会单独开一个部分来讲,那剩下三个文件呢?一个是 module, 一个是 service, 一个是 controller。 之前我们在学习 express 的 时候呢,我们也有把它分为 service 和 controller, 那 这里的 service 和 controller 呢,其实是一个意思啊,只不过可以发现 它们好像都和我们的 class 类做了一些强行的绑定。我们之前在学习 express, 将它划分成不同的 service 和 controller 的 时候呢,我们都是把它单独的写成一些函数,然后导出出来。 而我们的 nest 的 js 中呢,是把这样一些函数给它强制性的放在了一个类里面,然后呢,基于这样一个类进行绑定,或者是进行操作和组合,并且这里还多了一些 注解的写法,而且看起来注解还不少,对吧?比如这里的 controller, 它有 controller 注解,它有 get 注解,还有这里的 service, 它有一个 injectable 注解。但实际上呢,大家先不要被这些注解所迷惑啊,其实它实现的功能很简单, 它写了一个 service 文件,里面就只有一个 service 函数叫做 get hello, 然后呢,它把这个 get hello 用在了我们的 controller 这个函数里的这样一个叫做 get hello 的是路由处理器中,这个路由处理器调用了这样一个 service 类下面的 get hello 这样一个方法,所以呢,这个路由处理器它会在匹配到相应的路由请求之后呢, 调用这个 get hello 方法,然后呢返回这样一个 hello world 字母串。那么具体它是怎么匹配相应的路由的呢?这里它就是利用这里的这个装饰器来实现的。这里路由的写法呢,它是从我们之前的这样一个函数参数的部分给它 抽象到了我们的装饰器上,也就是之前我们需要通过 app get 写在这里,把它作为参数写成一个自创。而在我们的 nest 的 js 中呢,它就需要在 controller 文件中写在这个注解。当然同样是写这个自创,我们后面会做进一步的演示。那有了这样一个概念之后呢,我们来运行这个项目 p m p m start。 那 么运行过程中呢,它会打印非常多的东西,这些东西呢,都是基于 nest, 这是自带的这样一个日制工具进行打印的,它会显示这个项目的运行的这样一个基本的状态,比如哪些模块 依赖被出示化了,然后呢,哪些东西被检测到了,以及对应的路由地址,比如这里它检测到了我们 定义了一个跟路径下面的 get, 请求对应的一个路由处理器。最后呢,这些全部的检测完成之后,它告诉我们整个 ness 的 js 应用,它就成功地启动了,我们这里没有做任何的配置。所以呢,我们的路由就和我们之前的这个 express 项目一样,它都是 在根目录下面对应的这个端口中去定义了一个路由以及路由处理器。那么对应的端口呢?啊,它同样是三千,因为我们这里没有写对应的配置文件。那请求过程呢,也和刚才一样, c u r l h d d p local host 三千请求同样返回一个 hello world。 那 么相应的,如果我们换一个运行方式,我们来到 package json p n p m start 这里,我们换用 dev 这个命令,它就会以 我们的自动热存载的方式进行启动。那为什么要以这样的方式启动呢?因为我们要修改一下 service 里面的内容。正常来说的话,我们刚才请求的是 hello world, 那 此时我把这里的内容呢改一下,改为 hello next g s。 那 我直接点到这里的终端啊,可以看到终端这里它检测到我们的文件进行了修改,所以呢,它开始做一些增量的重新变异,也就是它一旦检测到这里的文件发生了变化,那么它会自动地对 发生改变的这个文件呢进行重新的翻译,也就是把这里的 type script 文件重新翻译成 disk 目录下面对应的 js 文件,并且更新相应的这个 type script 的 对应的类型定义。然后呢,再重新启动我们整个项目,并且可以看到速度是非常快的。好的,那此时我们再请求同一个这样一个端点, 可以看到访问的内容呢,就发生了变化啊。那么到此呢,相信各位通过我这一系列的演示呢,大致了解了我们的 ness 的 jess 它是怎样工作了的。当然我知道各位有很多细节还不太理解,比如这里明明没有创建一个 app service 这样一个 实力,却能够在 get hello 这样一个方法中去使用这个实力呢?我们只是在 constructor 这个构造函数中声明了一下,我们并没有实际的去创建它,为什么却能够使用它呢?看起来非常的奇怪,以及这里的这个注解,它到底实现了怎样的作用?还有这里的 service, 这里的注解,它又实现了怎么样的作用呢?我知道大家的疑问非常的多啊,但是各位先 简单的跟着我的思路过一遍 nas 的 js 它大致的一个流程是怎样的,我们后面呢再给各位慢慢的解答。那至少从目前来说,大家可以发现,其实我们的 nas 的 js 好 像就是基于我们的 express 项目做了一些比较复杂的封装,比如它创建这样一个项目的过程呢, 就从这里的 app express 还有 app listen 给它抽象到了这样一个叫做 bootstrap 的 函数种,而我们定义这里的路由和对应的这个路由处理器的这个过程呢,它就给它放在了 app controllers 中,并且还单独的把逻辑抽象在了这样一个 app service 文件中。所以呢, ness 的 js 其实没有大家想象的那么难,只是呢,大家需要了解一下我们之前编辑 express 项目的这样一个流程,你才能知道 ness 的 js 它为什么要这么做。如果你连 express 这个项目它是怎样工作,怎样创建,怎样定义路由都不知道的话呢?那么想理解 next jason 自然是比较困难的。那么以上就是本期视频的所有内容,希望你能关注或定义我的频道,感谢你的观看,我们下期视频再见!
粉丝1482获赞7108

hi, 欢迎回来,那么本节呢,我们就来实际的将我们写好的这样一些教学规则呢,应用在我们的端点中,也就是 加上我们的 guard 还有对应的装置器。但是呢,开始之前呢,我先补充一下上一节我讲的不是很清楚的一点,上一节呢,我们提到一个问题,在进行这个 j w t 的 加密和比对的时候呢,上一节我们发现呢,如果我们在 service 里面进行比对的过程中,我们 在 refresh 这个地方,如果我们使用这个 beecrap 进行 token 的 加密和比对的话,会发现不管我们的 token 是 新的还是旧的,它在比对的过程中呢,都是会判断我们这个 token 是 和我们数据库中的,这 和 token 是 一致的,但明明每次我们登录之后呢,我们都是会把这个数据库中存储的 refresh token 给它更新的,就是这里的 issue token 这个地方,明明我们是会把它更新的,但为什么我们在比对的时候,它每次 都会展示说这样一个结果,它是为 true 的 呢?那上期呢,我说这个是 becret 的 问题,它在比对的时候呢,会截取前面的一小段,这样的话就导致我们的 token 它 我们的 token 使用相同的这个密钥之后呢,它前面的内容都是一致的,它只有后面的内容不一致,而我们的 bekrap 呢,恰恰就截取了前面一致的内容来进行比对。 但是为什么会发生这样的情况呢?具体的这个官方文档指引在哪个地方呢?我们就来到 npmx, 找到 bekrap 它的页面,我们去搜索,我们直接搜索 security issues, 也就是所谓的安全问题。在下面我们能找到这个部分,它叫做 security issues and concerns。 注意,在这个地方,它明确地告诉我们什么呢? per beecrapped implementation only the first seventy two byte of the stream are used。 直接就告诉我们,每次这个 beecrapped 在 进行相应操作的时候,只有前面的 七十二个字节的字母串会被用到,那么多余的字母串呢,全部都会被忽略掉,也就是这里的 any extra byte i ignore when matching password。 这里它举的例子是 密码的这个例子。所以呢,我们在比对这个字母串的时候,尤其是使用 bigrib 进行比对的时候呢,千万千万记得不要比对太长的这样一个字母串,它默认是不会把 所有长度的自作串都进行比对和校验运算的,而恰恰我们的 jason web token 它就是非常长的,所以呢,就导致使用 becrafted 进行比对的时候,它只会比对前面 相同的七十二个字节的字母,后面不同的字母呢,恰恰没有比对到。所以呢,为了解决这个问题,我们就必须自己去实现另一个方式的这样一个功能。当然,我相信各位应该是第一次用到 note j s 内置的这样一个加密库,本身我们去使用 b c 就是 因为它内置的这样一个加密库呢,我们使用起来是比较的麻烦的,所以呢,这个部分可能很多人觉得写起来比较的累,所以呢,这个地方你也可以选择一些第三方库来替代,比如什么 crypto g s 这种工具, 它也可以实现我们的上二五六,那这里呢,本着最小依赖原则的原因呢,我们还是使用 note j s 内置的这样一个 crypto 的 这样一个 hash 算。那么解决了这个问题之后呢,我们接下来 来创建对应的 guard, 还有 decorator 来应用我们的这样一套健全规则。那么还记得我们的 guard 该怎么创建和使用吗?我们直接使用 nest g guard 来创建一个叫做 os 对 应的 guard 的 文件就在这里。 os guard, 那 具体的内容呢?我们直接来到 nest g s 它的官网, 找到 security authentication, 右边找到这里的 implementing the authentication guard, 我 们把这里 guard 具体的内容呢给它复制粘贴下来就好了,直接复制粘贴到这里,然后我们去导入缺失的内容。 而下面这里的报错呢,是因为我们这里的 request 这样一个类型,我们要从我们的 express 中去导入,导入之后呢,它才能够从我们的这个 request 的 请求中获取到我们的 authorization。 这么改了之后呢,很明显,我们待会传递我们 token 的 时候呢,就必须把它放在我们的请求头里面,而不能像现在这样给它放在我们的 post 的 请求的这个 body 中,这才是放在请求头中呢,才是最合规范的写法。然后呢,还有一点要注意啊,大家能够发现,在 官方给我们的模板代码中呢,它在这里其实就已经完成了 j w t 的 校验,所以呢,待会如果我们还需要校验的地方呢,我们可以 适当的给它免去。而且这个地方的校验呢,其实缺少一些设置啊,它默认是没有传递这个校验使用的这个密钥的。我们要手动地给它写进去。这里写上 secret, 传入我们的 process in v。 我 们这里应该是 j w t access token access secret。 然后呢,它把这个 user 直接给我们附在了请求的 请求体中。注意,是请求体中,不是请求头中。待会呢,我们可以在 controller 中直接去使用它,这也是一个非常便利的功能。待会呢,我们也可以在 service 中减去相应的代码。那这样写好之后呢,我 把多余的导入给它去掉。我们把这个盖应用在我们的 model 中。啊,还记得怎么写吗? model 写之前,我先把这个盖单独的放在一个文件夹内。 guards 来到我们的 os module, 在 provider 这里,我们先写上 provide。 provide 就 来自类似,这是内置的这样一个 app guard, 我 们要注册这样一个全职的 guard。 然后呢,它对应的类就是我们的 os guard, 就这么简单。然后呢,我们要创建对应的这样一个装置器,因为我们部分的这样一些接口,它应该是公开的,对吧?它不可能每一个接口都应该是受保护的。我们来看一下哪些是公开的。退出登录肯定是要求你在登录的情况下才能够访问,所以呢,这个接口是 需要得到保护的。 refresh 呢,很明显是我们的 refresh token 失效的情况下,那这种情况下呢,用户肯定是没有登录的。在这种情况下呢,很明显, 它应该是作为一个游客身份或者是没有登录的情况下来访问这个端点的。那这种情况下,我们的端点就应该是不被保护的情况,所以呢,这个应该是 public, 那 这里的 sign in 还有 sign up, 自然而然它也是 public, 所以呢,这四个端点中呢,只有我们的退出登录需要得到保护, 剩下的三个呢,全部都是公开的。那我们来创建这样一个进行公开的装饰器,还是一样使用我们的 nes 的 c r decorator 啊,之前我们已经创建过了, 写好之后来到我们的 decorator, 找到这里的 public decorator, 我 们从官方文档中去复制相应的模板代码,来到下面找到这样一个 decorator, 复制 粘贴过来。然后呢,将我们需要进行公开的这样一些端点呢,修饰上我们的这样一个 public 对 应的装饰器,让它进行公开。 public public, ok, 这样写好之后呢,一定一定要记得在我们的 guard 中去判断一下相应的这样一些 controller 它是否被 public 修饰,如果被 public 修饰,它就直接放行,就不做下面的这样一些 j w t token 的 检测啊,具体的逻辑呢,还是一样啊,我们的官方文档都已经帮我们写好了,不需要我们去 再重复的写。然后呢,这里需要注意,我们要注入这样一个 reflector, 通过它才能够访问得到对应的这样一些装饰器。 导入一下,导入一下我们这个 a 区类, ok, 非常简单啊,我们来看一下我们当前的这样一个加上了盖的之后的逻辑的话呢,我们接下来有哪些地方需要改正?那么首先呢,很明显可以看到我们 加上了盖的之后呢,我们的这个 jason web token, 它是从我们的这个请求头中来获取的,就不是像我们上次测试的那样在我们的 body 中获取。所以呢,我们的这个 controller 还有 service 要改相应的地方,就比如 我们受到保护的这个 logout, 它就应该从我们的这个请求体中去获取我们对应的这个 token。 并且呢,相应的,既然我们现在的这个 token 都是从我们的请求头中来获取的,那么自然我们就不需要在这个 body 中去写我们的 access token, 我 们这里的 body 就 不需要给它设置为 nobody, 那 整个这个端点呢,它就不应该是 post, 它应该是 get, 给它改一下,我们的 controller 也要改一下。在我们的 controller 这里,我们的 sign out 就 应该改为 get, 那 么相应的它的状态码就不需要改过来了, 我们只需要传入这样一个 request, 对 应的这样一个 header, 也就是在我们的这个参数列表中呢,获取我们的请求,它对应的类型肯定是 request 来自 express。 然后呢,我们直接从这个 request 当中呢去获取我们的 headers 点 authorization, 我 们可以直接这样写 authorization, 然后呢,从 authorization 中呢获取我们的 token, 我 们可以直接这样写,对吧?但是呢,这个步骤其实完全是多余的,我们完全可以在这里用上我们的装置器,直接从我们的请求头中获取我们的 authorization。 这个部分呢,其实和我们之前所使用的 body 或者是 parameter, 这些获取我们的 body 参数或者是路径参数的写法其实是一样的。我们来到这里的 overview controller, 我 们直接搜索 headers, 在这个地方呢,我们就能够找到这样一个装饰器,它叫做 headers, 它就能够直接从我们的请求头中呢获取相应的这样一些请求头,具体你要获取哪个呢,就取决于你往这个装饰器里面传递的这个参数。所以呢,我们这里直接把 request 替换成 headers, 它是一个字串,直接给它写进去就可以了。 authorization, 那 相应的,在 sign out 的 这个地方,我们这个参数就应该是 authorization, 它就不应该是一个 access token 了。那这个地方的 authorization 很 明显是需要一部分处理的, 那这个处理的流程呢?其实我们在 guard 中间就已经写好了,就在这里,我们的类似的 jesus 官方文档中,就提供了这样一个从我们的请求体中获取这个 token 的 这样一个方法,所以呢,我们直接把它粘贴到我们的 service 中, 导入一下这个类型。那这个地方呢,既然从我们的 guard 抽离到了我们的 extract, 那 我们干脆呢就把 office service 给它注入进来。在这个地方 private office service, 那 这个地方的方法定义我们就不再需要重复写了,我们在这里呢,去调用我们的 office service, 那相应的这个方法需要共享啊,所以呢,它不能是一个 private 的 方法,这样写,这样就没有任何问题了。再回到我们的 logout, 在 这个地方呢,我们就需要 调用一下刚才我们定义的这个 extract token from header, 那 需要注意啊,这个方法它接收的参数它是一个 request, 而我们传过来的是 authorization 啊,所以呢,干脆这个地方我们就传一个普通的 request 就 可以了啊,不需要我们多此一举, request 直接给它写进去,看一下这里的类型定义, 删掉这里的 signout 对 应的类型,我们就要改为 request, request 传入我们的 request, 接收一下我们的 token, 那 相应的这里的 token 它有可能为 undefined, 所以呢, 这个地方我们要对它做一下简单的判断,如果它不存在,返回我们的 unauthorized exception, 最后呢,把这个 token 进行一下校验就可以了啊。当然这个地方大家觉得在我们的 sign out 这个 service 里面,我们还需要对这个 token 去进行校验吗? 很明显是不需要的。所以呢,刚才我们做这么多操作有什么用呢?其实根本就没用啊,我们的盖的直接就把我们这里的都很娇艳,其实都已经做完了,所以呢,这个地方我们什么参数都不需要删掉,来到我们的三个这个地方全部都不需要啊, 我们只需要这样一个 user id, 通过它来判断一下我们该将哪一个用户对应的 refresh token 更新为 null 值就可以了。所以呢,这里的参数传进来的不应该是我们的 token, 而是我们的 user, 那 这个 user 该在哪个地方去获取呢?大家其实能够看到,我们在这里的模板代码中就已经有了一个 user, 发现没有,这个 user 是 从我们的这个 gar 的 中间来进行这个 j w t 校验的时候,它自己返回的这样一个 payload, 那 这样一个 payload 它的类型很明显和我们自己定义的这样一个 user 类型,它应该是不匹配的。那我们该怎样查看它的类型呢啊,很简单,我们打印一下不就好了。 然后呢,既然它给我们赋在了这个 request 里面,那我们就直接可以从这个请求中去获取它,所以呢,这里我们只需要把请求中间的这样一个 payload 获取出来就可以了。我们获取 request, 然后呢, request 点 user 啊,这里会报错呢?是因为我们的类型很明显是不匹配的,然后这里我们也没有接收啊,我们这里要接收的是 payload 的, 它应该是一个先写成一个 any 类型吧,然后这也报错呢,是因为默认情况下, express 对 应的这样一个请求体,它是没有这样一个 user 属性的,这个 user 属性是我们在高的中间给它强行加上去的,所以呢,它的类型不应该是 request, 我 们给它写成 any, 这样就可以了。好,一切就绪,最后呢,我们再返回一个简单的响应体,附上我们的 status code, 这样我们的退出登录就完成了。那完成我们的 signout, 我 们再来看一下 refresh, 此时呢,可以发现,我们的 refresh 它是需要自己来进行 j w t 校验的。为什么呢?因为我们的 refresh 它没有受到保护。既然没有受到保护,那么校验的过程呢,就不由我们的 guard 来实现,就由我们的 service 自己来实现。所以呢,这个地方我们是不需要改的,我们把这些 to do 给它删掉。不过需要注意的是呢,我们这里的 refresh token 和刚才的 logout 一 样,我们不能直接从这里的 body 中取,我们应该从这里的请求头中去取。所以呢,我们这里就要使用 headers, 我 们要获取 authorization, 把这个 authorization 给它传进去, 在我们的 refresh 中呢,去接收它 authorization, 然后呢,去调用一下这里的方法,去处理一下我们的 authorization, 得到我们的 token。 我 又忘了这个方法它是接收我们的请求体啊,不是我们的 authorization, 我 们再改回来 request。 同样的,我们先判断一下这里的 refresh token 是 否有效。然后呢,我们这里呢,简单地写上这个处理逻辑。然后呢, 既然我们这里都是直接从这个请求体中获取相应的数据,那这个接口呢,就不应该是一个 post, 它也要改为我们的 get, 然后这里的响应码我们就不需要进行覆盖。那来到我们的 bruno, 把这里的 refresh 给它改为 get, 把这里的 body 设置成 nobody, 设置一下这里的 authorization。 这个请求头啊,这里也是一样,加上 authorization 这个请求头, barrier ok。 那 剩下的登录和注册我们应该是不需要修改的,因为这里面的逻辑呢,其实是非常的稳定的。那么接下来呢,我们再来测试一下我们当前更改好的这样一个应用,它是否有效。我们先来测试一下数据库连接,然后我们来初步化。 好的一切就绪,我们来启动项目,来到我们的 bruno, 还是一样啊,我们先来注册,正常应该会返回 access token, 还有我们的 refresh token。 此时呢,我们如果拿着这里的 refresh token, 我 们尝试去 退出登录。好,大家觉得会怎样呢?正常应该会失败,对吧?因为我们这里是 refresh token, 并不是 access token。 回车,果然它判断我们的 token 是 无效的。那我们接下来呢?换成正确的这样一个 access token, 我 们来退出登录试一下。我们来换这个正常的 access token, 来到我们的 logout 来,尝试去退出登录 回车。哎,很奇怪,刚才是我复制错了吗?我重新试了一下,既然又行了,我们重新来一下啊。我们这里重新登录,登录之后,我们能获取两个 token。 然后呢,这里我们拿着 access token 来到 logo, 我 们拿着正确的 token 去重新的退出登录, 直接回车。好的,这里看到我们拿到正确的 token 去退出登录是没问题的。那此时呢,我们再拿着这个已经退出登录的 refresh token, 我 们尝试去刷新回车。好报五百错误,我们看一下,提示我 j w t 被篡改了,是我复制有问题吗? 回车, ok, 这样才是正确的。刚才应该是我复制有问题啊,复制出来的 token 可能漏了几个字母,所以它判断是这个 j w t 这个 token 有 问题。那这呢,很明显就是我们的 token 呢,它在教验的时候报错了, 它提示我们已经 valid credentials, 因为我们刚才已经退出登录了。那最后呢,我们再重新登录,我们再次复制这个有效的 refresh token, 来到我们的 refresh, 我 们去更新一下我们的这个凭证。 回车。更新完之后呢,我们刚才拿到的这样一个登录时返回的 refresh token, 它应该就失效了,我们只能拿着这个新的才能够使用。此时如果我们再回车, 果不其然,它就失效了,最开始获取的 token 呢,就已经在数据库中呢被删除掉了。嗯,那么那么到此我们就成功完成了整个这样一个 健全系统的全流程,我们添加上了 refish token 相应的内容,同时呢基于它添加上了 refish 还有我们的 logout 这样两个新的端点,这是我们之前的课程中没有讲过的内容,也可以看到我们为了实现它呢,也花费了不少的精力,因为 涉及到其他的一些内容嘛,毕竟我们要把 token 存储在数据库中,中间还是会涉及到一些问题的。那么具体的操作呢?各位可以看到这里,我们的 m 默认会打开所有的随口的执行日制, 大家可以查看具体的执行细节,看看在数据库的层面上,他到底做了哪些东西。那么最后呢,我再强调一下, 当前我们的这个项目呢,我们先不考虑角色的划分,我们只区分这个用户是否登录,后面呢,我们再加上在登录的前提下,我们再去判断这样一个用户,他是普通用户还是管理员。 我们先不做这么复杂,我们先把最简单的普通用户和游客这两个身份给他区分开就可以了。那么以上就是本期视频的所有内容,希望你能关注或定于我的频道,感谢你的观看,我们下期视频再见!

hi, 欢迎回来,今天呢,我们针对我们的 controller 做一些简单的类型胶印,那么在进行类型胶印之前呢,我们先让我们整个数据结构呢变得更加的复杂一些。来到我们的 to do service, 可以 看到当前我们所有的代办选项呢,它只有一个 id, 一个 title, 这么写的话会非常的简单,我们让它稍微复杂一点,比如这里我给它加上一些子段,比如这里的 content content one, 注意内容的填充啊,我就不自己去手写了,不然的话会非常的慢啊。同时呢,我还加上一个 is complete, is completed, 那 刚开始肯定是 false, 全部都给它设置成 false 啊,当然这个值呢不是特别重要,重要的是我们要先添加这样一些用于 本节的一些新的属性或者是字段,让我们的整个数据结构呢变得更加的复杂。那可以看到这样我改完之后呢,下面很明显就开始报错了,比如这里的 create to do, 它告诉我呢,这里它的类型是匹配不上的,因为 我在 deduce push 放进去的这个数据结构呢,它缺失了一些子段,因为我们的 new to do 呢,它应该是和上面的这个类型进行匹配的,我们把这个新的类型呢给它截个图,来到下面,我们给它添加缺失的一些内容,这里很明显我们还需要添加 content, 还有 is completed。 此时呢,再来到我们的 controller, controller 应该也会报错啊,因为 controller 中的这些类型对应的定义,我们全都要改一次这里的 content is completed, 甚至呢包括这里的 update to do body, 这里其实我们也应该改一下,我们 这样修改之后呢,我们就还可以基于我们的 content 或者是基于我们的 is completed 去修改,而不是仅限于我们这里的 title, 所以呢这个地方我们添加上缺失的字段,并且还有一个非常重要的事情,就是在 更新的过程中呢,我们完全可以只选择更新其中的某一个部分,所以呢我们打上对应的问号,那相应的我们的 update to do, 也就是这个 service 这个地方啊, service 这个地方,它的数据结构呢,我们也需要发生一定的改动,这里呢也需要打上相应的问号才行, 这样修改完成之后呢,可以看到,为了匹配我们当前的这样一个类型的修改,我们的 service 要改动, 我们的 controller 也需要进行改动,一旦呢我们每次都需要做这样的改动的话呢,相应的我们这里的不管是 service 还是 controller 都会发生比较大的改动,那每次呢都这么改,说实话是比较的麻烦的,对吧?就从我刚才的操作来看,本身呢就是一件非常麻烦的事情,那 我们能不能把这样一些类型单独的抽象出来,抽象成一个类也好,或者是一个 type 类型也好呢?那自然我们能想到呢, nest 的 js, 它也能想到,所以呢在 nest 的 js 或者是在这样一种后端框架中呢,我们一般 把这里的 controller 接收到的这样一些用于数据传输的类型呢,把它叫做 dto, 也就是 data transform object 这样一个数据传输的对象,也就是 dto 的 缩写。我们来到这里的 nest just 的 官网,来到我们之前一直在学习的 overview controllers, 在 下面 找到我们的 request payload, 在 request payload 的 中间,它就有简单讲解 dto 是 用来干什么的,它这里的定义是, a dto is an object that specifies how data should be sent over the network dto 呢,就是定义了 我们的接口,在网络请求的过程中,不管是接收还是发送,对应的这样一个数据,它的结构或者是它这个对象它是什么样子的。但就像我刚才说了,我们可以定义一个 class 类,也可以定义一个 type, 这在我们的 type script 的 中间都是允许的。 那 ness 的 jess 它推荐用什么方法呢?那么这里它给出的定义是, we would define the detail schema using type script interfaces or simple classes。 它说可以用 interface 接口,也可以用 class 类。但是呢,它的推荐是什么呢? we recommend using classes here 它推荐我们使用 class。 这里具体的原因呢,我就不念了啊。大致的说法就是因为 class 它是 javascript 的 原生自带的一个关键字,而如果我们用 interface 或者是 type 关键字的话,那么在 javascript 中没有对应的这样一个功能。那么在我们的 type script 翻译成 javascript 之后呢,这样一些限制呢,就会被 简单地擦除掉。所以呢,这里他推荐我们使用 class, 因为 class 在 typescript 还有 javascript 中都有相同的约束作用。那么明确我们选择的方案之后呢,我们来看一下它给的这个例子。它给的这个例子呢,创建了一个文件,这个文件名叫做 create cat, 点 dto, 点 t s, 那 么这里的 create 后面跟上的就是我们对应的这样一个数据结构它的名字,然后呢,对应的类就叫做 create cat dto, 然后里面这里的 字段或者是属性呢,就是除了 id 之外的,那么自然 create 对 应的就是这样一个创建的一个端点,对应的这样一个数据结构。那有了官网的这个例子呢,那我们就可以尝试着通过这样的方式来实现了。那我们 来创建一个相应的这样一个文件,复制这个文件名,来到 to do 里面,我们新建一个文件名,把这里的 create cat 改为 create to do, 把它这个类直接复制粘贴过来,这里就叫做 create to do。 那 我们的数据结构是怎样的呢?我们简单的截个图, 这里就应该是 title, content 以及我们的 is completed。 注意啊,这里的类型我们要进行简单的匹配, 那有了我们的 create to do dto 之后呢,我们就可以把这个类型呢用在我们的更新对应的这样一个路由处理器上,来到我们的 to do service 来到下面,啊,不对,不是更新啊,应该是创建,对吧?那这里我们就把 new to do 它的这个类型定义替换成我们的 create to do dto。 那 这里的下面会报错呢?是因为我们缺少了一个 id, 那 这里的 id 的 话,我们就通过时间戳的方式来自动生成 id date now, ok, 这样呢就没有任何的报错了。同样的来到我们的 controller, 我 们的 controller 中间对应的这个 create 这样一个方法,它的 body 也就是接收的这样一个内容呢,我们同样可以用我们的 create to d t o 来替代。那此时呢,我们之后对我们的数据结构做任何的修改,我们就只需要统一的修改这里的 create to do d t o 就 可以了,对吧?那么有了 create to do d t o, 那 么自然就应该有 update to do d t o。 所以呢,我们 来照猫画虎尝试一下,直接复制粘贴,把这个 create to do dto 给它替换成 update to do dto, 相应的这个类名 update to do dto。 那 么 update to do dto 和 create to do dto 它们俩 区别就在于呢,我们的 update 过程中,我们可以选择任意的一个字段来更新,或者是任意的属性来更新,所以呢,这三个对应的属性都可以给它加一个问号,表示呢它是可选的,那右边呢,我们就不加问号。那么自然而然我们的 update to do dto 呢,就可以用于我们的 这个 update 相应的 service 还有 control 中,那这个地方我们就可以给它替换掉了 update to do dto 还有我们的 controller 这个地方,这里的 body 我 们也可以给它去掉 update to do dto。 ok, 那 么到此呢,我们就成功地将所有和数据传输的这个对象结构类型呢,给它抽象成了一个单独的类。那么之后我们的数据结构有任何的改动呢?我们只需要去修改这个简单的 类就可以了,包括我们的本数据结构本身呢,其实也可以单独创建一个类。来到我们的 service 这里的这个数据结构,我们也可以单独创建一个类啊,我们直接 基于我们的 create to do 来进行创建就行了。来到我们的 to do, 因为它是表明我们整个完整的数据结构,所以呢,不应该把它单纯的叫做 to do t s, 然后创建一个类,我们的做法是把它称作 to do 点 entity, 也就是表明它是一个实体类,然后呢,再把它定义进去,这里,它要加上一个 id 啊,这样的话稍微正规一点,这样的话,我们的这个数据的实体类就和这里的 dto 有 一个明显的区别,那相应的可以看到我们的 dto, entity, 还有这里的 service, module 以及 controller 全部混在一起的话,是比较难看的。因此我们可以单独新建一个文件夹 dto, 把所有的 dto 呢放进去,以及我们这里的 entity。 如果我们的业务比较复杂,那么一个 to do 它可能下面会划分不同的一些相应的子项,所以呢, entity 实体类这个文件也可能会非常的多。因此呢,我们也可以单独建一个文件夹叫做 entities, 可以看到 entities 这个文件夹,它是有单独的一个文件夹图标的,我把这个 to do entities 给它放进去,那么此生来到我们的 to do service, 我 们把这个 to do 这个属性它的类型定义给它,加上 to do。 不 对,我们这个 entity 类,它的这个类名好像不太对啊,所以我就说嘛,这里应该是 to do, 好,这样才是正确的,我们给它加上类型限定它是一个数值。那么到此呢,我们就成功的创建了 dto 以及我们的实体类。但是呢,大家发现一个问题没有,虽然我们成功的创建了两个 dto, 用它来统一的约束了我们的 service, 还有 control 中间的这个数据传输的类型结构。但是呢,正常来说的话,一旦我们要进行 类型的修改,虽然我们只改这两个文件就行了,但是呢,一旦我们的 create 做任何的修改 update 就 要做一些 相同的修改,这么来看的话,好像还是非常的麻烦,对吧?那既然我们的 update to do dto 是 依赖于我们的 create dto 的, 有没有什么办法能够让我们的 update 自动的和我们的 create 来同步呢?也就是我们只需要修改我们的 create 就 可以了。 那我们的 nest js 呢,就提供了一个非常好用的工具,只不过呢,在它的教程中,至少在这个 control 里啊,这里它并没有展示出来,因为它这里只展示了 create dto 的 这个场景,并没有给我们展示 update to do dto 如何和我们的 create dto 进行同步。那这个地方呢,我们需要来到这里的 techniques, 也就是 nest js 中间的技术或者是科技这个板块,找到这里的 validation, 也就是验证。在验证这个板块呢,我们可以在右边找到 map types。 这里呢,它就介绍了一个简单的场景, as you build out features like c, r, u d it's often useful to construct variants on a base and types。 那 这里介绍的场景呢,就是我们想要的。也就是说,在我们构建这样一个 c r u d 增删改查的时候呢,经常我们会需要基于一些基础的实体类,然后做各种各样的变体,所以呢,这里它就告诉我们 nest 的 js 呢,它就提供了一些 简单的函数工具,帮助我们来自动地生成这样一些变体。那么这里它提供的工具呢,就是这个 partial type, 它所起到的作用呢,就是 it's often useful to build create and update variation on the same type。 然后后面举的例子呢,刚好就是我们想要的, the create variant may require all fields while the update variant may make all fields optional 啊,刚好就是我们想要的,它就点出来了。说我们的 create dto 呢,它需要保证所有的字段都是都是存在的,而我们的 update dto 呢,它又要保证所有的字段都是可选的,那为了 保证让它们两个呢同步,但是呢,又有这样的区别,我们就可以使用 partial type, 那 具体的用法就是这里它上面定义了一个 create t d t o, 那 么下面呢,这里的 update d, t o 就 不用再 重复这些字段,并且加上问号了,只需要通过 extends, 也就是这样一个 types group 中间的这样一个关键字,可以让一个类去继承另一个类,从而让这个类呢拥有另一个类 中间已有的这样一些字段。然后呢,它不是直接去拓展或者是继承这个 create dto 的, 它是利用我们的 partial type 对 我们的 create dto 做了一层处理。做了这个处理之后呢,就能够保证 create dto 中间的所有的字段呢,不仅能够被附加在我们的 update dto 上,还能够保证 update dto 能像我们所需要的那样,让它的所有的字段变成可选的。那么我们复制这里的写法,那么需要注意啊,这里的 partial type 这个函数,它需要 来自我们的 nest js map the types 这样一个工具库,所以呢,我们需要先安装这个工具库,至少我们的项目中,在创建一个 nest js 项目的时候呢,是不会自带这个东西的,我们只有这个三个核心的东西,是吧?刚才我们看到的这个 map the type, 它是没有存在于我们的依赖里的,所以呢,我们先安装一下,直接复制 安装,安装好之后呢,哎,我们来看一下它的用法,非常的简单啊,直接进行继承就可以了。来到我们的 update, 把下面字段的定义全部都删掉粘贴,我们要继承的是这个叫做 create to do dto 的 东西,然后呢导入我们的这个 partial type, 好, 就这么简单,我们直接来尝试一次啊,我们直接运行我们的项目,打开我们的 bruno, 来到我们的对应的这个检修中,首先呢获取所有的 to do, 那 此时呢,我可以看到所有的这个新的 内容呢,都是能够展示出来的。然后呢,我们上来就先创建啊,创建这个地方呢,很明显我们就要多加几个东西,首先这里的 id 肯定是不行,不能给它加进去的,但是我们这里呢没有对这个字段做这个校验啊,所以呢传进去如果是错误的字段的话,应该目前是不会报错的吧。我们看一下, 我假设这里多传入了一个 id 字段,然后这里的 content, 然后是这里的 is completed 啊,上面差一个逗号啊,所以它有警告,给它设置为 false, 保存回差创建 哦,它是能够成功创建的。那此时呢,来到我们的这个 getall 获取一下,可以看到刚才我的这个 id 是 多少二幺三四啊,它是能够成功的被覆盖进去的啊。这里其实我们 因为没有对这个多余的不符合我们要求的字段做校验,所以呢,这个字段他就蒙混过关,给他加进来了。那下一节呢,我们再去学习如何去去除掉这样一些未经过我们允许传进来的字段,那至少可以看到我们目前创建是没有任何问题的。然后是我们的更新,更新的话我就基于刚才的这个 新创建的内容呢,对它做一些修改, id 给它写进去,然后 body 这个地方我只改它的 is completed, 给它改为 true。 回车创建成应该是更新成功了,我们再回车,此时呢它就改为了 true。 经过我们今天创建的这个 dto 之后呢, 我们再想去约束我们的这个数据传输过程中传进来的这样一些数据结构的话,就比较的简单了,尤其是在进行更改的时候,如果我们的这个数据结构发生了任何的更改,我们就只需要修改这里的 entity, 再修改一下这里的 create 就 行了。而我们的 update dto 呢,它能够自动地和我们的 create 进行同步,借助的就是 nest js 提供的这样一个 partial type, 这样一个非常好用的工具函数。以上就是本期视频的所有内容,希望你能关注或订阅我的频道,感谢你的观看,我们下期视频再见!

hi, 欢迎回来,那么这节呢,我们不去讲最后的架构,因为呢,发生了一个突发情况,我也没想到,就在这个视频录制的 前一天啊,就是今天的凌晨,我在评论区收到消息说 mico r m 竟然发布了七点零啊, 然后简单看了一下,发现呢,其中的改动内容呢,说实话还挺多的啊,所以呢,本期视频呢,就临时做一个简单的调整,带大家看一下新版本的 mico o r m 该怎样和我们的 ness js 来配合。那么首先呢,还是一样,我们先创建一个最基础的 ness js 项目, ok, 然后呢,本节的资料呢,我就叫它 section one micro r m v seven 还是一样啊,清除掉不必要的文件,还有对应的项目依赖。 一切就绪,我们来安装项目依赖, ok, 那 接下来呢,我们来简单地创建一个模块儿,就是我们的用户模块儿,我们直接使用 let generate resource 输入 users 选择, rest for api 选择,让它给我们生成这里的 entry point。 那 此时呢,它会帮我们更新相应的模块引入,并且帮我们安装好 map type, 这样一个用来创建 dto 的 这样一个项目依赖这里,我们之前已经做过了,对吧,把这里不必要的测试文件呢,还有这里的 app service, app controller, 全部都给它删掉。 然后呢,我们简单创建一下今天我们要创建的这个实体类,它的子段,首先是 id, 我 们还是以最简单的这个 number 类型,然后是 username, email 以及 password, 那 相应的把这三个呢复制过去,放在我们的 create dto 中。 这里的 update d t o 呢,自己使用了这个 partial type 进行生成,不需要我们去理会。接下来就来到本期视频的重头戏,我们的 m c o r m v 七该怎样使用?还是一样来到它的文档。它的文档呢,改动其实并不多啊, e r 能够在左边这里找到我们的 usage with nested g s。 然后呢,要安装的东西呢?其实好像大差不差是吧?我们先把它安装进去,选择这里的 post grace s q l。 安装这里的项目依赖。那么我需要注意的是呢,这里所有的项目依赖都已经升级到了最新的 v 七版本。可以看到这里的 core nest js 以及这里的 post grs q。 它们全部都进行了更新 哦,甚至这个才是几十秒之前才发布的一个新版本啊啊,当然,我相信它不会有太多的更改。 ok, 那 么接下来的话呢,我们就要进行配置了。还记得我们之前的配置呢,我们要么把它写在 forroot 里面,要么呢单独写一个配置文件,对吧?那这里呢,我们还是一样啊,把它单独的写在一个配置文件中。这里我们来到这里的 config the c l i, 或者是来到 这里的 getting started 这里的 chapter one。 在 右边找到 setting up c l i。 需要注意这个配置文件呢,它可以用于整个 mico o m 项目,它也可以单独用于我们的 mico o m 的 c l i 工具。如果你不选择去配置这个东西的话呢,你也可以像刚才这个文档中的这样,只把所有的配置呢,写在我们的 microrm 的 module 中。这里呢,它也是能够使用的。只不过呢,写在这里的配置呢,它是没办法被我们的 microrm 的 c i 进行读取的,所以呢,为了 让我们的 c i 还有我们的 microrm module 都能够同时用到相同的这样一些配置,所以呢,我们依然还是选择去配置我们的这个配置文件,那么这里的配置文件同样的,它下面也给我们提示说, 我们可以把它放在整个项目的根目录,因此呢,我们直接复制这个项目的名称啊,注意啊,是 t s 结尾来到我们项目的根目录,创建这样一个 t s 项目,把这里的模板呢复制粘贴过来, 大家能发现这里的模板好像有很多东西和我们之前不一样,这里先给它改过来。首先呢是可以看到这里的 entities, 之前的模板中呢,它的 enum 内容里面都是一些字母串,表示我们要加载的实体类的路径,这里呢,它直接以这个 g s 类作为一个引入的内容了,也就是它直接使用了这样一个类引用,而不是我们的 基于文件路径的这样一个引用啊,这里我们先不要去操心啊,我们先把它取消注置,这里呢也取消注置,我们先来看一下我们的 micro r m c l i, 它能否检测到我们的配置文件,那相应的 c l i 我 们也是要安装。来到最上面的 setting up, 在 下面,这里我们刚才安装了这个 core, 还有对应的这个驱动,那么接下来我们要安装这里的 micro c l i, 注意把它作为开发依赖进行安装。那么接下来我们来使用 micro r m debug, 这是我们之前一直在使用的命令,通过它可以检测我们的 配置文件,检测我们的实体类,并且检测数据库连接是否正常,那很明显,这种情况下,它正常应该只能检测到我们的配置文件,不能够检测到任何的实体类,也不能够检测到我们的数据库,因为我们的实体类还有数据库对应的配置我们都没有写。那事实是这样的吗?我们来看一下回车, 那此时呢,我们可以看到它成功检测出来了这样一些版本,但是你会发现,它甚至连我们放在这个根目录的这样一个配置文件,它都没有检测到,它直接告诉我们 configuration not found。 但此时呢,大家会发现什么问题呢?它这里检测的配置文件呢?它全部都是检测的 gs 文件,它并没有检测我们的 ts 文件,这是为什么呢?这就是 v 七版本和我们的 v 六版本不太一样的地方了。 在 v 七版本中呢,它默认使用的这个加载 type script 的 文件的工具呢,它变成了 t s x, 还有我们的 swc。 所以呢,可以看到在它这里安装的这个命令中,它给的这个工具是 t s x, 而不再是 t s node。 如果是大家来到之前的文档六点六,可以看到这里,它之前的指定的工具是 t s node。 而之前为什么我们能够直接检测到呢?就是因为 我们的 ness 的 js 项目本身在创建的时候呢,它就自带了一个所谓的 ts node 啊,就在这里。而新版本的 micorm 呢,它不再使用 ts node, 它需要我们使用 ts node 以外的这样一些 type script 对 应的 运行器,这里可以是 tsx, 也可以是 swc, 但是呢,如果使用 swc 需要做一些额外配置,所以呢,这里我们需要额外安装一下 tsx, 同样的把它作为开发依赖进行安装。 那么它为什么要做这个转变呢?相信大家已经知道了,我在之前的 node js 剪辑教程中有简单对比过 t s node 在 新版本的 node js 中 存在的一些问题啊。当然,这也不意味着 t s x 能够完全替代 t s node, 毕竟我们现在的 t s x 呢,没有办法很好地去支持装饰器。那么有了这样一个 t s x 之后呢,我们再次使用刚才的 mico r m debug 这样一个命令, 此时呢,它就能够正确地检测到我们这样一个 micro o r m config t s 文件了,并且可以看到它检测到了 type script 的 支持。然后呢,它检测到我们使用了 t s x, 这就是和我们之前不一样的这样一些做法。 ok, 这是 v 七版本改变的第一步。 第二步的话呢,就是我们的配置内容了。首先这里的 entities, 也就是加载所有的这个实体类的部分呢,我们该怎么写呢?我们来到这里的 microrm 的 它的文档,来到左边的 configuration, 那 么在下面呢,我们要找到这里的 folder based discovery, 也就是基于文件夹的这样一些检测。那么这里呢,它就给出了我们缺失的两个命令,我们这里两个命令呢,都要给它复制,给它放在这里才行,给它替换掉。 ok, 此时呢,我们再次运行我们的 c l i, 此时呢,它才能够正常地去检测到我们的这样一个实体类 entity。 但是呢,现在它发现我们的数据库连接是失败的,因为我还没有传入相应的配置,对吧?那么配置的内容呢,和之前又有什么不同呢?我们之前的做法是什么呢?我们搜索 environment variables, 之前我们的内容中呢,只需要在我们的点 e n v 文件中写入这样一些带有 m 前缀的这样一些环境变量,就可以让它自动地来读取。但是在新版的 v 七中呢, 我们的 mico r m, 它不再能够自动地去读取我们的这个环境变量中的文件了。那之前的 v 六版本能够读取呢?是因为 v 六版本的 mico r m 呢,自带了 dot n v 的 这样一个工具包,所以呢,它能够自动地读取我们的配置。但现在呢,是不行的。那这个地方,我们的配置文件能不能通过我们的 node js 内置的这个 e n v file 这样一个 flag 来读取呢?很显然也是不行的,因为这里我们使用的工具是 mico r m 而不是 node js。 所以呢,我们想要通过这样的方式来读取我们的点 e n v 文件,很明显也是不行的。那为了解决它呢,我们就不得不在这里去引入我们的 dot n v。 所以呢,我们简单地安装一下 dot n v, 同样是作为一个开发依赖,然后呢,将这个 dot n v 的 配置呢放在最上面, ok, 那 接下来呢,我们去来到我们的根目录,创建这样一个 e n v 文件,我们使用最原始的这样一些环境变量的名称,比如这里的 d b name。 ok, 这就是我们最原始的做法,对吧?来到这里的 ps 文件,我们把这里所有的内容呢都给它替换掉。 好的,一切就绪之后呢,确保我们的数据库是在正常运转的情况下。那我们接下来来到我们的终端,还是一样运行我们的 c r i 命令 debug, 我 们来检测一下。那此时呢,我们的所有配置才算是正常了。这里它能够检测到我们的跟目录中的配置文件,能够检测到数据库的连接是正常的,同时呢,能够检测到我们的 entity 这里的实体类。最后呢,还有一点,我们在使用 micorm 和 ness 的 jess 的 同时呢,我们需要 micorm 拥有这样一个原数据反射的能力。所以呢,我们这里 和之前的配置一样,我们需要提供一个原数据反射的提供器。那之前的这样一个提供器呢?一直以来都是这里的 t s morph media data provider, 那这里呢,我们也同样使用它就可以了。当然在最新的 v 七中呢,它默认让我们使用的这个反射器呢,它推荐我们使用这个叫做 reflect meditizer provider。 它们两个呢,各有利弊。但是呢,这里我还是更推荐各位去使用这里之前的这样一个提供器,它的能力呢更强一些啊,虽然性能上可能有一定的损耗,我们来安装它, ok, 此时呢,我们的配置才算是圆满了,看着和之前好像差不多,对吧,但不太一样的地方是我们这里要显示的去加载我们的相应的配置内容。 那其他的部分呢?比如这里的 migrator 还有 cedar 的 配置呢,其实和之前是一样的,没有什么区别。那接下来最最不一样的地方是哪里呢? 来到我们的 s r c, 来到我们的 entity 之前呢,我们要让我们的 entity 和我们的 microrm 进行搭配,我们要在它的整个类上,还有它的字段上,属性上写上不一样的这样一些装饰器。比如这里的 id, 我 们要写上 primary key, 但是呢,此时你会发现我们好像找不到 primary key 这样一个装饰器啊,这是为什么呢?甚至呢,我们之前使用的 property 这些装饰器也消失了,还有之前我们在内上使用的 entity, 这个也消失了。为什么在新版本的装饰器中呢?我们没办法这样用的呢?我们还是一样来到它的文档,看一下是什么情况,还是一样在这里的 configurations 找到这里的 using decorators。 那 么在最新的 v 七版本中呢, 它提供了两种版本的这个装饰器。一个呢是 legacy, 也就是我们之前一直在使用的这样一个拥有原数据反射能力的 试验性的这样一个装置器。另一个呢是现在 textscript 默认支持的标准的装置器,那么它的能力呢,没有之前的这个试验性的装置器 强,也就是它下面所指示的,它不支持这些原数据反射这些能力。但是呢,重中之重是,如果我们要进行使用的话,和之前的方式截然不同。就比如,如果我们要使用这里的 legacy decorator 这样一个 试验性的装置器的话呢,之前的内容我们是直接从 microrm core 这个包中可以引入这样一些装置器,但现在的话呢,我们需要去引入另一个包,叫做 microrm decorators, 这个包呢是单独的一个包,也就是说它把之前的装置器呢全部都从它的核心库里剔除出去了, 放到了另一个库里面。所以呢,我们要使用这些装置器的话,必须要安装这样一个 micrormdecoris, 这样一个库才可以我们复制一下进行安装。 ok, 那 么此时呢,我们才能够在这里使用上我们之前用到的这样一些装置器 entity, 哎,不对,这里注意啊,它导入的内容应该是 legacy, 然后是这里的 primary key, 注意,一样是来自我们的 legacy, 而不是 yes, 这里就是 property, 上面也是一样。那么不知道各位好不好奇啊,为什么这里我们的装饰器它要来自我们的 legacy, 也就是老版本的装饰器,而不能是最新的标准装饰器呢?它缺失的这个原数据反射能力会导致什么问题呢? 我们可以来简单的试验一下,比如这里的 primary key, 如果我不从这个 legacy 里面去导入,我从这个最新版的装饰器里面去导入,我看看会发生什么,我把这个给它取消掉,我从这里去导入。那此时呢,你会发现它直接报错了,它提示我这样去使用。这个来自 yes 啊,这里的 yes 其实就是 alchemy script 的 意思,也就是标准的 javascript 内容里面的这个装饰器,那么它提示我们什么呢?它提示我们这个 primary key 这样一个装饰器呢,它是需要接收一个对象的,包括下面,如果我们这里的 property 也来自上面的 这个 alchemy script 的 话,可以发现它也会发生相应的情况,为什么只从这个 legacy 切换到 alchemy script, 明明看起来应该是一个 更好的改进啊,也就是从之前的试验性功能变成了一个更稳定的功能,为什么功能还比之前更差呢?如果你有看过我之前的 g s 装饰器相应的内容的话,你应该会知道现在的标准装饰器它不支持我们的原数据反射,也就是说它没办法直接根据我们在 type script 中写入的这样一些字段的类型定义,直接去推导得出我们的 o r m 应该在我们的 数据库中怎样设定这样一个字段在数据库中的类型,也就是它没办法直接通过一个装饰器来进行类型的推断,而我们之前的老版本装饰器它才拥有这个类型推断的能力。 所以呢,我们这里必须要使用老版本的装置器,不然的话就必须要在它里面的显示的指定上它的类型,就比如下面它这里有演示啊, ok, 那 么到此呢,其实就是整个 v 七版本的 micro ram 中我发现的比较重大的一些改动,其他的改动的话呢,我暂时还没看出来有什么前后不一致非常夸张的地方, 后面的话,如果还有更多的问题,我会持续的带各位去解决。那么现在看起来好像我们的新版本 mico r m 更新之后用起来好像是更麻烦了,是吧?我们要配置这配置,那比起我们之前 v 六版本和 nice 的 j s 的 配合呢,看起来是更加的 疏远了一些,但实际上它也提供了一些好处,就比如它添加的其中一个新功能,它可以直接在 mico r m 中呢去检测我们的慢查询, 我们可以直接搜索 slow query log, 那 这里呢,它直接告诉我们可以在 micoorm 的 配置文件中呢,写上我们要检测的这样一个慢查询的这样一个用时的门槛,以及这样一个慢查询日制它的实现,这里呢,我们就可以用上我们之前的 tino nest js 来进行覆盖, 这样的话就能够用 pin 来进行慢查询日制的这样一个打印,也就是所谓的慢 sql 查询。那么它的开启方式呢,其实非常的简单,就在我们的配置文件这里就可以了, sql slow query threshold, 比如我 指定成一秒,超过一秒呢都会被打印出来。然后呢,我们指定这里的 slow query logger factor, 这里呢,我们去参照它这里的写法其实就可以了,没有大家想象的那么难啊。那么到此呢,就是 我目前发现的从 micro r m v 六升级到 v 七的一些痛点和大家需要注意的点,尤其是这里的 decorator 这些装饰器,它和之前呢是完全不一样了,这里各位一定要注意。当然这其实也是无奈之举,因为现在的 javascript 或者说 type script 生态下的 装饰器呢,它就是非常矛盾且复杂的,它现在分为了两个版本,而大家在这种后端的重度依赖原数据反射的框架中呢,都还在依赖这个老版本的装饰器,所以呢, mico r m 能做出这样的改动,我觉得是非常的难能可贵的,它既要 维持老版本装饰器的这样一个功能的可用性和稳定性,他同时呢还在努力的去尝试向最新版的这个装饰器靠拢,所以呢,他给我们提供了不一样的选择,让我们自己去决定 是使用老版本的装饰器还是去迎合新版本的装饰器。那么以上就是本期视频的所有内容,希望你能关注或定义我的频道,感谢你的观看,我们下期视频再见!

嗨,欢迎回来,那么本节呢,我们就来完成游客相关的功能模块,虽然上一节呢,我们完成了整个健全的部分,但是呢我们的游客部分的功能呢,很明显我们还没有给他全部的完成清楚,那么今天我们要做的呢,就主要是两点啊, 一个呢是完成作为一个游客,他可以查看所有公开的这样一个文章的内容,另一个呢是他可以查看公 公开文章的具体信息,也就是这么两个对应的端点,然后呢我们写上相应的注示就可以了,听起来很简单,对吧,但是呢,我们要对当前的这样一些端点呢做一些简单的划分,并且呢弥补上我们没有添加上的这样一些内容。来到本节的模板, 我们来到 s r c articles controller, 我 们来简单看一下当前对应的方法有什么问题啊?那么首先呢,可以看到我们这里的 findall, 我 们定义了两个,一个是 public, 一个是 普通的受保护的 final, 那 它们俩的区别呢?很简单,那么定义了 public 对 应的这样一个接口呢,它就只能够查询所有的被公开的这样一些文章内容,并且相应的它应该不受我们的 这样一个 j w t 健全机制的保护,所以呢,加上我们的这个装置器,让它保持开放。我们来到对应的 service, 看看它的实现内容是否符合我们的要求,那我们之前呢,应该是已经完成了这个部分,对吧?那么此时呢,我们再来到这里的 find one, 此时这里的 find one 呢,很明显它是受到保护的,而我们想要定义的这样一个端点呢,它应该是不受保护的,所以我们这里再新建一个版本, 我叫它 find one public, 在 它的 get 这样一个装饰器里面呢,我们写上对应的这样一个路径,它对应的 service 肯定也是要改过的。然后呢,整个端点要进行开放,加上我们的装饰器,来到我们的 find one, 我 们基于这个 find one 定义一个新的方法 复制一下,那这个新方法呢?我教它 find one public, 那 么这个方法呢,很明显就只能够查看所有的这样一个状态为 public 的 这样一个文章内容。同时呢可以看到我们在使用这个接口的时候呢,肯定不能暴露相应的一些敏感信息,因此我们的 author 它对应的这样一个实体类应该是我们的 user, 我 们看一下我们的 user 实体类有哪些敏感信息啊?是 user 下面的 entity。 那 很明显这里的密码不能够暴露,邮箱呢也不能暴露,以及它的 refresh token 也不能暴露。所以来到我们的 service 在 这个地方,我们把相应的内容呢都给它排除出去。 refresh token 以及这里的 email 也不能暴露。 ok, 就 这么简单,那我们把这里的 to do 给它去掉,那来到我们的 controller, 在 这个地方 public, 我 们需要使用的就是 find one public 这样一个方法,返回的内容就是我们想要的这样一个 article 内容, 当然是排除了这些东西之后才能够得到相应的内容。那最后呢,我们来写上相应的注示就可以了,我们来到这里的 public 相应的内容,或把它俩放在一起吧,干脆 直接放在最上面。然后呢,这里的 find wall, 大家能发现我们的 find wall 呢,它是没有区分的,所以呢,会写得稍微麻烦一点, 所以呢,这个地方我们最好写上相应的注是 the articles。 然后呢,我们写上这样一个方法对应的描述,那么这个方法 find wall 需要注意啊,它应该是一个公开的方法,因为我们所有的人,不管是一个游客,还是我们的普通的用户,它都是可以通过这个接口 获取到所有公开的这样一些文章信息的。因此呢,为了避免和之后查找所有的这样一些用户自己的文章内容呢产生歧义,我们还是给他加上 public 的 这样一个后缀,这样的话理解起来会更简单一些。 public, 这里的方法描述就是 all the user without login can access to retrieve all the public articles。 那 下面的这里的 find one public 也是一样的, 我们来到这个地方,给它写上相应的注示, the id of article specified article。 那 这里呢,同样所有的用户呢,都可以访问这里的 public specified public article details。 好, 那么这里的 find one 呢,我们先不去管它,我们下一节呢再去完成,因为这里呢,就涉及到我们用户相应的内容了。 我们这里呢,只完成 public, 也就是普通的游客可以访问的部分,那这里的 controller 它发生了变化,是吧?这里的这个 find all 应该是受到保护的,所以呢,我们对应的这个 service 也是需要给它创建一个受到保护的 service 服务的。那么这里的 find all public 和我们要创建的 需要登录才能够访问的 find all 方法的话呢,它俩的区别就在于啊,我们的 final public, 它只能够访问所有公开的,而我们的 find all 也就是这样一个 不公开的这样一个服务呢,它是能够访问所有的所有状态下的这样一些文章的,也就是可以是公开,也可以是不公开。这里为了解决这个问题,我们简单的写上, 那么它的参数依然是我们的 filter article, 它可以接受这三个参数,对吧?那么既然这个端点呢,它是受到保护的,也就是这个地方,既然它是受到保护的,那么它既可以进行分页,又可以进行查询。因此呢,这个部分其实很简单啊,我们就直接 返回所有的符合条件的这样一些文章内容就可以了。我们借用下面的这里的写法,这里的维尔条件我们就直接给它加上就行了。我们就不需要像下面这里的 public 一 样,去判断一下我们的 query 是 否存在,我们直接这样写 where, 这里我们要基于 title 进行查询。所以呢,我们直接使用 curie 就 可以了。然后对应的文章状态,我们不需要去限定它,因为这是一个受到保护的这样一个端点对应的服务,那么这个服务呢,很明显应该是对于我们的管理员来使用的,那 我们之后呢,再去理会它,我们这里呢,只是为了保证这样一个端点它不报错啊,这里我给它加上 to do 吧,干脆,不然后面各位可能会忘了, only available for the main 这里也是一样。 好的,让大家最后呢,我们再把这里所有的方法呢,给它分门别类一下,我们再给它加上对应的内容, only available for current user only available for current user and admin 啊,我们的管理员应该是有权利删除掉这样一些有问题的文章的,这个应该是很合理的,它应该是有一个审查的机制的。 ok, 我 们最后再检查一下这里的 public, 我 们是已经完成了,那这里的 find one 呢啊,这个 find one 应该是只针对 我们的这个用户和管理员的 or and admin。 那 么到此呢,我们应该就完成了所有对应的游客需要的功能。我们来最后回顾一下我们的游客现在实现了哪些功能啊? 来到最上面的 user stories, 那 这里的笔记的链接呢?各位,来到这个整个项目课程的第一节,你 去找那个 notion 笔记链接就可以了。那么这里呢,我们已经完成了除了评论还有跳转之外的所有功能,一共就是这四个功能。然后呢,之前我们设计的作为一个游客,他可以进行查询,可以进行排序的这些功能呢,我就给他删掉了。 游客应该是不能够拥有这些功能的,这也能够间接的减轻我们后端的一些负担。那么至于用户相应的功能的话呢,我们就留到下一集再做。好吧,那么以上就是本期视频的所有内容,感谢你的观看,我们下期视频再见。

嗨,欢迎回来,那么本节呢我们就来解决我们现在的这套系统中没有实现的退出登录的这样一个功能。那么首先呢,我们先回顾一下我们现在的这样一套 jwt 体系,它到底是怎样的, 这也是我之前带各位在 node js 剪映教程中学过的内容。那首先呢,我们这里有一个客户端有一个服务器,那在我们 jwt 的 这样一套规则中呢,我们在登录的时候呢会先进行服务端的交易,这里呢会把数据库中的加密后的密码和 我们的用户在客户端传过来的密码进行一些简单的校验,校验成功就会生成相应的 token 发送给我们的客户端,那么客户端呢会直接存储这样一个 token, 然后呢在发起请求的时候,直接通过这个 token 去请求我们的端点,我们的服务端呢只需要去验证这样一个 发过来的 token, 判断一下它是否被篡改,是否在有效期内,然后呢返回这样一个请求就可以了,这是一个非常非常简单的过程,那么它的好处呢就在于 我们的服务器不管怎么拓展,只要所有的服务器都存储相同的一个密钥,这样的话就能够保证任何一个服务器都可以校验从同一个密钥签名生成的这样一些 token, 这样的话就能够非常轻松的进行水平拓展,也就是这样一个逻辑,并且呢我们的服务器本身是不会保存用户的状态的,但是这样做有一个问题,我们的退出登录 它该怎么实现呢?那么这里呢我们要引入一个新的机制啊,那么可以看到在刚才我们介绍的这样一套体系中呢,我们是没有办法实现退出登录的这样一套动作的,因为我们的服务器本身呢,它是不会存储我们的 token 的, 它只会存储我们的 j w t 密钥,而我们的客户端它是会存储 我们的客户端让它去删除这个 token, 并且呢我们也不能主动的让这个 token 的 失效。那为了解决我们没有办法实现 最初登录的这个操作的问题呢,我们要引入一个新的概念,它叫做 refresh token。 我 们先来看一下,引入了 refresh token 之后呢,我们在 j w t 的 流程中呢有哪些变化, 那么这里我们以注册的这个场景为例,那么注册同样是以用户名,密码或者是邮箱来进行注册,那这个地方呢就需要注意啊,在保存用户信息的时候呢,它同时还会保存一个叫做 refresh token 的 东西,那这个 refresh token 和我们之前的 j w token 其实是 同样的,它本质上都是基于 j w t 的 token, 只不过呢,我们在生成这样一个 token 的 时候呢,它会生成两个东西,一个呢就是我们之前一直在用的这个 access token, 另一个呢就是今天我们引入的这个新的 refresh token, 并且这个 refresh token 它会存储在我们的服务器数据库中,然后呢我们把生成出来的这两个 token 呢返回给我们的客户端,客户端同样进行存储,和之前是一样的,然后呢请求的时候呢,同样带着我们的这个 access token 来进行访问,然后呢,我们只会去检验这个 access token, 然后呢返回相应的请求,看起来好像没什么大不了的,那为什么我们还要多此一举去引入这样一个 refresh token 呢?那这里需要注意啊,我们的 access token 和 refresh token, 它们虽然都是 j w t 签名生成的这样一个 token, 但是呢,它们的区别就在于,我们的 access token 也就是用来实际的访问这样一个端点的这样一个 token 呢,它的有效时间是非常短的, 我们一般可能只给它设置成一个小时,或者是最多不超过一天,又或者是几十分钟。而我们的 refresh token 它的时间是非常的长的,它的有效期可能是 一周甚至三十天都有可能。那这么做的好处呢,就在于,就算我们的 access token 它被盗了,但是因为它的有效时间非常的短,过不了多久啊,攻击者 盗取的这个 token 呢,它就无效了,但是呢,相应的,因为它的有效时间很短,所以呢,用户可能用着用着这个 access token 就 过期了,那这个时候呢,就要借助我们的 refresh token 来对我们的 access token 做一个更新的操作啊,这也是为什么它叫做 refresh token。 那么更新是具体怎么做的呢?我们来看一下。那更新的过程呢,同样基于我们的客户端和我们的服务器,那首先呢,正常来说,我们应该是通过 access token 来访问我们服务器的端点,然后呢,这个时候我们的服务器检验我们的 access token, 此时发现我们的 access token 它过期了或者是无效了,就会发送一个四零幺的 客户响应,然后呢,我们的客户端再次基于我们的 refresh token 发起一个新的请求,去请求这样一个新的端点尝试去刷新我们的 token。 这个时候呢,我们就会叫验我们的 refresh token, 并且叫验的过程啊,需要注意,它不只是叫验这个 token 本身是否是有效的,它同时呢还会和我们 数据库上存储的这个 refresh token 进行比对,因为刚才我们最开始有说过啊,我们引入 refresh token 这个过程中呢,我们是会把它存储在我们的数据库中的,而不是单纯地进行 token 的 这样一个交易,那交易通过之后呢,我们会返回这样一个更新之后的 access token, 还有 refresh token, 这两个 token 都会被更新,因为这样的话就能够同时重置我们 access token 还有 refresh token 它们两个的有效期。然后呢,我们的客户端再存储这样一个更新好的这样一些 token, 再重新的发起请求。 那么到此相信各位就了解了我们这个 refresh token 它到底是用来干什么的,但是到现在我们好像还没怎么了解它到底是怎样进行退出登录的。那么了解了 refresh token 它的存储的过程以及更新的过程之后呢,我们来看一下它删除的过程。删除的过程其实也很简单,我们的客户端发起一个 退出登录的请求,那么我们的服务器就直接把数据库上存储的这样一个 refresh token 直接给它删掉,删掉之后呢发起一个响应,那此时呢,我们也会在客户端上去删除 存储到的这样一些 token, 那 当然这个时候其实也有可能我们的 access token 或者是 refresh token 已经被盗了,我们的用户可能还浑然不知,但是因为我们在我们的服务器上把这个 refresh token 已经删掉了,所以呢其实无伤大雅。此时呢,就算我们的攻击者他拿到了 这样一些 access token 还有 refresh token, 它尝试去访问我们的服务器进行交易,但是因为我们的 refresh token 它已经被删掉了,所以呢会返回四零幺,这个时候呢,攻击者 就算偷到了这个 reflash token 还有 access token, 它也没有办法起到任何的作用,那这就是我们的 reflash token, 它起到的这样一个退出登录,并且还能够保证我们的服务安全的这样一个作用,这样的话就能够避免我们的 token 被盗用的这样一个问题。并且呢大家能够发现,只要我们的 reflash token 它的时间设置的稍微短一点,比如七天的话,那么这种情况下呢,你连续七天不登录,那么就会提示你要重新登录,因为你的 token 已经过期了,这也是一个 非常好的来防止我们的 token 令牌被盗用的一个机制。那么了解了整套机制之后呢,我们来看一下该怎么实现它, 那今天呢,我们只实现最核心的逻辑,我们先不与我们的接口和我们的业务逻辑串起来。同样的,来到本节的 项目模板,我们来到 os, 来到 os service, 具体的逻辑呢,我们就要在登录还有注册中去实现,我们注册里面呢,首先要改掉的就是这里 生成我们的 token 的 地方,那很明显我们这里要多返回一个 token, 这里呢我们叫做 refresh token, 那 它的生成逻辑和上面是一样的,寄予同一个 payload, 然后返回的类型,我们改一下,这里要多一个 refresh token, 但是呢,就像我们刚才的那个演示文稿中 所演示的那样,这里的 refresh token 还有 access token, 它们的有效时间应该是不同的,所以呢,我们要在这里对它们做不同的一些设置。那相应的来到我们的 os module, 这里的 j w t module 这个地方呢,它们的 这个有效时间呢,其实就是无效的,我们可以直接给它删掉,我们要单独的进行配置。以及呢最好啊,我们的这个 refresh token 还有 access token, 它们两个相应的这样一个签名密钥,最好也是不同的,所以呢,这个地方 我们需要去多添加一个 j w t secret, 这里呢我们就叫它 access secret, 那 下面这个呢,我就叫它 refresh secret, 我 们去获取两个不同的这样一个密钥, 好的,我们先复制其中一个,刷新一下这个网页,再复制另一个作为我们的 refresh secret, 那 此时呢会到这里,那在这个地方呢,我们该怎么去设置它呢啊?其实很简单,在后面跟上一个对象,可以看到这里的 sign a think 这个函数,它的第二个参数呢就是我们的 j w t sign options, 在 这样一个对象里,我们就可以写上,比如这里的 x expires in 这样一个配置,它呢我就设置成十五分钟。那另一个呢,我们就设置成七天吧,稍微长一点,但是呢不能非常的长。然后呢,还有它们对应的这样一个秘钥, secret, 这里我们从环境变量中去提取,上面这个呢,自然就是 refresh, ok。 那 此时呢,我们就成功地返回了一个 access token, 还有一个 refresh token。 但是呢,我们的数据库表中,我们还没有写入这样一个 refresh token, 所以呢,我们要改一下我们的 entity。 我 们在这里呢,先添加一个字段,我叫做 refresh token, 它是一个字串, 然后呢写上 property。 那 需要注意的是啊,我们这里要存储的 refresh token 呢,它是可能被删除的,也就是可能被置空的,所以呢要设置成 nullable。 同时呢 需要注意啊,它的类型也是需要改一下的,我们这里不可能直接存储铭文的这个 token, 我 们需要对它进行简单的加密,就像我们的密码一样,所以呢,这里它的长度可能是比较长的,我们给它 设置一下它的类型为 text, ok。 然后呢,我们来到 dto, dto 这个地方,我们在我们的 create 这个地方添加上这样一个新的属性字段, refresh token, 那 么它应该是可选的,给它添加上一些内容, is optional, is not empty, is string。 回到我们的 service, 那 么在注册这个地方呢,很明显,我们就应该在这个 token 生成出来之后呢,我们把这个 refresh token 呢给它 写入在我们的这个用户中,所以呢,我们需要对我们的用户进行简单的更新,那我们看一下我们的用户更新这个操作,这里的 service 我 们有没有写好,可以看到这里有报错,是吧?那这里有报错呢?我们看一下它应该是类型不匹配导致的。我们来看一下这里的错误提示啊, 上面这些白色的我们都不要看,我们直接用这个 typescriptpretty arrow 给我们生成的这样一个简化的版本,它告诉我们这个类型和下面这个类型不匹配。我们看一下是什么地方不匹配啊?这里的 email, password, refresh token 这些呢都是一样的。那很明显可以看到上面这里是 refresh token, 后面有一个问号,下面这里呢是没有这个问号的。那这里的 create user dto 呢?我们刚才是已经改过来的, 这里是没问题的,那说明是我们的 entity 类型是有问题的。我们来看一下我们的 entity。 好, 对,这里我们忘记加上问号了,这样改好之后呢,就没有任何问题了。同时呢,我们还有我们的 update, 我 们来到这里的 update 去实现它给它改为一步的函数。同样我们要先 简单地查找这里,我们直接使用当前这样一个类中我们实现的 find one, find one id, 然后呢使用我们的 entity manager 调用 assign。 啊,大家应该还记得这个方法吧,前面呢是我们要更新的这个对象,后面是我们要传入的这些数据,那当然这些操作呢,全部都是 同步的操作啊,最终我们的操作要全部写入数据库中,我们要使用 flash, 最后返回这个对象,就这么简单我们就实现了更新,那么有了更新之后呢,我们再获取 payload 的 这个地方呢,我们去对它做一些简单的操作啊,我们获取这样一个生成好的 tokens 这样一个对象,先把它返回出去, 然后呢,在获取到这样一个 token 之后,我们调用 this user service, 我 们调用 update 对 应的 id 就是 这里的 created user id, 然后呢,我们要更新的是这里的 refresh token 这样一个词段, 那需要注意啊,我们传进去的这个数据库中的 refresh token, 它是需要加密的,所以呢,我们在前面使用我们的 be crypt, 使用我们的 hash, 我 们要加密的就是这个 tokens refresh token, 然后呢加密十次,那这个 round 我 们可以给它简单的抽象一下,或给它放到最上面一会,待会我们的这两个方法应该都是要用到的,我给它直接放在文件的最上面,写成一个常量 hash round, 在这里给它改成我们刚才抽象好的 hash rounds, 然后把报错的地方给它改过来 hash round, 那 么这个加密操作同样它是一个异步的操作啊,需要注意,我们写上了 wait, 返回得到我们的 hash refresh token, 再把这个加密后的 refresh token 呢给它写进去,这样才能够保证放在我们数据库中的这个 token 它是加密好的。 ok, 那 这样呢,最后再返回我们的 token, 这样就是 没有任何问题的了。那么相应的这些操作呢,我们可以给它简单地抽象一下,不然的话可以看到这里的逻辑有点太复杂了,比如这里使用我们的 command 加 句号,我们 extract to method in class, 这个方法我就叫做 generate token by user, 应该是改下面这个,下面这个生成出来的参数就是 user, ok, 然后呢,这里读取我们的配置的地方呢,我们可以简单地把它抽离出来,这里我们叫做 access secret, 那 下面这个呢,就是 refresh secret 啊,给它替换一下, 我们简单地做一层这个保底啊,万一这两个配置读取不到呢?我们下面这里的逻辑其实还可以再给它封装一下,又或者是这里 车里的逻辑,我们还可以再给它封装一下,我们直接选中再次 extract to method 这个新的方法,我叫做 issue tokens, issue 就是 分发的意思啊,然后接收的这个对象呢,同样是 user, ok, 此时呢,我们就只需要调用这个 issue tokens 就 可以了,然后 issue tokens 呢,又会调用 generate token by user。 为什么我们把下面这两个逻辑,也就是这里的逻辑给它合在我们的 generate token by user 呢?这是因为呢,我们的 generate token by user 从它的方法名上来看,它就是生成 token 的, 它不做其他额外的操作,它只做这个 token 生成。所以呢,我不想把这些更新用户 属性或者是自断的操作呢,也给它一股脑的扔进去,这样的话就显得这个方法呢不够纯粹。那么完成了我们的注册之后呢,我们再完成我们的登录就非常简单了,还是一样啊,这里我们直接调用 return this issue token 传入我们的 user, 注意加上 await, 那 么它的这个类型 access token, 还有这个 refresh token, 这里它的类型肯定也得改过来, 那么此时呢,应该就完成了,我们简单地给它加上一些注是,不然的话可能会有些迷惑啊,这里 generate jw tokens and returning, 我 们这里 给它加上注是 generate jw t。 下面呢就是 hash the refresh token, 这里呢, update the user refresh token。 然后这里呢也给它简单地写上来啊,那这里呢就是 define environment aribos, 那 这里呢,就是生成 token 的 地方 generate j w t。 那 么到此呢,我们就算是成功地完成了这样一个 生成 refresh token 的 整个的逻辑,那么大家可以发现,只要我给各位讲清楚了这样一套生成 token 的 流程之后呢,大家是能够很清楚地知道我们该怎样去生成这个 refresh token。 那 当然可以发现,我们这里其实只实现了登录还有注册的部分,那我们具体 退出登录,还有刷新 token 的 这样两个新的端点接口呢,我们就留到下集再讲,因为一次性讲太多的话呢,各位可能会反应不过来啊,如果你觉得还是不太理解的话呢,那么请再回头看一下我视频开头讲到的这里的 refresh token, 它的这样一套流程,希望能对你的 理解呢有更多的帮助。那么以上就是本期视频的所有内容,希望你能关注或订阅我的频道,感谢你的观看,我们下期视频再见!

hi, 欢迎回来,那么本节呢,我们就来完成 user, 也就是用户相关的一些功能。那开始之前呢,我们先来解决上一节的一些遗留问题。上一节呢,我们的游客的功能虽然是 全部做完了,但是呢,中间有一个细节,其中呢,就在我们的 articles, 我 们的 controller 这个地方,我们的 final public 这个地方,看起来好像没问题,是吧?但实际上呢,我们点进这里的 service, 它里面呢,大家能发现,我们在查找的过程中呢,并没有指定它的状态。也就是说,我们在这样一个 find one public service 中呢,我们能够查找所有的这样一些状态的文章内容,而这个端点呢,它是针对 public 的, 所以呢, 正常来说,只应该能够查询所有公开的这样一些文章内容。因此,我们这里漏掉了一个状态。那这里呢,我们需要在这里补上我们在这里的第一个参数,这里我给它改成一个对象,在里面写上我们的 id, 然后是我们的 status 对 应的状态,就是我们的 articles status 对应的美指类 published 啊,这样呢,才算是修复完成了。那么修复完成,我们来看一下本节我们要做的事情啊。本节我们要做的事情呢,我已经列在了 notion 的 笔记里。那首先呢,是我们的用户,他可以查看自己的个人信息。 这个部分呢,我们需要做一些优化,虽然我们之前已经做了 find one 对 应的这样一个端点。然后呢,是我们的 create, 那 create 的 地方呢?同样的,我们也需要做一些简单的区分,以及下面的 update, 还有 select, 以及最后的 delete。 这些呢,我们全部都要和我们的用户 id 结合起来, 并且需要注意啊,这里的用户 id 它全部都要来自我们的 token, 不 能由我们的前端来自己传输想要操作的这样一些用户 id, 这样的话就会导致权限的愉悦。而我们的用户 id 一 旦来自 token 的 话,就能够保证这个用户 id 是 不会被随意更改的,因为我们在 get 中能够检验这样一个 token 它是否是完整的,是否经过了篡改。 那我们先来完成第一个步骤,就是这里我们去获取当前用户信息的这个步骤,那在这个步骤中呢,我们需要来到 users controller, 我 们这里呢最好先添加一个端点,我这里呢就不叫它 find one, 我 叫它 find me, 并且我们不能接受这样一个 id 参数。我们这里先写一个端点 find me get, 那 么它的路径应该是 me 返回的内容呢,应该是来自我们的 user service find me 这样一个 service 函数,同时呢它需要从我们的 user service find me 这样一个 service 函数,同时呢它需要来自我们的这个 jwt token。 所以呢,我们这里要从我们的请求中获取我们的 token 对 应的 payload, 那 这里的请求中间的 payload 呢?我们已经在 get 中间完成了结构,大家还记得吗?在这个地方 我们的 guard 中间呢,它会叫验,我们的这样一个 token 叫验,成功之后,它会把这样一个 token 中间的 payload 结构出来,然后呢给它附在我们的 request 点 user 这样一个属性上,所以我们只需要在 control 中去获取我们的 request, 就 能获取里面的 payload 了,因此我们直接这样写, 在这里简单获取一下我们的 user id, 注意是 user 点 sub 给它传进去,而且需要注意这里的 user id, 它应该是一个数字的类型,给它做一下类型转换。 那么接下来我们就来到 service 去实现我们的 find me, 那 我们的 user service 里面呢?我们来到这里的 find one, 在 下面呢去实现一个 find me, 它接受的参数就是我们的 user id, 然后呢它做的操作其实也是比较简单的,它只会返回我们当前的用户信息,而不会像这里的 find one 一 样,却返回多余的文档信息。因此这里我们重新调用 user repository find one, 其他什么参数都不需要传进去。 我们应该这样写 id, user id 得到一个 user, 同时呢判断一下它是否存在,最后给它返回出去, 这就是我们的 find me。 那 么相应的上面这个 find one 呢?我们暂时用不到,对于我们当前的用户而言,好的,实现这个部分之后,我们来到 control 轮儿,注意把这里的 find me, 它的这个 control 定义放在我们的 find one 的 上面,以及我们的这个 find one, 它的类型必须是一个 number, 不 然的话呢,这里的 find one 和 find me 它们之间会产生一些冲突。大家可以想一想,如果这里的 find one, 它的这个路由定义中间的参数 id, 如果和之前一样是一个 string 的 话, 那万一刚好这里输入的这样一个 id 值,它就是 me 这样一个字母串呢?这岂不是和它产生了冲突嘛?然后呢,来到我们的第二个功能,我们来看一下第二个功能就是创建,创建文章, 一个用户可以创建它自己的一个新的文章,我们来到这里的 articles, 来到 create。 那 么首先呢,需要注意啊,在创建文章的过程中呢,可以发现,我们这里的 create article dto 呢, 它是会接收一个叫做 author id 的 参数的,这是我们之前加进去的,但实际上呢,我们是不应该 在请求过程中传入这个 id 的, 那这样的话,岂不是黑客可以随意地构造这个文章是哪一个作者的,那就能够让某一个人的 账号下莫名其妙多了这样一些文章信息。所以呢,这个 id 也应该来自我们的 token, 不 应该由我们的前端来进行传输给它去掉。来到我们的 create service, 那 这里的 author id 我 们自然就是不需要的,那么整个这个结构的过程呢,也是不需要的, 这里我们就直接写成 create article dto, 那 这里的 author id 就 应该来自我们的请求中间的这样一个 user。 所以呢,和刚才一样,我们去获取请求 request, 还是一样获取我们的这样一个 user id, 当然在这里呢,应该叫做 author id 给它传进去,来到 create, 修改一下参数列表 author id。 那么这么样改好之后呢?哎,这里加上逗号,这里我们的 create 应该就算大功告成了。那具体这里, 我们在创建的过程中呢,是给它设定成一个草稿,还是设定成一个公开的文章,这里就取决于我们的 dto。 dto 在 教验的过程中呢,也能够判断一下,这里传进来的内容必须是这里的违举类,所以呢,这里不需要我们操心。然后呢,我们能看到下面有一些报错啊,这是因为我们下面修改了 dto 的 这个类型,所以呢,这里的 update 也会相应的报错,那我们先不管,我们来完成接下来的内容啊,接下来刚好就是我们的 update, 那 么 update 呢,其实和刚才是一样的,我们还是一样来到这里的 ctrl 轮,我们来到这里的 update, 那 么在 update 这里呢,可以看到它会接收一个来自路径参数中的 id, 那 很明显呢,我们的用户它的 id 不 应该来自路径参数,因此这个地方我们给它删掉。 可以看到,在 update 的 这个地方呢,我们的路径参数是对应要更新的这样一个文章的 id, 但同时呢,我们还需要 去限制一下,我们更新的文章必须是和当前的用户相匹配的,所以呢,我们还需要传入一个用户的 id, 那 么这两个 id 呢,不可能在参数列表上都叫做 id, 所以呢,我们给它简单的区分一下,这里叫做 article id, 这里接收的就是 article id。 在 前面新添加一个参数, 我们从我们的这个参数列表中获取,我们的请求做法和上面的 create 其实是一模一样的。然后呢,通过请求获取我们的 author id, 再将我们的 author id 也给以并传进去, 来到 update 这里,这个参数就应该是我们的 article id, 给大家更新一下 article id, 然后呢,最前面这个就应该是我们的 author id, 那这里的 osid 呢,已经放在参数列表中,这里就不再需要结构了,这里就直接传入我们的 date, oracle, dto 以及我们在查找的过程中呢,我们还需要判断一下, 不仅是我们的 article id 要相同,我们的 author id 也需要相同,所以呢,在这里写上 where 条件 id 为我们的 article id, 这里调用的方法是 find one, 是 我们内置的这个 find one 方法,它不能接受这样一个对象参数,所以呢,我们这里要重新的去 使用我们的 article repository, 才能够使用这样一个 where 条件在下面呢,再写上我们的 author id, author id 等于我们的 author id, 同时判断一下我们的 article 是 否存在,不存在的话呢,就抛出异常,这个做法和之前是一样的,对吧? 最后这里的地方呢,就不用改,那到此呢,我们就成功完成了这里的 update, 这里的 to do 就 可以删掉了。到此呢,我们的 update 就 已经完成,我们再回到这里的文档,接下来就是 retrieve, 就是 select, 通过这个端点呢,我们的 用户可以查看它所有的这样一些文章信息,同时呢还能够进行查询和分页。来到我们的 control 中,那这个地方呢,我们就要和这里的 final word 做一些区分了。我们现在的 final word 呢,有两个版本,一个是给我们的管理员用的 final word, 另一个呢是公开的 final public。 那 还有一个呢,就是给我们的用户使用的这样一个 final word。 因此在这个地方我们再写一个简单的端点,还是一样 get, 那这个端点对应的 controller 方法,我叫做 find or by user。 那 么为了和下面的这个 find or 做区分,我们在今天加这样一个路径,叫做 me, 就 和刚才的那个 user controller, 刚才那个 user 中的 find one me 是 一样的,同样返回 find or by user。 然后呢,我们需要从它中间呢去获取到我们的 user id, 同时我们还应该接受这样一些 filter dto, 这是我们之前写好的,它可以接收 query 参数,还有我们的配置参数,这里就是第二个, 就这么简单,来到我们的 oracle service final by user, 给它传进去, 或者这个方法名来到 service, 我 们来到这里的 findall, 我 们在上面去先添加一个 findall 把 user, 然后是接收的参数, 那么它的查询过程呢?其实和这里的 findall 其实大差不差,只不过中间查询的过程呢,会多一个 where 条件,仅此而已啊。我们直接复制这里的 findall, 其实有一些问题啊,我们先不管,我们先完成这里的 findall 把 user, 那么有什么问题呢?首先我们这里的 query 它是有可能为空的,它是有可能为 undefined, 所以呢,我们要自己构建一个 where 对 象, 这个 where 对 象中间呢,它默认会存在的一个 where 条件,就是根据我们的 author 来匹配我们的 author id, 因为我们只能够查询当前用户对应的这样一些文章信息,因此呢,这个根据 用户 id 来判断的这样一个条件是必然存在的,但有可能会存在的就是我们的 query。 所以呢,我们要判断一下我们的 query 它是否存在,然后呢,再把这个根据开头来查询的条件呢,给它附加上去,最后再把这个开头给它写上去, 这才是一个正常的查询功能。那么相应的,下面的 final 其实也应该判断一下我们的 query 是 否存在啊, 好,其实和上面是一样的啊,那么到此呢,我们的 final by user 其实就已经完成了, 那么大家能发现在这个端点中呢,其实我们只完成了根据关键字来查询,我们并没有完成根据这个状态来查询以及进行排序的这些功能,那这个部分呢,我们留到后面再做,我们暂时不对我们的 dto 做更改,我们先把这些功能点先做好,然后再去细节的完成每一个功能点, 然后呢我们来看一下最后一个功能点,就是这里的 delete, 那 么 delete 其实和前面这几个是一模一样的,我们去找到 delete 对 应的这样一个端点,那同样需要接收我们的 article i d e, 所以呢给它改一下参数名, 同时呢我们还是需要获取我们的用户 id 或者说 author id, 然后一样的给它传进去。 ok, 来到这里我们去接收参数, 那么可以看到它这里依然用到了 find one, 我 们还是给它改回来,使用我们最原始的 repository 来进行查询,先判断一下我们的 id 是 否为 article id, 然后呢再判断我们的用户 用户 id 是 否是正常的,以及判断一下对应的文章内容是否存在。好的,那么可以看到这么改掉之后呢,我们当前的这个端点应该是仅限于我们的用户的,所以呢这里 remove by user 给它改一下,这里是 remove by user, 那 么它的路径呢?我们也稍微给它改一下,就叫做 me, 那 整个这个 ctrl 了,我们就叫做 move remove by current user, 那相应的我们前面刚才所写的这样一些和我们当前用户相关的,其实我们也应该改一下 final by current user, 以及这里的 create, 其实也可以改一下 by current user, 那 么这么区分的话呢,我们后面就能够和管理员要用到的这些 control 函数,在方法名上或者说函数名上有很明显的这个不同,当然这个命名方式只是我一拍脑袋想的,你完全可以使用不一样的命名方式。那这里的 service 呢?我们干脆也给它改掉 by user。 我们来检查一下,把这里的 current user 全部都检查一下,把 public 全部都省略掉,这里是管理员使用的,没关系,这个也是管理员使用的,对吧? 下面这里的 update, 同样 by current user, 这里呢也给它改掉 by user。 然后呢,是这里的 find one 啊, find one, 其实我们也应该给它先添加一个用户可用的版本, 那这两个 find one 和 find one 很 明显这是留给我们的管理员来使用的,而我们的普通用户还没有这样一个 find one 的 版本,因此我们自己来新建一个 get, 那可以看到整个流程的话,其实和下面这些大差不差啊。我们来到这里的 service 去新建一个, 这里的 find one 呢,就仅限于我们的 admin 可用了。在这里呢,我们新建一个 find one by user。 好 的,那这里的模板代码全部都是和刚才重复的,所以呢,我就不再重复写了。好吧,那么此时呢,大家能够发现啊,其实 这里呢有很多重复的模板代码,比如这个地方这个部分呢,很明显是会有较大的重复的,那我们干脆就给它抽象一下,我这里同样 command 加句号 extract to method in class。 这个新方法我们就叫做 find 啊,对啊,应该应该是 find one with with author id。 ok, 好, 我们再来看一下,哪些地方呢?我们可以给它改过来,比如这里,我们就可以给它替换成我们刚才写好的 article, 等于 this find one with already。 注意啊,这里就要加上 wait ok, 还有这里的 delete 也是一样。 我们来看一下这个方法呢,我给它放到最下面去啊,不然的话呢,放在中间可能会产生一些困扰 这个地方的注视给它删掉。我们再来检查一下所有 by user 的 地方。好的,应该是没问题了。 那么到此呢,我们就成功完成了除开这个排序,除开我们的评论功能之外的所有和用户相关的这样一些功能。 当然,我也不敢确保当前的所有的功能都已经是做完了的。那具体的测试呢?我就不再重复做了,有什么问题的话,我们下节再来修复。那么以上就是本期视频的所有内容,希望你能关注或订阅我的频道,感谢你的观看,我们下期视频再见!

hi, 欢迎回来,那么本节呢,我们来直观的对比一下 nest js 它的整个项目的设计,以及它为什么要这样设计这里呢,我准备了一个提前构建好的 express 项目, 我们上次的那个 express 项目呢,只是简单的从官网复制了一段代码,但这里呢,我将它中间的内容呢进行了划分,这也是我之前在我的 node js 解密教程中所做的事情。它大概的架构图呢,就是这样的, 从我们的 main js 中间引入相应的路由定义,而路由定义中呢,又引入了我们的 controller, controller 中用到了我们的 service, 其实大致就是这么几个文件。可以看到,在路由定义中,我们需要定义一个路由对象,然后绑定这个根路径,将这样一个路径 和对应的 get 请求绑定成一个端点,然后呢,将这个端点交由我们的 get hello 这样一个 controller 来处理。然后 controller 中呢,它又调用了我们的 service, 也就是这里的 app service, 它会返回我们的字幕串,那这只是我们在构建整个应用对它进行划分的过程。而真正当我们的应用需要启动的时候呢,我们又需要 通过 node 命令来运行我们相应的文件。并且这里可以看到,我为了让它实现和 nest js 中一样的这个热宠载的功能呢,我需要在这里添加一个 watch, 添加一个 node js 内置的这样一个 watch 功能,需要注意是在我们的 node js v 二零以上才可以使用。可以看到我做的所有的这些工作,不管是划分这里不同的文件夹, 还是添加这里的这个运行命令,这些所有的工作都是要我们手动去做的,它并没有自动的帮我们完成。而这些所有要我们手动去完成的工作呢,在我们的 nest js 项目中,全部都是自动完成好的。 所以呢, nest js 本身它给我们提供的能力最重要的一点就是它给我们提供了一个完整的架构,这个架构呢,不需要像现在这样需要我自己去手动创建,那这就是 nest js 它的设计哲学。我们可以来到 nest js 的 官网,在右边找到 philosophy, 这样一小段话就展示了他的设计哲学。 nest provides an out of box application architecture, which allows developers and teams to create highly testable, scalable, loosely coupled and easily containable applications。 并且非常重要的一句话, the architecture is heavily inspired by angular, 它受到了很多来自 angular 的 启发。所以呢,如果你有学过 angular 的 话,你会发现它里面的 很多设计方式,它里面的很多的写法其实和 angular 是 差不多的。那这就是 ness 的 它的设计哲学,它就提供了一个 开箱即用的这样一个架构。那作为对比的话,我们再看一下整个 ness 的 jess 的 项目到底是怎么组织的。那这里我们同样的还是从零创建一个新的 ness 的 jess 的 项目,我们在终端中打开,还记得我们创建 ness 的 jess 的 命令吗? ness new, 那 在创建这个项目的过程中呢,大家会发现它会自动地为我们安装相应的项目依赖,并且会自动地将这样一个项目初使化为一个 get 仓库,对吧?如果我们不想让它忘帮我们自动安装项目依赖,也不想让它帮我们初使化 get 仓库,我们该怎么做呢?这里我们可以看一下官方文档啊,在官方文档的左边,在下面找到 c l i, 比如是我们自己使用的这个命令行工具,找到 usage, 也就是它的用法。这里它其实提供的这个用法呢,就这么几个,只不过每一个命令它的后面都可以加上不同的这样一些参数。比如我们使用的这个 nest 的 new 命令,它只是用来创建一个新的项目的,但是呢,我们可以 根据不同的参数对它做一个调整,比如,哎,就像我们刚才所需要的,我们需要跳过 git 仓库的这个初步化过程的话,我们就可以输入 skip git, 或者是通过一个简写 杠 g, 又或者我们可以跳过这里的包管理安装的过程,我们可以输入杠 s, 所以呢,有这两个参数就可以快速的完成一个项目的创建,回到我们的应用中,这里我们就叫 nest app, 后面通过我们的杠 g 还有杠 s, 这样就可以快速跳过我们的项目依赖的安装。还有 get 仓库的抽象以及 nest 的 js, 它是可以通过 javascript 来创建的,但是我们之前好像并没有演示过好,这是因为你需要在我们的选项中,在这里通过杠 l 选择 对应的编程语言,不然的话默认都是以这个 type script 的 为主。那我们这里加上了杠 g 和杠 s 之后呢?回车选择包管理器,可以看到它马上就把我们的项目的创建好了,就没有之前那么慢,之前我们要等一下,其实就是因为它需要安装我们的项目依赖, 那此时呢,我们跳过了这个过程,那它就非常的快了,那我们还是照例把这些不必要的文件还有项目依赖都给它删掉。 ok, 那 我们该怎样生成一个和刚才我所演示的这个 express 项目相同的这样一张架构图呢?生成这个架构图的话,我们要借助一个工具叫做 match 啊,这个工具呢叫这个名字啊 match, 你 可以直接全剧安装,或者是安装在我们的 项目的依赖中。好,这里我带大家全剧安装来演示一下,安装好之后呢,我们直接使用就行了, 那使用的脚本呢?你可以借鉴我这里给各位提供的 express 项目中间的这个脚本,通过 match image, 然后 输入我们要生成的这样一个架构图,它的入口文件。比如这里的 express 项目,它的入口文件就是 main js, 如果是我们的 nest 的 js 的 话,它的入口文件很明显就是这里的 main t s。 来到我们的 nest app 这个项目中, match image architecture, 那 这个文件的后缀呢?你可以生成一个 p n g, 但我个人更喜欢 svg 啊。这里我们要对应的入口文件呢,是 s r c 目录下面的 main t s。 回车 等待它进行扫描,然后呢就生成出来了这样一个架构图。可以看到它的架构和我们之前这个 express 项目的架构其实大差不差,只不过呢,在我们的 nest 的 js 中呢,它多了一个叫做 module 的 东西,那这个 module 呢,其实就是将我们 nest 的 js 中的 这些 controller 和 service 整合在一起的这样一个简单的工具,或者说你可以把它当作是一个简单的基座,这个基座上面可以承载不同的东西, 最后呢,要把这个承载不同东西的这个基座本身呢,作为创建项目的依赖。所以呢,大家会发现,我们在 nest 的 项目的 main test 中呢,它是会 导入我们的这个 module 文件,以它为基础来创建我们的这样一个服务器对象的。当然我们在划分功能的过程中呢,也会基于 module 来进行划分,所以 module 是 nest, 这是项目中一个非常重要的概念,大家也可以看一下官方文档它关于 module 的 说法,那这里呢,我们要来到左边的 overview, 来到这里的 modules。 那 么关于 module 它的说法呢,其实也很简单啊,首先呢,它告诉我们 nest js 项目呢,它至少都有一个 module, 那 就是 root module, 这个项目呢就是我们整个 nist 项目构建的一个开始点,也就是这里的 starting point。 所以呢,我们的 nist js 项目呢,它本身一定要基于一个 module 来进行创建,同时呢,它还告诉我们 modules are highly recommended as an effective way to organize your components。 它同时还推荐我们通过 划分这个不同的 module 的 形式来组织我们的组建。其实说白了就是我们我们的整个类似的 js 上面要进行功能划分的话,一般都是会把它划分成不同的 module 或者说不同的模块。而 module 它的定义其实很简单,一旦一个类带上了一个 module 装饰器的话,那么它本身就是一个 module 或者是一个模块了,所以呢,你会发现在我们的 app module 文件上面,它都有一个 module, 那 么它既然作为一个接作,那么它肯定本身就会引入我们的 controller 和 service, 所以呢,大家就能够理解为什么在 一个 module 文件的上面,它会定义我们的 controllers, 还有这里的 services 了啊,至于为什么 services 前面是 provider, 而不和其上面的这个 controller 一 样写成 service 呢?那这个地方呢,就 涉及到我们的依赖注入,还有控制反转了啊,这方面的概念呢,说实话是比较的复杂的,要解决它的话呢,其实就需要解决我们的 controller 中,为什么它能够直接 引用我们的 app service, 而不需要创建这样一个对象实力。这个部分的内容呢,我们就留到下一集再讲,因为它本身是比较的复杂的,而且它的很多细节是隐藏起来的。我们本节的重点呢,还是去理解 ness 的 jess, 它的整个的项目结构,它比起我们的 express charm, 它到底做了哪些优化, 它的整个设计的哲学是怎样的?那么可以看到 ness 的 jess, 它对我们而言最大的帮助就是直接给了一个开箱可用的应用架构,不需要我们去手动的创建,以及这里的这样一些 启动的命令全部都是写好的,不需要我们去手动写,以及对于各位来说可能比较麻烦的一点,它引入了一个 model 的 概念,这个 model 的 概念如果你之前有学过 angular 的 话,你会觉得非常亲切,因为它的划分方式和 angular 中间的 model 划分方式是一模一样的,如果你不习惯这样的方式,你之前可能只写过 react view, 又或者是之前只写过 普通的 express 项目,你可能会比较难以理解为什么要以 model 的 方式来划分,这样划分的好处又到底是什么呢?那么 model 的 好处呢?我们会在后面做一个实际项目的时候呢,给各位体现出来,在 在当前这个阶段呢,各位只需要有一个简单的概念就行了。那么以上就是本期视频的所有内容,希望你能关注或订阅我的频道,感谢你的观看,我们下期视频再见!

hi, 欢迎回来,那么上节呢,我们大致了解了 refresh token 它的工作原理,以及它是如何为我们实现退出登录这个目的的。 但是呢,上一节我们只是在我们的登录还有注册中实现了 refresh token 相关的逻辑,我们还没有实现对应的刷新,对应的退出登录,那今天呢,我们就来完成相应的内容,先把这些方法呢给它收起来。那我们今天要做的,首先呢,第一个是这里的 refresh refresh, 那 么 refresh 呢,很明显,它要接受我们的 refresh token 作为我们的这样一个自助串。 ok, 然后呢,第一步先叫验我们的这样一个 refresh token, 那 这里呢,我们使用 jwt service verify a sin 传入我们的 refresh token。 那 么在校验的过程中啊,我们这里是可以指定它的 secret 的, 那么很明显,这里的 secret 我 们要多次的用到,因此呢,干脆啊,我们这里的配置内容呢,我们我们给它单独的拿出来,把这里 复制粘贴到上面去,我用全大写吧, access secret, refresh secret 给下面简单地替代一下, 这里也是一样给它收起来。那么在这里我们的 secret 呢,这里很明显就应该是 refresh secret, 然后呢,判断的结果,它是一个 promise, 对 吧?那么如果 判断它不对的话,它应该是默认会抛出相应的异常的,这里我们不用管。那么这里判断完成之后,一旦它通过了,接下来我们要做的就是在数据库中去进行查询,然后判断。因此接下来我们调用 users service, 我 们使用 find one, 那 么这里的 find one 呢,我们要基于我们的用户 id 来进行查询,所以呢,我们要基于我们的 refresh token 来获取我们的用户 id。 因为大家很明显能够看到我们在生成的时候呢,我们是把我们的 user id 作为一个 sub 给它传进到 我们的 payload 的 中间的,所以呢,我们是可以从这个 token 中间反向的结构得出我们的这样一个 id 的。 那么这个地方呢,我们想要从中获取到我们的用户 id, 其实这里的 verify i think 它返回的就是我们的 payload, 只不过呢,这里如果我们直接这样写的话呢,它是没有这样一个类型校验的,你会发现它返回的类型呢,竟然是一个 any, 那 当然这个是很正常的,因为我们没有在解析的过程中呢给它指定任何的类型,那它返回的这个 delete 自然就是一个 any 类型, 所以呢,这里啊,我们暂时这样写,我们后面呢肯定会对它进行一定的改正。那我们这里调用 find one 返回得到我们的 user, 那接下来呢,就是进行比对了,我们使用 be 匕,使用 compare, 因为我们的这个 refresh token 在 数据库中是通过我们的 be 匕进行加密的,所以呢,我们要比对一下 传进来的这个 token 和数据库中查询出来的这个 token, 当然这个是加密后的内容,它是否是一致的,然后这里呢,它提示我这里可能有问题。很显然,我们要判断一下我们的 user refresh token 它是不是存在的,只有当存在的情况下才应该进行判断,如果不存在呢,直接抛出我们的 unauthorized exception, 然后里面的错误信息还是一样统一的 invalid credentials, 也就是 如果我们的用户中间的这个 refresh token 它本身都不存在,就说明我们这个用户它是有问题的。那么比对的结果同样呢,我们要写一下,然后呢根据这个结果判断一下,如果为 false, 同样返回 authorized exception, 那 全部都通过之后呢?这里呢,我们再次调用之前我们写好的 this issue token, 传入我们的 user, 它的结构不太对啊,这里我想想我们能不能把它稍微简化一下呢? 我们其实只需要用到 user 对 应的两个东西啊,一个是 id, 一个是 email, 我 们其实只需要用到这两个东西就行了,我们不需要一个完整的 user 对 象,我们改一下,我们这里改为 id, 还有这里的 email, 这里两个就来自我们的 user id, 还有 user 到 email 拿到我们的 issue tokens, 我 给它改一下 id, 还有 email, 这上面同样的给它改过来, 然后呢,这里缺失类型提示啊,我们给它加上这两个基础的给它改好,改好之后来到上面,我们在 sign in 和 sign up 这个地方呢给它改过来, 那这里我们的 refresh 这样一个 service 应该就是完成的了,我们来简单的加上一些注士,我就让我的 ai 插件帮我写,好吧,因为这些东西呢,是很简单的,我们就不费神了。那最后我们来实现这里的这个退出登录 sign out, 那 么退出登录呢,就和刚才的 refresh 不 太一样, refresh 肯定是需要我们的 refresh token 来实现,那退出登录的话呢,我们 需要使用当前有效的这样一个 access token 来实现,这个相信大家应该是很好理解的,我们只有当前的用户 他的访问令牌有效的情况下,他才能够退出他的状态,不可能随便一个黑客拿着无效的一个令牌就能够让当前的用户被踢下线,这很显然是不正常的。 所以呢,这里我们接受的参数就是 access token, 那 么这里就非常的简单了,我们去校验一下就可以了,那么这里校验我们的 access token 没问题。那么接下来呢,我们就在这里调用我们的 this user service update 传入的用户 id, 那 这里的用户 id 依然要来自我们的 payload, 然后呢要更新的内容应该就是这里的 refresh token, 那 么这里的报错呢,其实很简单,它同样是提示我类型不兼容,对吧?它这里呢是和我们的 create user dto 呢,不对,这里为什么呢?明明我们已经打了问号的,为什么这里不能设置为 null? 那 么在 type script 或者说在 javascript 中,如果一个类它的属性后面跟上一个问号,那么表示这个类呢,它可以设置成一个 define, 也可以设置成它本来的类型,但是它和 null 是 没有任何的关系的, 而我们的数据库更新,它是需要设置成 non 值的,所以呢,这里为了给它改过来,我们只能在这里写上我们的这个 union type 组合类型, 这样写好之后呢,它就可以设置成 non。 这个是 javascript 还有 type script 的 中间一个非常经典的问题啊,那么成功更新之后,相信就没有任何问题了,我们返回一个相应的内容啊,这个返回的内容我们就留到 control 了中间去写, ok, 那 么此时呢,我们的 reference sign out 应该就 没什么问题了。我们接下来的话呢,去完成我们 controller 的 部分复制一下,这里应该是 refresh, 我 把它的名字改成 refresh token, 那 么它接收到的参数是什么呢?很明显就是这里的 refresh token。 然后呢,我们这里调用的方法就应该是 refresh 给大家传进来。那需要注意啊,这个东西本身啊,它是会被映射成一个对象的,而不是一个单纯的属性。所以呢,我们这里应该是叫做 body, 稍微好一点,它的类型呢,我们在这里手动地写上是这个 refresh, 然后呢,我们传入 body 点 refresh, 这样就可以了。 ok, 然后呢,是我们的 sign out, 应该是 log out, 我 想想, 这里所有的 controller 它都是叫以 sign 开头,那这里呢,都是 sign in register。 好, 我这样写法应该是没问题的,那它接收的内容,这里的 log in register refresh logout。 那 接下来我们就来测试一下相应的配置呢,我们先检查一下,检查我们的数据库连接, 需要注意啊,我们之前的内容中呢,我们是更改了 user 表中的这个 refresh token, 它的一个类型定义,而具体这个地方应该是要改过来的,它是可以为 non 值的,这个原因呢,就和刚才的 dto 其实是一样的,这里它是应该允许为 non 值的。那么接下来我们还应该把这个 anti user 表的这样一个迁移计划呢,给它创建出来 migration create, 我 们来看一下这个迁移计划是否是我们想要的。这里应该是三月十八号,今天我们创建的迁移计划,那这里呢,是新增了这样一个 column, 新增了这样一个列。好的,那我们来直接 migration fresh。 说实话,同时呢,同步我们的 migration 迁移计划。 此时来到我们的 nio 数据库,我们去检查一下,打开我们的 user 表,我们来看一下这里的这个列。 这里的 refresh token 可以 看到全部都是为 non 值的,因为我们在插入的时候就没有插入这个 refresh token。 那 我们接这里呢,我们只测试这么四个端点,因为我们还没有将我们的 j w t 这样一套健全系统应用在我们的 g r 的 中。并且呢,我们这套系统目前其实还有很多地方呢,我们没有解决, 尤其是刚才我们在获取这里的 payload 的 时候,比如刚才的 refresh 这个地方,我们在获取 payload 的 时候呢,这里是没有类型提示的,这样做很明显是有风险的。所以呢,我们这里加上 to do, 我 们后面再看具体的解决方法, 包括还有这里的 signout, 它去解析的时候,我们也是需要去给它加上类型提示的。 ok, 我 们来运行这个项目,打开我们的 bruno, 我 们选择去注册这里报五百啊,说明我们这里的服务器是有问题的,我们看一下 这它的异常错误是什么呢? j w t secret is not defined。 我 们看一下第一个它提出问题的地方。 generate token by user, 我 们跳转过去, 好,是这个地方,那说明我们的环境变量它没有正常的加载进来,并且这里的呃,变量名有点问题啊,那这里是因为啥呢?好,我先把这里删掉 啊,这里其实很简单啊,我们在加载的过程中呢,因为我们的 nest 是 基于我们的类进行注入的,那么注入的过程呢,它是类优先的,所以呢,我们整个这个 secret 相应的内容,最好是把它放在我们的类中间去使用, 所以这个地方我们直接剪切给它放到单独的这个类它的属性上,然后我给它改一下名字,啊,稍微这里应该是 secret, 这里也是一样 secret, 不 然我们这种不伦不类的编写 这种拼写有点有点奇怪啊。这里就是 refresh secret 前面加上了一个 this, 然后是这里 access refresh, 那 这里它应该会自动重启啊,但是呢,刚才我们注册的时候,它是会把这样一个数据先写进数据库,然后呢再生成这样一个 token 的, 所以呢,虽然我们这里生成 token 的 过程呢,它失败了, 他报五百错误了,但是呢,我们这样一个用户,他其实是被写进去了的。其实呢,这里我们的逻辑是有问题的,我们应该先生成,然后再去写,可以看到这里的用户他是写进去的,我把这个给它删掉,我们再来测试一下。回车啊,这样呢就是没问题的了,我们有这个 access token, 有 这个 refresh token, 我 们复制这里的内容, 我们再来登录,登录的话应该会拿到一个截然不同的 access token, 还有 refresh token。 因此我们把这里注册的部分呢,我们先截个图,和我们待会儿登录的这个部分呢进行对比 登录,登录没问题,然后我们对比一下深层的 token, 那 很明显,这里的 access token 还有 refresh token 全部都变成新的了。那相应的,如果我们拿着刚才注册的这个 refresh token, 我 们尝试去 refresh 的 话,那很明显应该是会报错的哦,这里呢, 很明显发生了错误啊,我们拿着刚刚已经失效的这样一个 refresh token, 我 们尝试去刷新,既然还能够刷新出来,那说明呢,我们的代码逻辑呢,肯定是有问题的,我们来检查一下。 好的,那么现在呢,这样一个诡异的情况就这么发生了,明明我们在注册在登录之后呢,注册时返回的 refresh token 应该会被登录时返回的 refresh token 给它挤掉才对。 我们能看到它返回的这两个 refresh token, 它的字母串本身就不是相同的,但为什么我们依然能够使用这样一个 refresh token 一 直不断地来刷新呢?这显然是一个 非常非常不正常的现象,就是因为我们的 jwt 在 签名的时候呢,它的内容本身就是有一定的重复性的,能看到我们在进行这里的 jwt 签名的时候呢,它只接收了我们的 id 还有 e mail, 它其实并不能从 id 和 e mail 上 来决定每一个签名的唯一性,我们还应该加入一个新的东西,来保证每一个签名都是独一无二的。 这里呢,其实很简单,我们可以加上一个简单的字段,比如啊,就比如这里的 j t i, 那 j t i 是 什么意思呢?那 j t i 就是 jason web token id 它的缩写, 那么对应这里,我们要通过随机生成的方式来实现,那这里我们的 id 呢,可以用上我们的 random u u id, 这里呢是 node js 内置的一个工具啊,我们直接这样返回就行了, 它是能够直接生成一个随机的 u u id 的, 那所谓 u u id 是 什么呢?这里我们先不做解释啊,大家就把它理解成一个 会随机生成的这样一个字幕串就可以了,它是来自我们 node js 核心库的,不用我们去安装什么额外的东西。那么这样实现之后呢,我们来把刚才注册的这样一些新的用户给它删掉, 删除,我们来重新运行一下,来到 bruno, 我 们重新的来注册一个新的用户 啊,可以看到我们加上了 jti, 也就是 jasonweb token id 之后呢,我们的 token 它的长度变得更长了,因为我们的 payload 的 它变得更多了,然后呢,我们来到这里的 log in, 我 们去登录一下,好的,那此时呢,我们拿着刚才注册时返回的这样一个 refresh token, 我 们去尝试刷新。 那么当前这个诡异的问题呢?其实啊,说实话比较棘手啊,这并不是我们的 jwt 签发的时候有问题, 米姐,就算我们等了很多秒之后呢,我们加上了刚才的 g t i 啊,依然没办法解决这个问题,这说明呢,我们在比对我们的签名的时候呢,存在问题,那么具体比对的地方呢?其实就是这里啊, 这个地方,这个地方比对的时候呢,它其实发生了问题,就是因为呢,我们的 b、 c, 它在比对的时候呢,它不会比对非常长的这样一长段的字母串, 而我们的 j、 w, d token 呢,它就是这么一长段的字母串,它只会截取前面的一些,而可以看到我们的这两个 token 呢,不管是 access token 还是 refresh token, 它们在用相同的算法 进行签名生成的时候呢,它前面的一小段其实都是一样的,所以呢,在对比的时候呢,很明显就是会出错的。 也就是说呢,在对比的时候呢,我们的这里的 beeper 它就不太可靠了,要么我们把它的这个 refresh token 给它做一次处理,要么我们换用其他的比对方式。那么这里呢,我们在深层的逻辑上呢,我们就要改一下,我们把这里 issue token 这里进行加密的部分呢,我们 改成其他的,因为本质上呢,这里我们并不是对密码做加密,所以呢完全没必要用 beeper, 我 们自己来构造一个加密的这样一个小函数, private hash refresh token 啊,接收的是 refresh token, 因为我们要对 refresh token 做一些处理,我们想让它变得更短一点,那这里呢,我们可以用什么呢啊?我们可以用 create hash, 这也是刚才我们这个 node js 内置的库,我们使用这个上二五六, 然后呢调用 update digest 传入 hexadecimal, 这样呢就能够保证它返回的这个内容呢,它的长度是固定的,我看看这里没有导入吗?导入一下,然后呢用它来替换我们这里的 becraft hash 这个操作, this hash refresh token, 然后呢,比对的时候,也就是在 refresh 这个地方,这个比对的时候呢,我们就不用 becraft compare 了,我们就直接使用刚才的 this hash refresh token, 我 们把传进来的 refresh token 再做一次简单的这个处理,然后呢和我们的这个 user 点 refresh 做一次比对就可以了,这样的话,我们就不用到 becraft 对 我们的 gwt token 做任何的处理。那这样修改之后呢,我们再次重置我们的数据库,重新来测试一下,看看刚才那个 token 不 过期的问题是不是依然存在。启动我们的项目,来到我们的 brono, 同样的先注册, 然后返回的 token 长度和刚才是差不多的,但是我们先来到这里的数据库,那此时呢,你会发现我们的 refresh token 它的长度呢,其实就没有刚才那么夸张了。此时呢,我们再来到 bruno, 我 们再登录, 登录之后呢,我们这里的这个 refresh token 它应该会被改掉。好,我们再拿刚才注册的时候返回的 refresh token, 我 们尝试去刷新 回车,那这个时候呢,我们的 token 它就真正的失效了,因为我们刚才在比对的时候呢,错误地使用了 beecraft, beecraft 在 比对的时候呢,就有我刚才所说的这么一个长度截断的问题。好的,最后是 logout, logout 的 地方呢?我看一下,我检查一下啊,应该是在 controller 这个地方,应该是 logout 保险啊,应该是 signout, signout ok, 它会接受我们的 access token, 那 么 access token 呢?应该是刚才登录时候返回的这个东西啊,这里如果我们复制刚才得到的 refresh token 的 话,那很明显它是会报错的,这里可以看到是我们的服务器内部报出。我们看一下 这里的错误,它提示的是什么呢? invalid signature 报错是正常的,只不过呢,我们的 nest 的 js 它默认不会处理这个错误。所以呢,之后我们需要写一些简单的 filter, 对 我们的这个 j w t 内部的错误呢做一些处理才行。这个错误呢叫做 j d json token arrow。 那 接下来我们传入正确的这样一个 access token 粘贴回车,那此时呢就正确地进行注销了。当然我们没有写这个响应体啊,所以呢,它什么也没有。我们刷新一下好这里的 refresh token, 它就成功地取消掉了。然后到此呢,我们就算是成功地完成了 refresh token 以及相应的退出登录的逻辑。我们的整个健全和 j w t 的 部分呢,算是做好了,当然目前我们还没有用它来保护我们的接口,对吧?那这个部分呢,我们就留到下一节的,再把它结合进去。那么相信本节呢,是比较的麻烦的,因为我们 涉及到了一些 refetch token 相应的逻辑,并且呢解决了一些其他的问题啊,我们发现了 becraft 在 j w t 这个场景下的一些简单的缺点,我们需要自己 对我们的 token 做一些处理,但是呢,其他的逻辑相信各位是能够理解的。我们上一节已经讲过了,我们要做的呢,就是去解决一些细枝末节的问题,那么以上就是本期视频的所有内容,希望你能关注或订阅我的频道,感谢你的观看,我们下期视频再见!

hi, 欢迎回来,那么本节呢,我们就实际的来上手看一下该如何配置我们的 micro r m, 并且呢,连接上数据库,以及将我们的实体类和数据库表进行同步。那么这里我们还是一样啊,来到 technics, 找到 database, 找到这里的 micro r m, 那 么第一个链接呢,它给出的是 micro r m 它的官网链接,另一个呢,是 nest js 中关于 micro r m 的 一个简单的说明,但是呢,我 并不推荐各位去看这个 nest js 中内置的这样一个说明。首先它的内容呢,本身就是偏少的,还有很多的内容呢,并没有讲到,并且它推荐给我们的这种配置的方式呢,在我看来其实是比较的麻烦的。可以看到它安装好了这样一些橡皮赖之后呢,它直接将这个 micro r m module 然后 写在了我们的根组键中,并且呢,它竟然将配置文件都写在了这个 module 里面啊,虽然它能够把配置文件单独的 抽离在其他的一个文件中,但实际上呢,这种方式啊,我觉得依然不够优雅。我们来看一下另外的方式。那么这里呢,大家能发现啊, 和我们之前所学的所有的东西都不一样, micro r m 它提供的和 ness 的 js 进行配合的这样一个工具包呢,它的前缀不是 ness 的 js, 前缀是 micro r m, 那 这就意味着呢,这个工具包其实是 micro r m 给 ness 的 提供的,而不是 ness 的 给 microrm 提供的。因此呢,我们要来到 microrm 的 官网来学习是比较好的选择。来到官网之后呢,找到 get started, 它默认的这个用法指南呢,它针对的是一个普通的项目,比如 express 项目,或者是 fastfine 项目, 所以呢,我们要在左边找到 recipes, 找到这里的 usage with nestages。 这里呢,就给出了我们需要安装的一些项目依赖,当然这里其实给的也并不完全啊,待会儿呢,我们再来看一下缺少什么。首先呢,我们先来出实化一个最简单的项目,来到我们的 part two 这里,写上本节的 课程文件夹, ok。 然后呢,打开我们的这个课程文件夹,我们来新建一个 nest js 项目, nest new 杠 g 杠 s, 选择 p n p m ok, 注意啊,我在创建的时候呢,后面加上了一个点,也就是指定当前这个目录为我们的项目目录,不然的话,它会在我们当前这个项目目录的下面呢,再创建一个新的项目目录, ok, 还是一样啊,删掉不必要的依赖, 删掉不必要的文件,安装我们的项目依赖。然后呢,我们来到 micro r m。 在 这里的 usage with ness d g s 中呢,找到我们所需要使用的对应的这些项目依赖。这里呢,它给的这么多项目依赖,区别就在于,最后, 关于不同的数据库,它给的依赖呢不一样的,那这里呢,我们还是以最经典的 postgrace q 幺数据库为例,我们复制这里的项目依赖,直接安装 okay。 然后呢,大家会发现,其实啊,这里的官网它给的这个配置方法呢,还是一样, 那依然把这里的 module 写在了我们的根组键中,同时呢,大家会发现,它这里的配置文件呢,依然写在了我们的这个根组键 上面的注解中,那这种方式呢,依然是不推荐的,那下面呢,给了另一个方法, alternatively we can use config the c i by creating a configuration like and then cause a forward without any arguments。 就是说呢,本来我们需要在这个 microrm module 在 它的 for route 函数中传入这样一个对象作为它的配置,那作为替代的话呢,如果我们不想这样写,我们可以去配置一下 microrm, 它的 c r i。 对, 你没有听错啊, microrm 它作为一个 o r m 工具呢,它也是有一个 c r i 工具的。 点过去看一下,这里呢,它推荐我们先安装。那这个 c r i 工具呢?还是不推荐像它展示的一样,把它作为一个依赖进行安装?我们还是把它作为一个开发依赖安装比较好。回到这里,杠 d ok, 那 这个 c r i 工具该怎么用呢?那用它的前提是我们必须要先创建这样一些简单的配置文件,所以呢,这个地方它给的指令其实不是特别好啊。具体创建这个配置文件的过程呢,我们要来到左边的 getting started chapter one first entity。 在 右边呢,找到 setting up c i。 那 这里呢,它提供了两种方案,一个是创建一个 config 对 象,另一个呢,是创建 一个 define config 对 象。下面这个的作用其实就是,如果你使用的是 javascript 而没有使用 type script 的 情况下呢,它推荐你使用下面这种方式,它能够 让 javascript 也能够得到相应的类型提示。所以呢,我们直接使用上面这种方式就行了。那可以看到它的配置文件呢,创建在 s r c 下面,但实际上呢,这个配置文件你可以把它放在项目的根目录,它也是能够自动地提取到的,大家不需要去操心, 我们先复制一下,而这个文件名,那具体的说明呢?其实在下面啊, alternatively, you can use micro or an config js file in the root of your project。 也就是我们可以把这个配置文件呢,放在项目的根目录,也可以放在 s r c 下面,它是能够 loaded automatically, 它是能自动加载的。 ok, 来到我们的项目根目录,我们直接创建这样一个新的配置文件,把对应的内容呢粘贴进去,我们来看一下具体有哪些报错。首先呢,很明显,我们使用的这个数据库呢,它不应该是 sqlites, 我 们使用的是 postgrads ql, 所以 这个地方导入的包名我们要改一下。 post grade s q l 这是我们刚才安装的,然后中间呢,有报错,因为它使用的这个 driver 驱动呢,很明显不应该是 s q light driver, 它应该是 post grade driver, post grade s q l driver, 下面这里的 driver 也要改一下, 那么下面这里的 dbname, 也就是数据库名字呢,很明显,这些东西,它应该作为我们的数据库配置,写在单独的一个配置文件中。虽然啊,这个 microrm configtes 它也是一个配置文件,但是呢,我依依然不推荐大家把所有的这些,比如数据库的 名字,数据库的 url, 数据库的连接密码这些呢,写在这个文件中啊,你虽然能够这样写,但是呢,我依然不推荐你这样做,我们更推荐像之前那样写在一个 点 emv 文件中,所以呢,我们把这里的 dbname 给它删掉,同时呢,大家会发现这里有一个 microrm reflection 这样一个依赖,它是找不到的。这个东西呢,是比较重要的,因为我们的 microrm 呢,和 nest 的 这些一样,都用到了 typescript 的 中间的装置器,并且用到了原数据反射这样一个能力, 所以呢,需要这个依赖来提供原数据反射这样一个能力,所以呢,需要这个依赖来提供原数据反射的能力。这么多缺失的东西,该去哪里安装呢?其实就在这里的 chapter one 上面的 setting up, 在这里它就告诉我们需要安装这些东西,所以呢,这里我们要安装这个 micro or i m reflection, 同时呢,这里还提示我们可以安装 micro or i m c l i。 那 下面这些东西呢,就不用安装了,这些呢,我们的 nest 的 这些都是自带的,这里的 v test 的 话,我们暂时不会设计的 安装 ok, 此时呢,我们的 microrm config is 我 们就成功配置好了,但是呢,我们还缺少和数据库相关的配置,对吧?那数据库的配置呢,我们要单独的放在一个点 e n v 文件中, 那难道我们要像之前那样写 d b name 或者是 d b password 吗?啊,正常来想,我们好像就是应该这么写。而我们的 microrm 呢,关于环境变量或者说点 e n v 文件中间的值呢,它有另外的一些理解。我们来它的文档中搜索 environments variables, 找到这里的 reference 下面的 configuration。 这一节中的 using environment variables 在 这里呢,它提示我们,其实我们只需要在点 e n v 文件中写上带有 micro o r m 前缀的这样一些环境变量,并且它的名字一定要是 和它指定的这些名字匹配的情况下,它就能够自动地读取这里的环境变量,我们就不需要手动地将这些环境变量呢用在相应的地方,它能够自动读取。那么具体我们要用到的东西呢?其实就这些啊, host, port, user, 还有 password 以及 dbname, 对 吧? ok, 那 我们复制一下, 直接粘贴啊,在中间加上等号,把我们的 host 呢改为 local hosts port 就是 非常经典的五四三二。 user 呢,就是默认的 post grease, 密码呢,大家就自己设置啊,这里你的数据库密码是多少就是多少,然后 dbname 也是很经典的 post grease。 而这里的数据库呢,大家需要自己来进行创建,这里呢,我更推荐各位使用 docker, 因为我们只是做本地测试嘛,那么如果你没学过 docker 的 话,可以看一下屏幕中间 弹出的链接,我之前有做过一个简单的该如何使用 docker 的 一个视频,同时呢,我之前也做过该如何创建一个 postgrease q, 要数据库的视频, 这里呢?我来到我的 docker desktop, 具体的流程我就不走了,我这里已经创建了一个数据库容器,我直接启动,启动完成。那么这里的配置配置好之后呢,我们该怎样去验证它,我们该怎样进行测试呢?难道要我们实际的去 写一个简单的连接脚本去测试嘛?就像 synchronize。 那 这里呢,我们的 microrm 提供了另外一个能力,来到我们之前的这个文档中,在我们的 setting up config 这里,这里呢,如果你跟着它的所有的流程,把这里的配置文件全部都写好,并且保证你像我一样把所有的配置文件都写在 environment to arrive, 也就是环境变量中。配置完成五五之后呢,在最下面,它会提示我们就在这里。上面这些呢,全部都不用看,因为它默认呢,我们不会使用一个 nest 的 js 的 项目。所以呢,这些脚本呢,它会指引我们进行配置,但我们呢,可以跳过这些过程。这里呢,它会提示我们使用 micro i m c l i 进行测试。它这里有一个命令叫做 debug。 这个 debug 命令呢,能够打印并且检测我们的数据库连接,甚至能够检测我们的配置文件它是否存在,以及我们定义的实体类是否存在。这是一个非常好用的工具。那为什么它这里有一个 e s m 后缀呢?啊,很明显,它就是针对于专门的 e s m 环境的。那这里呢,我们不用去操心,因为 e s m 还有 common j s 的 这个 兼容性问题呢。 nest js 它是默认帮我们配置好的,我们不用去操心,我们直接复制这个命令来到我们的这个终端,我们使用 micro i m c l i d bug 命令来进行测试。 ok, 我 们看一下测试结果。首先呢,它这里会打印相应的这样一些依赖版本信息。然后呢,它会尝试去扫描我们的配置文件,可以看到它这里会 扫描 type script 和 javascript 两种形式的文件。这些扫描成功之后呢,它会 继续往下面去找,看一下对应的这些驱动是否正常。同时呢,他会进行一个数据库的连接测试,这里他提示我们数据库连接成功,说明我的配置没问题,数据库也没问题。最后呢,他才会尝试去扫描一下我们整个项目中是否有相应的这个 实体类文件,也就是点 n t t 点 t s 或者是点 n t t 点 js 文件。那很明显,这里呢,我们还没有创建任何一个相应的实体类文件啊,就算创建了,我们也还没有将它和这个 m 进行结合,对吧?那这里呢,接下来我们就来创建相应的 实体类文件,并且将它和我们的 microrm 进行联动,并且同步在数据库中。那这里呢,快速创建一套 entity dto, 还有 controller service 的 方法呢?大家还记得吗?很明显,我们可以使用,我们可以使用 resource, 这里还是一样啊,我们还是要创建一个基于 to do 的 这样一套 resource。 那我们使用 nest c l i g resource, 首先是它的名字,这里一定要注意啊, nest 的 这一次中呢,它推荐我们使用复数形式,当然对应的实体类它会自动变为单数形式,所以呢,这里我们使用 seduce 回车。然后呢,选择我们的类型,依然还是 restful api 回车 输入 y, 此时呢,它能够自动地生成并配置,并且大家发现没有,它刚才好像还安装了一些项目依赖,我们可以看一下它到底干了什么。首先来到 deduce, 这里的 service, 还有这里的 controller, 这些呢,都是自动创建的,还有这里的 module, 并且这里的 module 能够自动地注册到我们的 app module 中间这些呢就不不说了,非常简单,对吧?来到我们的 entity, 可以看到 entity 文件中的 to do, 它自动的就由复数变为了单数,所以呢,这里是不用我们去操心的。还有这里的 dto, dto, 这里的 update to do dto, 它中间自动的就用到了我们的 partial type, 它来自我们的 map the type。 那 这里它刚才自动安装的这个项目依赖其实就是我们这里用到的 nestage map the type。 所以呢,使用 resource 能够自动地帮我们完成这里所有的人物,不需要我们去操心其他的东西。我们这也可以来检查一下在下面的这个 dependencies 啊,这里它就用到了 map the types。 ok, 那 我们接下来只需要去定义这里的 entity 它里面的类型就可以了,比如这里的 id, title, content, 还有最后的 is completed。 然后呢,我们把这里除了 id 之外的这些字段呢复制一下,来到我们的 create to do dto 给它粘贴进去。好,就这么简单,我们就成功创建了。那我们来看一下,我们 再次运行 micro r m 这里的 c l i 进行 debug, 我 们来进行检测。好,此时呢,你就会发现,我们刚才创建的 entity 实体类,它就能够自动地被检测到,但是呢,我们这里只是单纯的把它检测到了而已,我们还没有将它和我们的 数据库里面的数据库表进行同步。这是因为呢,我们的数据库表想要和这里的 entity 进行同步呢,我们需要 micro r m 的 帮助才行。我们来到它的文档中下面这个部分 first entity, 我 们需要 在我们的实体类中去添加一些装饰器来进行同步,这里它给的例子呢,其实非常的简单啊,我们要在内上添加这个叫做 entity 的 装饰器,然后呢,主键上面必须要是 primary key, 其他的话都是 property, 并且你不需要在这个装饰器里里面去填写对应的这个字段,在数据库中对应的类型,它是会自动地进行推断的,当然你想要手动设置也是可以的,就像现在这样,我们直接截个图来到我们的 entity 来定义 primary key, 然后是 property, 这里下面同样是 property, 最后千万别忘了在我们的类上写上 entity, ok, 就 这么简单。当然如果你想要自定义这样一个 entity 实体类在数据库表中的表明的话,你也是可以在这里的 entity 注解上进行配置的。那这里呢,我就不再演示了,大家直接去看 micro ram 的 文档就可以了。那一切进去之后呢,我们 最后要做的就是将这里的实体类和我们的数据库表进行同步,那么同步这一步的话呢,我们需要做一点额外的配置,我们需要去搜索 一个叫做 migration 的 一个操作。在 node js 这个生态工具中呢,不管是我们的 droo, 还是这里的 micro m, 又或者是 type r m, 又或者是我们后面可能会学到的 prism 这些所有工具呢,它将 当前的这个 nest js 中的 entity 实体类和数据库表进行同步的过程呢,我们全部都把它叫做 migration 或者是 migrate, 所以呢,一般来说我们在文档中搜索 migration 就 可以了,在 migration 这个操作中呢,它要求我们啊需要安装一个额外的工具,叫做 micro i m migration, 复制一下 安装。同时呢,我们要把这样一个配置项啊,写在我们的这个 micro m 的 配置文件中,我们需要把它当做一个拓展进行设置,来到我们的配置文件,我们把它放在根目录 粘贴,然后呢这里导入一下, ok, 就 这么简单。那这里只是配置好,那具体该怎么使用的,其实就在右边,它展示了 using via c i。 啊,这里的 c i 呢就可以帮助我们进行迁移,那这里它给出了 很多的命令啊,但实际上呢,我们只需要两个命令,一个是 migration create, 另一个呢是 migration up, 那 为什么一个叫 create, 一个叫 up? 待会儿我们就知道,我们先复制这个 create 的 这个命令,它告诉我们这个 create 是 什么呢? create new migration with current schema diff, 也是基于当前的这个这个 entity, 还有数据库表的这个这个情况呢,去尝试着创建一个迁移计划, 它只是创建一个迁移计划,它并不是真的将我们的这个实体类和数据库表进行同步,这里一定要注意啊。 ok, 我们来粘贴运行,运行成功之后呢,我们可以发现,在我们的项目目录中呢,它会创建一些文件,首先呢有一个 migrations 这样一个文件,它里面有一个 migration t s 文件,这个 t s 文件呢,下面刚好就有两个方法,一个是 up, 一个是 down, 那 它就对应着呢,将我们当前的更改呢推送到数据库。另一个呢是把这个推送呢给他取消掉,也就是呢,我们随时可以基于每一次的这个迁移计划进行 步或者是回滚,也就是每次对数据库的这个同步操作呢,它都是有记录的,可以看到它这个文件名本身就是带有我们的时间戳的,还有这里的 snapshot, 这里也是记录了我们对应的这样一个表结构,还有字段中间的这些内容。我们刚才的命令中呢,我们发现了除了有 creation, 有这个 migration create 之外,还有一个 up, 这里的 up 呢就对应着我们的 migration 中间的 up 函数,它就可以将我们的这个计划呢直接实施,并且可以看到它是直接运行对应的这个 cycle 语句的,我们直接 migration up 运行。 那此时呢,它就真的将我们的 entity 实体类和我们的数据库表进行同步了,我们可以打开我们的 navigate, 或者是你用其他的什么工具去连接上我们的数据库,进行查看本地的 post grace post grace, 此时呢它就会多出来两个表,一个是 to do 表,我们看一下 to do 表里面的四个字段对应的类型,其实它都是自动生成的,它都是自动地选择和我们这个 entity 实体类类型相对应的这样一些类型的,当然你可以自己在这个装饰器中手动调整,同时呢它还会创建另一个表,叫做 micro i m migration, 那 这个 migration 表呢,它就记录着所有的这样一些迁移的记录,并且呢打上了相应的时间戳,也就是你每次和这个对这个数据库表做的任何操作呢,它都是有记录的,这个就记录着这次操作是什么时候做的,以及这个操作对应的 这个迁移计划是对应着哪个文件。比如当前我们做的这个迁移,我们是在二月二十二日,然后对应的文件是这个 二零二六零二二二,对吧?如果我们想要回滚这次操作的话,它就能够基于这样一个 name 标签里面的内容呢,去尝试着查找我们这里的贴纸文件。然后呢,运行这里的,当将我们刚才的操作呢全部都回滚掉啊,我们可以来尝试一下 把后面的 up 改为 down 回车,可以看到它此时呢运行了这样一个 migration 文件,但是呢,它运行的是其中的 down 命令。我们再来到数据库 刷新一下,可以看到刚才这个操作的记录,它就直接删除掉了我们这个表呢,刚才这个 to do 表它也成功地消失了,非常简单。然后呢,我们再运行 up, 它就会还原回去,还是一样的,它就变为了 to do 表。 ok, 那 么到此呢,我们就成功地 学习了该怎样配置我们的 microrm, 以及该怎样将我们的实体类 entity 和我们的 microrm 进行 连接,并且将它同步在我们的数据库表中。那么具体该怎样使用 microrm 来操作我们的数据库呢?我们就留到下一节再讲。那么以上就是本期视频的所有内容,希望你能关注或订阅我的频道,感谢你的观看,我们下期视频再见!

hi, 欢迎回来,那么本节呢,我们就来快速地实现一下上一节我们没有实现的登录啊,同样来到我们的文档,在左边找到 techniques, 不 对,应该是下面的 security, 找到这里的 authentication, 右边找到 implementing the sign endpoint。 那么之前我们借鉴的部分呢,本来就是登录的地方,我们直接复制这里的代码片段,我们跳转到我们的 office service 文件中,在这里新建一个我们需要的文件,那么这里呢,可以发现它同样利用到了我们的 findall。 然后呢,这里我们的参数同样应该是从这个 user name 修改成我们的音标才对。 然后呢,我们应该先简单地判断一下这个用户存不存在啊?如果不存在的话呢,那这个时候呢,我们应该抛出这个四零四的异常,它也就是 not found exception。 然后呢,在里面稍微写一点东西,如果密码不匹配的话呢,我们才应该抛出这里的这个 unauthorized exception。 那 这个地方我们对它的密码进行匹配的过程呢?很明显,我们不能够直接和我们从数据库中查询出来的这样一个密码支付串进行匹配,因为一个是用户输入的加密前的密码, 一个是加密后的存储在数据库中的密码,这两个直接来进行对等的字母串比较的话,很明显是不可能正确的。那我们呢,只能够借助 b c r t 它的 compare 这样一个函数方法。 b c r t compare 第一个参数呢,是我们要进行比较的这样一个 用户传进来的密码。第二个参数呢,是经过加密后的密码,也就是这里的从数据库中的查询出来的这个 user dot password, 然后前面加上 await, 因为这同样是一个一步的操作,然后呢返回的结果我们就叫做 compared result, 那 么基于这个结果进行判断一下就行了,也就是如果它为 false 的 情况下,此时呢我们才应该抛出相应的异常,以至于这里的 on authorised exception。 这里里面对应错误的这个提醒的字段的话呢,大家就随便写,这里一般都是 invalid credentials, 然后下面这里 如果参照我们的设计来说的话,这里在我们的用户成功登录之后,我们应该基于用户的邮箱来生成一个 j w t, 也就是对应的功能,那很明显我们这里还没有实现 j w t 相应的功能,所以呢,这里我们还是简单地把用户的邮箱返回出去啊, 我们就不改下面的代码,下面代码他的做法是什么呢?他就把用户的数据中的除了密码之外的所有的信息全部都给它包含进去,然后呢把这些登录好的用户的所有数据呢,除了密码之外呢都给它返回出去。这个做法呢是很常见的,因为我们要对我们的用户信息做一些简单的脱敏嘛。 那么进一步的操作呢,我们就留到下一集来实现, j w t 的 时候呢来实现,那么接下来我们来看一下 controller, 同样是在下面啊,复制这里的 sign in 这样一个 post controller 依然是来到我们的 os controller 粘贴,然后这里的类型呢,它叫做 sign in dto, 那 我们之前已经有了一个 sign up dto, 所以呢我们简单地复制粘贴一下,然后呢基于它进行修改就行了,可以改为 sign in, 这里也是一样, 那么这个类型呢,我们就改为 sign in detail, 下面这里注意一定要是音标。那么到此呢,我们的登录的这样一个端点其实就已经完成了,非常简单,对吧?因为大致的 工作呢,我们在上一节完成注册的时候,其实就已经差不多做完了,只是呢,我们没有实现 j w t 相应的一些逻辑而已。那么一切就绪,我们来启动我们的项目, 打开 bruno, 来到我们的这个注册的这样一个端点,我们去请求,那此时呢会返回这样一个邮箱,我把请求过程中的这样一个创建好的邮箱,以及刚才创建这个用户用到的密码呢,我都复制一下,我们来新建一个新的请求,这就叫做 user sign in 或者是 log in 吧,那么这里它的路径就应该是 log in。 好, 这里的内容呢是不变的,我们回车,那此时呢,它返回的内容就是我们新创建的这样一个用户除了 密码之外的所有的字段信息,有这里的邮箱,还有它的用户 id。 那 么到此呢,我们就成功实现了基于上一节的 这样一个登录的端点,或者是你叫它接口也可以。那么具体的 j w t 的 功能呢,我们就留到下一集再讲,因为 j w t 的 功能实现呢,会比较的复杂,在内斯的 j s 里的话。那么以上就是本期视频的所有内容,希望你能关注或订阅我的频道,感谢你的观看,我们下期视频再见。

hi, 欢迎回来,那本节呢,我们就针对现在我们所有的这样一些端点做一些简单的测试,同时呢简单的复习一下我们的 open api 相应的内容。那么首先还记得 open api 的 配置方法吗?我们来到 ness 的 g s 的 官网左边,找到 open api, 首先安装这样一个工具包。 然后呢,我们在使用的过程中呢,必须要先配置一下我们的 swagger, 也就是这个文档相应的内容。我们复制这里的代码内容,来到我们的 main tis, 就 这么简单,然后呢,把里面的内容呢全部都给它改过来, 把这个 tag 给它删掉, ok, 那 最后呢,我们不需要给每一个 control 函数,或者是每一个 d t o 都写上相应的注示,我们只需要添加上这样一个简单的 c l i 插件就行了。找到右边的 using the c l i plugin, 粘贴在我们的 next c l i j c 这个配置文件中,我们直接来启动项目。 此时呢,我们打开浏览器访问本地的三千端口,对应的路径是 api, 此时就能够通过我们在线的 swag 文档访问到所有的接口。那此时呢,大家会发现我们这里的接口呢, 很明显,它有些部分呢是看起来有些迷惑的,就比如这里的 articles 的 部分,这里的很多地方呢,它有些是带有这个 me 后缀的,有些是没有的。那正常来说,带有 me 后缀的,我们就会天然的认为这应该是给当前的用户使用的,但没有这个后缀的地方呢, 那我就认为它应该是给我们的管理员来使用的。但事实上呢,我们之前构建的时候呢,不管是这里的创建还是更新,这里都是给我们的用户来使用的,所以我们这里要把这个路径稍微给它改一下,来到 articles controller, 我 们搜索 create 这里的 create by current user, 我 这里给它加上一个 简单的路径前缀 me, 刷新一下,此时呢,这里的 post 请求对应的路径就多了这样一个 me 后缀。然后说我们的更新也是一样啊,我们去搜索 patch 在 这里 me, 并且同时呢它应该是需要一个参数的,而这个参数呢,不仅需要在我们的路径参数中通过这个 prime 去获取,我们还需要在 patch 这样一个装饰器里面呢去声明它才可以。我们再刷新一下, 此时呢我们的 patch 它就变为了 me, 那 此时我们就能发现带有 me 后缀的就是我们用户使用的,那没有后缀的就是我们的管理员使用的,这样的话才比较的清晰明了。那接下来的话,我们测试该怎么做呢? 你当然可以选择在这里的在线的这个 swag 文档中去测试,但是呢,这个测试方法很明显是比较的麻烦的, 它是不如我们的 bruno 了来的方便的,更别说我们的 bruno 还可以把测试文件给它单独的存下来,但是呢,一个一个的去根据这里的端点去创建对应的 bruno 请求又是一个比较麻烦的事情。 所以呢,这个地方我们更应该根据这个 open api 生成出来的这样一些规范内容呢,去直接往 bruno 中导入相应的接口定义, 让 bruno 直接生成相应的请求。那么还记得该怎么做呢?那么首先我们需要生成这样一些 open api 对 应的 json 格式的文件,然后呢,才能够让 bruno 去夺取它。我们来到这里的 introduction, 我 们去搜索 json, 在 下面这个地方,我们就能够找到这里的 hint, 它就提示我们可以生成这样一些可供下载的 json 格式的文件。那么配置的方式呢?就是在 setup 下面呢,添加这样一个配置项就可以了, 我们把整个这个配置对象给它复制下来,来到 main t s, 注意是在 set up 这个地方,在它的后面跟加跟上这样一个配置对象。好,就这么简单。那此时呢,我们打开 bruno, 点击,添加,点击 import collection, 点击 url, 那 这里的 url 就 应该是来自这个地方。 local host 三千 swagger json 粘贴导入,然后选择一个存储的本地内容,那这里呢,我叫它 alien sync。 打开,然后呢,这里有一个 folder arrangement, 也就是我们该以怎样的方式来组织我们的文件夹,这里它默认选择是 tag, 但是呢,我们的这个 swagger 文档中呢,我们并没有配置它的 tag, 我 这里换一种方式, passes 导入,那此时呢,就多了这样一个 alien 的 部分, 我看一下是不是这个应该是没问题的。然后呢,这里缺是一个 base url, 这个 base url 呢,我们是需要去给它设置的,我们在这里去简单地设置一下啊,就像这样写就行了,要注意把我们的 add server 写在我们的 build 前面。 那此时呢,再来到 bruno, 我 们再次去重新同步一下,把这个给它删掉, 我们再来重新的导入,把这个给它删掉。 open 还是一样 pass is import, ok, 那 此时呢,应该是没问题的了,那我们注意啊,要在这里选择上我们的这样一个文件变量 local 啊,这样呢才是正确的。 好的,可以看到这里呢,它同步出来的这样一些端点呢,它是按照我们的前缀来进行划分的,那这样划分呢,可能不是非常符合我们的意思啊。首先是这里的 public, 这里的 public 呢,我干脆给它拿出来,给这个 folder 删掉。然后呢,我给它重新的命名一下, ok, 那 么 article 的 部分呢,我已经重新命名好了。那么其他我们暂时不测试的。这两个地方,一个呢是管理员可以查看单个文章,一个是管理员可以查看所有文章。这些呢,我们先给它删掉, 我们暂时用不到这些内容,我们就不对它进行测试了。剩下这里呢,其实是大差不差的,还包括这里的 user, 同样的,管理员的部分呢,我们就先给它删掉。 好的,那接下来我们来测试。首先呢,先来到我们的左侧,输入对应的邮箱名, 直接注册这里,获取到我们的 access token, 我 复制一下。 然后呢,这里的登录,登出以及 refresh, 我 们就不再测试了,我们之前已经测试过了,我们先来测试一下我们上一节做的这样一些功能。首先是 find me, 在 这个地方呢,我们需要设置一下 authorization, 传入我们的 token, 通过这个端点呢,我们应该是能够获取当前用户的所有的这样一些信息的回车,好的,没问题,但是可以看到它返回了一些敏感数据啊,这里的 password 还有 refresh token。 所以呢,我们 要回到我们的 user controller 对 应的 service, 我 们要把这些信息呢都给它排除出去。在这里我们写上 exclude, 把这里的 password 以及 refresh token 全部都给它排除掉。然后我们再来测试一下,此时就是比较符合规范的了。然后呢,我们再来一一测试之前的这样一些功能,我们来看一下之前有哪些功能啊。那首先呢是创建,来到 bruno, 来到 articles 下面的 me, 找到这里的 post, 创建我自己的这样一个文章。这里的内容呢,我稍微多写一点。然后呢直接创建这里,它提示我失败,因为我还没有设置这个请求头 authorization barrier 复制这个 token 回车, 创建成功。然后呢,我们来到这里的 public get all public articles 这个地方,我们去验证一下刚才我们创建的这个文章呢,它对应的内容是一个 draft, 它是一个草稿,那我们公开的文章列表里应该找不到它哦。这里的配置我要传入数字。 我们来到最后啊,可以看到是没有刚才这样一个 alex title 的 文章的。那接下来我们就来进行更新好,也就是这里的第二个部分,将我们的这样一个文章内容进行更新。更新的部分呢,就在这里的 me 下面的 patch。 首先我们要传入这个文章的 id, 我 们看一下刚才的文章 id 是 多少呢?这里我们就只能够通过 get 文 article, 也就是获取所有我们自己的这样一些文章内容,才能够查看到这样一个创建进去的文章它的 id 是 多少。所以这里我们来到 header, 设置一下 传入当前这个用户的 token, 我 们去请求。此时我们的用户很正常,只有一个文章内容,它的 id 为十五。来到这里的 patch, 我 来更新一下它的 id 十五,同时千万不要忘记传入我们的 token, 而此时我要更新什么呢?我先看一下它创建的时候是什么样子,我们更新的时候只需要更新它的其中一个字段就行了。所以呢,我们这里只保留 status, 我 给它设置成 publish 的 回车, ok, 更新成功。那怎么判断它更新是否成功呢?还是一样 get or public articles? 我 们刚才已经将这个文章给它公开了,那么在公开的这个端点中,我们应该是能够查询到的,就在这个地方 ilex article, 那 相应的我也可以把它重新的设置成这样一个草稿的状态, 然后再来到这里的 public, 我 们去获取。那此时呢,就没办法获取到我们已经隐藏的这个文章了,那我们的更新呢是没问题的。然后最后这个是 retrieve, 也就是获取。 那获取的部分呢?我们刚才已经检测到了,我们刚才已经获取的部分呢?我们刚才已经通过这个 get on article 可以 发现,我们是能够获取到当前用户中的所有的这样一些文章内容的。然后我们要测试的就是 我们能否进行查询以及呢?我们能否查看单个文章的这样一个具体的信息。那这里我们需要来到 get on article by id, 我 们查看这样一个 id 为十五的这个文章的具体的内容。 直接回车,那此时呢,它就能返回这里的 content, 这就是我们想要的,以及呢,我们可以进行查询。查询的部分呢?假设啊,我这里传入一个 q, 那 应该会返回四零四,对吧? 哦,他返回两百啊,这里,这也行啊,其实没什么影响我们前端呢,做一下简单的长度判断也是可以的。好的,那么查询也没问题了。最后是我们的删除来到 delete, 那 刚才的这个文章 id 是 十五,还记得吗?啊,其实也不用记得,然后设置我们的请求头 barrier, 直接回车,删除成功,我们再获取所有的当前用户的这样一些文章。这样就应该返回为这样一个空数组,是吧?那我先把这个 query 参数给它去掉,回车,同样是空数组,没有任何问题。那最后我们再退出登录吧。来到这里的 os logout, 直接退出登录。 ok, 那 么到此呢,我们就成功地演示了目前为止我们所有这个项目中的这样一些端点。当然,目前仅限游客,还有正常用户可以访问的内容, 我们还没有添加这个管理员的功能,我们也没有添加文章评论的功能。但就目前而言,我们这个项目的后段部分其实已经可以使用了, 只需要在配合前端进行一些简单的展示,这就是一个比较完整的项目了。那么以上就是本期视频的所有内容,希望你能关注或订阅我的频道,感谢你的观看,我们下期视频再见!

下雨天不出门和一岁半女儿在家能干嘛?解锁高质量低成本哄娃方式。 最近天天在下雨,都没有机会去户外了,今天也是宅家的一天,我们就不给他换衣服了,换个睡袋扎个头发就行,去沙发上妈妈给你扎头发,扎好我们跳舞快 选一个舒服的睡袋真的是太重要了,因为我们穿睡袋穿的时间是最多的。文文身上这个是奈斯特的分腿睡袋,这个是百分之三十的棉加百分之七十的竹纤维,比普通的棉纱还要柔软,透气性还是纯棉的三倍,不管是居家穿还是当睡袋都很舒服。 裆部这里的开口设计也方便我们换纸尿布,他侧边这里还有很长的调节扣,一件就可以穿很久了。 我已经暴汗结束了,小文文玩他这个小玩具玩了有半个小时了,他一看见我流汗就要给我拿纸了,谢谢宝宝哇,他又重新选了一个玩具来玩, 真棒!嘿嘿嘿,拿一本你最爱看的书,妈妈带你看一会。好,走,去小桌子上。对了,点赞,妈妈看看文文要画一个什么 i love reading, 一 二三四烟花 屁股撅多高的?嗯嗯嗯,我们睡觉了好不好?

hi, 欢迎回来,那么本节呢,我们就来完成上一节遗漏的作业啊,虽然是上一节遗漏的作业,其实我漏讲了一个关键的地方,就是该怎样应用我们的 controller 和 service, 并且将它和我们的 module 进行整合。那么本节的内容呢,我是基于上节遗留的内容,然后做了一些修改在我们的 controller 这里。那么 在开始本节内容之前呢,我们先看一下我们的根模块,也就是 app module, 那 此时呢,你会发现我们导入了两个 to do controller 啊,这是因为呢,每次当我们创建一个 controller 的 时候呢,我们的 nest js, c l i, 它就会尝试着将我们先创建的这些 controller 或者是 service 给它导入到相应的地方。但是呢,很明显,我们的 to do controller 还有我们的 to do service, 它应该放到一个 单独的 module, 也就是单独的模块中。说白了,我们这里呢,所有的 to do controller 和 to do service 应该放在单独的一个机座里,然后把这个机座整体放在我们的 app module 中,而不是像现在这样的,直接把我们的 controller 和我们的 service 放在这里的 app module 中。虽然这样做是可以的,就像现在这样, 我把这个多余的导入给它删掉,这样做肯定是可以的,但是呢,我们从这个设计原则的角度来说,我们应该以功能为划分,并且这个划分的功能呢,应该体现在我们的模块,也就是 module 的 划分上,所以呢, 很明显,我们不应该这么做。那么为了解决这个问题呢,我们应该先创建一个 module, 来到我们的 c l i。 我 们上一节呢,学会了通过 c l i generate 创建一个 controller, 那 自然呢,能创建 controller 就 能创建我们的 module, 在 这里是吧,我们只需要打开我们的 命令行 nest g module, 我 们创建一个 to do module, 此时呢,你会发现它在创建一个文件的同时呢,它还会更新我们的 app module。 就 像我说的,我们的 nest 的 js c l i, 它会默认在我们每创建一个新的模块的时候呢,就把这个新的模块给它 导入到我们的根组建中。那有了我们的 to do module 之后呢,我们的 to do controller 就 不再需要给它导入在我们的 app 这里了,把这个引入给它去掉,来到我们的 to do module, 要在它里面呢,去导入我们的 controller。 那 么具体一个 module 中怎么导入 controller 呢?这里呢,各位可以直接借鉴 app module 的 写法,我们需要写上 import, controller 还有 provider, 那 很明显,我们需要 controller 的 话,就需要加上一个 controllers 的 这样一个注解的属性,如果是需要一些 service 的 话,就需要这样一个叫做 providers 的 属性。那相应的说明呢,大家可以来到官方文档,在这里找到 overview, 找到 modules, 在 下面滑 这里呢,就能够找到 module 这样一个装饰器,它关于不同属性的一些说明,这里呢,我们很明显只需要使用这里的 controllers, 还有这里的 provider 就 行了。好在这里写上我们的 controllers, 注意它是一个数据库,然后引入我们的 to do controller。 最后呢,只需要将我们的 to do module 给它引入在我们的 app module 中就行了。那么需要注意啊,在我们的根组建中去引入其他的组建或者是模组,我们需要把它写在 input 中。当然我们刚才 通过 c l i 创建的过程呢,它就已经自动帮我把这个 import 给我们写好了。所以呢,之后我们所有的基于 nest 的 js 的 开发,我都推荐大家基于我们的 nest 的 c l i 来创建 不同的这样一些模块,因为它能够自动地帮我们解决这样一些导入的琐屑问题,不然的话,我们自己去写这样一些导入语句,很有可能会产生一些小错误, ok, 那 么完成我们的 module 之后呢,我们自己去写这样一些导入语句,很有可能会产生一些小错误。 ok, 那 么完成我们的 module 之后呢,我们自己去写这样一些导入语句,很有可能会产生小错误, ok, 那 么完成我们的 g service, 我们直接写 to do, 那 么相应的我不需要让它生成一个测试文件,所以呢,后面加上 no specs 创建好之后呢,可以看到它同样更新了我们的 to do module, 在 to do module 这里,它自动地就加上了这样一个 service 的 导入。此时呢,再来到我们的 service 这个 service 类呢,它是自动加上了这样一个 injectable 的 装置器的,就表明它是一个 provider, 所以呢,可以写在我们的 to do module 里面的 providers 这个数值里。那么接下来的话呢,我们就只需要去实现这里的 to do service 内部的内容了。那这里呢,我们 参照上一节我给各位提供的那个 express 项目的资料,我来到上一节的文件夹 express app 找到 service, to do service, 把这里的所有的内容呢,全都给它复制过来,来到我们的 service 中, 把我们的 to do 这个东西呢,我给它就放在这里,把我们的 export function, 这里的关键字全部都给它替换掉。 那么在我们的一个类的方法里面去调用对应的属性,那么这里我们需要使用 this 关键字,所以呢,这里所有的地方啊,都是需要加上这样一个 this 关键字的, ok, 这样修改之后呢,应该是没什么问题了,但这里呢,它缺少一些类型提示,所以呢,这里我们要给我们的 id 加上类型,还有这里的 new to do, 那 create to do 它对应的类型呢?很明显就是 id, 还有我们的 title, 还有这下面的 id, 我 们同样给它加上对应的类型提示。然后这里的 update to do 的 话呢,这里就稍微麻烦一点,我们类型呢,是和上面这个 create to do 一 样的,只不过它里面的两个参数呢? 我想想这里的两个参数的话,呃,我们就只写上一个 title 参数就行了,然后这里的 delete to do 同样写上 number, ok, 一 切就绪,我们来简单地试验一下,看看行不行啊。在我们的 controller 中呢,我们要注入我们的 service, 那 么在 controller 中通过依赖注入的方式去注入我们的 service, 还记得这里的写法吗?之前我们讲这个依赖注入,还有控制反转,也就是 d i 和 i o c 的 时候,我们有讲过类似的 g s, 它实现依赖注入的方式,对吧?我们只需要在这个 controller 里面的这个构造器,也就是 constructor, 它的构造器的参数内部写上我们要使用的这样一个 service, 它的声明就行了,如果你忘记怎么写的话,没关系,来到我们的 provider, 在 下面找到这里的这个势力,在这里啊, service 下面 它的写法呢,就是我们所需要的,我们只需要在这个构造函数里面,在它的参数列表中列出你想要使用的这样一个 service, 那 前面需要加上一个 private 关键字,因为它只能够将这个 service 用在当前这样一个 control 中,我们不需要将它共享。然后呢,我的 这个 ai 插件还提示我要在前面加一个 read only 的 这样一个关键字啊,这个呢也是需要加上的,因为我们不需要对这样一个 service 做修改,所以呢要加上 read only。 然后是 to do service 类型就是 to do service, 我 们的 ness 的 jess 会自动帮我们解决这里的依赖注入,然后我们把下面的这些 to do 要完成的内容呢,全部都给它完成就行了。 this to do service 下面这里也是一样啊, this to do service ok, 这里有错误啊,它提示过 delete to do service does not exist, 应该就是 delete to do。 再检查一下我们的导入,没什么问题。那需要注意的是呢,在删除这个地方,我们 不管是成功删除还是我们删除失败我们返回的内容呢,我们都需要使用这里的 response 进行返回。正常来说的话,我们不需要去设置它的这个 响应码的时候呢,好像我们不需要使用这个 response, 这个 response 是 我们通过 ness 的 js 依赖注入的方式,得到了一个基于 express 项目的这样一个响应体,可以动态设置我们想要的这样一个状态码。但是呢,如果我们不需要设置状态码的话, 按理来说,我们的 ness 的 js, 它应该能够自动地将这样一个 object 的 对象处理成一个 json 格式的括号串才对啊。正常来说,我们应该是在这里成功的请求中,不需要去 用我们的 response。 那 这里呢,其实有一个问题啊,我们先不管,我们先来测试一下整个这个项目中间所有的这样一些 c r u d 的 接口或者是端点。那么测试工具呢,你可以选择使用我之前的 node js 解密教程中所用的 a p f fox。 那 这里呢,我个人更推荐各位使用 bruno。 bruno 呢是一个开源的工具,而且它是把所有的文档都存储在本地的,所以呢,不需要各位去做什么额外的设置,不需要你去登录账号。那 bruno 的 使用呢,可能稍微有点麻烦,因为它只支持英文,不支持其他的语言, 你想要切换呢,目前也是不行的,官方没有提供这个选项。那它的使用呢,其实很简单啊,我们可以创建不同的 workspace, 也就是相当于不同的这个工作区。然后呢,创建不同的 collection collection 呢,你就把它当做是项目就行了。我们创建一个 collection, 写上这个 collection 的 名字,我们就叫 to do nice 的 js。 然后呢,保存这样一个 browser 的 文件创建创建好之后呢,点开我们的 collection collection, 中间呢,我们是没有任何一个请求文件的,并且呢, 我们也没有做任何的设置。那这个地方呢,我推荐各位去我们的 environment, 也就是这里创建一个环境变量。这里呢,我创建了一个全局的环境变量,如果你没创建的话,在这里 点击,然后呢在右边创建这个环境变量对应的变量名就行了。这个环境变量呢,我就是直接通过 base url 来替代我们的这个 local host 的 三千,这样的话我们每次写请求的时候就不用写这个前缀了,那具体这个环境变量怎么用的?在我们的这个 collection 中间,我们点这个三个点 创建一个 request, 然后呢写上这样一个请求的名字,比如第一个请求就是 get or to do, 这个很简单,对吧?我们先不写它请求的 url, 我 们先创建。创建好之后呢,我们通过两对这个波浪括号的方式来使用我们的这个环境变量, 我输入 base, 它就有提示了,然后 type 不 全,那此时我把鼠标放在上面,它就提示我这个全局的环境变量,它指代的值就是这个 local host 的 三千。后面呢跟上我们对应的这样一个 路由的地址,那我们这里的路由地址是多少呢?来看一下我们的应用,我们的应用的路由地址呢,它取决于我们的 controller 上面 这个装置器里面写的参数是什么?那很明显,我们这里的 to do controller, 它上面的参数是 to do, 那 么我们的地址就应该是 我们的根目录后面跟上 to do。 当然如果你还想在下面针对不同的这样一些 h, g, d, p 方法,或者是对应的路由处理器写不同的这样一些路由地址的话,你还可以在这里的和 h, g, d, p 同名的这些方法里面写上它的针对性的路由地址,比如这里我还可以写上 o, 那 这样一来的话,它的地址就是在上面 control 了的基础上,加上这里的 o, 它就是 to do o, 所以呢,这个地方各位是可以自定义的,那这里呢,我就 一切从简,我们就不搞的这么复杂,那很明显,这里所有的这样一些端点,它都是位于 to do 这个路径下的,那我们只需要在 环境变量的后面呢,跟上 to do 就 可以了,那这就是一个获取所有 to do 的 这样一个 api 请求,我们直接回车运行。哦,我还没运行呢,那运行我们的 nest 的 js 项目呢?非常简单啊,还记得 它使用的两个脚本命令嘛,一个是 start, 一个是 start dev, 这两个都是我们可以在开发环境下运行的两个脚本命令。 p n p m start, 我 们输入 dev, 让它能够自动地检测并且重新运行,只要这里的所有的这个日制能够检测出来我们目前印收到的这样一些地址,那就说明我们的 next 的 js 项目识别到了我们所写的 controller, 那 可以看到它识别出来有哪些这个 api 端口或者是 api 地址呢?首先有它自带的这样一个 跟路径下的 get 请求,这个地方是来自哪里呢?啊?这个地方是它自己创建的时候自己写的这个 app controller 啊,这是我们之前教学的时候以它为范本来教学的,对吧?如果我把这个地方给它删掉 保存,此时重启之后呢,可以发现它已经就检测不到根路径 get 的 这样一个路由了,它只能够检测到我们自己写的这五个路由。所以呢,你就可以通过它启动的这个日制来判断一下你所想要编辑的这样一个接口或者是 端点,它有没有被 n s 的 j s 应用给它识别到。之后呢,来到我们的 bruno 再次回车,那此时呢就能够返回正常的数据,如果返回的是一个重文本的话,你可以在这里选择它 要进行阅览的这样一个格式,刚开始它可能选择的是 html 或者是什么其他的格式,这里选择一下 json 就 可以了。我们在基于这里的 get all to do 进行拓展,我们 clone, 接下来就是 get to do by id client, 那 这里就会多复制一个 get to do。 那 在我们的这个 bruno 中,如何实现这里的路径参数呢?那我们的这个参数呢?很明显是 to do, 下面再写上一个参数,那它的参数写法和我们这里的 nest js 应用的这个参数写法是一样的,都是一个 冒号,跟上我们的参数名,所以呢,在这里冒号 id, 那 下面呢,自然就多出来这样一个参数名,叫 id, 就 可以写上它的值。我们输入一 保存一下再回车,此时又返回了 id 唯一的这样一个 to do 代办设相对吧,那么接下来我们加快速度完成剩下的三个啊,然后是 update to do along, 那 么 update 它对应的 h g d 的 方法就是 patch 需要这个路径参数吧?应该是,我看一下对,它需要这个路径参数,同时呢它还需要这样一个 update to do body, 这里它应该是不需要的吧,我看一下我们的 service 是 怎么实现的,应该是不需要的, 把这个 id 从我们的 update to do body 这个类型的束缚里面给它删掉。我们更新的时候呢,正常来说,我们只会更新这里的 to do, 因为我们整个这个代办事项,它只有两个字段,一个 id 和一个 title。 那 么 id 用来查找自然的我们的这个 title 字典,它肯定是必须要存在的,不然的话,我们就是更新个寂寞。然后按理来说,我们在更新之前,我们应该先基于我们的 id 进行查找,如果查找不到的情况下,我们要抛出相应的错误,抛出四零四,找不到,对吧?那这里呢,我们因为没有接入数据库啊,所以呢, 暂时我们先不做相应的操作,我们在这里写上 id, 同时呢,我们还要在我们的请求体中写上我们的 title, 因为我们的 update, 它同时拥有这里的路径参数,还有我们的请求体。所以呢,我们要来到这里的 body, 选择这里的格式,选择 jason。 好, 我们自己来手写一个啊, title 在 更新之前呢,我们先看一下所有的这些数据,那么 id 为一的这个代办事项,它的 title 为 to do one, 那 我们给它改,为什么呢?我给它改为 alex, 保存一下,然后把光标放在上面,再回车请求,哦,它提示找不到是吧?哦,我知道了,我这里的 id 还没输入呢,我输入 id 为一, 然后再回车。欸,此时呢,它返回的内容就是我传进去的这个 body。 此时我们再来到 get all to do 回车,此时呢, id 为一的这个代码思想,它的 title 就 从刚才的 to do one 变成了 alex。 我 们再来实现这里的创建,可弄一个 create to do, 那么创建它对应的这个请求方法就是 post, 把这个参数给它删掉,我们只需要一个 body, 那 么创建的时候呢,我们就需要一个 id 了,这里的 id 呢,我稍微写大一点保存,然后回车, ok, 创建成功,我们再获取,此时呢,它就多了一条代码选项。最后呢我们再来完成删除, delete to do by id, 它的方法就应该是 delete, 那 么删除这个方法呢?或者是这样一个 control 呢,它只需要接收一个参数是 id, 然后在这个地方我们删除的过程中呢,我们正常说应该会进行一个判断,那这里呢,我们也是一切从简,让它返回处,也就是不管 有没有成功删除啊,我们都返回处提示我们的 controller, 它应该会正常地返回内容,然后我们直接返回了这样一个对象,这个对象呢,只有一个 message 属性。正常来说,我们的 nest 的 js 检测到一个对象之后,它会自动地把它处理为 js 格式的自由串,这是我们上一节学到的内容, 只有当他删除失败的时候,才会触发下面的这里我们自定义的这个响应体。那正常来说,我们这里不管怎么做都会删,都会删除成功。然后呢返回这样一段 json 格式的自助串,那我们看一下,真的是这样吗?我们首先呢需要一个路径参数 id, 输入 id 为一,我们要删除这个 id 为一的回车。哎,此时呢,你就会发现它竟然卡住了,这个就非常的奇怪了,它为什么会卡住呢?我们的 ness 的 jess, 它也没有崩溃,但是呢,我们的请求它竟然就卡住了,我们都没有连数据库,它竟然就发生了这么诡异的情况。那这个地方呢,就涉及到 ness 的 jess, 它在 我们使用特定的这样一个 response 对 象时候的一些特殊的机制和处理。我们这里呢需要多传入一个参数,让它能够正确的工作。那这个部分呢,我们就留到下一集再讲。那么以上就是本期视频的所有内容,希望你能关注或订阅我的频道,感谢你的观看,我们下期视频再见。

hi, 这里是 alex, 非常高兴你能点开这个视频,这意味着你决定要学习 nest js 了。那么 nest js 本身呢,其实是基于 type script 来实现的一个后端框架,但这么多后端框架,为什么我们要选择 nest js 呢? 这里呢,我给各位提供了一个简单的图标,这个图标呢,对比了一下 spring boot 和 ness 的 js 它们在 github 上面的 star 数量,那么可以看到 ness 的 js 它作为一个后起之秀,它在 star 数量上其实是逐渐趋近于我们的 spring boot 的。 再加上 nest 的 js 和前端应用同时都会使用 text word 作为它的基础编程语言,所以呢,前后端的交互以及协助呢,会更加的方便。那么 nest 的 js 本身它又解决了怎样的问题呢?又或者说 为什么我们不去选择使用更简单的 express 呢?明显 node js 生态中,或者说基于 typescript 来实现的后端框架中,我们明明可以选择更简单的东西,为什么要选择 nest 的 js 呢?那很明显是因为我们的 express 这类框架,它其实有相应的一些问题。这里呢,我给各位引用一段 paypal 他 们之前的一篇技术博课中间的一段话,这里他强调了一下 express 这类框架,它虽然非常的灵活,但是它对于大型的团队或者是项目来说,最大的一个问题就是它很难保证项目的一致性,也就是它太过灵活,导致没有办法让大家在一个 统一的框架下,或者是统一的一个规范下进行写作,这样的话就很难进行管理。那么关于这个部分更多的一些细节,你感兴趣的话,可以看一下屏幕中间弹出的链接,我之前有直播讲过这篇 paypal 他 们写的 blog。 那 么说了这么多,我的 ness 的 js 课程到底是怎样的呢? 那么首先我先给各位声明一下,我的课程中不会出现什么东西。那么首先呢,我个人是很讨厌这种所谓的一个小时或者是半个小时带你速通,甚至说让你学会某些东西的。这种东西在我看来完全是穴透,没有任何的价值。 点进去一看,他只是把官网上的内容呢在这里手敲了一遍,但具体这些东西在什么时候会用到,他其实并不会跟你讲。 看起来你一个小时就学完了所有的知识点,但你只是在脑子里过了一遍。真正当你需要用这些技术来尝试构建应用的时候呢,你根本想不起来该在怎样的场合下使用怎样的技术,以及呢,我个人非常讨厌所谓的 中文文档,如果是官方做的还好,但是呢, nas 的 j s 本身在中国呢,比较的小众,所以呢,它的 所谓的中文文档很多都是第三方个人自己做的。比如这里,我在 google 上面找到的一个中文文档,他甚至在右边挂出了他的微信公众号的这样一个广告。所以呢,我非常不推荐各位 去学习中文文档,并且它的版本还是落后的。那么相应的呢,我带各位学习的内容呢,也不会仅仅局限在官方文档的内容中,因为官方文档难免会存在一些纰漏。我自己也多次改过很多技术战的官方文档,比如 angular v 二十的官方文档呢,我就给他提交过两次 pro request, 所以呢,各位也不要盲目的去迷信所谓的官方文档,官方文档也是会出错的,我们更应该自己去辨,真的看待这样一些知识。那么相应的我的课程中会提供什么呢?那最简单的一点就是我的课程呢,不会像那些 培训机构或者是那些速通的课程那样,只给你讲相应的理论或者是相应的概念,只带你简单的把这些功能点写一次,我会设计一些循序渐进的场景,让大家尝试去发现我们之前的这些技术中所存在的一些问题。然后呢, 通过我们之前的技术来尝试解决问题,实在是解决不了问题的话,我们才会去尝试学习一些新的技术,新的知识,来解决 我们当前无法解决的问题,这样的话就能够形成一个正向的循环,我们就能够在问题中去学习新知识,而不是单纯的为了学而学。那么相应的,我也会在官方文档的基础上呢,进行进一步的拓展。那么说了这么多,我的课程呢,会包含哪些东西呢? 其实后端应用,不管是 nas 的 g s 又或者是其他的基础站,它其实包含的内容都是大差不差的,无非就是如何实现一个 rest for 风格的 api 接口,或者是端点如何在框架中连接数据库, 或者说使用 o r m, 以及最重要的如何确保我们接口的安全。当然我的课程呢,肯定不会仅仅局限于 nas 的 g s 的 基础,之后呢,我们还会对这些内容呢 做进一步的拓展,比如之后呢,我会给各位带来关于 graphic 二的一些内容,以及该如何对我们的 ness 的 js 应用进行测试,当然这里会用到最新的 vtest, 以及如何与一些第三方的服务进行集成, 比如和 superbase, 和 neo 数据库,又或者是和一些日间看,或者是一些数据分析的平台,比如 data dog, 又或者是 sentry 之类的平台。那么在学习之前呢,需要注意啊, nas 的 js 本身它不是一个适合初学者学习的工具, 你需要学一些前置的内容,各位只需要去看一下我之前推出的 node js 解密教程,还有 node js type script 的 教程就可以了。在这两个教程中呢,我会讲很多的一些基础概念,这些基础概念不仅仅会举现在 node js 中,而是对整个后端开发都是有用的。那么本期视频的最后呢,我再强调一下 课程的时效性,那么这里我直接给各位展示一下我录制当前这个视频的时间,那么现在呢,是二零二六年的 二月九号,所以呢,如果你看这个视频的时候已经是比较晚了,比如二七年甚至二八二九年的话,那么请你看一下视频的剪辑或者评论置顶,又或者说弹幕中我有没有给出更新的视频的版本,因为很多的培训机构呢,他都会刻意的把这个电脑上的时间给你隐藏掉, 误导各位小白,让你以为这是最新的视频,他只需要改下标题就行了。那么相应的现在 ness 的 js 的 最新版本呢?是多少呢?我们来搜索一下 ness 的 js, 找到它的官网,找到它的 gitlab 链接, 来到它的 release 发布页,可以看到它上周刚刚发布了最新版,是 v 十一点一点一三,所以呢,如果你现在看到的 n s 的 j s 最新版已经比起我当前的这个版本已经超过非常多的话,那么请你尝试去找更新的视频教程,不然的话,很有可能视频中的内容与你实际的操作会产生很多的不一致。那么以上就是本期视频的所有内容,希望你能关注或订阅我的频道,感谢你的观看,我们下期视频再见!

hi, 欢迎回来,那么今天呢,我们就来解决一直困扰我们的一个问题,那就是为什么在它自动生成的这样一个 nest js 的 项目中,我们的 controller 中间没有显示地去创建一个叫做 app service 的 一个对象,但是呢,在我们的 controller 里面的 方法函数中呢,它却能够直接调用这样一个对象,并且调用 service 里面的这样一个方法,甚至它没有报错。非常奇怪啊,它到底是怎么实现的呢?首先呢, 既然我们用了 type script, 那 么 type script 它有没有这个能力呢?那很明显, type script 啊,它只是针对 javascript 在 类型上的一个增强,它不可能有这样的能力。那难道是 node js 给我们实现的这个功能吗?那很明显也不可能,因为 node js 它本质上只是一个 javascript 的 运行器,还有连接 javascript 和我们的操作系统之间的这样一个桥梁,它如果要实现这么一个复杂的功能的话,那它就不应该 只是一个简单的 javascript 运行环境。那很明显,答案只有一个,这个功能呢,应该是 nest js 为我们实现的。既然在我们的 controller 中间,它没有创建这样一个叫做 app service 的 对象,那么和我们框架相关的地方,用到了这个 app service 的 地方只有哪里呢? 很明显就只有我们的 app module, 因为只有在 app module 这个地方,它显示的将我们的 app service 作为一个 provider 属性之一给它传了进去。而我们的 app module, 它作为 nest js 的 一个基础,一个基石, 它又传递给了我们的 main t s 中间的这个 create 函数,创建出来了一个 nest js 的 服务器。那么具体它到底是怎么创建的呢?那这里呢,我们就可以根据 modular 中间它使用到的这样一个叫做 provider 的 这样一个关键字。首先 provider 它到底是个什么东西?为什么上面这里要叫做 controller, 下面这里使用 app service 的 地方它要叫做 provider 呢?我们来到 ness 的 jess 的 官方文档, 在官方文档的左边找到 overviews, 我 们可以找到这里有一个小结,就叫做 providers, 我 们来看一下它针对 providers 的 定义。那可以看到关于 providers 它的说法呢,是比较的简单的, many of the basicist classes, social services, repository and helpers can be treated as providers。 也就是说呢,类或者是 service, 或者是 repository, 它都可以 当作是一个 provider。 所以呢, provider 它只是一个泛化的概念,并不是说只有我们的 service 才能够作为一个 provider。 那 问题是我们的框架它到底是怎样知道一个东西它是一个 provider 的 呢?这里我不把这个 provider 这个单词进行翻译,我是叫它供应商还是叫它 什么提供者呢?翻译出来就很奇怪,所以呢,我之后会一直叫它 provider。 我 们再往下面看关于 provider 进一步的说明,在下面这里的 service 这个地方,它就展示了一个和我们一模一样的一个场景,它这里创建了一个 service, 叫做 cat service。 然后呢,它需要将我们的 cat service 用在我们的 controller 中啊,也就是在这个地方 可以看到它的写法和我们是一样的,它只是在 controllers 这样一个类中间的构造函数中声明了一下我们的 service, 但是呢,并没有实际地去创建它,然后呢,它却能够在 controller 这个类里面的方法中去使用我们这样一个并没有显示创建的 service 对 象,那它到底是怎么实现的呢?那这里它就简单地告诉我们, the cat service is injected through the class constructor 啊,它是通过我们的类构造器被注入进来的。这里它强调了一下 inject 这样一个关键词,它是被注入进来的。同时呢,它还告诉我们, this shorthand allows us to both declare and initialize the cat's service member in the same line streamlining the process。 也就是说,单纯地将我们的 service 在 controller 这样一个类中的构造器这个函数中进行声明,它同时就能够实现声明和创建的这个过程。也就是说,仅仅这一行,它其实就已经完成了我们这个 service 对象的创建。为什么这么一行就能够实现创建呢?明明在我们的角度看来他只是做了声明,就算他告诉我是被注入的,但是问题在于,他为什么会被注入进去呢?他到底是怎么来注入的呢?这里我们就产生了更多的疑问。那么紧接着他下面一节就讲述了 dependency injection。 那 么这里的所谓的 dependency injection, 我 们一般的叫法叫它依赖注入,那它告诉我们 nest is built around a powerful design pattern known as dependency injection。 它告诉我们 nest just。 它本身呢,其实就内置了一个非常强大的 这样一个设计模式,叫做依赖注入。那么这个依赖注入本身呢,它就是用来解决我们刚才上面所演示的,将这样一个 service 注入在我们的 controller 中间的这样一个过程。然后呢,具体相关的一些概念的话,它推荐我们去到 anger 官方的一个文档,我们打开看一下 这里它会跳转到 angular 官方的一个文档,然后呢,会告诉我们非常多的一些概念。但我说实话,这里的概念呢,其实是比较的难懂的,它也只是讲了一些 angular 相关的一些知识,所以呢,这里的解释我个人觉得并不是特别的好理解。 那么为了让各位能够进一步的理解,我们的 service 到底是怎样被注入在我们的 control 中去使用的呢?那这个地方呢,我推荐大家来到 nest 的 js 官方文档的另一个部分,我们收起 overview, 来到下面的 fundamentals, 也就是基础。找到这里第一个部分, custom providers。 那 么在刚才的那个文档中,我们知道了 provider 可以 是各种各样的类,不仅仅是我们的 service, 它还可以是我们的各种 factor, 各种 repository。 好,当然这些东西呢,我们之后都会用到,那么在这样一篇新的文档中,它告诉我们什么呢?我们来看这个部分,这里的 d i fundamentals d i 就是 依赖注入的简写,就是这里的 dependency injection, 它这里对我们的依赖注入做了进一步的说明,它告诉我们, dependency injection is an inversion of control technique wherein you delegates instantiation of dependencies to the ioc container 啊,它告诉我们,首先 d i 依赖注入它是一个 什么什么的技术啊?这个 inversion of control, 我 们一般把它叫做控制反转啊,这里又出现了一个新的名词,对吧?我们连依赖注入它的原理都还没搞懂呢,这里又多出来了一个叫做 i o c 控制反转的东西,并且它告诉我们什么呢?依赖住它本身是将我们实力化依赖的这样一个过程呢,交给了我们的 i o c 容器,那这里它又出现了 i o c 容器这样一个概念。我们先来解决最关键的一个点,就是我们的一个类,比如这里的 service, 它到底是 怎样被我们的 nest 的 js 标注成一个 provider 的 呢?不可能所有的类都是一个 provider, 比如我们的 controller, 那 很明显 controller 它就不是一个 provider, 因为我们没有 在其他的地方像我们的 service 一 样去使用我们的 controller, 很 明显它是有一定的方法来区分到底哪个类是 provider, 哪个类不是的。那么在这个地方,它就明确地告诉了我们, first we define a provider the injectable decorator marks the cat service class as a provider。 它这里就明确告诉我们,其实要识别一个类是不是一个 provider, 就 只需要使用这样一个注解,叫做 injectable。 那 很明显,我们的 app service 它就自带了这样一个注解 injectable, 那 么就意味着只要一个类上带有了这样一个 injectable 的 注解,它就是一个 provider, 它就可以被依赖注入到其他的地方。那么解决了这个问题之后呢, 我们再来看接下来的问题,到底是谁来把这个标记好的 provider 放进去,然后又是谁把这个放进去的 provider 拿到我们的 controller 中间去使用的?我们继续接着往下看, 来到这个地方,在这里同样是 d i fundamental。 下面这里它详细地告诉了我们整个这个依赖注入的过程。首先呢,第一步, in cat service t s then the injectable decorator declares cat service class as a class that can be managed by the nest i o c containers。 它首先告诉我们, 我们第一步做的就是在我们的 service 文件中给这个类添加上一个 injectable 的 注解,那很明显,这个类它就变成了一个 provider。 同时呢,这个 provider 或者说这个类它就会被我们的 nest 内置的一个 ioc 容器进行管理。注意,是 nest 的 机制内置的容器,那这个容器呢?我们在 nest 的 机制的使用过程中是看不到的,因为它是隐藏在框架下面的,我们是看不到它的细节的。第二步呢,在 controller 中, 它又显示的声明了一下,在我们的 controller, 它会使用到我们的 cat service。 这个声明的过程呢,就是在这个 controller 类,它的构造函数的参数列表 去声明了一下。那这个写法呢,也和我们之前是一样的,就在我们的这个地方, controller 这个地,只要在这里声明,那么我们的框架它就能够自动地为我们进行注入。那注入的前提就是我们要 给我们的框架提供一下,到底哪个东西是我们要注入进去的 provider。 那 么第三步是什么呢?在我们的 module 文件中,它将这个 cat service 这样一个类和我们的 t s 文件进行了关联,那这样的话它就能够实现将我们的这个 cat service 进行注册。所以呢真正起作用的一步呢,就是在我们的 module 这里,它将我们 需要用到的所有的 provider 都放在了这个 module 注解里面的这个 providers 这样一个属性中,这样就直接告诉了我们框架这个树组里面所有的类全部都是要作为一个 provider 进行 注册,注册完了之后呢,再去查看哪些地方用到了这些 provider, 然后呢就让我们的框架给它注入进去。好,我知道大家这么听下来的话,还是会觉得有点麻烦,还是会感觉不太直观,并且刚才我们提到的这个 ioc 容器,为什么它要叫做 version of control 呢?我们来到刚才的这个概念,为什么它要叫做 控制反转呢?这里到底哪里反转了呢?它到底控制了什么东西呢?这里呢我给各位简单提供了一个图标,中间这个 ioc 容器呢,就是我们的 list 的, 这是整个框架给我们提供的。而左边的 provider 呢,它可以是任何东西。刚才我们也看过了它的概念, provider 可以 是一个 service, 也可以是什么 repository factor, 而右边的 controller 就是 我们实际使用这样一些 service 类或者是 service 类对应方法的这样一些东西。而很明显我们的 controller 中,它是不会直接了当地去从我们的 provisor 中去创建一个新的对象。我们的写法中呢,也没有这一步,那它所实现的过程呢,就是在我们的 provider 这些类中添加上我们的 injectable 注解之后呢,我们的 i o c 容器,它就知道这样一些东西是 provider, 它就把这些所谓的类呢,在它的容器里自动地完成了 对象的创建。就比如这里的 app service, 它只要打上这个标注,并且在我们的 module 中引入为这个 providers 之后呢,在我们的 i o c 容器里啊, 或者说在我们的 n s j s 框架的内部,它就自动的会创建这样一个 app service, 它的对应类型的这样一个对象。注意啊,这个创建的过程它是自动的,不需要我们去操心。创建好了之后呢, 所有的这样一些对象都会放在这个容器里,那么具体什么时候使用呢啊?很简单,就在我们的控制器中,如果它在它的这个构造函数中去声明了一下,那么这个声明呢,同时就意味着我们的容器要为我们的这个 controller 去解决 它所依赖的这样一个 service。 所以呢,容器检测到我们的 controller 使用到了这样一个 service 之后呢,那么这个 service 刚好 又在这个容器里面注册好了相应的类,那么这个东西呢,就会由容器交给我们的 controller。 所以 为什么我们这个 ness 的 jess 看起来它没有 显示的创建对应的 service 类,但是能够直接使用它呢?就是呢,我们在使用这个 ness 的 jess 框架,在编辑对应的 type script 代码的时候呢, 我们没有办法体现出来这一点,其实要查看它到底是怎样创建的,也很简单。还记得我们运行一个类似的 js 项目,它的过程吗?它肯定是先将我们的 type script 代码给它翻译成 javascript 代码,然后再由 node js 去运行的,所以呢,它会生成一个 所有 type script 代码对应的 javascript 代码的版本。因此呢,这个地方其实很简单,我们直接在终端中运行我们整个项目, ok 运行好项目之后呢,我们照例会在左边,它会生成一个 disk 目录, disk 目录下面就是所有编辑好的这样一些 javascript 版本的代码。那此时呢,如果我们来到这里的 app controller js, 来到下面,你会惊奇地发现,在我们这个 app controller 类的定义中呢,在这里的 constructor 构造函数里, 它的写法呢,就和我们刚才的 type script 的 代码中的写法就完全不一样, type script 代码中只是在这里的参数列表声明了一下,但是呢,编辑好的 javascript 版本之后呢,它是会实打实的去 创建一个 app service 对 象的。所以呢,这个创建的过程呢,只是让我们的框架或者说对应的 ioc 容器给我们进行了抽象,并不是 完全把这一步舍弃掉了,它只是让我们不用自己去操心这样一个创建的过程。那我们简单再来对比一下,来到这里的 app controller, 我 们来对比一下这里的构造函数,那左边呢就是翻译好的 js 的 版本,右边呢就是没有翻译的 type script 版本。很明显,在我们的编码层面,我们是没有实现这样一个创建的过程的, 所有创建过程呢,都是由我们的框架为我们能完成的。那么到此,相信大家就大概了解了,我们是怎样实现 在不创建一个 service 对 象的情况下,却能够调用它里面的方法的。这个过程呢,就是由框架或者说 nest js 它的依赖注入为我们实现的。那么最后一部分呢,我再给大家简单的讲解一下,为什么 这里这个容器它要叫做 ioc, 为什么要叫做 inversion of control 控制反转这个部分呢?你不想要了解,没关系,我们之后的话呢,一直都会把这样一个机制叫做 依赖注入,因为叫做控制反转的话,很多人他其实会感到非常的困惑啊,但是为什么要叫做控制反转?首先控制反转这个词本身大家听起来就很难理解,我个人其实也不是很喜欢这个词啊,包括这个词 最开始在 java 论坛好像是零四年的时候,大家讨论的时候,当时发的一篇 blog 里面, blog 的 作者就明确地表示,他们发现很多人对控制反转 ioc 这个词呢感到非常的困惑,所以呢,大家都会使用 依赖注入,也就是 d i 来替代这个叫法。但是呢,为什么要再控制反转呢?要理解它的话,我们就需要理解一下我们之前这个代码的写法。上一节我给各位提供的这样一个 express 项目,我们来看一下这个项目它的结构啊,这个项目的结构看起来和 ness js 项目的结构差不多,但是呢,它的最重要的问题就在于,在我们的 controller 这个地方,它去调用我们的 service 这样一个函数的过程中的,我们的 service 函数它是实打实的被创建了的啊,如果真的要进行一对一对标的话,那很明显我们这个 get hello 它的最外层如果包裹一个 class 类,那么这个 class 类我们是很显然要基于它去创建一个 对象的,然后基于这个对象才能够去使用里面的 get hello service 这样一个方法。所以呢,我们之前的这样一个写法呢,我们是需要显示的去在我们的 controller 中 去声明一个 provider 这个类对应的一个对象才可以的,所以呢,方向是从 controller 主动去引用我们的 provider 的。 而有了我们的 ioc, 有 了我们的控制反转之后呢,你会发现整个箭头它就是反过来的,它是先有我们的 provider, 或者是这样一些 service 类主动的 去声明它自己是一个 provider, 然后呢放到容器里,最后再由容器去决定哪些 controller 需要使用到哪些 provider 类,然后呢再由容器提供给它们, 所以呢,这个反转,其实反转呢,就是这样一个调用的过程,之前是由 controller 去主动调用我们的 provider 或者是这样一些 service 类, 而现在呢,是由 provider 主动去声明,向容器去报备,再由容器去统一的调度哪些 controller 去用到它,那么这就是控制反转中反转它的意义。相信各位现在听来就明白了这里的 inversion of control 控制反转它到底是什么意思了吧,但是呢,很明显,如果你是 新手小白,没有接触过这样一个依赖注入的方式,第一次听说 iuc 控制反转这个词,相信你是两眼一抹黑的,因为我们的框架本身它是把这个控制反转,或者说这个这样一个容器注入的整个过程呢,它是隐藏在框架之下的, 我们日常使用的时候呢,是根本看不出来,它会自动的为我们完成整个这个流程,这也是为什么很多人根本理解不了为什么它要叫做控制反转,它到底反转了什么?那么以上就是本期视频的所有内容,希望你能关注或订阅我的频道,感谢你的观看,我们下期视频再见!