各位同学大家好,欢迎大家来学习苍穹外卖这个项目,通过这个项目名称呢,大家应该也能猜到啊,我们这个项目呢属于这种外卖点餐类的项目, 那么这一类的系统呢,对于大家来说应该不会陌生了,对吧?那么大家可能经常通过手机是不是来进行点餐,那么我们这个项目呢,就属于类似的这种点餐项目, 那接下来呢,咱们来看一下,咱们这个项目呢,整体是分成了这么两个端,一个呢叫做管理端,还有一个呢叫做用户端,那么这个管理端呢,他是给我们外卖商家来使用的,而这个用户端呢就是给我们这些点餐的用户来使用的, 那么我们现在这个用户端呢,是集成到了微信的小程序当中,也就是通过小程序来进行点 点餐,那么我们这个项目做完之后,具体是一个什么样的效果呢?那接下来咱们就一起来展示一下,给大家一个直观的感受。 好,大家现在看到的这个页面呢,就是我们管理端的一个登录页面,而这一端呢主要就是给我们这些外卖商家来使用的。 那么另外一端,点餐用户是不是需要通过手机微信小程序来点餐呢?那这个地方呢,我是通过手机投屏这种方式啊,能够让大家看到我手机屏幕,大家可以看到,现在呢这个呢这就是我手机屏幕,那么已经投屏到电脑上面来了。 好,那接下来呢,咱们先来看一下我们外卖商家这一端,那么我们只要输入用户名,输入密码,点击登录按钮,那这个时候呢,我们就已经登录到这个系统里面来了,那进来之后我们能做哪些操作呢? 咱们可以大概浏览一下。首先我们可以看到左侧这是不是有一排菜单,那么可以对相应的一些数据进行管理,比如说有员工,有分类,有菜品,有套餐,还有订单,对吧?还有相应的数据统计,还有工作台。 而这个右上角这个位置呢,那么也有几个地方可以点,比如说这个地方有一个营业状态设置,对吧?这个呢其实就是来设置我们这个商家店铺他的营业状态的,比如现在呢我们要打烊了,对吧?不对外营业了, 然后呢我就可以把这个状态给他设置成打烊中,然后点击确定,那这个时候呢,从这个位置就能看到我们这个店铺的状态,同时呢在我们这个微信小程序这一端呢,用户也就点不了餐了。 好,然后呢我们把这个状态呢还给他设置回来,重新给他改成这种营业中。然后还可以做什么操作呢? 大家可以看到在这个地方是不是可以修改密码,然后呢也可以退出登录,这些呢都比较简单啊,咱们浏览一下就可以了。 然后接下来我们就来看一下左侧这排菜单,那么从下边往上看啊,一个是员工管理,这个呢可以对我们餐厅内部的这些员工来进行管理,比如说可以去新增员工,对吧?还可以修改他的信息, 然后也可以去启用或者禁用这个员工账号。大家可以看到现在我们这个地方有这么一个账号叫小明,对吧?然后呢他现在是一个禁用的状态,那么我们现在呢使用他来登录一下,大家来看, 那么这个密码呢,就是一二三四五六,我们直接点击登录,大家可以看到他现在显示的是账号被锁定,对吧?并没有登录成功,然后呢我们重新使用 admin 这个管理员账号登录进来, 然后呢把他这个账号呢给他启用,然后我们重新退出去,还是使用小明这个账号来登录,那这个时候呢,咱们是不是就已经登录进来了?好,那么我们退出啊,还是使用这个管理员账号来登录好,那么这个地方呢可以显示是管理员,对吧? 这是咱们这个员工管理,这个模块相对来说呢应该是比较简单的。然后呢就是这个分类管理,那这个分类主要是管理的什么数据呢?其实就是我们的这些商品的分类, 那么你看啊,这个地方我们主要是分成了这么两大类啊,一个是菜品的分类,比如说有这个薯味烤鱼啊,还有薯味牛蛙呀,特色蒸菜啊等等。那么这些分类主要体现在哪呢?它其实主要是体现在我们这个移动端,大家可以看到这个地方左侧这一排,对吧?那么这其实就是 我们的分类数据,然后还有一类呢,就是我们这个套餐的分类,但是套餐的分类呢,现在并不多,就这两个,对吧?一个是人气套餐,一个是商务套餐,从这个移动端这个位置呢,大家也能看到,是不是就是这两个,对吧?人气套餐,商务套餐这两个分类。 好,那么我们可以对分类呢来进行维护,比如说可以去新建分类,对吧?然后呢也可以修改,也可以删除,或者是启用禁用都可以。 好,这是我们这个分类管理这个模块,然后再往下呢就是这个菜品管理,那么我们可以去新增菜品,当然也可以对已有的菜品是不是来进行修改操作,比如说这个价格 我们可以给他改一下,那么你看我这是不是有一个测试菜品,那么这个价格呢?我可以给他改成三十八元,原先是不是五十八呀? 好,那么我们直接点击保存好,这样的话我们把这个价格是不是就改过来了?当然也可以对他起售,停售或者删除,都是可以的,大家可以看一下啊,现在我们这个菜品也就是测试菜品,他是在这个薯味烤鱼这个分类下边,但是呢他现在这个售卖状态呢?是不是停售状态啊? 所以在我们移动端小程序这个地方,其实是看不到他的,对吧?你看我们来操作一下,我来重新刷新一下这个页面, 那么刷新完这个页面之后呢,你看还是看不到这个菜品,对吧?那么接下来呢,我们把这个菜品呢把它起售一下,咱们点击起售。 好,那这个时候呢就表示我们对外售卖了,对吧?然后回到我们这个移动端小程序这边重新刷新一下这个页面,注意啊,他是在我们这个烤鱼这个分类下边,对吧? 所以现在呢我重新刷新了一下这个页面,从这个地方就能看到这个测试菜品,对吧?那么这个菜品呢就已经显示出来了,然后接下来呢我们再把它停售掉啊, 这只是一个测试的数据,所以把它停售掉,那么停售掉之后继续回到这边来,那么我们重新刷新一下这个页面,你看现在在烤鱼这个分类下边,是不是又变成了原先的这几个数据?好, 那么这是我们这个菜品管理,在菜品管理这个模块呢,可以进行增商改查,包括起售、停售,对吧?然后呢接下来就是套餐,那么同样呢也是对套餐进行增商改查、维护,还有起售、停售等等。 好,那么这是关于我们这个商家这一端啊,那么这些呢都属于一些基础数据,对吧?那么有了这些数据之后,我们呢就可以到我们这个移动端 小程序当中来点餐了,咱们呢可以来点一个,比如说我来点一个这个牛蛙吧,你看这是不是有一个香锅牛蛙,对吧?然后呢我们点击这个加号,把它加到购物车里面来,点开这个购物车,从这呢能够看到点了一份,然后呢我们可以再来点一个其他的,比如说我来一个特色蒸菜, 然后呢来一个梅菜扣肉吧,然后呢我们来一个不要蒜,对吧?然后点加入购物车,从这呢能够看到购物车里面这个商品,对吧?好,然后呢我们点击去结算, 从这个地方呢能够看到他跳转到了这么一个提交订单的页面,对吧?然后呢我们在这个地方可以选择这个地址,但是这是我以前维护的这个地址信息啊,然后呢我们可以对这个地址信息进行修改,当然这呢我就不再去修改了,咱们重新跳回来,然后点击去 支付,然后从这个地方呢能够看到我们可以使用微信支付,对吧?然后点击确认支付, 那这个地方我们需要说明一下,因为我们这个代码里边其实已经真正实现了支付功能,那这个地方呢,我们就没有真的按照这个金额去支付,咱们只要能够测试通过就可以了,所以这个地方呢,我们统一都是支付零点零一元,也就是一分钱。 好,那接下来呢,我来刷脸支付一下,我们点击这个确认支付按钮, 您有新的订单,请注意查收。 好,大家可以看到我支付成功之后呢,在我们这个商家管理后台,也就是在这个地方呢,他其实就弹出来这么一个提醒,并且呢还配有 语音播报,对吧?其实就是提醒我们这个商家有用户是不是下单了,然后呢我们可以点击这个待接单提示框, 那么这个时候呢,他就会给我们弹出来用户的订单信息,你看这个订单号还有用户名,对吧?收货地址,包括我具体点的哪些菜,从这呢都能看到,那么我们可以点击聚单或者是接单, 但是现在呢咱们先不点击啊,那么我们回到这个移动端,那么到这个地方来看一下,我点击完成, 那么同时呢他会提醒我们支付成功了,点击确定按钮,那么就跳转到了一个下单成功的页面,对吧?然后呢我们点击查看订单, 那么这个时候呢,我们可以看到现在这个订单的状态呢,是等待商户接单,对吧?那么如果商户一直不接单,那用户是不是就等的比较着急,对吧?所以这个时候呢他就可以催单 来催促商家赶快接单,那么我们点击一下这个催单按钮,大家来看一下有用户催单了, 好,那么你看我点完这个按钮之后呢,他是不是又弹了一个催单的提醒,对吧?那么他就催促商家赶快接单,好,那么我们还是点击一下这个地方, 那么弹出来这么一个框,对吧?我们可以点击这个地方这个接单按钮,那这个时候呢这个单就已经接到了,接到之后呢你看他是不是就进入到这个代派送这里边, 所以接下来呢咱们就可以来看一下我们订单管理这个模块,那么从这个地方能够看到全部的订单,然后从这个地方呢看到的是代接单的,对吧?但是现在没有,那么现在呢代派送的有一个就是刚才我下的这个单子,因为他已经接完单了,那么下一个状态呢是不是就是派送呢?所以现在呢他就是这种代派送,那么 从这能够看到,那么比如说这个菜品已经做完,并且派出去了,那这个时候呢我们这个商家呢就可以点击这个派送按钮,那这个时候呢他这个带派送的是不是就没有了?他就变成派送中,从这呢能够监控到这个订单, 那么同时呢在我们这个移动端,那么这边呢其实也能看到这个订单,好,那么我们退出一下, 然后我们现在在这个页面,其实就是看到这个订单列表页面,大家可以看到现在这个订单是不是派送中,然后呢我们可以点击这个图片这个位置,然后呢从这个地方可以展示订单详情,对吧?你看这个地方也能看到是订单派送中。好,那么我们退回来, 然后呢我们接下来回到商家这一端,比如说现在这个商品呢,用户已经收到了,对吧?那这个时候呢,我们就可以点击这个完成 按钮,这就表示这个订单是不是已经完成了?好,点击这个按钮,那么你看他就会进入到这个已完成里边,好,从这里就能看到,那既然这个订单已经完成了,那么我们到这个用户这一段看看他有没有更新过来,咱们呢可以退出一下, 然后呢我们可以看到现在呢在这个地方他是不是已经展示这个订单已完成,对吧?从这也能看到。 好,那么这就是我们一个订单完整的一个流程啊,从我们下单到商家接单,然后呢派送,对吧?包括派送中,最后这个订单完成,那么整个流程,不管是从我们商家这一端, 还是从用户小程序这一端,是不是都能看到这个订单的状态,那么他们都是同步更新的。好,这就是一个购物的完整的流程。好,那接下来呢,我们回到商家这 端,咱们这个地方呢,除了有订单管理呢,还有数据统计,他其实就是统计的我们各个维度的数据,比如说有营业额的,还有用户统计,对吧?还有订单统计,还有销量排名, 那这个时间呢,既可以统计昨天的,对吧?也可以近七日的,近三十日的,或者本周的、本月的,那么都是可以的。这些功能呢,在我们课程当中也都会实现。 好,这是我们这个数据统计,最后呢还有一个工作台,他其实主要监控的呢,就是我们当天的一些数据,比如说当天的营业额,还有有效订单数,订单完成率,对吧? 那么下边这块呢,其实主要展示的就是每个状态的订单,那么具体都有几个,比如说带接单的有几个,带派送的有几个等等这些,然后再往下呢,是不是就是展示的我们这些菜品,对吧?套餐总栏,然后下边呢就是订单信息, 但是现在呢是没有这个订单信息啊,因为刚才我们看到那个订单是不是已经完成了,那么如果又有新订单呢,从这个地方应该就能看到了。好,那么以上呢就是给大家展示了一下,咱们这个项目做完之后,具体的一个效果是什么样子的。 通过刚才的项目展示啊,大家应该也能看到咱们这个项目的功能呢,还是非常完善的,他就是一个餐厅的外卖系统,对吧?但是大家注意啊,咱们这个外卖系统呢,可是和美团还有饿了么不一样, 他们呢那是一个外卖平台,允许很多商家入住的,对吧?而我们这个呢是某一个餐厅自己的外卖系统,但是从公证上来说呢,我们这个也是很完备的。那么我估计呢,很多同学可能会问啊,那为啥不给我们直接做一个类似于饿了么或者美团 玩的那种外卖平台呢?哎,我想说的是啊,我们毕竟呢才刚刚开始做项目,这个呢才是第一个项目,对吧?那么路呢还是一步一步走出来的,当然呢,你学完咱们这个项目呢,将来开一个餐厅,那么可以直接用我们这个系统,起码呢是不是给你省几十个 w 是没问题的吧。 好,那么最后呢,我们再来说一下,学完咱们这个项目之后的一些学习收获。 那么首先第一点呢,就是可以了解我们项目完整的开发流程,可以增长咱们的开发经验。 那么第二点呢,就是可以了解我们需求分析的过程,可以提高咱们的业务分析能力。那第三点呢,就是可以了解接口设计的过程,提高咱们的接口设计的能力。第四点呢就是对我们前面所学知识呢, 进行灵活的应用,可以提高我们的编码能力。然后第五点呢,就是可以和第三方服务进行对接,对吧,可以提高我们的文档阅读能力。那么最后一点呢,就是通过解决各种异常情况呢,可以提高咱们的代码调试能力, 那么这就是我总结的啊,那么学习完我们这个项目之后,我们至少呢从这六个方面呢可以得到提高。好,那么通过以上的介绍呢,大家是不是已经迫不及待的想要学习我们这个项目呢?那接下来就开始我们的学习之旅吧。
粉丝5689获赞7691

接下来咱们继续来看新增员工,刚才呢我们是把这个代码呢已经开发完了,那接下来呢,我们需要来做一个功能测试, 那做测试的话呢,我们通常呢有这么两种方式啊,一个就是通过我们这个 swagger 接口文档来测试,还有一种方式呢,就是通过前后端连条来测试, 那么我们在前后端分离开发过程当中啊,有可能我们这个后端已经开发好了这个功能,那对应的这个前端呢,可能他还没有开发好,对吧? 所以这个时候呢,我们想通过前后端连调呢,其实是测试不了的,对吧?那你必须等前端后端都开发好之后才能连调,所以说呢,我们在开发阶段更多的呢,还是通过第一种,也就是通过我们这个接口文档这种测试方式来测试,所以你看这个地方呢,我们其实也做了一个说明,对吧?在开发阶段呢,我们后端 测试主要是以接口文档测试为主,那接下来呢,咱们就回到我们的代码区,把咱们这个项目跑起来,先通过接口文档来测试,当然咱们现在这个前端呢,其实也都已经提供好了,对吧?那 那么如果我们通过这个接口文档测试没有问题的话呢,咱们呢也可以进行一个前后端的连调测试,这样的话,因为有界面,对吧,相对来说呢会更直观一些,好,那么现在呢,已经回到了我们这个代码区,那么我们需要把这个项目呢启动一下。 好,那么启动成功之后呢,咱们就可以通过我们这个接口文档来测试了,咱们可以刷新一下我们这个接口文档,对吧?然后呢从这个菜单里边能够看到有一个新增员工,对吧? 那这个呢,就是刚才我们开发的新增员工这个功能的接口,对吧?那咱们呢直接点击这个调试这个按钮,进入到这个页面,对吧?那么你看我们可以 输入这几项啊,像这个什么 id number, 这是不是就是我们的身份证号,对吧?还有 name, 这是姓名,还有 phone 手机号, sex 是性别,还有 user name, 这是我们的用户名,也就是账号。 那么把这些录入好之后呢,咱们呢可以在我们这个后端这个地方呢来加一个断点,如果请求过来之后呢,他是不是就会停在这个位置? 好,那接下来呢,我们回到我们这边来,直接点击发送按钮,那么大家发现呢,好像我们这个程序并没有执行到我们这个方法,而是直接返回了一个四零一,对不对? 那这个地方是怎么回事呢?是我们刚才开发的这个代码有问题吗?其实并不是,那他现在为什么没有调用到我们当前这个方法呢?其实呢是在我们这个初始代码里边呢,其实存在这么一个拦截器,你看叫做 j w t token at me interceptor, 那这个拦截器呢?它其实是进行 j w t 令牌教验的这么一个拦截器,那么它在这里边呢就会教验我们的令牌,你看 request 点 get header, 对吧?从请求头里边把我们这个令牌取出来,取出来之后呢是不是来进行教验呢? 那现在我们这次请求他交验通过了吗?那很显然肯定是没有通过的,为什么呢?因为刚才我们在执行这个接口的时候,我们并没有把这个令牌是不是给他提交过来, 所以我们可以先在这个地方呢加一个断点,咱们来跟一下,比如说我们在这个地方来加一个断点,那么我们直接点发送,你看他是不是已经走到这来了,然后呢我们 f 六往下走一步,那么现在呢,我们可以看到这个 toon 是不是一个闹啊?是个空对不对? 那如果是空的话呢?它在较艳的时候是不是就会抛异常?所以你看直接进了 cash, 对吧?然后呢我们是不是 response 点 set sdat, 设置我们这个 http 的状态码是四零一,然后是不是直接返回了就,然后我们给它放过去,所以回到我们这个页面上,我们看到返回的这个状态码就是四零一,也就是我们真正想调用到的,我们这个 ctrl 的这个 save 方法并没有被执行到, 对吧?那现在我们这个原因已经找到了,那我们如何来解决这个问题呢?那后边我们还经常会使用这个接口文档来进行测试,对不对?那他这个令牌教验一直教验不通过,那我这个接口还怎么测试啊? 那接下来呢,咱们其实就需要来介绍一下,我们如何在这个接口文档这里边统一来添加一个 j w t 令牌,那么后期呢?他在请求的时候呢,都会把这个令牌给提交过去,那现在呢,我们首先需要先获取一个令牌,那怎么获取令牌呢? 咱们可以通过调用这个登录这个接口来正常获取到一个令牌,比如说我们的用户名,对吧?我的密密码是一二三四五六,咱们来调一下登录的接口,你看他是不是就会给我们返回一个 tok, 那么这个呢,就是一个合法的一个令牌了。 好,那么我们把这个令牌这个字符串啊, ctrl c, 咱们复制一下,那么复制完之后怎么办呢?注意看在我们这个地方有一个文档管理,对不对?那这里边呢,是不是有一个全局参数设置, 然后我们在这个地方呢需要来添加一个参数,那这个参数名叫什么呢?那么按照我们这个项目的约定啊,咱们这个参数的名字必须叫 token, 那为什么叫 token 呢?其实是因为我们这个地方,咱们这个地方在获取这个令牌的时候啊,是从请求头里面是不是来获取,而请求头的这个地方呢,我们 其实是指定了这个参数的名称,而这个参数名称,当然我们在这并没有直接写死,而是通过配置文件配置的,那么具体在哪呢?其实就是在咱们这个地方,这个配置文件这个地方, 你看我们是不是配置了这个 admin token name, 对不对?这其实就是设置我们这个令牌的名称,那么我们这个地方是不是指的是 token, 所以到时候呢,他其实就会从我们这个请求头里面按照这个名字是不是来获取我们的令牌? 正是因为我们这个配置文件里面,这个地方配置的这个名称叫 tok, 所以这个地方呢,我们也必须设置这个参数名叫 tok, 那到时候呢,他是不是就能取出来了? 那具体这个参数值呢?我刚才是不是已经复制上了,直接在这 ctrl v 把它粘贴到这个位置就可以了,然后这个参数类型呢就是 higher, 对吧?也就是说我们是通过请求头这种方式传过来的,好,那么我们直接点 点确定好,那么点完确定之后,我们相当于就添加了一个全局的参数,添加完这个全局参数之后呢,咱们呢就可以把这些啊都给关上,重新打开我们这个新增员工, 然后呢点击调试,大家发现有什么区别?你看这个地方是不是有一个红色的一对吧?他其实就是提示我们,我们这次提交的时候呢,就会携带这么一个请求头,而这个请求头怎么来的呢?就是刚才我们通过这个全局参数在这个地方统一添加的, 所以说呢,我们现在呢把这个请求头相当于就追加上了,对吧?那追加上之后呢,我们再来点击发送,那么你看他现在是不是又过来了,但是这个呢还是我们这个拦截器,对吧?现在呢我们就可以 f 六来查看一下,看这回这个请求头能不能获取到,是不是已经获取到了,对吧?那接下来呢,他是不是就可以进行 正常的这个教验,对不对?好,那么我们这个地方呢,就不再看了,直接给他翻过去,你看现在是不是就已经到我们这个 c 五方法了?好,这就是我们真正想测试的这个方法,对吧?那首先呢,我们看一下这个 dto 数据有没有封装好, 大家可以看到这里边这个数据是不是已经封装好了, id 为空,这是正常的,因为我们这边是不是并没有给他传 id 啊?因为 id 不是必须的啊?好,那么我们这个数据已经拿到了,拿到之后呢,咱们是不是进到这个里边来? 那么我们进到这个 service 方法里面来,在这加一个断点,直接把这个程序放行放到这个地方来,然后呢我们单步来调试一下,首先 new 了一个 employ 对象, 用出来之后呢,大家可以看到这里面的属性呢,是不是都是空的?然后接下来咱们是不是进行对向属性拷贝啊?对吧?把我们 d t o 里面对应的属性给它拷到我们这个 employ y 属性上,然后呢我们直接 f 六考完之后翻回头来再来看这个对象,你看这里边相关的这些属性是不是就已经考好了,对吧? 那么额外还有一些空的属性呢?咱们是不是需要给它进行设置?比如说我们这个 status 需要来设置一下,还有就是我们这个 password 需要设置一下,还有这个时间,对吧?还有这个创建人、修改人等等, 那么这些呢都已经设置好了,咱们可以来看一下,你看这里边除了 id 是空之外呢,其他的是不是都已经复上值了?好,复上值之后呢,咱们是不是调用音色的方法,那么最终呢执行一个音色的语句,是不是把这条数据给它插入? 好,那么我们直接放行啊,直接放行放过去到控制台来看一下,你看他是不是发了个音色的语句,然后呢我们到这个页面来看,他是不是给我们返回了这个状态码就变成两百,那 那具体显示回来的这个数据呢?是不是一个 jason 的,然后包含这么三项,对吧? code, message 还有 data, 那它是不是由我们这个瑞造的对象转成的?因为我们这个 ctrl 方法是不是返回的就是一个瑞造它,对吧? 那么我们可以看到这个 success 方法最终返回的其实就是一个伪造的对象。那么这里边我们是不是设置的这个 cod 是一,而这个 message 跟 data 呢?在当前这个接口里边其实是没有使用到,所以说这两项呢都是空的,那么你看这个 code 是一, message 跟 data 都是 not。 好,那么这个接口呢?我们其实就已经测通了,然后我们再到数据库里边来刷新一下这条数据,是不是就已经插入成功了?好,到这的话呢,我们就测试了一下咱们这个后端,对吧?通过我们这个接口文档来测试的话呢,是没有问题的。那接下来呢,我们可以进行一个前后端的连调, 那现在呢,我已经将这个前端工程也已经跑起来了,还是通过我们这个 angex 啊来启动的好。那么现在呢,我们可以先登录到这个系统里面来,然后呢我们点击员工管理, 但是这个地方他报错是因为我们的分页查询还没有写,对不对?那现在呢,我们先不用管这些,我们直接点击这个添加员工,咱们在这呢来做一个前后端连条,比如说我再录入一条数据,这个账号呢叫李四,对吧?这个姓名呢也叫李四,然后这个手机号呢,我就随便输一个啊, 当然你需要输入一个合法的手机号,比如说我在这输入一个 a, 对吧?那么你看他叫验的时候呢?他是不是认为不正确呀?那么我们在这个地方需要输入合法的手机号,包括这个身份证号呢,我们其实也是做了叫验了,是一个十八位的数字。好,然后 后呢我们直接点击保存按钮,你看现在他是不是又提交过来了,对吧?然后这个数据呢,应该又可以封装了,那其他的我们就不再看了,直接给他翻过去就可以了,然后呢这边也直接翻过去, 好,到这边来,你看员工添加成功,对吧?那么添加成功之后呢,我们到这边来刷新一下,那么是不是又插入一条数据,并且我们可以看到这个密码是不是加密了? 好,这是咱们通过两种方式测试,对吧?一种呢是我们这个接口文档测试,还有一种呢就是前后端连条,那么经过测试呢,验证我们后端写的代码呢,是没有问题的。

