1.活动创建需求分析



接下来进入活动创建模块,可以观察我们已经做好的UI界面,我们发现活动创建比之前的人员创建以及奖品创建复杂一些,他除了有活动名称以及活动描述外还要与奖品关联和参与抽奖人员关联,这里观察活动表有一个状态属性,这里是因为,活动有已经结束和还在进行中。由于我们关联的奖品表里面有数量和几等奖,这些属性放在奖品表里面显然是不合理的,所以我们因此才设计了活动奖品表,那些属性只有活动和奖品关联起来才有意义,所以有(活动id,奖品id,奖品数量,奖品等级),这里还有一个status(是因为后续显示的时候他根据奖品数量如果是0,点击开始抽奖直接显示结果,不是0就继续抽奖),活动用户表也是一样的,一个活动可以对应多个用户,这里将用户名放在关联表中是因为用户名我们后续会比较经常使用所以放在减少去用户表查询次数,status(是因为一个普通用户只能参与一次抽奖,用来区分是否参加过抽奖),分析完了ui界面,需求,以及库表可以看看我们后端的时序图

后端首先接收前端传来关于活动的信息以及关联奖品,关联用户信息,然后校验信息各方面符合要求,将相应数据存入活动表,活动奖品关联表,活动人员关联表,后续为了整合信息,我们用奖品id查一下相应的奖品信息,最后我们将各数据整合存到redis中,为了后续我们可以直接通过redis(在内存中速度快)中活动id查到最完整的活动信息(活动,奖品,人员),这样子就不需要再去各个表里面去查了,可以让速度加快,是为了后续抽奖服务的
2.活动创建后端实现(1)
这里我们到了activity活动,我们到了一个新模块创建一个新的activitycontroller,看一下请求体依照请求体构造相应的接收对象(列表的中的对象也需要构造),以及看一下响应data是一个对象里面只有一个元素,注意接收对象的属性加上注解校验(列表用notempty,string用notblank,其他用notnull),注意假设列表中的元素也需要参数,需要加上外面加上valid其元素的注解校验才有效
写完controller的代码以后定义service接口以及他的实现类,在该create方法中我们主要做这几个事
我们根据需求分析来写出该方法的流程,有个问题因为该方法中涉及到多表入库,我们需要保证一致性,不能有一个表入库了,然后发生了异常导致另一个表没有入库,所以我们需要保证数据的事务统一所以我们加上了Transactional注解来保证事务的一致性(并且设置rollback来设置发生任何异常的时候就回滚,注意这里回滚只能回滚关于数据库的相关操作),相关数据入库之后我们将完整的数据存放到redis中,方便后续快速查询(当然这里就算存放到redis失败也没有关系,因为我们查询活动完整信息采用的策略是,如果redis有就直接拿没有我们再去库表查),这里我们先来完成校验活动信息是否正确,以下其实还需要校验奖品中设置的几等奖也需要符合预期,不过后续等我们定义了相关表的类再来校验该问题,这里注意虽然前端(获取到人员id然后再传给后端不可能存在人员id不在表里面,但是这个仅仅限制于正常用户使用,需要考虑到非正常用户,比如他直接后端访问接口攻击呢?还有比如param判空虽然controller层已经完成了,但是假设还有其他人调用该方法呢,所以有一些看似没必要的校验其实有作用),
id我们可以先去数据库,查找id然后根据出来的param的id逐个查看是否在里面即可判断是否在数据库中,动态sql可以参考以下代码,这里需要先将参数id列表从param提取出,再去查数据库中的id列表,再看一下是否都在里面即可
3.活动创建后端实现(2)
在实现该方法的时候我们先来介绍一个新的插件GenerateAllSetter,这个插件可以直接帮你,比如你new一个新对象,然后想写他的set方法,可以一致生成相关的代码,你只需要填入相关信息
这里我们来完成一些相关数据的入库操作,因为这里设置到许多新的表如活动表,活动奖品关联表,活动人员关联表,所以我们这里需要新建一下新的mapper以及DO,然后我们需要实现他们相关的插入方法,注意这里插入活动表的时候我们需要把拿回插入活动的id,后续需要,所以要使用Option注解,对于这些表有个属性status,对于controller层和mapper层都是string,但是我们想要在service层对他进行管控所以,我们在service层里面设置相关的枚举类,参考以下的枚举类
对于活动奖品关联表和活动用户关联表是需要批量插入,且我们也同样把插入的后相关的id写回传入的DO,参考以下动态sql写法
对于活动奖品表写入的时候我们其实之前校验信息没有校验奖品等级是否有效可以添加相关的校验来校验一下奖品等级是否有效,我们这里为奖品等级创建一个枚举类,对于活动人员表批量插入和活动奖品表类似,我们直接复制修改即可,这里我们就可以完成三张表的插入操作,完成了数据入库
4.活动创建后端实现(3)
到这里我们要进行的是将活动的完整信息整合一下存放到redis中去,是给将来的抽奖去做准备的,注意我们这里采用的策略是,我们抽奖时候去查活动完整信息的时候,如果redis中有就省事了直接拿,如果没有就需要我们去库表里面去查,因为是在service层我们定义一个ActivityDetailInfoDTO对象来存放完整的活动信息(活动,奖品,人员),活动的基本信息+奖品信息列表(这里面不仅有奖品等级个数,还有奖品的价格,图片的具体信息,需要我们去库表里面根据id查,目前没有这个类,我们可以定义一个内部类,给这个列表使用)+人员列表(人员列表里面的类也同样需要使用内部类定义,不过我们不用再去库表里面查了),需要的属性参考如下
我们定义的完整活动类里面可以写一个valid方法来通过枚举类快速拿到是否有效(后续可能会使用到)方便controller层操作,同意可以给他的奖品和用户内部类也写上,接下来我们构造好了存放完整信息的类以后,我们要往里面存放东西了,我们需要使用,activityDO,activityPrizeDOList,activityUserList,以及PrizeDOList(奖品详细的信息关联表没有我们需要去奖品表中查出来然后使用该list),写一个convert方法来构造出ActivityDetailInfoDTO,这里构造会比较复杂,因为他内部还有两个list也需要构造,里面关于奖品列表的构造比较麻烦,因为需要activityPrizeDOList,PrizeDOList这两个表共同来构造其内部的奖品列表,需要提取出他们id相同的那个对象一起来构造,可以参考以下写法
这里findfirst只拿到第一个与他相等的,optional是一个容器,他可以用来防止一些空指针的错误,ifPresent,这个方法是当容器里面内容不为空才会执行该方法里面的东西,为空就不执行了,帮我们省去了一个if判空环节,然后我们再构造一下内部的人员列表
5.活动创建后端实现(4)
我们已经将完整活动信息构造出来了,接下来是把他缓存到redis中去,注意我们redis存放的规则是key,value的形式,以及key和value都是String类型,这里的key还需要加上一个前缀,这里我们就采用ACTIVITY+id:activityDetailInfoDTO(json),以及我们设置一个过期时间,注意我们这里redis中间进行一些校验,或者有可能发生异常的地方我们要么直接返回,或者把异常捕获到,因为到了缓存这里,我们前面的数据已经保存进库表了,我们不希望因为这里抛异常,前面落库操作回滚,因为我们这里的redis其实只是一个优化的操作,我们后续来查活动完整信息,是先看redis有没有,有的话直接用,没有也没有关系,直接去数据库中去查,然后再构造redis,后续查也可以直接去redis中查,所以我们这里缓存的时候有设置过期时间,已经有问题直接返回,有异常直接捕获,这里可以采用整个代码都trycatch中来避免因为redis缓存问题回滚