接下来咱们继续来看微信登录,刚才呢咱们是说了一下微信登录的流程,并且呢我们是通过 postman 是不是测试了一下微信接口服务,对吧? 最终呢我们是能够拿到微信用户的唯一标识,也就是那个 open id, 对吧?那接下来呢,咱们就需要来做一个需求分析还有设计。 首先呢我们还是先通过这个产品原型来了解一下我们这块的需求,那这块的需求呢,其实还是比较简单的,最终呢其实就是完成登录功能,对吧?能够让我们的后端知道当前这个用户是谁, 那具体他这个登录的方式呢,其实跟我们以前传统的用户名密码登录方式呢,还是有很大的不同的,那么用户名密码登录呢?很显然他是不是就需要去注册账号,并且设置密码,并且呢使用他注册的这个账号密 密码是不是来进行登录?而我们这个微信登录啊,还需要去注册吗?是不是就不用了,对吧?那么我们直接来获得这个微信用户他的唯一标识,能够知道当前这个微信用户是谁,是不是就可以了, 所以你看从界面上来看呢,也是完全不同的。那么首先我们进入到这个小程序的时候呢,他会先弹这么一个窗口,提示我们需要微信登录之后是不是才能点餐, 然后我们点击这个确定按钮之后呢,就会弹这么一个窗口,对吧?那这个时候呢,他其实就是来获得一下我们当前这个微信用户,他的昵称还有头像, 那我们点击允许,这样呢就能进入到这个小程序当中,一旦他进入到我们这个点餐页面之后啊,其实就说明已经完成了登录,那这个时候呢,我们其实就已经知道了当前这个微信用户是谁,对吧?所以你看我们这块这个业务规则 呢,就是什么呢?就是基于微信登录来实现小程序的登录功能。那么如果当前这个微信用户是一个新用户,比如说他是第一次用我们这个小程序来点餐,对吧?那对于咱们这个外卖系统来说呢,这是不是就是一个新用户?那这个时候呢,我们就需要把这个新的用户啊, 给他保存到我们的数据库当中,其实就是完成自动注册,对吧?那接下来呢,咱们是不是就需要来设计一下,要完成我们这个微信登录这个过程,那么需要设计一个什么样的接口,对吧? 那前面呢通过这个微信登录的流程呢?咱们其实已经知道了,要完成微信登录的话呢,我们其实最终就要获得这个微信用户的 open id, 对吧?那么要获得这个 openid 呢,咱们是不是就需要使用到他的一个临时的登录凭证,也就是那个扣的授权码,对不对? 所以说呢,这个接口应该是什么样子的?是不是需要小程序这一端给我们后端发送请求,并且把这个授权码是不是给我们提交过来,对吧?然后呢我们拿着这个授权码,是不是就可以去调微信的接口服务? 然后呢我们需要给他返回什么数据啊?咱们是不是需要给这个用户返回令牌啊?对吧?而这个令牌里边是不是就包含这个用户的唯一标识,对吧?好,那接下来呢,咱们就具体来看一下我们这个接口, 哎,你看,因为是一个登录的请求,所以说呢,这个请求方式呢,是 pose 的,然后呢,这个请求路径是什么呢?是 gun user, gun user, gun login。 哎,这个地方这个路径好像挺奇怪的啊,那么为什么有两个杠 u 字啊?注意,这两个杠 u 字其实它的含义是不一样的啊,那第一个杠 u 字呢,代表的是用户端发过来的请求,那前面我们是不是做了一个约定啊?就是咱们整个这个 项目是不是分成了两个端,那么一个是管理端,一个是我们这个用户端,对吧?而管理端统一是以杠的命,是不是作为前缀,而我们这个用户端呢,是不是就是杠 user 作为前缀?所以说呢,我们第一个这个 user 呢,代表的是某个端它的标识, 然后后边这个 u 字呢,代表的是用户模块,对吧?然后老根印呢,就是具体做什么事,对吧?就是登录,所以这块这个路径呢,看起来好像比较奇怪,对吧?但是呢,他还是整体符合我们这个规范的好,这是关于我们这个请求路径啊,为什么这样来设计。 然后接下来呢,就是我们需要把这个扣的是不是给提交过来,然后呢可以通过请求题你看,以 jason 的方式给我们提交过来就可以了。好,那么我们返回的数据是什么呢?主要就是这么几项啊,一个是 id, 这个呢就是用户的唯一标识,注意这个 id 呢,指的是当 前这个用户在我们的这个数据库里面那个主见值,而这个 open id 是什么呢?是我们这个微信用户在微信那个服务器里面他的唯一标识,对吧?然后最后这个呢就是一个头感,也就是我们这个 j w t 令牌, 好,那这就是我们这个具体的接口设计。那刚才我们在做这个需求分析的时候呢,其实也提到了,就是什么呢?如果当前这个微信用户是一个新用户,那这个时候呢,我们就需要把这个用户的信息,把它保存到我们自己的某张数据库表当中, 所以接下来呢,咱们就需要来看一下我们这个用户这张表,哎,你看我们这个地方呢,涉及到了一个 user 表,对吧?那这里面存储的是什么呢?就是我们这个用户端的这些微信用户的信息,那么你看有 id 对吧?这个呢就是一个主见值,还一个是 open id, 就是我们这 微信用户的唯一标识,还有 name 就是用户的姓名,还有 phone, 就是手机号对吧?还有 sex 性别,还有 id number, 身份证号对吧?呃, what? 这个是微信用户的头像对吧?还有这个 courage time, 就是这个微信用户的注册时间, 那这里边呢,最重要的一个字段是什么呢?那很明显是不是应该就是这个 open id, 对吧?因为它是微信用户的唯一标识。 那在这个地方呢,我们需要来说明一下,就是这个手机号这个字段呢,其实是比较特殊的。那前面呢,咱们其实也提到过了不同的小程序啊,我们能够使用到的权限其实是不一样的, 如果我们是以个人身份注册的这个小程序啊,其实是没有权限获取到这个微信用户的手机号的,如果是以企业的资质注册的这个小程序呢,实际上就能够拿到这个微信用户的手机号, 那这一点呢,大家需要注意一下。好,咱们这是整体做了一个需求分析,还有接口的设计,包括呢还有这个用户表他的这个结构。那么一会呢,咱们就通过代码的方式啊,把咱们这个微信登录功能把它实现出来。

接下来呢,咱们继续来看我们这个营业额统计这个业务功能的开发,那么刚才呢,咱们是把这个 ctrl 的这个类已经创建好了,并且对应的呢,咱们是不是还创建了这么一个 service 接口,还有对应的这个实现类啊。那接下来呢,我们是不是就需要来实现咱们这个实现类里面的这个方法。 那么你看我们是通过这么两个参数啊,一个是 begin, 一个是摁的,是不是来指定了我们这个时间区间对吧?那么最终呢,我们就需要把这个数据给他统计出来,并且呢封装到这个 vo 对象里面,而这个 vo 对象里边呢,我们可以看到就这么两个数据对吧,一个是 details 的,还有一个是 ton overlish 的。 那么首先呢,我们先来考虑第一个就是这个 data list, 那这个应该如何获得啊?那么你看它这个字符串的规则是什么呢?什么应该就是我们从这个开始日期一直到结束 日期当中的每一天呐,你看这个地方是不是十月一号,十月二号,十月三号,把这些日期是不是给它连到一起,中间呢?以逗号是不是来进行分隔。那现在呢,我们就需要先把这个 detailis 的把这个字符串给它计算出来, 然后有了这个字符串之后呢,咱们是不是再去把每一天的营业额数据对应的去统计出来,然后封装到这个 turnover list 里边啊。所以咱们这个地方呢分成这么两步来走啊。第一步呢就是什么呢?先把这个 deter list 把它计算出来,那这个计算呢还需要查询数据库吗? 是不是应该就不需要了对吧?因为它其实是一个日期的计算。那相当于我们现在知道什么呢?我们知道整个这个字符串,它两头的这个日期分别对应的是不就是咱们这个地方的这个 begin 还有按的。那么基于这两个日期呢,我是不是就需要把咱 这个 detailis 的这个字符串给它算出来?那这个应该如何来计算呢?咱们可以这样,咱们呢先把我们这个从开始日期到结束日期,然后每一天都给它算出来。然后呢,把这些日期算出来之后呢,可以先放到一个类似的集合里边, 然后呢我们再把集合里边每个元素给它取出来,然后呢中间用逗号分隔开是不是就可以了?好,那接下来呢我们就可以先来计算一下啊。咱们在这呢先来整一个绿色的集合 来存放什么呢?从开始 begin 一直到 and 这个时间范围之内的每一天的日期。然后呢,我给他放到这个集合里面去。好,那么我们在这呢可以整这么一个集合,来一个类似的集合对吧?然后这里边放的是什么呢?就是我们这个 local data 是不是就可以,然后这个对象的名称呢?咱们就叫 detailis 就可以了。哎,咱们直接 new 一个 release 的好。那么我们在这呢可以加一个注视啊,当前这个集合用于存放什么呢?存放从这个 begin 这个日期对吧?到这个 end 这个日期范围内的每天的日期。 好,那么从 b 根到按的一共有多少天呢?那具体这些天都是哪些日期呢?咱们在这呢就需要通过这个日期来算一下啊,那么我们看咱们现在是相当于知道这么两头对不对?一个是开始,一个是结束。那现在呢,我们就需要把从开始到结束每一天是不是都给他算出来。 那么我们其实可以很容易的想到什么呢?就是我们这个集合里边第一个元素应该是谁啊?第一个元素是不是应该就是这个 begin 啊,然后最后一个元素呢,是不 应该就是这个 and, 但是具体它中间应该加了哪些日期呢?咱们是不是可以一天一天往后推着算呢?相当于就是从 begin 这一天是不是一个一个加到这个 detailis 这里边一直加到哪呢?一直加到我们这个 and 这一天是不是就可以了? 好,所以说呢,我们在这呢可以这样啊,先来 details 的点 add at 一下,把谁加进去呢?把这个 begin 给它加进去,然后下一次呢,我是不来 details 的点 add at 的是谁呢?是不应该就是 begin 这一天的往后推一天对吧?比如说 begin 这天呢,是四月一号, 然后再后一天呢,是不是应该就是四月二号。那关键是这个四月二号我怎么算出来呢?哎,实际上呢,我通过这个 begin 就可以算出来,因为这个日期类型呢,它其实是可以计算的。咱们直接 begin 点什么呢?叫做 plus day。 哎,这 其实就是用来加几天,我加一。那这样的话,他算出来之后呢,实际上就是咱们这个 begin 后一天。假设 begin 这一天呢,是四月一号,那我们这一段算出来之后呢,应该就是四月二号, 那这样的话,我是不是就可以一天一天的给他加下去,一直加到哪呢?一直加到摁的这一天。那也就说我加完之后,我是不是需要来判断一下,看看我们加完这一天之后,他等不等于摁的呢?那假设这个摁的就是四月二号呢,那我算完这一天之后是不是就完事了? 所以咱们在这呢,是不是就需要来计算这个边界值?当然了,我们不可能这样一个一个这样加下去对吧?那具体我加多少次呢?他是动态的,所以我们在这呢,很明显,我是不是需要去写一个循环呢? 咱们在这呢直接来一个 well 循环就可以。哎,就是我们这个 begin, 它如果不等于按的对吧?那么你看按的然后呢,应该是不等 等于的时候,咱们是不是就一天一天一直往下加下去对吧?就是这样的就可以这样来。 begin 等于什么呢? begin 点 plus days 一,这样的话,我们这一天一天是不是就往后移了? 那么算完之后呢,还重新复给 begin, 这样的话就把 begin 的后一天是不是给算出来了。那么算出来之后呢,别忘了把它加到我们这个例子的集合里面去,然后艾特一下对吧?然后呢,把这个 begin 是不是再放进去,大家要注意啊,你看我们经过计算之后呢,这个 begin 是不是就变了? 那么如果 begin 一直不等于 n 的,这就说明什么呢?还没有到这个结束这个时间对吧?那这样的话呢,它就一直便利,一直便利,那么一直便利到哪呢?一直便利到是不是相等, 那么相等的话,相当于就是最后一天对吧?那么笔根就等于 and 了,那么等于 and 之后呢?那这个 well 是不是就跳出了?所以咱们在这儿呢就是通过这种方式啊, 来计算一下,从我们这个开始到结束对吧?把这个范围之内的每一天,然后都给他装到这个 deter list 这个集合里面去。然后呢,我们可以在这啊再来加上这么一个注视啊。这个呢,其实就是日期计算对吧?那么计算什么呢?计算我们指定日期的后一天, 后一天对应的这个日期。那这个方法名呢,就是这个叫做 plus days 对吧? plus 其实就是加的意思对不对?那么就是加几天,然后呢,我们这儿写的是一是不是就应该就是加一天。 计算完之后,注意我们又重新复给了 begin。 所以说这个 begin 呢,它相当于是一天一天往后移对不对?一直移到哪呢?一直移到跟这个摁的相等。那这样的话呢,是不是就跳出这个循环,最终呢,我们这个 details 的里边装的是哪些元素呢?是不是应该就是从 begin 一直到摁的哎,这个范围之内 每一天的这个日期。好。那么到这的话,咱们这个 deter lis 的其实就已经搞定了。但是现在呢,你注意看啊,它是一个类似的类型。而最终呢,我们是不是需要给它封装到这个 vo 里边,给它转成一个字符串啊,对吧?那这个应该怎么转呢? 咱们在这呢,实际上就可以把我们这个集合里面每个元素是不是给他取出来,然后呢,我们中间呢用逗号给他分隔开。当然我们在这呢可以自己去便利这个集合,然后呢把这里面每个元素取出来,然后呢以逗号是不是来进行分隔, 那么我们也可以直接使用我们的一个工具类,然后这样的话写起来呢,这个代码其实会更加简洁一些。那么哪个工具类呢?哎,实际上就是我们这个 string utils, 大家看啊,在我们这个浪三这个包里边,咱们只需要使用这个工具类里边的这么一个方法,叫做 joy, joy 是不是就是连接的意思对吧。然后呢我们可以 传进来一个集合,那这个集合呢,很明显是不是应该就是咱们这个 detailis 的这个集合。然后呢,我们需要第二个参数,其实就是这个 separator, 说白了就是分割符。那么我们实际上呢应该是以什么风格呀,是不是应该是以逗号来进行分割呀?所以这个分割符呢就是一个逗号。 好。那么通过这个方法呢,咱们呢就可以得到这么一个字符串,而这个字符串呢,实际上就是把我们这个类似的集合里边的每个元素给它取出来,并且呢以逗号来进行分隔,最终呢拼成这么一个字符串。 那现在呢咱们在这呢就可以一步到位啊,直接把我们这个 v o 创建出来,然后呢把这个字符串给它设置进去啊,你看我们这是不是返回这么一个 v o 啊。然后呢我们直接调用这个 builder, 然后在这呢来设置一下我们这个 dat lis 的, dat lis 的是谁呢?是不应该就是我们在这算出来的这一段。然后呢我们直接 ctrl x 啊,把它剪切走,然后这行呢也给它去掉,然后直接放在这个位置。好,这是设置我们这个 detailis 的。然后最后呢我们直接来一个什么呢 build 方法来构建出来这个 vio 对象, 好在这呢我们可以这样给他稍微整理一下,你看这是调用这个 builder 对吧?然后呢设置这个,最后呢调用这个 build 方法把这个对象构建一下。 好。那么到这的话呢,咱们这个 deta lis 的这个字符串呢,其实就已经搞定了。当然现在咱们整个方法还没有完全写完对吧?相当于我们只是完成了第一部分,也就是构建我们这个 vo 对象里面这个 deta lis 的这个字符串。 然后还有呢下边这个 turn over list 呢。那这块的话是不是应该就是查询我们这个时间范围之内每一天的营业额数据,并且呢也是给他拼成这种字符串。但是这些营业额数据呢,那么我们是不是就需要查询数据库了?那么一会呢,咱们再来看这个营业额数据具体应该如何去查询。

刚才啊,咱们是通过一个入门案例的方式啊,给大家演示了一下我们应该如何来开发微信小程序,对吧?那么这里边呢其实主要编写的呢都是 g s 代码,也就是本质上呢它其实都属于前端的开发 好,那假设呢咱们这个小程序的代码都已经开发好了,那最后呢我们这个小程序应该如何发布上线呢?让所有的用户都能使用到这个小程序呢?那这个时候呢,大家来看,在我们这个开发工具右上角这个位置呢有一个上传按钮,那么我们点击这个上传按钮,然后呢点击确定, 然后呢在这呢来指定一个版本号,实际上呢就是将我们本地开发的这份代码把它上传到微信服务器,那么这个版本号呢,比如说我就来个二点一,好这么一个版本,然后点击上传按钮,好,你看 他现在提示我们上传成功,那是不是说我们这个代码上传到微信服务器,就表示我们这个小程序已经发布了呢?哎,其实并不是啊,当前我们这个小程序这个版本呢只是一个开发版本,咱们呢可以到我们这个微信公众平台上面来看, 你看啊,在这呢有这么一个菜单啊,叫做版本管理,对吧?那么我们点击这个菜单,这样的话呢就能打开这么一个版本管理页面,对吧?然后呢从下边这个地方大家能够看到有个开发版本,而具体这个版本号呢,就是我刚才指定的这个二点一, 这就是刚才咱们在这个地方点击上传按钮传上来的这个版本,对吧?那么你看他现在呢是开发版本,所以说呢,现在我们所有的微信用户呢,实际上在微信里面是搜索不到咱们这个小程序的,那么要想发布的话呢,我们需要来提交审核, 也就是点击这个按钮,然后呢你按照他这个向导去填一些资料,然后在审核过程当中呢,就会显示在这个位置,哎,你看在这个地方是不是有个审核版本,对吧?那假设我们这个审核版本通过之后呢,你就可以在这个地方点击一个发布按钮, 那么如果发布成功之后,那这个版本呢,就会显示在这个线上版本这个地方,那一旦成为线上版本呢,这就说明我们这个小程序呢就已经发布上线了,对吧?那这个时候呢,我们所有的微信用户啊, 就可以在微信里面去搜索到咱们这个小程序,最终呢来使用咱们这个小程序。好,这是咱们说了一下,我们如何来发布微信小程序。

接下来呢,咱们继续来看我们这个订单统计业务功能的开发。呃,刚才呢,咱们是分析出来了这么两条 circle 对吧?那么分别可以查询什么呢?哎,上面这个呢,就是查询每天的订单总数,而下边这个 circle 呢,查询的就是每天的有效订单数。 那对应的我是不是就需要在咱们的 map 当中来扩展相应的方法,最终呢来发出这样的 circle 啊。那么我们可以看到啊,这个地方我们写了这么两个 circle 对吧?那是不是说我们在 map 当中就需要来扩展两个方法呢? 哎,其实你会发现啊,咱们这两条 circle, 它的主体结构呢,都是一样的,区别在于哪呢?区别在于是不是我们这个条件不太一样。那这样的话呢,我们其实没有必要去扩展这么两个方法,咱们是不是只需要扩展一个方法,然后呢,给他做成动态条件, 这样是不是也可以啊,对吧?那这样的话,我们就不用去写那么多方法了。而我们这个地方这个条件呢,你看我们是不是大于某个值,小于某个值,咱们这个呢,是不是就可以称为叫做 begin, 然后下边这个呢,是不是叫做 n 的,最后呢,还有个 status 状态,对不对?那么这三个呢,给他做成动态的是不是就可以了? 你看咱们是不是查的是订单表啊。所以很显然我们是不是应该就在这个 all the map 里边来扩展就可以了。咱们呢在这就直接打开这个 all the map, 搜一下 all the map, 然后呢,在这个接口里边来扩展这么一个方法,对吧?那么它是统计数量啊,所以我们这个地方呢,就来返回一个 int 着是不就可以?然后呢,这个方法呢叫做 count by map, 然后呢,这个条件呢,还是一个 map, 然后在这呢来加上这么一个注释啊。那这个方法 它的作用是什么呢?是不是就是根据我们的动态条件来干什么呢?统计我们这个订单数量啊,对不对?好,那么应该就是这么一个方法。 由于它是一个动态锁口啊,所以说呢,我们还是写在咱们的 map 映射文件当中比较合适。呃,那么我们在这呢,直接 alt 回车,然后呢让它给我们生成一下,那么你看它是不是就生成在这个位置了,就是这个 count by map, 然后这个 circle 呢,咱们在这来写一下啊,那什么应该就是 select 什么呢? count, 然后 id 对吧,然后 from 什么呢? others, 这是咱们这个表名。 然后后边呢是不是就是我们这个动态条件。而这个条件的话呢,你会发现实际上跟咱们上面这个 sum by map 这个条件是不是都是一样的呀,对吧?你看是不是我们有 begin, 然后呢,有 end, 然后呢还有 sweeters。 所以说呢,我们就直接从这个 地方上来复制一下就可以了,因为都是完全一致的,那我们直接复制到这个位置。好,那这样的话,我们这个 circle 就已经搞定了。那么你看这个条件呢,跟上面这个是完全一样的。 然后呢,回到我们这个业务方法这边,那么我们在这呢,是不是就可以直接调用他了?咱们在这个地方呢,你看是不是两次查询最终都是调这个方法,那么我们要去勾到这个 map 对吧?呃,实际上我们这个地方呢,还有更好的一个方法就是什么呢?我们单独提一个方法出来,然后在这个地方呢,统一去调这个方法也是可以的。 那么只需要生命成 private, 也就是说只在我们当前这个方法里面来调用,那么返回结果呢,就是一个 int, 这就可以了。然后呢,就叫 get order count 是不是就可以?那这个方法它的作用是什么呢?是不是应该就是查询我们这个订单的数量啊?说白了就是我们在当前这个方法里面去 调 map。 当然了,我们是不是需要把条件给传进来啊,而这个条件的话呢,是不是应该就是这三个呀, begin and 还有我们这个 sweeters。 所以我们在这呢给它传这么三个参数进来是不是就可以了?呃,注意啊,一个是 local data time 这种类型,那么这个呢就叫 begin, 是不是就可以了?然后呢,再来一个 local data time, 这个呢就叫 end。 然后呢,我们再来一个什么呢? status, 然后呢,通过一个 int 着对吧,这个叫 status, 这是我们这个状态值。那这个方法它的作用是不是就是根据我们这个条件来干什么呢?统计我们这个订单数量对不对?好,那么我们在这里边呢,是不是来调我们这个 mapper 就可以了? 而钓 map 的时候呢,咱们是不是需要封装一个 map 呀?所以说呢,我们先把这个 map 呢给它创建一下,直接 new 一 个哈西 map。 然后呢,我们在这是不是来封装 map 点儿 pro 的一个呢,叫做 begin。 注意这个 key 的话呢,你是不是不能乱写,是不是应该跟我们这边这个地方是不是来对应啊?对吧,包括上面这个地方,你看都是 begin, 然后呢,我们这个地方是不是写 begin 他就能取出来了?好具体的这个值呢,是不是就是我们传进来的这个值 begin。 呃,然后呢,再来一个 map 点 put, 然后再来一个摁的吧,对应的值呢,是不是就是咱们这个参数摁的。最后呢,还有一个 map 点 put 什么呢? status 对吧?就是我们这个状态好, status 好。那么封装好,封装好之后呢,咱们是不是就可以直接调用我们这个 map 了,那么也就是我们这个 order map, count by map 这个方法对不对?然后呢,我们是不是直接返回就可以了?直接 retin 就可以, 相当于我们在这呢就是封装了这么一个方法,在这里边是不是直接去调就可以了?那这样的话,我们是不是就可以在咱们这个 fox 网里边直接调我们这个方法是不是就可以? 那么我们是不是需要准备这三个参数啊,对吧,一个是 begin, 一个是 n 的,还有一个是 status。 而这个 begin 的话,我们应该怎么准备啊?是不是应该就是获得我们这个 data, 它所对应的那个开始时刻呀, 是不是应该就是 local daytime, 点儿什么 up 对不对?然后呢,把这个 date 是不是传进去,然后呢,再来一个 local time, 什么呢?点面,这样呢,就能获取这个开始时刻啊,这就是咱们这个 begin time 吧。 好,这是那个开始时刻。那么同理我们这个结束那个时刻呢,是不是也是这个方法,然后呢,这个地方呢,这个长量是不是改成 max 就可以了?然后呢,我们是不是就能获得一个 and time, 然后接下来呢,我们是不是就可以去调下边这个方法了,对吧?那么我们直接调用这个什么呢? get order count 的这个方法。然后呢,把这个 begin time 是不是传进去,然后呢,再把这个 end time 传进去, 那第三个参数是不是 status? 那么你看我们这个订单总数,是不是不需要根据这个 status 去查呀?那我这个地方应该怎么传呢?哎,注意,我们其实只要传一个空进来就可以了, 那么你传一个空进来的话,相当于我们这个地方铺的进去是不是就是个空啊?那么他如果是空的话,在我们这个动态 c 口执行的时候呢,你看我是不是先来判断呢?那么如果拿出来之后,他是空呢,那么我们这个语句是不是就不会追加上, 那相当于是不是只是根据这个 order time 去查对不对?所以说呢,我们这个地方传一个空进去就可以了,哎,就是这个位置,那这个查出来呢,就是我们这个 order count 对吧?也 就是我们这个每天的订单总数。呃,然后接下来呢,我是不是就可以查每天的有效订单数啊?那这个有效订单数呢?我是不是也是调下边这个私有方法哎,也就是这个 get outercom 的。然后呢,把这三个条件是不是传进去哎,一个是 begin time, 还有一个是 end time, 还一个呢,就是我们这个状态值,这个状态值呢,实际上就是五对不对?但是呢,我们建议呢,还是写这个常量啊,这样呢是比较规范的,也就是这个 complete 的这个常量。那这样的话呢,我们就能查出来这么一个叫做 valid order 抗的哎,这个叫做有效订单数。 好,那么你看我们每次便利呢,是不是都会查出来这么两个数据啊?那对应的我是不是就需要整两个集合来存放这个数据啊?所以说呢,我们在这呢就需要来整这么两个集合,都是类似的这种类型就可以了。然后这个饭型呢,是不是就引起这就可以了。然后这 个编量名呢,就叫 order count list, 是不是就可以。然后呢,你有一个 are release 的这个呢是存放什么呢?哎,这个集合是存放我们每天的这个什么订单总数, 然后接下来呢是不是再来一个,再来一个类似的,然后这个范型呢,还是这种类型对吧?这个呢就是我们这个 valley 的 order count, 累死的哎。那么你看啊,这个呢是用来存放什么呢?就是存放我们每天的有效订单数。在这来写一下存放每天的有效订单数。 好,那这样的话,我是不是在这就直接来一个什么呢? add 就可以了哎,也就是这个 other countless 的,然后点 add 谁呢? add 我们这个 other count, 注意啊,分别去存放啊,不要存错了。呃,然后呢,还有一个是 valid other countless 的对不对?然后呢,他 添加的是谁呢?就是我们这个 valid altercat。 那么你看每次便利呢,是不是都会查出来这么两个数据,然后呢,我们是不是分别给它存放到我们这个集合里面去就可以了。 好,那么到这的话呢,大家来看啊,咱们是不是已经查出来了三部分数据了对吧?分别一个是这个得特利斯的,哎,是我们算出来的。然后呢,还有这么两个集合对吧?就是每天的这个订单总数,还有每天的有效订单数。 呃,然后呢,我们对照着咱们这个 vo 来看一下,现在相当于我们是不是已经准备了这么三个数据了。哎,就是这三个数据, 当然了,还需要给他转一下对吧?因为我们现在呢是封装成了一个类似的集合对象对不对?那么需要给他转成这种字符串啊。这个转的话呢,我们前面其实也都说过了,那么我们一会呢再去转,那相当于这三个我们就已经搞定了。呃,然后下边呢,还有三个 对吧,就是订单总数,还有有效订单数,还有订单完成率。那这三个数据应该如何确定呢?哎,你看啊,我们这个订单总数。首先呢,我们需要搞明白它的含义是什么呢?是不是应该就是我们整个这个时间区间之内订单总的数量。 所谓这个时间区间之内呢,指的就是从我们这个 b k 一直到 n 的这个时间段之内的订单总的数量,对不对?那这块的话,我们查询数据库能不能查出来哎,那很显然是不是也能查出来?我们是不是还是根据这个时间去卡就可以了,包括这个有效订单数,是不是也可以去查数据库。 但是实际上呢,我们这个地方呢,其实你不插数据库,我们应该也能搞定。哎,为什么呢?哎,大家来看呢,我们这个地方是不是已经搞定了这么两个集合呀,而这两个集合里边存的是什么呢?是不就是我们这个时间段之内每天 天的这个订单总数,还有每天的有效订单数啊?那我现在是不是只需要干什么呢?便利一下这个集合,然后呢,把每天的订单总数加到一起,是不是就是我们整个时间段之内的订单总数啊? 所以说我们这个地方呢,其实不查数据库也是没问题的。咱们是不是只需要把这两个集合变了一变,把这里边的数字是不是给他累加到一起就可以了? 咱们在这来写一下啊,我们需要计算的是什么呢?一个呢,是不是就是计算咱们这个时间区间内的内的这个订单总数量对吧?还有呢,就是计算我们这个时间区间内的这个什么呢?有效订单数量。 那这样的话,我是不是就需要来便利这两个集合呀?那么我们这个地方呢,实际上有一种简单的写法是什么呢?哎,咱们是不是 直接使用我们这个 stream 流,通过这个 stream 流呢?其实就可以将我们这个集合里面所有的元素啊,给它累加到一起。这样写起来呢,其实比我们传统的放循环呢,要简洁很多。接下来看我们是不是来 order count 类似的,然后点儿什么呢? stream 是不是获得我们这个 stream 流对象? 呃,然后呢,我们调用这里边的一个什么方法呢?叫做 reduce 哎,其实就是用来合并对吧?那么合并的时候应该是求平均数呢,还是求和呢?那很显然是不是应该是求和。 那么你看我们这个例子的里边放的元素是不是 int 着这种类型啊?所以说呢,我们就可以这样来写。再来看 int 着,然后呢,使用双冒号这种语法,然后来一个什么呢? sam。 这样呢,其实就是求和,说白了就是把我们这个类似的里边所有的元素变了一变,然后呢,进行求和计算。 修复完之后呢,直接来一个 get, 这样呢就能获得我们最终算出来的这个值。你看这种写法是不是非常简洁。这个呢,就是我们这个 total order cond, 哎,这是我们这个订单总数了啊。那么同理呢,咱们这个有效订单数呢,是不是也可以这样来算哎,只不过我们使用的就是这个集合了啊,就是这个 valley 的 outer count 例子的对吧?然后呢,来一个 stream, 然后呢,再来一个什么呢? reduce 对吧?然后呢,它还是 inter 这种类型。 所以说呢,我们还是引体者双末号。然后呢,来一个 sum, sum 是不是就是求和,然后呢,来一个 get, 这样呢就能获得最后计算的值。好,这就是咱们这个 valid order count。 哎,也就是有效订单数。好。那么这两个数据计算出来之后呢,咱们是不是就可以通过这两个数据来计算我 我们这个订单完成率啊,大家还记得吧?是不是就是两个数相除就可以了。所以说呢,咱们在这个地方来计算一下啊。这是计算我们这个订单完成率对吧? 那这个订单完成率呢?注意它是 double 类型的对吧?它 double 类型。然后我们这个变量名呢,就使用这边这个就可以了。你看叫做 order completion read, 那么就是它,然后它等于什么呢?是不是就是我们这个 valid order count, 它除以谁呢?除以我们这个 total order cond 好,那么易除就可以了。但是在这你会发现有点问题,因为这两个呢,是不是都是 inter 类型,而它呢是 double 类型对吧?那怎么办呢?我们是不是需要把这个数据啊,给它转一下,把它呢转成这种 double value, 这样的话呢,再相除,那么它的返回结果是不是就会变成 double 类型?好,那这样 这样的话呢,算出来就是我们这个订单完成率。但是在这呢,其实还有一个细节问题啊,就是我们这个 total outcome 的,也就是订单总数量如果是零的话,那这样一除的话是不是就有异常了。 所以我们在这个地方呢,就需要来处理一下这种情况。那怎么处理呢?哎,就是我们先来判断一下这个 total outcome 的他是否等于零,那么他如果等于零的话呢,我是不是就不能再除了?所以我们在这呢可以先给他一个初始值啊,就是我们这个订单完成率是什么呢?零点零。 然后呢,我们在这再来判断一下 if 什么呢?就是我们这个 total order count, 如果不等于零,这个时候呢,我是不是就可以放心大胆的来除了,那么我们再把它付给他就可以了。这样这就是计算我们这个订单完成率。哎,我们把这个注视 给他放到这个位置来,那这样的话,我们这个订单完成率是不是就计算好了?好,那么到这的话呢,你看我们这六个数据是不是都准备好了,那么都准备好之后呢,咱们是不是就可以给他封装到我们这个 vo 里边去了啊?所以说呢,我们在这呢就需要来创建一下这个 vo。 那么我们直接来一个什么呢? other report vo 吧。然后点儿什么呢? builder 对吧?然后在这儿呢来构建。 呃,首先呢,我们先来这个 data list。 呃, data list 呢,是不是还是通过我们这个死坠 youtube 四点二 joy 哎,这个代码我们其实都写过很多遍了啊。那么上面是不是有个 data list 的。然后呢,这个分格符呢?是使用逗号对吧?然后接下来呢,还有就是什么呢?就是我们这个 order count list 的。 那么这个 other count 的意思呢?咱们是不是还是通过 string you to 点儿 joy, 然后他处理的应该是哪个集合呢? 是不是就是我们上面这个奥德康的利斯的呀?哎,就是我们这个集合,你看我们在这是不是声明的啊。那么这里边存的呢是每天的订单总数。然后呢,我们在这个地方给他放进去。然后这个分割符呢?还是逗号。 呃,然后呢,就是我们这个 valley 的 other count 的,类似的对吧?那这个呢,也都是类似的啊。然后呢,再来 joy。 然后呢,这个就是我们这个 valley 的 other count。 类似的,把这个几何放进去。然后呢,分格符是逗号。 然后呢,接下来是不是就是我们的三个数字了对吧?哎,一个呢,是我们这个 total other count 对吧?这就是订单总数。那么订单总数呢,我们刚才是不是已经算出来了?就是这个好,那么给他放在这个位置。 呃,然后呢,就是我们这个 valid altercom 的。那么这是有效订单数,有效订单数就是这个,最后呢就是我们这个订单完成率对吧? 那这个订单完成率呢,我们刚才也算出来了。好,最后呢,是不是再调一个 biu 的方法,这样的话是不是就把我们这个 vo 给构建出来了。呃,咱们在这啊,直接把它剪切过来,直接返回就可以了。那么直接放在这个位置 好,把这呢稍微整理一下,然后呢把这个空行去除掉。好。那么到这的话,咱们整个这个订单统计的业务代码呢,其实就已经写完了。咱们可以来看一下。整个这个代码呢,其实还是比较多的,因为我们是不是要查询的一共是这六个数据啊,对不对, 所以这个代码呢稍微有一点点多啊。那么你看我们是先准备这个 dat list, 然后呢是不是在便利这个 dat list, 把每天的这个有效订单数还有订单总数是不是都查出来,并且放到这两个 list 集合里面去。然后呢我们再根据这两个集合是不是去计算这个区间之内定 订单总数,还有这个区间之内的有效订单数,对吧?最后呢,这两个数一除,就算出来我们整个这个订单完成率。然后呢算出来之后,再封装到这个 vo 里边来返回就可以了。好,那这个代码咱们就已经写完了。那么一会呢,咱们再来做一个功能测试就可以了。