既然我们完成了缓存的cache方法,我们再写个从缓存中拿的方法后续也是需要使用的这里一起完成了为后面省事,注意通过以上图示我们希望查看活动完整信息的时候也会先去查redis,但是我们不希望redis这里没有查到的时候,抛异常,或者在查的过程中发生了异常,因为我们后续还会去数据库中去查然后重新构造redis,所以这里我们也需要trycatch,进来参数校验不通过返回null,或者从redis中没有查到直接返回null,有异常直接捕获,不影响后续操作,也可以直接整个trycatch,因为redis的相关操作我们已经在前面封装好了,这里直接调用很简单,最后构造一下返回即可,controller需要的只需要一个有activityid的对象,定义一个返回的DTO构造一下给controller返回即可
6.活动创建后端实现(5)
由于该方法流程较长,所以我们对该接口可以进行一个打断点的测试,测试时候,可以先把拦截器给排除掉,也许可以排查出来一些空指针异常,最后记得查看库表是否落库,以及redis中是否存在
7.活动创建前端实现
这里前端ui大部分已经实现好了,我们只需要填写一些交互的部分,注意创建活动的时候,我们需要圈选奖品以及全选人员,所以我们需要用到我们之前写的获取奖品列表和获取人员列表,获取奖品列表,后端给我们返回的比较全不过我们这里只需要展示id和奖品名字,不过没关系,可以让我们重复使用一个接口(对于获取奖品的接口我们复用的话需要给他传当前页,以及数量,我们需要给他把数量调大,让他近似于获取到全量),获取人员列表,我们当时写的时候是通过身份来查且没有分页,我们这里只需要身份为normal的所以发起请求的时候给他传normal即可,对于我们勾选的人员列表或者奖品列表我们可以用一个数组存储[{}.{}.{}],最后点击提交的时候,一起打包所有需要的参数发给后端,注意我们已经写好的前端代码,对创建活动接口,失败的部分代码没有写,可以补充一下,前端写完之后,别问了拦截器相关的排除路径要修改
8.查询活动列表后端实现
查询活动(摘要,只需要一些基本信息,不是我们之前要的完整信息)列表我们这里是可以支持翻页的,我们可以查看前端已经给我们写好的ui图,来分析一下需求,得到下面的时序图,以及接口文档


对于这里的request我们之前已经定义了该类直接使用即可,只需要构造一下response(如果属性是列表,列表里面存放的类型也需要定义)即可,这里controller层没什么事情需要做,进行一些校验然后对service层传回来的DTO进行一下convert就可以了,这里有个小技巧对于每一行方法调用的时候我们可以分行,这样子我们有报错的信息他给我们提示的行数就会更加准确
service:然后对于service层给我们返回的DTO我们之前定义了一个泛型的包含total和records的DTO这里也可以重复使用,只需要定义一下record内部元素的类,注意一点我们controller和mapper层statue使用的是枚举类,service层使用的是枚举类记住这个原则即可
写个这个valid方法方便controller层直接拿到valid是true还是false,进入service层的代码编写,我们service层只需要做两件事情一个是,查活动总数,查当前页的活动列表,这里需要两个sql可以参考之前获取奖品列表来编写,对于查询回来的DOList我们需要转换成DTOlist,构造一下返回即可
9.查询活动列表前端实现
前端页面基本已经实现好了对于我们之前要求接口返回valid,我们需要这个参数来渲染相应的字样
就像是这样子,以及其余的点击后会跳转到抽奖页面(true和false跳转后的不同,一个是展示抽奖名单一个是去抽奖,不过都是抽奖页面,只是处理逻辑不同,他会跳转页面后也会在url拼接一些参数后续会使用),其他实现其实和我们之前的奖品列表获取页面很像,自行阅读即可,然后补充一些相应的ajax部分即可