接下来咱们继续来看员工分页查询,刚才呢我们是做了一个简单的需求分析,还有接口的设计,对吧?那接下来呢,我们就需要来进行代码的开发, 那么代码开发的时候呢,首先我们还是先来设计一下这个 dto, 因为前端呢,是不是给我们提交过来了请求参数,对不对?那现在我们就需要通过一个 dto 来封装一下, 那么前端给我们提交过来的数据呢?我们刚才看到了,有配置,有配置 size, 还有内幕,对吧?那对应的我们是不是就封装这么一个 d t o? 那在咱们的初始代码里边其实也已经提供好了啊,那么就是我们这个 employ 配置 carry d t o, 那么这里边的属性呢,跟我们看到的这三个参数都是一一对应的 好,那么这个 d t o 呢,是非常简单的啊,咱们直接拿过来使用就可以了。然后接下来呢,我们就需要来考虑一下我们返回来 这个数据类型,那前面呢,咱们其实也都强调过了,我们后端返回给前端的呢,统一是不是都会封装成一个瑞造特对象,对吧?那这个瑞造特呢,它其实是带有泛行的, 那么对于我们这种分页查询来说呢,咱们其实统一呢都会封装成这么一个对象啊,叫做配置 re 造的,对吧? 在我们这个类里面呢,就这么两个属性,一个是 total 还一个是 recose, 这个 total 呢代表的就是总的记录数,而这个 recose 呢,代表的就是我们当前这一页要展示的数据集合,所以你看它是个类似的集合,对吧? 那么我们这个地方这样设计,是不是恰好跟我们刚才看到的这个接口能够对应上,咱们呢?可以再来看一下我们的接口,那这边呢是我们的接口文档,对吧?咱们可以来看一下这个地方是我们员工分页查询的这个接口, 然后呢我们可以看到咱们返回来的这个数据,你看这个 dat, 这个 dat 呢,其实这里边封装的是不是应该就是我们这一个配置伪造的这么一个对象?那这里边呢,是不是就这么两个字段,一个是 total, 一个是 recouse, 对吧?而这个 recouse 里边放的是什么呢?是不是应该就是我们这个实体对象啊? 那么我们通过这个接口当中的这两个字段,就能看到他跟咱们设计的这个地方配置瑞造特对象里边的这两个属性是不是都能对应起来?好?所以后期呢,我们所有的飞跃查询呢,统一呢都会封装成这么一个配置瑞造特对象, 那么这是我们飞页查询之后的结果,是不封装成这个对象,但是我们要返回给前端的话呢,还需要这个对象再来封装一下,也就是统一封装成一个瑞造的对象,而这个瑞造的对象,他的发型是不是咱们这个配置瑞造的?那这 这样的话呢,我们把这个对象转成 jason, 把它返回给我们的前端,那么最终呢,是不是就是这么一个数据格式? 哎,你看这里边是不是有 code, 有 message, 还有 data, 对吧?而这三个属性呢,是不是就来自于我们这个 result? 而我们这个 result 里边的 data 属性呢?它是不是对应的又是一个对象,而这个对象呢就是咱们这个 page result, 那么后边呢,我们所有的分页查询呢?其实都是这个规则啊。好,那接下来呢,我们就打开咱们的开发工具,然后按照我们刚才设计的这个接口来开发我们后端的代码, 那现在呢,我已经打开了我们的开发工具,对吧?那这是我们的 employ ctrl, 然后呢我们在这个地方是不是需要来开发一个方法,就是我们这个分页查询的方法,对吧?然后呢我们在这儿来 public, 然后这儿伪造它,然后呢我们这个发型是不是需要明确一下,就是我们这 这个配置瑞造他,对吧?那么这个配置瑞造他在哪呢?我们可以按着 ctrl 点进来,然后呢点击这个按钮锁定他,你看是不是就是在我们这个 common 这个模块里边,对吧?这是配置瑞造他,那么统一来封装我们分页查询的结果。 好,那么回到我们这边来,咱们这个方法名呢,就叫配置就可以了, 暂时呢还是瑞特一个闹。然后我们在这个地方是不是需要来声明参数,接收我们前端提交过来的数据,对吧?那么前端提交过来的是什么数据呢?我们可以通过这个接口来观察一下,是不是应该就是这三个参数,对吧?就是这三个, 那么注意这个类型,刚才我们也说了,是不是应该是 carry 的,并不是 jason 格式的,对不对?那么我们要封装这几个参数呢?咱们是不是就可以通过我们刚才说的那个 dto, 也就是这个 employ, 哎?配置 carry d t o 吧?配置 carry d t o, 那么通过这个 d t o 就可以封装, 刚才我们也看到了,他这个数据格式呢,其实并不是 jason 的,所以我们这个地方呢,就不要再加这个什么 requires body 注解了,那么我们直接是不是声明这个参数就可以了,那么这样的话呢, supreme vc 框架呢,其实就可以正常的把这个数据啊给我们封装好。 好,那接下来呢,我们是不是需要在这来加一个 get 买品这种注解?因为我们这个是一个 get 方式的请求,对吧?你看这能看到, 然后这个请求路径呢?这个地方是不是就写一个杠 page 就可以了?你看它整体这个路径是 god mean gun imply, 对不对?而我们这个地方呢,是不是只要再加上这个杠配置就可以?因为在我们上面这个类上面,咱们是不是已经加上前面这一段了?好,这样拼起来呢,恰好就能对应上。 好,这是咱们这个注解,然后呢我们再来加一个 a p i operation 啊, 这个呢就是我们的员工分页查询,然后呢在这同样再加一个注视员工的一个分页查询, 咱们在这呢为了方便调试啊,我们还是要通过 log 点音符咱们来输出一下,对吧?那么我们这个地方呢,是一个员工分页查询,然后呢参数为,然后呢我们可以把这个参数给他输出一下,也就是我们这个 dto 对象,对吧?给他输出一下, 然后接下来我是不是就需要来调用我们的 service 实际来进行分页的查询呢?那我们是不是就需要调用这个 imply service, 那么我们希望他这里边是不是给我们提供一个分页查询的方法,比如说叫做配置 carry, 然后呢我们是不是把 这个参数给它传进去,也就是这个 dto 对象,然后我们希望这个方法给我们返回一个什么样的数据呢? 那么我们这个地方是不是希望他给我们返回这么一个配置 result 呀?因为我们刚才也已经说过了,我们所有的分页查询最终呢是不是都要返回这么一个对象?所以说呢,我们就希望啊这个方法给我返回这么一个对象, 当然现在这个方法还没有,一会我们再去完善它,对吧?那么我们暂时呢先把这块代码写完,那假设这个数据已经返回了,那这个时候呢,我是不是就可以 给它带进一步封装,封装到我们这个 result 里边,也就是通过 result 点什么呢? success, 对不对?然后呢我们把这个配置 result 对象给它放进去,咱们可以看一下这个 success 方法,你看这里边呢,它是不是会传进来一个 object 的对象,然后呢把这个对象付给我们这个 data, 对吧?而这个 dat 呢,是不是就是我们上面声明的这个属性,对吧?那么你看并且把这个扣的给它设置成一,最后呢把这个伪造的对象返回就可以了。好,那么到这的话,咱们这个 ctrl 的这个方法其实就已经写完了,当然现在呢他还报错,对吧?因为我们 service 里边现在是没有这个方法的,所以我们 out 回车, 咱们在这呢是不是需要来扩展一下这个分页查询方法,这是分页查询,然后呢我们找到它的实现类,对吧?在我们这个 service 实现类里边来实现我们这个配置 carry 这个方法。 好,这是我们的分页查询,那么我们要实现这个分页查询呢?其实最底层的是基于什么呢?是基于我们这个数据库层面的分页,比如说我们要查第一页,那么查实条,那这个时候呢,我是不是 就可以写 select 星 from 我们这个表,对吧? employ, 然后后边是不是来个 limit, 那么咱们是不是通过 limit 关键字来进行查询?比如说我写个零,这是不是表示我从第一条开始查,一共查十条,对不对?所以说我们底层呢,其实是基于买 circle 的这个 limit 关键字来实现的分页查询。 而我们这个 dto 里边是不是已经把我们的页码还有美页的记录数传过来了?那这样的话,我是不是就可以动态的去把这两个参数给它计算出来,然后给它拼到这个色口里边去,对不对?当然呢,如果是这样写的话呢,那就需要我们自己去计算,自己去拼。 而实际上呢,我们在项目当中啊,往往这些细节问题,有一些框架或者是一些插件呢,其实都已经给我们搞定了,那这个地方呢,咱们会介绍一个新的插件,其实是买 这个框架,他给我们提供的一个插件叫什么呢?叫做 page helper, 那么通过这个 page helper 呢,可以简化咱们这个分页代码的编写, 当然我们要使用它的话呢,首先需要在我们的泡沫文件里边把它引进来,那咱们这个出示工程里面呢,其实已经导入过了,那么我们可以看到就是这个,你看就是这个配件还有款,那么他给我们提供了这么一个 stat, 对不对?那么我们直接把它引进来, 那么引进来之后呢,我们再编写咱们这个代码,其实就非常简单了,那怎么做呢?我们在这个地方是不是打算要进行飞页查询呢? 那么我们这个地方就可以来写开始分页查询,那你是不是就需要告诉我们这个插件,你现在是想查第几页?并且呢你想查几条?那么我们通过什么样的方式来告诉我们这个插件呢?那其实就是通过钓这么一个方法 叫做 page helper, 然后点儿什么呢? stop 配置,你看这叫做开始分页,对吧?然后呢我们把这个页码给它传进去, 而这个页码的话,我们是不是通过这个 d t o 已经封装了,对吧?所以我们通过这个 d t o 点 get 配置,这是不是就是页码?然后第二个参数呢?就是我们这个 ph size 对不对?也就是美业的记录数, 所以我们现在呢只要一掉这个代码,这个其实就表示我们开始分页,把这个页码还有每页的记录数传给他, 那么传给他之后呢,我们就可以正常去调用我们后边的这个 circle 语句,这个插件呢,它其实底层是基于我们这个 my batis 的拦截器来实现, 说白了就是什么呢?他会把我们后边的一条 circle 进行一个动态的拼接,其实就类似于咱们买 beitis 的动态 circle, 它会动态的把这个 limit 关键字给我们拼进去,并且把这两个参数动态的去计算,因为我是不是已经把页码还有每月记录数传给他了,对不对?他可以动态的去算 给我们拼进后边的那条 c 口当中,那么最终是不是就实现了我们这个分页查询?那这样的好处就是什么呢?就是我们这个字符串的拼接就不用自己做了,对不对?而是交给了我们这个小的插件。 好,那接下来呢,我们是不是就需要来编写一条 circle 来执行我们这个查询呢?对吧?那这样的话,我是不是就需要在我们这个 employ mapper 当中来提供一个查询的方法,对吧?比如说我们叫配置 carry, 然后呢我们把这个参数给他传进去, dto 传进去, 因为我们这个 dto 里边是不是还有我们的查询条件,还有这个内幕,也就是这个员工姓名是不是需要动态的给他拼到我们这个搜口语句里面去?所以 说啊,这个地方呢,我们需要把这个 dto 作为参数传到我们这个方法里面去,那我这个方法需要返回一个什么样的结果呢?注意啊,这个地方因为我们使用了人家这个插件,那么我们就需要遵循他的规则,他要求我们后边这个方法返回值是固定的,就叫做配置, 注意这个配置不要导错了,是我们这个 page helper 这个包里面的,对吧?然后呢,这里边这个饭型写什么呢?应该就是写我们这个实体类型就可以了,也就是 inpuli 实体。好,这样的话呢,我们是不是就得到了一个配置对象,当然现在呢还没有这个方法,对吧?咱们呢可以把这个方法来扩展一下啊。 好,那么我们扩展到这个地方来,这个呢就是我们这个飞页查询的方法,飞页查询, 那现在我们是不是需要来编写一条 circle, 而这个 circle 的话呢,我们这个地方就不再使用这种注解的方式了,因为我们这个地方呢需要使用到动态 circle, 那么对于动态 circle 呢,我们通过注解这种方式来编写的话呢,其实不太方便,对吧?因为我们会使用到动态标签, 所以这个时候呢,咱们就把这条 circle 啊,把它写到我们的映射文件里面去,那现在呢,我们是不是需要一个映射文件?其实呢在我们这个 server 当中啊,咱们已经将这个映射文件准备好了, 就是在我们这个 mapper 这个地方,你看是不是有一个叫做 imply mapper, 对吧?那么我们可以看到这个 name space 按着 control 点能点过来,这就说明他们是不是对应好了。 那现在我们这个插苗映射文件能不能被我们这个项目扫描到呢?哎,咱们这个地方呢,其实是可以扫描到的,因为在我们这个配置文件这个位置, 是不是配置了这么一个 mapper locations, 对吧?大家看这个其实就是去扫描我们这些插苗应设文件,而咱们这个地方是不是已经指定了 clus pass 在 mapper 下边星点插苗,你看我们当前是不是就是在这个 mapper 下边,这样的话呢?他是不是就能扫描到我们这个插苗文件? 好,那接下来呢,我们是不是就可以来编写咱们这个 circle 语句了?那么我们现在是不是已经装上这个 my betty 的插这个插件了,对吧?那么如果有这个插件的话呢?它其实会提示我们,你看这个地方它是会报错呀,那么我们直接 out 回车,是不是来生成这条 circle 就可以了? 咱们选择 select 啊,它是不是就生成到我们这里面来?当然这只是一个基础的配置,对吧?你看这个 id 是不是对应的就是我们这个方法名称对不对?而我们这个地方这个返回值类型呢?是不是对应的就是我们这块这个范型,它不应该是个配置吗? 注意,这个配置呢,它其实是我们这个配置 helper 插件提供的,它其实本质上是一个类似的集合,你看它是不是继承了一个耳为类似的呀?那么我们这个地方其实描述的是什么呢?是这个集合里边的这个元素类型,对吧?也就是我们这个 employ 实体, 所以说啊,这个是没有问题的。好,那么我们在这呢,是不是就需要来编写一下咱们这个动态 circle, 那么我们这个 circle 写起来呢,应该也是比较简单的,是不是就是根据我们这个内幕动态来查询就可以了?那咱们在这呢一起来写一下,是不是应该就是 select 星 from, 我们这个表是不是就是 employ 表? 然后呢,我们接下来是不是应该写 where 条件,而这个 where 的话呢,咱们是不是可以使用这个 where 标签?可以吧?这不是我们这个 my batis 里边提供的这个标签吗?然后呢,这个条件的话呢,我是不是可以通过 if 去判断 算一下,对吧? test 等于什么呢?如果 name 不等于空, 然后呢?我们再来个按的吧,按的 name 不等于空串吧,那么判断的全面一点,这样的话,我们是不是就给他添加一个条件?条件是什么呢?是不是就是根据这个 name 来进行查询? 注意这个内幕是员工的姓名,对不对?那么这个姓名作为条件的时候,我们用等值查询呢,好像并不是很合适, 那应该用什么呢?是不是模糊查询会更合理一点啊?也就是我们通过 like 模糊查询,对不对?我们这个地方呢,可以使用这个什么呢? concate, 咱们这个买色购的函数是不是来拼接字符串就可以了?动态给它拼进去, 咱们是不是可以把这个通配符也一块给他拼进去,对吧?也就是把我们这两个百分号是不是给他拼进去?但是中间呢,是不是应该就是我们这个内幕?这样的话呢?是不是做一个字符串的拼接, 然后中间呢,是不是应该就是我们动态的这个内幕的这个值,对不对?比如说我传进来的是个章,那这样的话呢,它最终拼完之后是不是就是按 the name like, 然后呢?是不是一个字符串就是百分号章,然后百分号,对吧?这样是不是就动态拼起来了? 好,那么这个地方呢,我们是通过买 circle 的这个 can cat 这个函数,也就是字符串拼接的函数,把我们这三部分是不是动态的给它拼到一起,组成一个字符串,而这个字符串呢,就是我们整个 circle 语句的一部分,对吧?那这个地方我们要注意啊,我还需要自己去写 limit 关键字吗? 哎,其实就并不需要了,对吧?因为我们刚才也说了,咱们是不是通过这个配置 help 这个插件呢?他其实可以动态的给我们去追加这一串,所以咱们这个地方呢,只需要把这个条件啊动态给他拼进来就可以了。 好,拼完之后呢,我们再给他加一个排序条件吧,来一个 order by。 呃,那么我们这块呢,就根据我们这条数据的创建时间就可以了,也就是 correct time, 然后呢 d, e, s, c 降序来排就可以了。 好,那这样的话,咱们这条 soco 语句就已经写好了啊,然后回到我们 map 这个地方,那么你看这边是不是就不报错了,对吧?那么是没有问题的。然后在我们 service 里边,是不是直接调用 map 的这个配置 carry 这个方法,然后呢就可以返回这么一个配置对象, 但是我们整个方法呢,想返回的其实是一个配置伪造的,对不对?而我们这个 map 当中的这个方法呢,给我们返回的是一个配置对象,那么我们是不是需要对这个配置对象进行一个加工处理,把它处理成我们这种配置伪造的对象啊?哎,那这个如何处理呢?你看配置伪造的这个对象要构造的时候呢,是不是需要两 两个参数,对吧?一个就是 total, 还一个是 recos, 那么我们现在返回的这个配置对象能够拿到这两个属性吗?哎,其实是可以的,你看在我们这个配置里边呢,其实就可以 get 一个 total, 你看这就是总的记录数, 然后呢我们通过配置点 get result, 这个其实拿到的就是我们那个 records。 好,所以你看啊,我们返回来这个配置对象呢,就可以获得这两个值,那么既然已经获得这两个值了,我是不是就可以把它封装到我们这个配置 result 里边去?所以我们在这呢直接 new 一个 page result 对象, 然后呢第一个参数就是这个头头,然后第二个参数呢,就是这个 recounts 好,把它传进去,其实就是调的我们这个配置 result 的一个有参的构造方法,对不对?你看我们在这儿是不是已经加了这个注解,对吧?那这样的话,它就会 给我们生成一个有参的构造方法,所以我们其实就是调的这个构造方法,然后把这两个属性给它传进来,从而来构造这个对象。那这个地方呢,大家可以看到我们使用配置 helper 这个插件进行分页查询的时候呢,从代码层面来看,好像非常简单。 那大家呢,可能会有一点疑问,就是什么呢?也就是我们后边调用的这个配置 carry 这个方法跟你前面这个 star 的配置这个方法到底有什么关系呢? 对吧?他并没有给他传什么参数啊,那他怎么就知道后边这个要进行分页查询,并且他还能够计算出来我们这个页码还有美业记录数呢? 哎,那这个地方他是怎么做到的呢?哎,其实啊,我们可以简单来看一下他这个原码啊,那么你看我们在这个地方是不是调了这么一个死达的配置这个方法, 而这个 star 的配置这个方法里边他又掉了这个 star 的配置方法,他这里边重载的方法会很多,对吧?那么把我们这个页码还有美业记录数是不是传进来,那么传进来之后呢?这里边重点代码其实是这么一个,你看叫做 set local page, 然后呢他把这个配置对象是不是设置进去了?而这个配置对象里边是不是就已经封装了我们传进来这两个参数,对吧?这两个关键分页的参数,而这个 set local 配置这个方法是干什么事的呢?你看他在这其实是调了这么一个 local 配置点 set, 而这个 local 配置是什么呢?大家从这个地方其实能够看到,它其实是一个 thread local 这个对象,而这个对象呢,咱们前面是不是刚刚讲过,所以说咱们这个配置 helpful 底层呢?它其实也是基于这个 thread local 来实现的,那么其实就是在这个地方, 他把我们这个配置是不是存到我们这块存储空间里面去了?然后后边我们是不是开始进行分页查询,那么在分页查询之前呢,他其实就是通过这个索要的 logo, 把我们这个页码还有美业记录数啊,其实又给取出来了, 那么取出来之后呢,它会动态地把这个 limit 关键字给我们拼进去,并且把我们这个页码还有每页记录数啊动态地算出来,然后最终呢构成这么一个 circle, 这就是它的底层实现原理。 好,那么到这的话呢,咱们这个飞页查询的代码呢,就已经写完了,那么一会呢,我们就可以直接来测试了。

前面呢,咱们是已经完成了缓存菜品这个功能的开发,那接下来呢,咱们继续往下看啊。下一个呢就是缓存套餐。 那么在这个章节当中呢,咱们会通过下边这四个部分来讲。首先第一个呢叫做 spring catch, 实际上呢,这是由 spring 给我们提供的一个缓存框架,那么使用它呢,就可以进一步来简化我们的代码。 那具体他是怎么做到简化的呢?哎,其实呢,他是给我们提供了一系列的注解,那么我们要操作缓存数据呢,只需要在相应的方法上加入他给我们提供的这些注解就可以了。 而这种使用方式啊,就非常类似于我们前面已经学过的事物管理,大家可以想一下。那么我们要进行事物控制的时候呢,咱们是不是只需要在 service 的方法上面加入 一个失误注解就可以了,对吧?那现在呢,我们要操作缓存数据呢,也是在我们相应的方法上面,只要来加入一个缓存注解其实就可以了。 所以说我们使用这个框架呢,就可以进一步来简化我们缓存代码的开发,从而呢来提高咱们的开发效率。好。那么我们第一部分呢,就需要来学习一下这个框架它具体的使用方式。 然后接下来第二部分呢,就是实现思路,也就是说我们要使用这个 spring catch 来缓存咱们的套餐数据对吧?那具体我们应该如何来操作?那么具体的一个实现思路是什么样子的?那么我们在这一部分呢,就需要来分析一下 好。那么分析好之后呢,接下来呢,我们就会进入代码的开发,那么通过加入他相关的一系列的注解呢,就可以把这些数据给缓存起来。那最后呢,我们来做一个功能测试就可以了。好,那 那接下来呢,咱们先来看第一部分啊,也就是我们这个 spring catch。 那么首先呢,我们先来了解一下这个 spring catch。 你看啊, spring catch 呢,它其实是一个框架对吧,它实现了基于注解的缓存功能。那么我们只需要简单的加一个注解,就能实现缓存功能。 那这个地方呢,咱们需要特殊来强调一下,就是这个 spring catch 这个框架呀,它其实只是提供了一层抽象。 那么底层呢,可以切换不同的缓存实线。那具体都有哪些缓存实线呢?那比较常见的呢,就是下边这几个,一个呢叫做 eh catch。 哎,这是具体的一个缓存产品。然后呢,还有就是 caffeine。 那再往下呢,就是我们这个 release。 当然在咱们这个项目当中呢,我们是把这些数据呢,都会缓存到 redis 当中。所以说呢,我们这个地方的缓存实现呢,就是 redis。 那么使用这个框架呢,相对来说就非常灵活。如果说我们后期这个项目啊,想换一个具体的缓存实线,比如说我不想使用 redis 了对吧?那么我想使用 eh catch。 其实我们只需要在咱们这个项目当中啊,再额外导入 eh catch 相关的价包就可以了。 而这个 springcat 提供的这些注解呢,其实都是通用的。那这样的话呢,我们就可以非常轻松地来切换不同的实线。而我们的代码呢,不用做任何修改。 那么我们要想在咱们这个项目当中啊,使用 spring catch 这个小的框架呢,就需要在咱们的项目的 power 文件当中来导入它的铭文坐标,哎,就是这个叫做 spring boot start catch 对吧? 那具体在咱们这个项目当中,我们要使用哪个缓存实现呢?那这个 spring catch 他是怎么知道的呢?哎,其实这个地方呢,我们不用 做任何配置,我们只需要在咱们这个 power 文件当中来导入 redis 的一个 java 客户端就可以了。就比如说我们这个 spring data redis 对吧?那么我们只需要导入这个坐标,那这个时候呢, spring catch 呢,就会使用 redis 来作为我们真正的缓存实现,说白了就会把我们的数据呢,把它保存到 redis 当中。 那么如果你想使用 e h catch 来作为缓存实现呢?那你只需要导入 e h catch 它的眉文坐标就可以了。 那这样的话呢,使用起来是不是就非常方便了。那么我们要使用这个 spring cash 啊,其实主要就是需要去使用它的一系列的注解。所以接下来呢,咱们就需要先来了解一下这个 spring cash 给我们提供的一些常用注解。 首先第一个呢,叫做 enable catching, 那这个注解它的作用是什么呢?它其实就是用来开启我们这种缓存注解功能的, 一般呢是加在启动类上,那么加到启动类上之后呢,这就表示我们要开启这种注解方式的缓存功能。然后第二个注解呢,就是这个 catchable, 那这个注解呢,一般呢是加在我们的方法上面,它的作用是什么呢?就是在某个方法执行前,那么他会先查询缓存中的数据, 那么如果查询到缓存数据了呢,就直接返回缓存数据。那么如果没有查询到缓存数据呢,那么他就会通过反射来调用这个方法,并且呢,将这个方法的返回值把它放到缓存当中。哎,这就是这个注解它的作用。 而这个地方我们看到的这个处理逻辑,是不是恰好跟咱们前边自己实现的我们缓存菜品那个逻辑是非常类似的,对吧?大家想一下,我们前边缓存菜品的时候,是不是就是这样来处理?首先呢,咱们是不是先去查 reddies, 看看 有没有缓存数据对吧?那么如果查询到了呢,我们是不是就直接使用这个数据,然后呢,返回给前端对吧?那么如果没有查询到呢,咱们是不是再去查数据库,最终呢,把数据库里面查到的这个数据再给它放到缓存当中。 而现在呢,我们这个注解呢,其实就是这么一个作用。说白了就是什么呢?我们只需要在这个方法上面加入这个注解,就能实现相同的效果。好,这是咱们这个注解它的作用。 然后接下来呢,咱们再来看第三个注解,也就是这个 catch put 这个注解。那这个注解呢,一般呢也是放在某个方法上面,那么他是将我们这个方法的返回值把它放到缓存当中。 那接下来呢,咱们需要来对比一下,就是下边这个注解也可以将返回结果放到缓存当中,对吧?而这个 catchable 呢,它是不是也可以将返回值放到缓存当中啊,那它们的区别在哪呢?哎,区别就在 于我们这个 catch put 呀,他只是放缓存数据,而不取缓存数据。而这个 catchable 呢,是不是他既可以取,然后呢,又可以放,对吧?所以说呢,他们之间其实是有很大的区别的。 然后接下来呢,咱们再来看最后一个注解,就是这个 catch evict 这个注解。那么它的作用呢?就是将一条或者是多条数据啊,从缓存当中来删除,说白了就是用来清理缓存的这么一个注解。 好,咱们这是先整体了解了一下这个注解啊。那么一会呢,咱们就会通过一个入门案例的方式啊,具体来学习一下这个 spring catch 它的用法。

那刚才呢,咱们其实也提到了,那么我们要使用 spring catch 啊,其实主要呢就是去使用它提供的这些注解,对吧?那么通过它这些注解呢,就可以操作我们缓存当中的数据, 那现在呢,咱们可以来看一下,那么我们要想使用它这个框架的话,首先呢是不是就需要来加入这么一个注解,就是这个 inable catching, 对吧?这就表示什么呢?要开启这种缓存注解功能,对吧?那么一般呢是加在咱们这个启动类上的,所以说呢,我们在这个地方就需要打开它的启动类, 在这个地方呢来加入这个开启的注解,叫做 enable catching, 对吧?开启我们这种缓存注解功能,那接下来我们就需要来考虑他给我们提供的这几个注解应该加在什么位置呢?咱们呢其实一般呢就给他加在这 个 ctrl 的方法上面就可以了,那比如说我当前呢有这么一个 save 方法,那么我们这个方法是干什么的呢?那很明显是不是应该就是新增一个用户啊?他是不是就是把传过来的这个用户数据给他插到我们这个优则表里边? 那现在呢,我是希望做到什么效果呢?就是当你这个数据插入到数据库的同时,我还需要把这个数据啊给他保存到我们的 redis 当中一份。 那原先呢,我们是不是通过那个 redis 他们 later, 然后通过编码的方式来操作这个缓存数据啊?哎,那现在呢,我希望把这个 user 对象给他存到我们的 redis 当中,那么我通过 spring cash 应该如何做到呢? 哎,大家来看它是不是给我们提供了这么一个注解,叫做 catch put, 那这个注解它的作用是什么呢?是不是就是将我们这个方法的返回值把它放到缓存当中啊? 哎,那你看我们当前这个方法的返回值是不是恰好就是一个 user 对象?那这样的话,我是不是就可以在咱们这个 ctrl 的这个 save 方法这个位置来加入这么一个注解啊?对吧?那么叫做什么呢?叫做 catch put。 哎,你看就是这么一个注解, 然后呢,这个注解的话呢,我们在这里边需要来指定一个什么呢?叫做 catch names, 你看这个叫做缓存名称,对吧?那这个缓存名称给个什么值呢?哎,注意,缓存名称啊,它其实对应的就是一个字符串,而这个字符串的话,其实你写什么都可以, 那具体我们应该写什么比较合适呢?哎,一般呢,我们写的应该是跟业务相关的,比如说我们当前呢,这个要缓存的是不是一个用户数据啊?所以说呢,我们在这呢就起这么一个名字,叫做 user catch 就比较合适了,代表的是不是就是用户 缓存?那注意啊,咱们最终这个数据是不是要存到 redis 当中啊?那么我们知道 redis 当中它其实是这种 key value 结构的数据,对不对?那现在呢,我们这个数据存到 redis, 那这个 key 是什么呢? 它这个地方呢,其实是有这么一套规则的,那么它这个 key 是怎么构成的呢?注意,我们这个地方呢,如果使用这个 spring catch 来缓存数据,那么这个 key 的生成是什么呢?它是跟我们这个 catch names 有关系的,那具体这个值呢?就是我们设置的这个地方,这个 user catch, 然后呢再加上两个冒号,然后后边呢会跟上一个值,那具体这个值是什么呢?哎,注意是跟我们这个地方设置的这个叫做 k 有关系。哎,你看这个地方,比如说我设置一个 a、 b、 c, 那最终呢,他这个地方呢,就会变成 a、 b、 c, 那最终存到 redis 当中啊,整体就是这么一个 k。 当然了,我如果真的把这个地方写成 a、 b、 c 的话,那我是不是保存每一个用户的时候,他算出来这个 k 都是固定的这个值啊? 那这样的话是不是就没有意义了?因为我们是希望什么呢?每一个用户保存到数据库的同时,是不是需要对应自己的一个缓存数据啊? 那也就说我们是不是应该这些 k 需要区分开?那么如果你写成 a、 b、 c 的话呢?它是不是就一样了?那相当于后边保存的缓存数据就会把前面的缓存数据给覆盖掉啊?因为它算出来这个 k 是不是都是相同的? 所以说呢,我们这个地方呢,就需要有一种手段来动态地计算出来一个 key, 那最终呢就能保证什 什么呢?每一次我们插入一个用户的同时,他计算出来的这个 k 是不是都是动态的?那我这应该写个什么呢?是不是应该是跟我们当前这个用户的唯一标识有关系啊? 因为咱们这个数据库表当中的不同的数据,它的主见值是不是都是不一样的呀?所以说呢,我们这个地方呢,如果能够拿到当前这个用户的 id 是不是最好了? 那么我们能不能拿到呢?哎,这个地方其实是可以的,那么我们需要使用它的一种新的语法,叫做什么呢?叫做 sp e l, 那这个 sp e l 呢,实际上就是 spring e l, 实际上呢就是 spring 的表达式语言,那么它这个地方呢,是有一个固定的语法的,具体是什么语法呢?就是必须以井号开头,然后呢我们就可以写 user 点 i d, 那 这个地方这个 u 字是不是固定写法呢?哎,注意他并不是固定写法,他应该对应的是谁呢?大家看,你看我这个参数名是不叫 u 字呀?他其实呢是跟这个行参保持一致的,那比如说我这个行参叫 a, 那你这个地方呢就需要写 a, 所以说只要上下统一就可以了。 好,那这样的话呢,它就可以动态地取到我们这个用户的 id 字段,所以最终呢就会拼到这个位置,那比如说我这个用户的 id 是一,那么它算出来之后,整个这个 k 是不是就是这一段? 那如果说我这个 id 是二呢?那他算出来之后是不是就是这个样子的?所以说呢,我们通过这种井号,对吧?然后后边跟上这个字符串,这种语法呢,就可以动态的来算出来这么一个值,然后最终呢拼成一个完整的 key, 那么存到我们 redis 当中呢,使用的就是这个动态的 key, 而这个 地方计算 k 的这个表达式呢,咱们可以来看一下。咱们呢直接按着 ctrl 点进来,你看现在呢,是不是已经进到他的这个代码里边来了,对吧?但是现在呢,他是 class 文件,对吧?那么我们需要来下载一下原码,那么我们直接点击这个 download sauce, 把它的原码下载下来。 好,那现在呢就已经下载下来了,大家来看一下,那么在我们使用这个 key 的时候呢,就可以使用这种 s, p, e, l, 那么你看就叫 spring expression language, 那么翻译过来呢,叫做 spring 的表达式语言,那么使用它呢就可以干什么呢? for computing the key dominically, 这是什么意思呢? 说白了就是可以动态的去计算我们这个 key, 那这个地方呢,它的这个用法其实很多,我们既可以使用这个井号 result, 然后呢也可以使用 这个什么井号 rut, 这个地方呢,咱们可以来写一下啊,那么回到这边来,大家来看,那我这个地方呢,既可以这样来写,那么实际上呢,还可以这么写,他这个语法呢,其实非常灵活, 咱们把上面这个注视起来,然后呢你看,那这个地方这个 k 呢,我除了可以这么写之外呢,还可以怎么写呢?你看他其实有一个瑞造的这个关键字。 好,这个 result, 那这个 result 代表的是什么呢?它代表的是我们当前这个方法的返回值,那么返回值是不是就是这个 u 造对象, 对吧?而这个 u 字对象里边是不是有个 id 属性啊?那么我们点 id 就可以了,那这样的话呢,他其实也可以动态的取到我们这个 id 的值,只不过这个 id 值呢,他是从这个 u 字这个返回结果这取到的,对吧?而上面这个地方呢,是从我们这个参数 这个地方取到了,但是最终呢,它算出来的这个 key 是不是都是一样的?哎,因为都是这个用户的 id 嘛,对吧?无非就是这个写法不太一样。 那这个地方,这个字符串,大家千万不要写错了啊,他是固定的,你看人家这个地方,他是不是通过这个井号瑞造他,对吧?然后呢可以引用到什么呢?我们这个方法的返回值, 这不写了吗? the result of the method, 对吧? invocation, 那么可以获得我们当前这个方法的返回结果,那么通过这个 result 就可以引用到当前这个 user 对象, 那么我们再通过一个点,注意这个点呢,他其实也有一个名字啊,叫什么呢?叫做对向导航,对向导航说白了就是什么呢?这个瑞造的呢,得到的是一个优字对象,然后优字里边呢,可能还有相应的属性,对吧?那么我们通过一个点呢,就能得到他这里边的属性,这是对向导航这种写法。 好,那现在呢,我们除了这种写法之外,还可以怎么写呢?哎,咱们可以把这个注视起来,然后呢再来看,还可以怎么写, 你看我们还可以怎么写呢?就是在这个地方,我可以通过这个井号 p e 或者是什么 a e, 那么这样的话呢,其实获得的是什么呢?是第二个参数,那比如说我当前这个参数啊,是不是只有一个 user, 对吧?那么我如果想获得当前这个 save 方法的第一个参数, 那我就可以怎么写呢?哎,我就可以写这样的井号 p 零,注意他这个编号呢,是从零开始的,那么零的话呢,代表的就是第一个参数,如果你写 p 一呢,他其实取到的就是第二个参数, 当然了,咱们现在根本就没有第二个参数,对吧?那么如果你真的写 p e 呢,它其实就报错了,因为我们这儿只有一个参数,所以这儿呢 就只能写 p 零,那这样的话呢,他其实就可以拿到我们第一个参数,那第一个参数呢?实际上就还是优的啊,对不对?然后呢,我现在还想取他的 id 属性,所以还是点 id, 我们通过这种方式呢,其实取到的就是第一个参数,那么除了这个 p 零呢,还可以写什么呢?还可以写 a 零, 好,那么我们在这呢,再复制一个,那么给它改成 a 零,实际上也是可以的,那么 a 零的话呢,代表的也是第一个参数,那具体它有多少种用法呢?哎,大家呢,其实通过这个地方来查看它这个文档,其实都能看到,对吧? 你看这个地方,人家这不写了吗?是不是井号 p 一,或者是井号 a 一,是不是都可以,对吧?甚至呢,你还可以这么写,井号 rut 点二,什么 a r g s 一,那么跟它的效果也是一样的, 咱们呢,可以再来写一个,好给它放到这,也就是说我在这个地方呢,还可以这样写,井号 root, 点儿什么呢? a r g s, 然后呢方括号零,这代表的是什么呢?其实也是我们当前这个方法,它的第一个参数,那第一个参数是不是还是 u 的,对吧?那还是 u 的的话,我是不是点 id 就可以了? 所以大家可以看到,我现在一共是写了这么几种啊,五种写法,对不对?那这五种写法其实最终的效果都是完全一致的,这就是它这个 spel, 那么使用起来呢是非常灵活的, 当然在这些方法里边呢,我们还是建议大家使用哪一种啊?是不是应该是使用第一种是最直观的,对不对?那么我们直接跟他这个参数名保持一致就可以了,具体这些方法呢,大家可以 自己来测试一下,咱们就不再一个一个测试了,那么我们就直接使用第一个就可以了,最终的效果呢,其实都是完全一致的。好,那接下来呢,咱们就可以具体来测试一下这个效果,咱们呢可以来启动一下我们这个项目。 好,那么这个项目呢,已经启动成功了,大家可以看到我们现在这个端口号呢,是四个八,对吧?并且呢已经生成了接口文档,所以说呢,我们要测试这个方法呢,咱们就直接通过这个接口文档来测试就可以了。好,那么我们在这加一个断点,然后呢我们通过接口文档来测试一下。 好,那么我们输入这个,你看 logo hose 四个八,然后呢 dock 点 a t m l, 这样的话呢,你看是不是就打开了我们这个接口文档,对吧?然后呢我们现在要测试哪个方法呢?咱们是不是要测试这个 save 方法呀? 对吧?然后呢我们在这点调试,那这个地方呢,你看我们只需要输入 name 跟 age 就可以了。 id 为什么不用输入呢?哎,因为这个 id 啊,他是自增的,你看咱们这个数据库里面这个 id 啊,他其实是一个自增的,所以说呢,我们并不需要给他提交过来,咱们可以来看一下, 你看这个地方呢,能够看到它是 out to increment, 对吧?然后呢我们也可以打开这个用户表,你看现在这个表里边是没有数据的,对吧?那还有一点我们需要注意一下,就是在插入完这个数据之后啊,大家来看这个地方,他是不是会把这个主见值给我们重新复到这个 u 的对象上啊? 对吧?你看这是我们使用的这个 use generated keys 等于处,然后 keep property 等于 id, 对吧?那这样的话呢,当咱们这个 circle 语句执行完之后啊,那生成的这个主见值呢,是不是就会附给我们这个 user 对象的 id 属性啊?你看在这里边是不是有 id 属性?那么也正是因为付给这个 id 了,所以说呢,咱们这个地方,你看这个 user 对象啊,那么最终呢,他是不是会返回这个 user, 对吧?那这个呢,跟这个是不是同一个对象,对吧?是同一个引用, 所以说我们这个地方写悠着点 id 实际上是没有问题的。哎,因为呢,他是先插入到数据库,那么插入到数据库之后,这个 id 是不是就有值了,然后再去操作缓存数据,那这个时候他是不是就能取到这个 id 的值?好,那接下来呢,咱们可以先到我们这个 readys 当中啊,再来观察一下我们这个缓存数据。 呃,现在我们用的是几号数据库呢?我们需要来确认一下。到这边,你看我们使用的是一号数据库啊,你看这不是 database 是一吗?对吧?所以说呢,我们需要切换到咱们这个一号数据库里面来,在这个地方找到我们这个一号数据库,好,那现在呢是没有数 据的,对吧?然后呢我们通过这个接口文档点击这个发送按钮,好,那么现在呢这个请求就已经过来了,大家可以看一下我们现在这个 user 对象里边这个 id 是不是空的,对吧?然后呢内幕跟 age 是已经传过来了,那具体这个值呢?就是咱们接口文档这个地方是不是给的这个值, 那这个也是没有问题的。然后呢我们向数据库里面是不是来插入一条数据,直接 f 六走,那么你看他是不是发了一个音色的语句,那现在的话呢,我们的数据库里边应该就有数据了,对吧?你看这条数据就已经进来了, 那现在我们这个 redis 当中有没有数据呢?哎,其实现在还没有数据呢,咱们到这边可以来刷新一下,你看现在是没有数据的,那么他必须执行完这个 save 方法之后,那么才能有数据。然后呢我们也可以看一下咱们这个 user 对象里边,你看这个 i a d 是不是就已经负上值了,对吧?好,那接下来呢它就要操作缓存数据了,然后呢我们直接 f 八让它翻过去,好,那么翻过去之后呢,咱们到 red s 当中来刷新一下,然后呢大家会发现这里边是不是就多了这么一个 user catch, 然后呢在 user cat 下边是不是有个 empty, 然后在 empty 下边还有这么一个注意啊,咱们 redis 当中的 key 啊,它其实也是可以做成这种树形结构的,那什么是树形结构呢?大家来看, 你比如说我们当前这个包结构,你看在我这个扎瓦下边是不是有这么一个 com 包? com 下边有什么 it 黑马?然后下边有 ctrl, 对不对?有 entity, 有 map, 这是不是一个树形结构的这个包啊? 而我们这个 reddis 的 key 呢?它其实也可以做成这样,那它这个层次是怎么划分出来的呢?实际上就是通过冒号,所以大家可以看到我们整体这个 key 是这么一个结构,你看是不是就是 user cat, 然后两个冒号,然后呢后边跟上一个一,这其实是我们这个整体的一个 key, 而这个 key 的话呢,你会发现它是不是会展示成这种竖形结构啊?对吧?那么你看它是可以展开的,那这个地方为什么有个 empty 呢?哎,就是因为我们这个地方其实有两个冒号, 那中间呢?他就会形成这么一个 mpt 这么一个包。比如说我们可以再来做一个试验,大家来看我们在这呢可以打开一个黑窗口,咱们自己来设置一个数据,比如说我 set 一个 a, 冒号 b, 冒号 c, 冒号 d, 大家来看我现在写的这堆字符串,这其实是一个完整的 k, 并且呢你看它是不是中间有冒号分隔呀?对吧?然后呢我们具体的这个值呢?我来一个 it cast, 然后呢我回车,大家来看这个效果,你看这个地方是不是有个 a, 然后 a 下边是不是有个 b, 然后 b 下边有个 c, c 下边有个 d, 那么整体它这个 k 其实就是这一段, 然后呢我们在这个地方展示的时候呢,他就会展示成这种竖形的结果。那接下来呢,我们可以再来保存一个数据,比如说我现在啊 set a 下边有个 b, 然后 b 下边有个 cc, 下边有个 e, 然后具体这个值呢叫做 it 黑马。 然后呢我执行一下,哎,大家看这个效果,你看它是不是就是个竖形的 a b c, 然后 c 下边是不是又分成这两个,对吧?然后呢你需要知道它其实完整的这个 k 呢,就是这一段,那么中间呢,是不是通过冒号进行分隔,这样的话呢,展示的时候呢,就会展示成这种竖形的结构 啊,在这呢我们简单了解一下就可以了。然后呢回到我们这边来,你看我们最终生成的这个 key 是不是就是 user catch 双冒号,然后 加上这个一,对吧?那这个一是怎么来的呢?哎,是不是就是来自于我们当前这个主见值啊?对吧?你看这个主见值是不是一,好,那现在呢,我们可以再来保存一条数据啊,呃,咱们把这呢稍微改一下就可以了, 然后呢直接点发送,这边呢,他是不是又过来了?然后呢我们直接给他放过去。好,那这样的话先看数据库里边 是不是产生了一条数据,然后呢再到 redis 当中来刷新一下,哎,你看是不是又出来一个,就是这个 user catch 冒号,冒号二,对不对?那么就是这么一个结构, 那么通过这个地方咱们也能看到咱们在存储这个数据的时候啊,他是不是使用的是 redis 当中的死菌字符串这种类型来存储的,对吧?那么我们这个 u 字对象呢,其实已经进行了序列化,那最终呢其实就是这么一个数据。好,那现在呢,大家回过头来来看一下, 咱们现在来操作这个缓存数据是不是就变得非常简单?我们只需要怎么做呢?咱们是不是只需要在我们这个方法上面加入一个 catch put 注解,是不是就可以操作缓存数据啊?那这个地方呢,我们重点需要强调的是什么呢?其实就是这块它这个 key 的写法 对不对?那么它的写法是非常灵活的,你只需要知道它是通过这种语法,其实是在动态算这么一个字符串对不对?而这个字符串呢,就会成为我们最终整个 k 的一部分就可以了。 好,那么知识咱们说了一下,咱们这个 spring catch 当中这个 catch put 这个注解它的使用方式。

接下来咱们继续来看用户下单这个业务功能的开发。刚才呢,咱们是根据我们这个用户下单的接口设计啊,把我们这个 ctrl 是不是已经创建好了,并且呢,我们把对应的 service 还有 map 都已经创建出来了,但是具体这个 service 里面这个方法呢,现在是空的。那接下来呢,我们就需要来实现咱们 service 当中这个用户下单这个业务方法。 那首先呢,我们需要先来分析一下,我们要实现这个用户下单,他的这个逻辑应该是什么呀?那前面呢,咱们在做数据库设计的时候呢,当时其实也提到了,那么我们用户下单这块呢,主要会涉及到几张表啊,是不是应该主要是两张表的操作呀,一个就是我们这个订单表, 也就是用户下单之后呢,需要向这个订单表里边是不是来插入一条数据。那么用户下单之后呢,我们是不是也 也需要像这个订单明细表来插入数据,那是一条数据吗?哎,其实并不一定啊,通过前面咱们数据库设计啊,咱们也知道了,我们这个订单表呢,跟订单明细表是不是一个一对多的关系, 那也就是说我这个订单明细是不是可能会有多个对吧。所以说呢,我们用户下单的时候呢,这个订单明细表当中,我们有可能会插入多条数据。所以我们在这个地方呢,先来写一下这个处理逻辑。首先第一个呢就是什么呢?我们需要向我们这个订单表来插入什么呢?一条数据。 这个呢,其实是可以确定的,就是不管用户买的是多少商品,那么只要他提交,这就是一个订单。所以说呢,对应的就是一条订单数据。然后呢,我们还需要向这个订单明细表插入 n 条 数据,那也就说有可能是一条,也可能两条,也可能更多。然后呢,我们再来思考一下,假设这个用户已经下单成功了,那么我们用户的购物车当中的这些数据还需要吗? 哎,其实就不需要了对吧?所以说呢,如果说用户下单成功之后呢,是不是应该是清空购物车呀?清空这个用户的购物车数据。 其实咱们呢应该都有类似的经验啊,就是我们在外卖平台点餐的时候,咱们往往是把这个商品先加到购物车对吧?然后再去提交订单。那么提交完订单之后呢,购物车里面应该就没有数据了。所以咱们这个地方呢,需要记得去清空用户的购物车数据。 然后清空完之后呢,咱们在这个地方还需要做什么事了?哎,你看我们这个方法是不是有返回结果呀?所以我们在这呢就需要来封装一下我们这个 vo 这个返回结果。 好,这是咱们这个方法主要的处理逻辑。那么实际上呢,在真正像我们这个订单表插入数据之前呢,往往呢我们还会做一些较任性的工作。比如说用户提交过来之后呢,我们是不是需要先来看一下,那么用户的这个收货地址到底有没有问题。 如果这个收货地址有问题,比如说他根本就没有提交,那这个时候呢,我是不是就没有办法给他配送啊?如果出现这种情况呢,我们这个用户下单应该就会失败。 还有就是用户啊,他虽然提交过来了一个请求,但是呢,他这个购物车数据是空的,那你说他什么都没有买,对不对?那么如果这种情况呢,我是不是应该也不能给他完成下单呢? 所以说呢,我们一般呢在做这个业务操作之前,需要先来处理一下什么呢?各种异常情况。注意,这种叫做业务异常啊,各种业务异常。那么具 体呢,就包括这么两个,其中一个呢,就是咱们这个收货地址的问题,也就是我们这个地址部哎,如果为空这种情况, 那么如果出现这种情况呢,肯定是不能下单的对吧?还有一个呢,就是我们这个购物车数据为空,那么也不能下单。所以我们在这呢,需要先来判断一下是不是出现了这种情况, 那这个地方要操作我们的地址部还有购物车这两张表,那对应的我是不是就需要注入两个 map 呀,对吧?比如说操作我们这个地址部的话呢,那我是不是就需要来注入一个什么,是不是叫做啊, dress book map。 那么通过这个 map 来操作我们这个地址部这张表, 然后呢,我是不是还需要来注入一个购物车的,也就是这个 shopping cat mapper 好,这个 mapper 好。那么把这儿处理一下 好。那接下来呢,咱们就需要按照我们这个顺序啊,先来判断什么呢?地址布,看看是不是空的。哎,那么我怎么知道他这个地址布是不是空的呢? 看我们这个 dto 里边是不是有一个叫做啊,主要是 book id 啊,这个是不是就需要我们前端小程序给咱们提交过来?那么现在呢,我们就需要把这个 id 给它取出来,然后来查一下数据库,看看能不能查到,对吧?那么如果查出来是空的呢, 那就说明他是不是不存在啊?那不存在的话呢,我们这个时候呢,就不能继续往下处理了,而是干什么呢?是不是应该直接抛一个业务异常就可以了?所以我们在这个地方啊。再来看 我们来处理各种异常情况。首先呢,我们先来调用这个 address book map 点儿什么,应该是 get by id 对吧?而这个 id 的话,刚才我们看到了,在 dq 里面是不是已经封装了,所以我们在这儿直接 get address book id。 这样的话呢,我是不是就可以获得一个啊,主要是 book 对象。然后呢,我们再来判断一下对吧, if 什么呢啊,主要是 book, 如果等于空, 哎,这是不是说明我们根据主见根本就没有查到这条数据啊?那如果查不上来呢,那我是不是就没有办法正常下单?那怎么办呢?咱们直接抛一个业务异常就可以了。在这来抛出业务异常, 咱们在这来直接 throw new 一个什么呢,叫做 address book business exception。 哎,就是这个。然后呢,我们在这呢直接使用这个 message constant 的咱们自定义的这个常量类,对吧?然后这里边有一个什么呢,叫做 address book is now。 哎,这个时候呢,前端小程序呢,就会提示这个错误信息,你看就是这个什么用户地址为空,不能下单,就是这么一个提示啊。 好,那么这是这种异常情况对吧?然后还有一种情况是什么呢?咱们是不是需要先来检查一下用户的购物车是不是空的?哎,如果购物车是空的呢,是不是也没有办法正常下单?所以我们在这呢就需要来查询当前用户的购物车数据。 那当前用户是谁呀?我是不是需要来获得一下?咱们是不是还是使用这种方式 base contact, 点 get 什么 current id, 这样的话呢,就能获得我们当前用户的 id。 然后呢,我们是不是就可以调用这个 shopping cart mapper 的一个什么方法,是不是应该叫做 list 的方法呀? 但是你注意看啊,他这个方法要求传入的是一个 shopping card, 也就是购物车这种实体对象对吧?那么我们在这儿是不是就需要给他来创建一下?好,那么他需要一个 shopping card 对象,咱们就直接给他 new 出来就可以了,你有一个 招聘卡的。然后呢,我们的条件是不是需要来封装一下,条件应该是什么呀?你看我是不是要查的是当前用户的购物车呀,所以这个条件很显然是不是应该就是这个用户的 id 啊,对吧?那么我们在这来封装一下 烧瓶卡的点 set 什么?应该是 uzid 吧, uzid 咱们这儿是不是已经取出来了?好,那么我们把这一句啊,给它往下挪一下,给它放到一起。好,这样。那么这其实整个构成的就是我们的查询条件,也就是我们这个烧饼 cut 这个对象,然后呢,给它传进去, 是不是应该只要封装这一个条件就可以了,就是查询当前用户的购物车,所以只要把用户 id 设置一下,其他的呢?就没有了。然后呢,我们在这是不是来调用 list 方法来获取一下这个呢?就是我们这个 shoppingcat list。 然后我们在这来判断什么呢?看看能不能查上来购物 车数据,如果查不上来呢,那是不是说明当前这个用户的购物车是空的呀?那么如果是空的,是不是应该也不能正常下单?所以我们在这呢就需要来判断一下 if 什么呢?这个 shopping card list, 它如果等于空 或者是什么呢?沙宾 cut list 点 size, 如果等于零,这个时候呢,就说明没有查上来数据。那如果没有查上来数据呢?我们是不是也需要来抛出业务异常啊?业务异常 好,咱们在这直接 through 还是 new 一个什么呢?这个就是我们这个 shopping cat business exception 购物车的业务异常。然后呢,我们在这呢还是使用这个常量类啊, message constant 点什么呢?叫做 shopping cat is now。 哎,你看这个提示信息, 我们在这呢其实也提前定义好了,就是这个购物车为空,不能下单。好。那么到这的话呢,咱们是把我们用户下单之前需要处理的这些各种异常是不是来判断了一下。 但是这个地方呢,我们需要来强调一下,就是对于绝大部分情况下来说呢,实际上你不做这个判断呢,应该问题也不大。因为什么呢?他如果是通过小程序提交过来的请求呢?其实在小程序那端呢,也做了判断,如果购物车是空的话呢,他其实是没有办法提交的, 包括我们这个啊, dresbook 就是这个地址布,如果是空呢,前端呢,其实也做了判断,也是不能提交的。但是为了我们这个代码的健壮性啊,我们建议呢还是在后端再来多次判断一下。因为如果说用户他并不是通过小程序提交的,而是通过其他的一些方式, 比如说刨损门呢,来提交这些请求。那这个时候呢,其实是没有做任何教验的。那么如果说我们后端还不做教验的话呢,那在处理的时候可能就会出现各种问题。所以说呢,虽然说前端小程序对这些参数进行了教验,那么我们后端呢,其实也是需要进行教验的, 这样呢才能提高我们这个代码的健壮性。好。那么到这的话呢,咱们这个各种异常情况啊,就已经处理好了。

其实呢,我们今年呢,也是去年底哈,我们把这个瑞吉外卖呢升级了一版哈,叫苍穹外卖啊,不是说改了个名哈,我们加了很多很多的功能哈,比如说以前瑞吉外卖里边, 你比如支付,我们没有去做,因为很多同学呢没有支付账号,不懂得去练习,我们把支付系统加上了,那比如说报表啊,通知啊,我们经常点外卖,我们可以 听到,比如说我们扫码下单,下完单之后呢,那个餐厅里边是不是会有你有新的订单,请注意查收等等哈,类似于这样的声音,就是 web socket 来通知的,语音播报的,还有呢,各种统计的图形化报表,练大家复杂搜索的查询能力等等哈,把这个项目呢功能业务更加丰富了, 更完善跟完整了,包括呢各个表怎么设计,结构怎么分析,把这个项目呢,从头的去升级了一个大版本啊,升级完了之后呢,这个课呢已经做完了哈,我也准备下个月吧,把它免费的送给大家哈。

接下来呢,咱们继续来看我们这个营业额统计。那么刚才呢咱们是做了一个简单的需求分析,还有我们这个接口的设计。 那接下来呢咱们就进入我们这个代码开发这个环节。咱们可以再来看一下我们刚才设计的这个接口,那么主要呢就是这个返回结果。咱们可以看到,在我们这个 dat 当中呢,是不是分成了这么两项对吧,一个呢叫 data 类似的,还有一个叫做 ton over 类似的。 那我们这个数据呢,我们最好是给它封装成一个 vo 对象来返回,那这样呢是比较合理的。当然了,咱们这个 vo 呢就比较简单了, 就跟我们这两项是不是来进行对应就可以了。那也就是说我们提供这么一个 vo, 大家看叫 turn over report vo。 而这个 vo 类里边呢,就这么两个属性啊,分别对应的就是这两个字段对吧?一个是 deterless 的,还有一个 是 turn overlease 的,哎,大家来看就是这个死钟类型的,哎,这么两个字符串。好。那接下来呢,咱们就直接进入到我们的开发工具,然后呢按照我们设计的这个接口,把这个业务功能把它开发出来。 好,那么大家可以看到啊,我现在呢是已经打开了我们这个开发工具。那接下来呢,咱们是不是就要基于我们刚才设计的这个营业额统计这个接口来开发我们这个代码, 那现在我们这个代码应该写在哪呢?再来看我们这个营业额统计。这个接口呢,是这样设计的啊,你看是 go on admit, go on report, 然后 go turn over statistics 首先呢,杠啊的命,这是不是说明应该对应的是一个商家管理端的这么一个功能啊,对吧?那既然是商家管理端的,那咱们这个 ctrl 应该是在哪啊?是不是应该是在我们这个啊的命这个包下边对吧?那么前面呢,我们一直是研 续这个规则的啊,所以我们这块呢还是在 ctrl 下边的这个额的命这个包下边是不是来编写。然后呢,我们可以看到这个路径呢,叫做 gun report。 report 呢,其实指的就是统计的意思啊。那你说咱们在这呢可以创建一个叫做 report control 了,是不是就可以? 那接下来呢,咱们在这来写一下哎,也就是在我们这个 admin 这个包下边,咱们来创建一个新的 ctrl 啊,就叫 report ctrl 就可以了。哎,这就是统计的这么一个 ctrl。 好。那么我们在这呢可以先来加一个注视啊, 这是我们这个数据统计啊相关接口。然后呢,该加的注解是不是给它来加一下。首先呢,我们加一个什么呢? rest ctrl。 然后呢,再来一个 request mapping。 然后这里边呢是不是应该是 go admin gon report。 那么你看跟我们刚才设计的这个接口,前面这个路径是不是能够匹配上了就对吧。然后回到这边来。然后呢,再来加一个什么呢?是不是再来加一个这个 a p i 这么一个注解。然后呢,这儿来一个 tax。 这个呢就叫数据统计相关接口就可以了。然后呢,再来加一个 sleep for day, 方便我们来记录日志。 好,这是把相关的注解呢,来加一下。然后接下来呢,我们是不是需要来创建一个方法,那这个方法应该是什么呢?是不是应该就是统计我们这个营业额的呀,对吧?那么我们在这呢,就需要把这个方法来创建一下 public, 然后呢,这个返回值 result。 然后这里边这个范型应该是什么呀?是不应该就是刚才咱们设计的这个 vo 对象啊,对吧?那这个 vo 大家还记得 吧?是不应该叫做 turn over 哎, report vo 哎,就是这个。然后呢,我们打开可以来看一下,你看这个 vo 里边是不是就这么两个属性啊,一个就是这个 detailis, 还有一个就是 turn overlis 的。好,这个刚才我们在课件上呢,其实也都见过了。然后回到这边来。 好,这就是我们这个返回结果就是这个样子的。然后这个方法名叫什么呢?哎,咱们就叫 turn over。 然后后边这个统计这个单词呢,比较难写啊,叫什么呢?叫做 statistics。 好,这是这个统计这个单词, 然后暂时呢我们先 reat 一个 not 啊,让他能编译通过。然后这个方法应该有几个参数啊,是不是应该有两个参数啊?咱们可以看一下我们刚才设计的这个接口,你看我们是不是提交过来这么两个参数对吧,一个叫 b g, 还一个呢,就是这个按的对吧?那么这么两个参数,那现在 单呢,我们是不是就需要来接收一下。注意这个参数呢,并不是 jason 格式的对吧?他其实是这种 carry 参数,说白了就是在地址栏里边通过问号这种方式是不是来传餐呢?所以大家需要注意一下这个格式啊,那么他并不是 jason 的好。那么我们在这呢,就需要来接收这两个参数。 那这两个参数我们可以看到它其实是日期这种格式的对吧?也就是年月日这种格式的。那么我们在这应该怎么接收呢?当然我们通过一个字符串是不是应该能够接收到,但是更好的应该是什么呢?咱们是不是应该对应的,就使用我们的日期类型来接收。 那具体哪个日期类型呢?哎,实际上我们这呢就可以使用这个 local data 就可以 local data, 你看一个叫 begin, 然后再来一个什么呢? local data, 然后这个应该是什么呢?这个视频应该就是按的呀。但是在这呢,你如果直接这么写的话呢, 实际上是有问题的。因为这种日期类型啊,他实际上是比较特殊的,因为我们知道这个日期类型啊,他其实是有固定的这个模式的对吧,比如说什么年月日啊,还有什么年月日十分秒啊,甚至还有月份是不是在前面,然后年在后边,那么他其实是不同格式的。 那具体你这个传过来的这个串,它是什么格式的呢?那么我们在这呢,就需要通过一个注解的方式来描述一下这个日期格式, 否则的话呢,它是不能正确封装的啊。那么我们在这呢就会使用到这么一个注解啊。那这个注解呢,其实就是这个 dayt time format, 哎,就是这个注解。然后呢,我们在这里边来指定什么呢?就是指定这个 pattern 好时间呢,就是指定一下我们这个日期他的这个格式。那咱们这个地方传过来的应该是什么呢?时间是这种年月日啊,年月日这种格 是这个格式呢,不要写错了啊,就是这种年月日。然后中间呢,是以这种短横线来进行分隔的。好,这是我们这个注解对吧?注意他是加在哪了,他是加在我们这个 begin 这个属性上面了对吧?那对应的我这个 and 呢,他其实也是这种格式的呢。 所以说我们在这呢,就需要再来复制一份。放在哪呢?放在我们这个按的这个属性上面。好,放在这就可以了。但是这呢,我们也可以把这个给他整理成一行也没有问题。 这是这两个参数啊,一个叫 begin, 一个叫 on 对吧?并且呢,我们使用这种 local day 这种日期类型来接收。 然后呢,咱们再在这个方法上面啊,来加上相应的注解。哎,一个是什么呢? get myping, 因为我们刚才看到了它属于 get 方式的请求。然后这个请求路径呢,实际上对应的就是我们这个方法名哎,也就是我们 这这一段哎,你看叫做 turn over statistics 对吧?那这个单词呢,是比较难写的啊,那么我们就可以直接复制一下。然后呢,放在这个位置, 注意是以斜线开头。然后呢,把这个单词放在这个位置。呃,然后呢,在这呢再来加上一个什么呢? a p i operation 吧。 a p i operation 这么一个注解。然后呢,我们在这里面来写一下,这就是我们这个什么呢?营业额统计对吧?营业额统计。 然后在这呢再来加一个注视,这就是我们这个营业额统计的啊。这么一个方法好,放在这个位置 好,这是根据我们刚才设计的这个营业额统计的接口,咱们呢把这个类呢包括这个方法就已经创建好了。那具体我这个营业额应该是如何统计呢?那我这个业务逻辑应该如何来书写呢?咱们是不是需要给他封装到一个 当中去完成啊。所以说呢,我们在这呢就需要一个新的 service 啊,咱们呢就跟这个 ctrl 命名保持一致,你看这个 ctrl 呢,是不是叫 report ctrl 了,所以我们这个 service 呢,就叫 repot service 就可以了。好,那么我们在这呢先来声明一下, private 叫 report service, report service, 然后呢,通过一个 auto where 的是不是来注入一下,当然现在这个 service 接口呢,还没有,对吧?那么我们可以复制一下。然后呢,在我们这个 service 这个包下边呢,来创建这么一个接口,哎,就叫这个 report service, 注意是接口啊。然后呢,双击 这样的话呢,我们就把这个接口给创建好了。那下边呢,咱们是不是就需要在我们这个接口里边来声明一个方法。那么通过这个方法来干什么呢?来统计我们这个营业额。那么 这个方法的返回值应该是什么呢?咱们是不是应该就使用我们刚才定义的这个 vo 啊,对吧,那么我们就使用这个 vo 就可以了啊。哎,因为呢,我们这个接口最终就要给他返回这样的数据哎,所以说呢,我们就直接让这个 service 把这个 vo 给我们返回。 然后这个方法名就叫什么呢?咱们就叫 get turn over statistics, 这就是获得我们这个营业额统计这个数据对吧? 然后呢,我们这个方法需不需要参数呢?那很明显肯定是需要参数的,那你让我进行营业额统计,那统计的是哪些日期的呢? 你是不是需要通过参数传给我呀?并且咱们在这呢,其实已经看到了,你看我们这个 ctrl 这个地方呢,是不是已经获得了一个 begin, 还有按的这是不是就是咱们这个开始时间还有结束时间呢,对吧?那么我们是不是就需要把这两个参数传给 我们这个 sos 的这个方法。然后这个方法呢,就负责查询这个范围之内的每天的营业额数据是不就可以了?哎,所以说呢,我们在这呢,就把这两个参数传进来哎,一个是这个 local data, 这个 begin, 还一个呢,还是 local day, 哎,这种类型的来个摁的是不是就可以?那这个方法呢,它的作用是什么呢?是不是应该就是统计我们指定这个时间区间内的这个营业额数据? 这就是咱们这个方法它的作用。然后呢,咱们是不是在这个 ctrl 的这个地方,直接调我们这个 source 的这个方法就可以了。然后呢,直接来一个 result, 点 success, 然后呢,调用我们这个 report service 的这个方法对吧? get turn over statistics。 然后呢,把这个 begin 还有这个 and 是不是都传进去? 然后这个方法呢,是不是就会给我们返回一个 v o, 而我们这个地方是不是声明的恰好就是这种类型。所以这样的话,我们这个 ctrl 的这个方法其实就写好了。 大家可以看到,我们这 ctrl 的方法呢,往往都是比较简单的,因为真正的核心逻辑呢,一般都是在 service 当中,所以我们的 service 呢,一般是比较复杂的。而这个 ctrl 呢,就直接调它来获取这个数据,是不是就可以了? 呃,当然了,咱们在这呢可以加一个日志啊,便于我们来查看控制台来调试对吧?那么我们在这直接来一个 log 点音法,这就是我们这个营业额 数据统计。那么统计哪个日期的呢?咱们在这呢可以这样来输出一下。哎,你看我是不是写的这么两个花括号对吧?分别对应的是什么呢?哎,一个是 begin, 一个是按的哎,他其实就是把这两个日期是不是给我动态的输出在这个位置就可以。 好,那么这是咱们这个 ctrl 呢,就已经写好了。然后呢,我们接下来是不是就需要为这个 service 接口来提供一个实现类啊,对吧?那么这个实现类呢呢?很明显应该就放在我们这个 i m p l 这个包下边,然后这个实现类叫什么名字呢?就叫 report service, i m p l 是不是就可以? 然后呢,我们让它实现一下咱们这个 report service 接口,好实现这个接口。那我就需要来实现这个方法对吧?先在这个类上面呢加上我们这个 service 注解啊, 然后呢,在这再来加一个 sleep for day 啊,这边也加一个,把这行去掉。然后呢,把这个注释呢,我们从这边来复制一下,然后把它放在这个位置。那这里边呢,要进行这个营业额统计对吧? 那么我们统计这个营业额呢?那实际上本质上应该查的是哪张表啊?实际上查的应该是我们的订单表,所谓的营业额指的是什么呢?是不是应该就是这种状态,是已经完成的这些订单,然后他的总金额呀,对吧?所以说呢,我们其实要查询的应该是订单表。 那么要查订单表的话呢,我是不是就需要把我们这个订单的那个 map 给注入一下呀?哎,咱们就把这个 other map, 也就是我们这个订单的 map 对吧,给他注入一下,那么一会呢,肯定会用到啊,所以我们提前把它注入进来。 到这的话呢,咱们整个架子其实就已经搭起来了。那么一会呢,咱们再来看一下我们具体的这个业务方法应该如何来实现。

刚才啊,咱们是把我们这个新增菜品啊,所有的这个业务代码呢,都已经写完了,那现在呢,咱们就可以来测试一下。咱们呢可以在我们这个 service 的这个 save 方法这个位置啊,加一个断点,让程序停下来,然后呢我们 debug 方式来启动一下我们这个项目。 那这块这个测试啊,咱们可以直接通过前后端连条来测试,因为这块呢,如果你想通过这个接口文档来测试的话呢,我们需要录入的这个数据项其实还是比较多的,并不是特别方便,咱们可以来看一下,我们可以刷新一下, 你看这是不是就多出来这么一个叫做菜品相关接口对吧?然后呢,有一个新增菜品,那么我们在这个地方是不是就需要来录入相关的这些数据项对吧?那这样的话呢,就需要我们自己去构造数据,包括我们这 这个 categor id 分了 id 你肯定不能随便写啊,对不对?所以咱们直接通过这个前后端连调这种方式啊,那么会快一点。那么我们在这呢刷新一下,这是咱们这个新增菜品这个页面对吧?然后呢,我们可以录入一个新的菜品,比如说叫做测试菜品吧, 呃,然后这个分类呢,我们随便选一个,呃,这块这个代码我们其实已经实现了对吧?那么他其实请求的就是我们原先的这个分类查询的一个接口啊,那么我们随便来一个,然后这个菜品的价格呢,也随便来一个。 这个口味呢,你看在我们这个前端页面呢,其实已经定死了,就是这么四个维度啊,包括有甜味对吧?温度,忌口,辣度。注意这个数据呢,其实并不是从数据库里面查出来的,那么它其实是前端已经定好的,那么我们就根据实际情况去选择就可以了。 那比如说除了甜味之外呢,你还想加其他的,比如说温度或者忌口对吧?那么都可以根据实际情况去添加。然后呢,我们可以来给他传一张图片,随便来传一个啊 啊,你看这个图片回显也是没有问题的。然后呢,我们这个菜品描述呢,就随便来一个,然后点击保存按钮,哎,你看现在他这边是不是就已经跳过来了。咱们先来看一下什么呢?就是我们这个数据的封装有没有问题,也就是我们这个 dto 的封装, 你看这里边这个数据 id 是没有提交的,所以是空对吧?然后内幕已经提交了 category id, 这就是我们分类的 id, 这是那个价格,这个图片的路径对吧?那么都是没有问题的,包括这个描述。然后就是这个状态对吧?然后再往下呢,是不是就是个 flares, 这是不是就是我们的口味,刚才呢,咱们是不是提交了 两种口味对吧?分别是什么呢?一个叫甜味对吧,你看它是封装到我们这个内幕这个属性上的。而这个 y 六呢,其实是一个字符串,就是具体的这几个值,什么无糖啊,少糖啊等等这些。那么除了甜味之外呢,是不还有这个忌口这个维度, 那么你可以看到 id 跟 d 是 id 是不是都没有值啊,都是空的。好,那这样的话,我们这个数据封装呢是没有问题的。然后呢,我们程序继续往下走, 咱们是不是自己扭了一个地势对象,现在呢,他这里边所有的属性全都是空。然后呢,我们通过这个对象属性拷贝,拷贝完之后呢,再来看, 哎,你看相关的这些属性是不是就已经考上值了对吧?当然了,这几个公共属性是不是还没有这个呢?我们也不用管。因为什么呢?咱们在这个地方是不是已经加了这个自动填充了对吧?所以这个地方呢,也就不用去设置他的 值了。然后呢,我们直接执行一个音色他,咱们先把控制台来清理一下,来,往下走一步,你看他是不是发了个音色的语句。然后呢,我们可以看到他这里边传进来这些值对吧?包括我们这个创建人,创建时间这些是不是也都有值? 好,那么这就说明是通过我们这个公共自断自动填充,是不是已经给我们填好了。哎,从这个地方呢,其实也看到,你看我们这个切面是不是已经执行了。好, 那这是像这个菜品表,是不是插入一条数据。然后呢,我继续往下走,是不是来获得我们这个口味,然后来便利对吧?然后接下来是不是要批量插入走。然后到这边来看 音色的音兔,这是 disfler, 是不是口味表。然后呢, value 思应该是不是插入了两组数据对吧?也就是两条数据对吧?好,那么这个数据呢,检查一下应该也是没有问题。 好。然后呢,我们直接放行给他放过去就可以了。然后到这边来,这块为什么没有展示出来啊,哎,因为我们这个分页查询还没有写呢,对吧?那么后边写完之后呢,这个数据就能查出来了。 好,这是咱们通过这个前后端连调这种方式啊,测试了一下,保证我们这个代码是没有问题的啊。那么测试完之后呢,咱们就可以把我们这块开发的代码呢来整体来提交一下。 好,那这块这个要提交的代码还是比较多的对吧?这个呢,是我们这个新增菜品业务代码开发,然后呢,我们提交并且推送一下。 好,已经推送成功了。那么关于我们这个新增菜品呢,我们就先说到这。

接下来咱们继续来看公共自断自动填充,刚才呢咱们是进行了一个问题的分析,并且呢把这个实现的思路呢说了一下,那接下来呢,我们就按照咱们这个实现思路啊,把咱们这个代码把它实现出来, 好,咱们呢可以把这个图呢给他来截一下,那么我们按照这几个步骤来开发我们的代码就可以了。 好,那么首先第一步呢,我们可以看到咱们是不是需要来自定义一个注解呀?那么这个注解的名字呢,就叫做 out to feel, 那么这个字面翻译呢,就叫做自动填充啊, 那么它其实只是一个标识,用来标识一下我们哪些方法是需要进行处理的,对吧?那么这个注解呢,主要是放在我们这个 map 的方法上面。好,那现在呢,我们还没有这个注解,对吧?所以接下来呢,我们需要来创建这么一个注解,咱们呢可以 先来创建一个包,那么我们所有的注解呢,全都放在这个包下边,那么我们就在这个地方 com 点 scar 这个下边呢,咱们来创建一个包,那么就叫 annotation 是不是就可以了? 那么这就是注解的意思吧。好,然后呢我们在这个包下边来创建这么一个注解,那么这个名字呢,就跟我们这个课件保持一致了,就叫做 auto few, 那么这叫自动填充,那注解的话呢,我们是不是使用这个,对吧?好,来创建出来这么一个注解。 好,这个呢其实就是用于什么呢?这就是我们自定义的一个注解,那么用于标识方法需要进行公共自断的自动填充处理,对吧?填充处理 好,那么这个注解的话呢,我们是不是需要给它来加入相应的一些标识,对吧?首先呢我们 需要来指定一下这个 target, 那这个 target 呢,其实就是来指定我们当前这个注解会加在什么位置,对吧?咱们呢这个地方呢,就直接加在我们的方法上就可以了。然后呢这是 element type, 点 messer 的,就是指定我们这个注解只能加在方法上面, 然后还要加一个什么呢?通常我们是不是都要加一个这个 retention, 对吧?然后呢 retention 点 run time, 好,这个呢都是一些固定的写法。 然后呢我们在这个注解里边呢,还需要干什么呢?需要来指定一个属性,就是指定我们当前这个数据库的操作的类型, 所以这个地方呢,我们可以通过一个媒体的方式来指定,而这个媒体呢,我其实已经定义好了,在哪呢?其实就是在我们这个 command 这里边,在我们初始代码里边其实就已经提供好了, 叫做什么呢?叫做 operation type, 你看在我们这个媒体这个包下面有这么一个叫做 operation type, 这叫做操作类型,对吧?那么这里边呢,我们其实定义了这么两个啊,一个叫 update, 还有一个是 insert, 指定的就是我们数据库操作的类型。 那么我们在这个地方呢,就需要来指定一下 operation type, 就是我们自己的这个啊,不要弄错了。好,然后呢这个呢就叫 value 就可以了。 好,那么我们这个呢,就是指定我们这个数据库操作类型。数据库操作类型, 那这个类型呢,我们就定义了这么两个啊,一个是 update, 还有一个是什么呢? insert, 对吧?那为什么不指定 delete 还有 select 呢?哎,那很明显,我们这几个公共字段是不是只有在 update 还有 insert 的时候才有必要去设置,对吧?那如果你是一个查询操作呢? 我们这个公共自断自动填充,根本就没有必要去执行,对不对?所以说啊,我们这个地方只是涉及到这么两个啊,一个是 update, 还有一个是音色的,用来标识我们当前这个数据库操作的类型,是一个更新操作呢?还是一个插入操作 好,那么这是把我们这个自定义注解是不是就定义好了?那这块呢,还是比较简单的,那 接下来呢,我们就需要来自定义一个切面类,咱们主要的逻辑是不是应该都是在这个切面类里边,对吧?在这里边呢,我们会统一去拦截什么呢?加入了这个注解的方法, 然后呢,我们在这个方法里面干什么事呢?是不是通过反射为我们这几个公共的字段是不是来复职啊?对吧?好,那接下来呢,我们就需要把这个切面类啊,把它创建出来,咱们现在把这个呢先关上,那这个切面类呢,我放在什么位置呢? 哎,咱们呢还是把它放到我们这个 server 这个模块里边?呃,然后呢,我们在这个地方呢,可以创建一个包啊,切面呢叫做 aspect 的,对吧?所以我们这个地方呢,就创建这么一个包, 然后在这个包下边呢,来创建我们这个切面类。这个类名呢,就跟我们这个课件还是保持一致啊,就叫做 out to feel aspect 的这个字面翻译呢,就是自动填充的这么一个切面。好,那么咱们在这儿来创建一个 out to feel aspect, 好,那么这是我们自定义的这个切面类啊,咱们在这加一个注视,这是我们的自定义切面,然后呢,实现我们这个公共自断自动填充,对吧?他的这个处理的逻辑,好,把这个移动一下, 那这个切面类我们是不是需要来加一些注解呀?你想让它是一个切面的话,我们是不是需要来加入一个什么呢?叫 aspect 的这么一个注解,同时呢我们还要加一个什么呢? component, 它其实也是一个病,对不对?也需要交给我们 spring 容器来管理。 然后呢我们再来加一个什么呢?呃, sleep for j, 方便我们来记录一些日志。那接下来我们应该做什么事呢? 咱们这个切面类啊,在这里边我们是不是需要来定义什么呢?我们的切入点,还有就是我们的通知,因为本身切面呢,其实就是通知加上切入点,对吧?所以说呢,我们在这个地方需要来指定一下我们的这个切入点, 切入点说白了就是对哪些类的哪些方法来进行拦截,对吧?那么这么一个定义,这就是我们的切入点。好,那这个切入 点的话呢,我们还是通过注解的方式来指定啊,咱们在这呢来定义一个切入点的方法, public void 叫做什么呢?叫做 all to feel point cut 吧, point cut 是不是就是切入点的意思?好,然后呢,我们在这个方法上是不是来加入这么一个注结,就叫做什么呢?就叫做 point cut, 好,加入这么一个注解,然后在这里边呢,我们是不是来写我们这个切点表达式,对吧?也就是对哪些类的,哪些方法来实施拦截,对吧? 那么我们是不是可以通过这个 execution, 对吧?来指定一下,我们要拦截的是什么呢?是不是应该是返回值是所有的星,对吧?然后呢,我们拦截的是 cam, 点什么呢?咱们是不是指定我们这个包啊? scan, 然后点什么呢? map 吧,对吧?那么因为我们要拦截的是我们 map 当中的某些方法,而 而当前这个 map 呢,其实指的是我们这个包,对不对?指的是我们这个包,而我们那些 map 接口呢?是不是全都在这个下边?所以说呢,我们这个地方呢,是不是来指定在这个包下边所有的类,还有所有的方法,对吧?然后呢在这来点点,那么匹配所有的参数类型, 好,那这样的话呢,他其实就会拦截到哪些呢?是不是就是我们这个 map 包下边所有的类,所有的方法,当然这些接口呢,其实也都会匹配到,对吧?那么这些方法是不是都会匹配到?那现在呢,我们就需要考虑一下,是不是这个 map 里边所有的方法都需要被拦截呢? 其实并不需要,对吧?那么我们希望拦截的是哪些呀?是不是应该是当前这个操作对我们这些公共字段有影响的这些方法?那比如说我们当前是一个查询方法,对吧?或者是一个删除方法, 需要拦截吗?其实并不需要,对吧?所以说只有这么一个切点表达式好像还不太够,因为他这个力度太粗了,对不对?那么他可能把不需要的一些方法也给拦截到了, 那现在我们是拦截哪些呢?除了这个包下边这些类这些方法之外,还需要这个方法上加入了我们刚才定义的这个注解,那么如果没有加这个注解呢?我们其实也不需要拦截,对吧?所以说呢,我们接下来呢,还需要再来加一个表达式,那这个怎么写呢?哎,我们在这个地方来空格来按的 这个呢,可能大家用的少一点啊,那么可能以前这个 execution 表达是用的比较多,对吧?那现在呢,除了要满足前面这个条件之外,还需要什么呢?还需要当前这个方法上加入了我们这个 auto few 注结。所以我们在这个地方呢,是通过一个 at annotation, 你看通过这种方式来指定加入哪个注解呢?是不是应该就是我们这个 come 点 scan 点儿什么呢?是不是在我们这个 annotation 这个包下边,对吧?有一个叫什么呢?叫做 auto few 前面这个表达式呢,是锁定了我们这个 map 包下边的所有的类,所有的方法,对吧?同时还要满足什么呢?还要满足这个方法上加入了我们这个 out to feel 这个注结。好,这就是我们定义的这么一个切点表达式啊。好,那么拦截到之后具体做什么事呢? 咱们是不是希望为我们这几个公共的字段来统一复职啊,对不对?那复职的这个逻辑应该写在哪呢?那很明显是不是应该写到我们的通知当中?那么前面呢,我们其实都讲过 aop, 对不对?在 aop 这里边呢,有切面的概念, 还有切入点的概念,还有呢,是不是就是通知?说白了就是对我们这个代码进行增强的那一部分,对不对?所以接下来我们是不是来定义一个通知?而说到通知的话呢,是不是又分成不同的类型,有前置通知,后置通知,对不对?环绕通知、异常通知等等。那么通知类型很多,那咱们这个地方应该用哪一种啊? 那么很明显应该用的是什么?是不是应该是前置通知啊?因为在我们执行这个 inser 的方法或者 update 方法之前,是不是需要为这几个公共字段给它复上值,对吧?如果你这个 circle 都已经执行完了,你再给它复值是不是就没有意义了? 所以啊,我们这个地方应该定义的是一个什么前置通知,对吧?好,那么我们在这呢来写这么一个方法, public void, 然后呢,我们这个方法名呢,就叫做 ah to feel 吧,然后别忘了在这个地方是不是需 要加上一个比方注解呀?这是不是表示是一个前置通知,然后在这里边呢,我们是不是还要指定什么呢?就是我们这个切入点,对吧?也就是当匹配上我们这个切点表达式的时候,他是不是就会执行我们这个通知的方法,对吧?好,那么这个切点表达式呢?是不是就是这一段,其实指定的就是这个方法名称就可以了? 好,那么这个呢,就是我们自定义的这个钳制通知啊。好,钳制通知在我们这个通知中来进行什么呢?进行我们这个公共自断的复值,对吧?为这些公共自断给它复上值。 而这个前置通知这个方法呢,我们往往是不是需要他给我们传进来一个参数啊?就是我们的连接点,对不对?也就是说通过这个连接点就可以知道我们当前哪个方法被拦截到了, 以及被拦截到的这个方法它具体的参数是什么样子的,对吧?它的参数值,它的参数类型其实都可以获取到,就是这个叫做 join point。 然后呢我们在这个地方呢,可以先来一个日志啊,通过日志来记录一下 log 点音法,对吧?这个就是开始进行公共自断的自动填充, 那么我们如果能够在控制台看到这句话,这就说明我们这个方法确实是被执行了,对吧?咱们写到这的话呢,其实就可以先来测试一下了,看看我们当前这个方法到底能不能处罚,对吧?能不能拦截到。 但是要想拦截的话呢,咱们是不是需要把这个注解啊?就是我们自定义的这个 auto few 这个注解,然后呢把它加到我们 mapper 的方法上面来,呃,咱们呢可以来试一下,比如说 我们这个员工的这个 maper, 对吧?然后呢你看这上面是不是有一个音色的这么一个方法,对吧?我们如果想实现公共自动自动填充呢,就需要为这个方法来加这么一个注解,就是刚才咱们自定义的这个注解啊, to feel 好,加上这个注解,然后呢在这来指定一下我们操作的类型,对吧?那么我们这个操作的类型呢?这个是不是应该是一个 insert 操作呀?所以我们在这来 value 等于什么呢?也就是我们这个 operation 点 insert, 注意这个 operation type 呢,是不是就是在咱们这个 comment 这里面定义的那个美举啊?对吧?我们再来观察一下,你看我们从这来锁定一下,刚才我们其实也看到了,对吧? 这个呢,我们根据实际情况去编写一下就可以了,然后呢我们把这段来复制一个,再给他放到什么位置呢?在 update 这个方法上也加一个, 但是这个类型呢就不是 insert 了,对吧?应该是什么?是不是应该是 update 呀?那么因为我们当前要执行的是一个 update 的一个数据库操作, 所以说呢,我们把这个操作的类型呢给它设置成这个。好,那这个时候呢,我们这个注解是不是就已经加完了?咱们是分别在音色的方法还有 update 的方法上加入了这两个注解。好,那现在呢,我们就需要来验证一下,看看我们当前这个通知方法到底能不能执行到, 咱们呢?可以在这来加一个断点,然后呢我们 debug 方式来启动一下,那么如果说他确实能执行到这个方法, 那这就说明我们定义的整个这个流程是没有问题的。咱们呢再来细化一下,把这个具体的实现逻辑把它编写好就可以了啊,那么为了能够快速测试啊,咱们就直接通过前后端连调就可 可以了。好,现在呢我是已经打开了我们这个前端,然后呢我们登录进来,咱们呢直接通过这个员工管理,然后呢我们点修改,因为我们刚才看到我们在 update 那个方法上是不是加入了那个注解,对吧?然后呢我们点击保存按钮,哎,你看已经开始闪了, 他停在哪了呢?他是不是就停在了我们这个 auto few, 咱们这个前置通知这个方法这儿,对吧?然后呢我们可以来看一下这个转音 point, 把光标放在这个位置,那么能够看到他确实已经拦截到了,对吧?具体拦截的是哪个方法呢?其实从这个地方应该也能看到啊。然后呢我们点进来 它拦截到的是不是应该就是我们这个方法,就是这个 employ mapper 的 update 这个方法,对吧?那么咱们这是验证了一下咱们自定义的这个切面 类呢,是能够执行到的,对吧?那么你看我们这个通知方法是已经过来了,好,然后呢我们直接给他放过去就可以了,那么一会呢,咱们再把这个通知方法给他完善出来就可以了。

然后接下来我们要做的是什么呢?咱们是不是就需要像我们这个订单明细表来插入 n 条数据啊?那具体要插入多少条数据啊?应该是由什么决定啊? 是不应该是由购物车当中的商品来决定的呀?对吧?因为我们前面其实在做需求分析的时候呢,其实也提到了,就是我们真正下单购买的这些商品呢,其实是由购物车里边这些数据来决定的。 所以我们在这呢这个订单明细数据应该怎么封装呢?是不是应该就看我们这个购物车里边的数据啊?好,那么我们来看一下购物车咱们在这是不是已经查过了,然后呢,他还没有抛异常,就说明我们购物车里面确实有数据。那么我们在这呢就需要来干什么呢?是不是把我们这个购物车数据来便利一下, 然后呢,我们在这来便利一下,来一个 for 循环便利一下,然后干什么呢?每次便利完之 之后呢,咱们是不是就需要对应的封装成一个什么,就是我们这个订单明细对象啊。而订单明细的话,咱们可以来看一下。咱们来看一下我们这个订单明细,也就是我们这个 other, 低调,这个实体 就是这个叫做 order 第一条。然后你看这里边是不是有 id, 这就不用说了, name, 这是应该是我们这个商品的名称, 还有订单 id 对吧?还有 dish id, 这就是我们这个菜品的 id 或者套餐 id 对吧?还有口味,还有数量,还有单价,也就是金额。然后呢,就是这个图片路径,那现在呢,我是不是就需要一条购物车数据对应的,就需要封装成这么一个 order detail 对象啊。然后呢,我们再来看一下购物车 这个类里边的属性。你看,好像跟我们刚才看到的这个 order 第一条是非常类似的。那么你 看这里边这个属性命名啊,其实都是一致的。那既然这些属性命名都是一致的,我是不是就可以直接进行对象的属性拷贝啊?我就不用自己一个一个去 set 了,哎。那么我们到这边来,你看,现在我们要封装这个订单明细对象,也就是 order detail 这个对象啊的第一条。这是订单明细啊,咱们在这来写一下,这是订单明细。直接来一个什么变 youtube 四点 copy properties。 然后呢,把这个购物车相关的属性拷贝到我们这个啊的第一条对象的属性上面来。 好,这样就考过去了。但是考过去之后呢,我们需要来检查一下,你看这里边他考过来的应该是什么呢?是 name, disi id, set meal id, 还有 disflair 等等下边这些东西。而这个 all the id 其实是没有的。哎,因为在我们这个购物车 说这个实体里边,你看根本就没有 order id 对不对?所以说我们在这呢,需要自己来设置一下啊。那我们在这个地方需要来设置一下这个 other id, 也是 order 第一条点 set 什么呢?就是个 order id。 而这个 order id 是不是应该就是咱们刚才插入的那个订单的主见值?而这个主见值我是不是通过这个 order 私点 get id 就能获取到了?但是前提是什么呢?你看我们这个地方是不是返回了这个主见值对吧?所以这个地方呢才能获取到啊, 如果你不返回的话呢,他这个地方肯定获取到的就是个空了。所以在这呢你需要注意一下。好,那么我们再单独设置一下这个主键值就可以了。 这是我们关联的这个订单 id 啊,设置当前这个订单明细是不是关联的。呃,订单 id。 好,这是一个重点的字段。 那这个订单明细对象封装好之后呢,我是不是就需要来插入数据啊。那插入数据呢?咱们这其实有两种方式可以选择,一种是什么呢?就是单条数据插入,那么我编了一次,我就插入一次。还有一种方式是什么呢?我是不是可以批量插入啊。 那么我们在这呢建议是批量插入,这样的话呢,效率会更高一点啊。所以我们在这呢可以来整一个类似的集合,把我们获得的这些订单明细数据是不是给他放到这个集合里边,然后呢一次性的来批量插入啊。所以我们在这来一个类似的里边呢,就放我们这个 order 第一条是不是就可以了? 好,这是 order detail list, 等于直接 new 一个 or release 的。那我们在这儿呢,直接来 order detail list 点 add, 然后 order detail 便利完之后呢,咱们这个 list 里边是不是就装满了数据。然后呢,我们再批量插入就可以了。 就是像我们这个订单明细表批量插入 n 条数据。那么我们在这呢就需要调用我们这个 order detailmiper 对吧?然后它的一个什么呢?叫做 insert bench 吧。这是不是应该是批量插入这么一个方法。好,然后呢,我们把这个类似的集合对象是不是放进去。 好,那么这样呢,他是报错啊,因为还没有这个方法,咱们在这呢给他来扩展一下啊。好,这是批量插入啊,订单明细数据。那对应的我是不是就需要来创建一个 miper 映射文件呢?啊,那这个映射文件呢,咱们还是给他放到这个位置。然后呢,在这来啊的第一条改一下名, 然后在这给他改成 order detail mapper。 好,这个呢就没有了。然后到这点这个图标,哎,能够关联过来就说明没有问题了。然后呢,我们在这 alt 回车, 好,把这个 insert 语句呢来编写一下。那具体呢是不是应该就是 insert in to。 然后我们这个表名是不是叫做 order d 条。然后这里边这个字段应该是谁呢?是不是应该也是除了 id 之外的其他的这些字段呢? 那么我们在这直接来双击啊,把它带过来就可以了。哎,你看除了 id 之外的这些字段。然后呢,在这来一个 value 丝。 然后呢,我们刚才也看到了咱们传进来的其实是一个类似的集合对象,对吧?那么我们在这是不是就需要来干什么来便利这个集合呀?所以我们在这是不是需要通过一个什么动态标签,叫做 for each 对吧?然后呢, collection 是不是应该就是我们这个集合的名称,也就是这个行餐的名称。 好,重新点回来,应该就是他。注意啊,应该是在这个位置。然后呢,我们每次便利 的时候呢,是不是需要给它起一个别名啊,那这个别名呢,是不是应该就用这个 item, 然后这个别名呢,我们就简单一点,就叫 o d 就可以,对吧?就是 order detail 的缩写。哎,这个名字叫什么其实都无所谓啊。 然后我们每一次便利呢,是不是应该都有一对括号。然后这里边具体就是我们传进来这些参数。比如说这个应该是 od 点 name, 注意按顺序来,你看这个地方是不是 name, 所以我们应该是第一个字的应该是 name。 然后呢,第二个应该是什么呢?是不是 o d 点 image 对吧。然后呢,再来井号,这个地方是不是应该是 o d 点 all the id。 注意这个地方你就不要写下划线了,应该是属姓名对吧。那么属姓名的话呢,我们刚才看到它其实是用的这种驼峰命名法,所以这个地方呢,需要注意一下。呃。然后呢,下一个是不是 od 点什么呢? d 是 id, 也是驼峰命名法。然后这个地方下一个是 od 点 set meal id。 然后下一个 o d 点儿什么? d 是 flare。 哎,这个是口味对吧?下边儿一个呢,就是 o d 点儿 number, 这是我们这个商品的数量。然后还有最后一个 amount, 就是单价。 好,那么在这呢给他调整好。那么你需要注意啊,你看我们这是一个批量插入,那么每一次他便利完之后啊,是不是都需要中间由逗号给他隔开啊。那么我们在这呢,别忘了还要加一个什么 separate 等于逗号。 好,那这样呢,其实就可以了啊。那最终呢,就是一个批量插入的这么一条 circle。 好,那这个 circle 语句呢,我们就已经写好了,回到我们 service 当中来。好,到这边来,你看这是批量插 入 n 条数据对吧?这是我们这个订单明细表。好,那么插入完这个订单明细数据之后呢,接下来我是不是需要清空购物车呀?那么要清空购物车的话呢,我就需要来调用我们这个 shoppingcat mapper。 点什么呢?应该叫做 delete by 什么呢?由仔 id 吧, 应该是把当前用户的购物车数据给他清空。而这个 uzid 呢,我们前面是不是已经获得了?哎,所以这个地方呢,就非常简单了,直接一调这个方法就可以了。然后最后呢,我们是不是来封装这个 vo 对象啊。好,那这个 vo 对象呢?就是这个,你看叫做 order submit vo。 好,那么我们在这直接来调用这个 build 方法。你看这里边有什么呢?有 id。 注意,这个 id 呢,代表的就是我们这个订单的 id。 订单 id 我是不是直接 orders 四点 get id 就能获取。然后还有就是什么呢?我们这个下 下单时间,而这个下单时间我们刚才是不是已经设置过了?在这边是不是已经设置过了?就是这个 order time。 所以我们在这呢就直接通过这个 other 思点 get 什么呢? order time 就可以了 啊。然后还有什么呢?就是这个订单号对吧? order number。 那这个呢,也是啊,通过 orders 四点 get number 就可以获得。 然后最后呢,还有一个总的金额对吧?我们通过 alders 其实也能获得,就是这个 amont。 好。那么这几个属性设置好之后呢,咱们再调用一个 build 方法,把这个 vo 对象是不是构建出来。那么最后呢,来返回这个 vo 对象就可以了。 好,那么到这的话呢,咱们整个业务方法呢,其实就已经写完了,咱们再来简单看一下。你看我们是分成了这么五个关键步骤对吧?第一步呢是处理异常情况,然后第二个呢, 是不是就是像我们这个订单表来插入一条订单数据,然后第三步呢,就是批量插入我们这个什么订单明细数据。第四步呢,是清空购物车。 最后一步呢,就是来构造我们这个 vo 对象,并且返回。好,那么到这呢,我们整个功能代码呢就开发好了,那么一会呢,我们再来测试一下就可以了。