开发Kaptcha验证码功能
说明:
(1) 本篇博客内容说明:
●【在【图书详情页开发之显示评论列表】中,我们实现了在图书详情页显示短评内容】→【这些短评需要添加,给短评点赞,和,写短评的功能】→【为了完成点赞和写短评功能,我们首先需要登录】→【一般来说,我们在登录或注册时,安全考虑,需要使用验证码】→【因此,本篇博客先介绍一款常用的一款验证码工具Kaptcha】
● 然后,本篇博客仅仅单独开发了Kaptcha的功能;与前台界面的交互工作尚未开发(这在下篇博客中会介绍);
一:验证码简述;
在一个系统中,注册和登录的时候,一般都要添加验证码;主要目的是进行人机校验,防止脚本恶意注册和登录;
验证码有很多种,比如图形验证码、字符验证码、滑块验证码;
这儿我们先学习比较简单的字符验证码;
二:验证码生成工具:Kaptcha:简述;
1.Kaptcha简介;
说明:
(0) Kaptcha是行业中非常注明的验证码生成工具;
(3) 比如,银行在线申请信用卡,就需要添加验证码,以确保是一个真实的人在操作;
2.Kaptcha使用步骤;
说明:
(1) 验证码生成参数包括生成的字体、大小、颜色等;
(2) 开发kaptchaController,用于生成验证码的图片;
(3) 实际使用验证码时,将【前台输入的验证码】和【后台保存在Tomcat的Session中的验证码】比对;
三:在项目中使用Kaptcha;
1.在pom.xml中,引入Kaptcha依赖;
老生常谈的问题:引入新的依赖后,如有需要记得要把这个依赖添加到发布中去;
2.在applicationContext.xml配置文件中,配置Kaptcha;
说明:
(1) 配置说明;
(2) 上面的配置结果是:在实际生成验证码图片时,会生成一个没有边框、总长度为120像素、字体为蓝色、单个字符最大占用40像素(如果总长度不够,字符会自动缩小)、每个验证码包含4个字符的图片;
2.创建KaptchaController控制器;
(这个Controller类的名字,可以随便起)
KaptchaController类:
说明:
(1) 说明1;
(2) 说明2;
(3) 说明3;
(4) 设置这个方法的url;
(5) 效果,启动Tomcat,访问【“/verify_code”】:发现我们的Kaptcha验证码没问题;
我们开发好了Kaptcha验证码后,具体要把这个东西应用项目中,就需要涉及到与前台界面的交互工作了,这在下篇博客中介绍;
Kaptcha验证码功能应用到注册模块
说明:
(1) 本篇博客的内容:本篇博客主要是把开发的验证码功能,应用到注册模块上;只是开发到了这一步,注册模块的完整业务逻辑并没有开发;
(2) 本篇博客合理性说明:【我们开发了Kaptcha验证码】→【但是,这个验证码,需要和前台界面(注册和登录页)联系在一起后,才有实际使用的价值】→【然后,本篇博客结合开发注册功能时,把前面开发的Kaptcha验证码给用上】
(3) 本篇博客遇到的点:
● 遇到了【在访问get请求时,在url后面增加ts时间戳,用来解决浏览器缓存可能带来的问题】;
● 本篇博客通过【data:$(“#frmLogin”).serialize()】,把表单的数据给序列化了;serialize()方法是JQuery中的ajax方法,其作用是编码表单元素集为字符串以便提交;然后,自己想起了【后台系统FileUpload组件】这篇博客;这篇博客主要内容是【提交的表单的时候,涉及到了文件上传】,然后我们通过【enctype=“multipart/form-data”】设置表单的编码方式,以便能够上传文件;
● 在本案例中,我们把验证码的校验工作放在了Controller层;而没有放任到Service层;这应该是个普遍采用的开发习惯和规则;
一:在项目中,引入注册页的前端文件:register.ftl;
register.ftl初始内容:
二:开发;
1.创建MemberController类:开发【访问注册页的入口方法】;(也就是,开发register.ftl注册页的访问url)
MemberController类是会员控制器类。因为用户注册和用户登录,都是与会员相关的功能;所以,注册和登录相关的内容,都可以放在这个类中;
MemberController类:
说明:
(1) 类内容说明;
(2) 这样以后,比如这儿,我们通过【localhost/register.html】就能够访问到注册页了;
2.在注册页上,显示验证码;
重启Tomcat,观察效果:
3.点击验证码图片时,更换验证码;
这就需要我们编写 img id=“imgVerifyCode” 这个验证码图片所在的img标签的单击事件了;
重启Tomcat,观察效果:
4.注册页点击【注册】按钮后,提交注册表单信息;(这儿目前,专注于验证码信息的提交)
在前台我们填写了验证码,提交了以后,将其与服务器端Session中的验证码,进行比对;如果一致,校验通过;如果不一致,就表示输入的验证码是不对的;
说明:
(1) 这儿我们通过【data:$(“#frmLogin”).serialize(),】:把表单的数据给序列化了;serialize()方法是JQuery中的ajax方法,其作用是编码表单元素集为字符串以便提交;(如有需要可以参考【jQuery中Ajax函数:.ajax()、.post()、$.get()的使用、区别;】)
(2) 然后,上面的表单serialize()序列化;让自己想起了【后台系统FileUpload组件】这篇博客;这篇博客主要内容是【提交的表单的时候,涉及到了文件上传】,然后我们通过【enctype=“multipart/form-data”】设置表单的编码方式,以便能够上传文件;
(3) 接下来,我们要做的就是开发一个处理注册请求的Controller(并且该Controller处理注册的方法的url需要是“/registe”);
5.前端点击【注册】发起注册请求后,后端处理请求;(这儿目前,只专注于验证码的校验)
在MemberController这个用于处理用户的Controller类中,添加处理注册的方法;
说明:
(1) 后端的url和方式,需要和前端对应上;
(2) 后端使用方法参数的方式,接收前端数据时,参数名需要写对;(如有需要可以参考【后台Controller使用【方法参数】接收【前端传过来的,请求中的数据】
(3) 上篇博客提到过:如果需要HttpServletRequest或者HttpServletResponse的话,直接在方法参数中写上就行,Spring IoC容器会帮我们注入;
(4) 后台处理后,需要根据前端要求返回合适的信息;
(5) 这个方法直接返回了Map对象,但其可以被序列化为JSON字符串;
通过Json序列化可知,当我们在Spring MVC中使用jackson时,服务器端Controller中的方法,直接返回实体对象就可以了,jackson会帮我们把对象序列化为JSON;
(6) 效果:启动Tomcat,观察效果:分别输入正确验证码和错误的验证码,其是OK的;
至此,在注册这块,我们就成功的把验证码功能给引入了;下面我们就是去实现注册的完整业务逻辑了;
完成会员注册功能
说明:
(1) 本篇博客合理性说明:【我们把Kaptcha验证码应用到了注册功能上】→【然后,本篇博客就彻底完成会员注册功能】;
(2) 本篇博客遇到的点:我们在实现注册功能的时候,有些部分需要考虑:【开启事务】、【判断用户名是否重复,这儿包括了自定义异常】、【密码的MD5和加盐混淆】、【java.util.Date】【java.sql.Date的转换说明】等;
(3) 其实,本文最重要的就是熟悉【注册,这种业务逻辑】;
零:前置的一点说明;
一:开发Service;
1.创建,MemberService接口;
MemberService接口:
2.创建,MemberServiceImpl实现类;
然后,创建其实现类;可以通过【Alt + Enter】快捷键来快速创建;
说明:
(1) 首先,Service实现类在IoC容器中的beanId,需要与接口保持一致;;;然后,因为这个会员的实现类,其主要的操作是与会员相关的功能,比如会员注册、登录、评价、点赞等功能;而这些功能大多是“写操作”;所以,我们配置这个类的声明式事务,默认类中的方法全部开启事务;
(2) 因为我们要求新创建的会员的username,不能与已经存在的username重复;所以,我们首先要检查一下;(这是业务逻辑层面的需要)
(3) 上面我们用到了自定义异常;自定义异常类作为一种【自己定义的警报器】还是很给力的;上一次遇到在项目中自定义异常可以参考【实现后台登陆】;然后,有关自定义异常的内容,如有需要可以快速参考【Java自定义异常】;
主要是,在开发某一个业务的时候,当需要的时候,得有自定义异常的意识,这个非常重要!
BussinessException自定义异常类:
如果对于这个自定义异常类有不明白的地方,可以参考上面提到的两篇文章;这儿只重复说明两点:
然后,这个异常类定义成了BussinessException;可以看到其是两个s;Bussiness表示业务逻辑,Business表示商业;在很多业务系统中,因为业务主要服务于商业,所以在这些业务系统中,也可能看到异常类写成了BusinessException;一个s和两个s的情况,差不多,都可以看成是与业务相关的异常;
(4) 当判断用户名没有出现重复后,就可以正式开始新增过工作了;其中密码部分,用到了【MD5+加盐混淆】策略;
(5) 有关【MD5算法】+【加盐混淆】策略的内容,如有需要可以快速参考【封装Md5Utils加密工具类】;
MD5Utils类:
这个类没什么好说的,如有不明白的地方,可以快速参考【Md5加密】中的内容;
(6) 然后,就是【java.util.Date】和【java.sql.Date】转化的问题;
有关这个问题,可以参考【附加:【原始JDBC】,【DbUtils】,【mybatis】,【Spring JDBC】批处理;(还没写,不要看……)】;虽然这个问题,目前并没有彻底、完全搞明白,但是目前能勉强糊弄自己,勉强形成自洽;
3.及时的单元测试很重要,【Ctrl + Shift + T】快速创建测试类;
二:完善Controller;
1.在Controller中,完善补全注册功能;
在前面,我们已经创建过了MemberController类,并且创建了注册方法registe()方法(只是,当时我们只在registe()方法中编写了验证码的逻辑);下面我们把注册的逻辑,写在registe()方法中;
说明:
(1) 前后的变化:
(2) 后台再向前台传递处理结果时,要根据前台的情况来决定要传什么样的数据;
2.启动Tomcat,观察效果;
正确的情况下:(用户名没有重复,用户名和密码符合格式,验证码输对了)
如果输入错误;(比如用户名重复):此时就会抛出异常:
至此,会员注册功能就开发完成了;下篇博客将开发会员登录功能;
实现会员登录功能
说明:
(1) 本篇博客主要内容:实现会员登录功能;(包括会员登录的前端和后端业务逻辑)
(2) 本篇博客的几点说明:
● 在前面的【实现用户登陆】中,也实现过会员登录;可以快速参考下那儿的内容,与本节内容对比一下,能帮助理解【登录,这种业务逻辑】;(虽然,二者的复杂程度不一样,但看一下呗,花不了多长时间)
(3) 本篇博客,登录功能开发流程:【先确定登录页的前端文件login.ftl,编写登录的前端文件】→【然后编写Controller方法,设置访问login.ftl登录页的入口方法,也就是设置访问login.ftl登录的url】→【然后,设置首页上的,登录超链接,让其指向我们在上一步设置的url】→【编写登录功能的Service,主要是编写登录的处理逻辑】→【编写登录功能的Controller方法,接收登录页的登录请求,调用Service逻辑,向login.ftl登录页返回登录结果】→【如果登录成功了,别忘了把当前登录用户的信息,存到Session会话对象中去;这样以后,登录成功后,在需要显示当前登录用户的地方,我们就能直接取数据了】;
(4)其实,本文最重要的就是熟悉【登录,这种业务逻辑】;包括在处理登录时,需要注意的点、习惯采取的操作等;
一:登录页前端文件login.ftl简介;
引入登录页login.ftl;
login.ftl:
说明:
(1) 脚本内容说明1;
(2) 脚本内容说明2;
前端登录页login.ftl已经准备好了,我们主要的任务就是按照要求,完成登录功能的后台逻辑了;
二:登录功能后台逻辑;
1.在MemberController类:开发【访问登录页的入口方法】;(也就是,开发login.ftl登录页的访问url)
说明:
(1) 之所以url后面要附带.html,这是因为:该系统的登录页是一个允许被外界访问地方,加上.html后,会有助于百度谷歌等搜索引擎的抓取,有助于网站的营销和宣传;(在【完成注册功能】中第一次遇到的)
(2) 然后,启动Tomcat,我们通过【localhost/login.html】就能够访问到登录页了;
2.在MemberService接口中,定义一个登录的方法:checkLogin()方法;
3.在MemberServiceImpl实现类中,去实现登录的方法:checkLogin()方法;
说明:
(1) 这个方法逻辑很明确:【先根据用户输入的用户名,去数据库查】→【如果没有查到,则说明,该用户不存在】→【如果查到了,但是密码不对,则说明密码错误】→【如果一切OK,就返回查到的那个Member对象】;
4.在MemberController中编写处理登录的方法checkLogin(),接收前端的请求,调用到Service中定义的逻辑;
说明:
(1) 这儿的很多内容,和前面的【完成会员注册功能】等文章中的内容很类似,前面的几篇文章都提到过;这儿就不重复啰嗦了;
(2) 只是强调一点:和HttpServletRequest等一样,HttpSession对象想用的使用,直接在方法参数中写上就行,Spring会自动帮我们注入;
启动Tomcat,观察效果;
如果输入的用户名、密码、验证码都正确:发现其登录OK,并且跳转到了默认首页(即根路径);
补充1,登录成功后,把【Service层返回的Member用户对象】存到Session中;然后,我们就可以在需要的地方,获取当前登录用户的信息了;
补充2,用户登录成功后、跳转到默认首页后,在页面右上角显示“当前登录用户的会员名”;(其实就是获取补充1中存储的用户信息)(背后的与原理是,FreeMarker也可以去Session对象中的数据)
说明:
(1) 可以看到,首页上的这个登录是个超链接,其地址对应了【/login.html】登录页;即,当我们点击这个登录按钮后,其就会跳转到登录页;
(2) 内容说明;
(3) 很显然,当我们把数据存到ModelAndView中时,其实是存到了HttpServletRequest对象中,我们在前端可以通过FreeMarker去取;那么如果我们把数据存在了Session对象中,我们在前端也可以通过FreeMarker去取;
(4) <#if loginMember??的??的意思是,如果loginMember存在的话,才进入这个判断结构;有关FreeMarker语法,如有需要可以参考【FreeMarker基本语法-分支判断】及附近的文章;
启动Tomcat,观察效果;
至此,登录功能就实现了;接下来就开始【与用户相关的操作】;如变更阅读状态、发布短评、点赞短评;
三:临时Summary:【后端返回JSON】,【数据放在了session对象中】,【数据放在了ModelAndView中】时:前端取值的分析;(暂时性总结,以后有了新认识,随时修正补充)
1.把数据放在了ModelAndView中时:其实,数据是放在HttpServletRequest对象中的;在前端,我们可以使用FreeMarker去取;
前端,ftl直接取就行了;
有精力可以研究下【SpringMvc中使用ModelAndView返回值,为什么将数据存放在request域中【源码分析】_撸码的男人的博客-CSDN博客】;
2.把数据放在了Session对象中时:其实,数据是放在HttpServletRequest域中的;在前端,我们也可以直接用FreeMarker去取;
3.后端返回JSON数据:这种情况,一般对应的是页面ajax请求(比如登录、注册、页面局部刷新),跨接口传值等不涉及页面跳转的情况;
(即此时,我们的数据仅仅是JSON,其只是在响应中;数据并没有在三大作用域对象中;)
因为JavaScript对JSON有着良好的支持,所以可以直接使用JavaScript去取JSON的值;
获取会员阅读状态
说明:
(1) 本篇博客开发内容:前面我们实现了会员注册和登录功能;那么会员登录后,就开始介绍与会员交互有关的功能;第一个功能就是【想看/看过的阅读状态变更】;
然后,本篇博客的主要内容就是获取会员状态:即,当某个会员登录后,该会员点击查看某本图书的详情;就显示该会员针对该本书的阅读状态(如果该会员以前点击过这本书的【想看】或【看过】的话)
(2) 一点说明:SSM开发很多细节,在本专栏前面的几篇博客中都详细介绍了;自本篇博客开始,如果没有遇到新的内容或者需要强调的内容,那么就不再重复啰嗦说明了;
(3) 通过本篇博客能够感受到一个点:当我们的业务需要多表查询的时候,我们一般不在Dao层面使用多表查询的SQL语句来解决这个问题,而是在Service业务层面来化解,从而使得我们我们每次操作数据库的语句都是针对单表的;(PS:但是这点,我感觉不靠谱;)
零:前置说明与分析;
1.需求说明;
2.底层数据表分析;
一:获取会员阅读状态;
1.创建与memner_read_state表对应的实体类:MemberReadState类;
MemberReadState类:
2.创建操作member_read_state表的Mapper接口:MemberReadStateMapper接口;
MemberReadStateMapper接口:
3.创建MemberReadStateMapper接口对应的xml:member_read_state.xml;
member_read_state.xml:
说明:
(1) 通过这个案例,能够感受到,我们的xml文件的名字,没有驼峰命名的feel,而是要尽量保持与数据表的表名对应;
4.在MemberService接口中,定义【获取阅读状态的方法】:selectMemberReadState()方法;
说明:
(1) 这个方法的查询结果,包括两种情况:
● 情况一:会员从来没有看到过这本书,自然这个会员和本书,自然没有阅读状态,即在member_read_state表,就没有这个会员对于这本书的阅读状态;
● 情况二:对于这个本书来说,这个会员之前点过【想看】或者【看过】按钮;自然在member_read_state表就有对应的阅读状态数据;
5.在MemberServiceImpl实现类中,实现【获取阅读状态的方法】:selectMemberReadState()方法;
其实,我们目前编写了阅读状态的Service层逻辑; 那么在什么时候,调用这个逻辑嘞? QueryWrapper使用方法
经过分析项目的业务情况,可以分析出,当我们在显示图书详情页的时候,可以调用【阅读状态的Service层逻辑】;也就是在BookController类的showDetail()方法中去调用;
即,不是MemberService的逻辑,一定得是MemberController去调用的;
6.在BookController类的,显示图书详情页的showDetail()方法中,去调用【阅读状态的Service层逻辑】;
说明:
(1) 自然因为要用到【阅读状态的Service层逻辑】,所以要先注入MemberService对象;
(2) 内容简单说明;
(3) 新增内容说明;
7.在detail.ftl图书详情的前端界面中,获取阅读状态;
(1) 首先,可以看到,前端页面上有两个button,分别对应【想看】和【看过】两个按钮;默认情况下,这两个按钮都没有施加highlight高亮效果;
(2) 当某会员查看某本图书详情时,我们会去查到对应的阅读状态记录,并且把会员阅读状态数据memberReadState保存到ModelAndView(其实就是保存在request中)中,然后返回给detail.ftl前端;
(3) 如果该会员没有设置过针对这本书的阅读状态;那么,在member_read_state表中,就没有对应的记录;即,会员阅读状态数据memberReadState就是空的;是空的,就代表该会员针对该本书,没有阅读状态;我们什么都不用做;
(4) 如果该会员曾经设置过针对这本书的阅读状态;那么,在member_read_state表中,就会有对应的记录;即,会员阅读状态数据memberReadState就是非空的;
(5) 所以,我们可以在detail.ftl图书详情页上,添加一个页面就绪函数;这个函数判断会员阅读状态数据memberReadState是否为空,如果是非空的;那么,就获取对应的阅读状态;然后,根据阅读状态,去获取【想看】或者【看过】的页面元素;然后,在对应元素上添加highlight高亮效果;
如下:
不难,很简单;
8.启动Tomcat,观察效果;
(1) 如果没有登录的话:
(2) 如果登录的话;
至此,我们就实现了获取会员阅读状态的功能;下一篇博客将介绍更新会员阅读状态的功能;
更新会员阅读状态
说明:
(1) 本篇博客开发内容:前面我们实现了会员注册和登录功能;那么会员登录后,就开始介绍与会员交互有关的功能;第一个功能就是【想看/看过的阅读状态变更】;
然后,本篇博客的主要内容就是更新会员状态:即,某个会员登录后,该会员点击查看某本图书的详情;当该会员点击【想看】或者【看过】按钮后:如果原先没有阅读状态,就创建一条;如果原先有阅读状态,就更新原先的阅读状态;
(2) 一点说明:SSM开发很多细节,在本专栏前面的几篇博客中都详细介绍了;自本篇博客开始,如果没有遇到新的内容或者需要强调的内容,那么就不再重复啰嗦说明了;
(3.1)本篇博客特别需要说明的一个点:
(3.2) 由(3.1)就引出了一个问题:【前端的一个请求,到底使用哪个Controller去接收;Controller要调用的处理逻辑,到底要写在哪个Service中;某个Dao层方法,到底在哪个Service中被调用;】:
● 对于这个问题,能感觉到,如果自己开发,自己有能力严格按照规范开发,是没问题的;;;只是,这样做,项目可能会显得死板、臃肿;
●(3)中也提到了所谓的灵活性();自己能够感觉到,要想做到很好的灵活性,【深入理解业务,合理规划程序、模块结构】是很重要的;虽然觉得这个能力也不是那么遥不可及,但需要自己留意并逐渐加强;
● 有一点澄清:整个项目肯定要严格按照规范,可能只有个别地方需要灵活性;
(4) 前端的部分,目前没必要过于深究;能看懂,能基本上手,就可以了;
零:点击【想看】或【看过】时:需要先检测,会员是否已登录;
更新阅读状态的操作,其前提得是会员登录的状态下;所以,我们正式开始前,需要先检查下,当前是否有会员登录;
1.在页面就绪函数中,编写代码:点击【想看】或【看过】时,如果没有登录,就弹出提示登录的提示框;
在detail.ftl的head中的script中的页面就绪函数中,编写代码;其作用是,如果当前没有会员登录,如果点击了【想看】或【看过】按钮,就弹出提示框,提示要先去登录;
说明:
(1) 如果没有登录的话,在页面就绪函数中,编写【想看】和【看过】按钮的单机事件:如果单击了这两个按钮,就弹出请登录的提示;
(上图有个书写错误,不是JavaScript选择器,而是jQuery选择器)
(2) 弹出请登录的提示;我们是通过Bootstrap来实现的;
2.启动Tomcat,测试观察效果;
启动Tomcat,观察效果:可以看到,没有登录的时候,点击【想看】或【看过】按钮时,会弹出提示登录的弹出框;
如果登录的时候,点击【想看】或【看过】按钮时,就不会弹出提示登录的弹出框;
3.【给短评点赞】和【写短评】也需要登录;所以,顺手也给这两个功能上也添加这个判断逻辑吧;
说明:前后变化;
这样以后,在没有登录的情况下,点击写短评的按钮或者点赞短评时,也会弹出提示登录的弹出框;
重启Tomcat,观察效果;
没有登录的时候,点击写短评的按钮或者点赞短评时,会弹出提示登录的弹出框;
登录的时候,点击写短评的按钮或者点赞短评时,就不会弹出提示登录的弹出框;
一:接下里就是任务就是,会员登录后,更新阅读状态;
1.在MemberService接口中,定义更新阅读状态的方法:updateMemberReadState()方法;
说明:
(1) 在MemberService接口中,定义updateMemberReadState()方法;
(2) 已经知道,为了操作member_read_state,我们前面已经定义了MemberReadStateMapper接口;那么,为什么一个涉及到操作member_read_state表的逻辑,要定义在MemberService接口中;而不是创建MemberReadStateService接口,然后在这个接口中定义逻辑嘞?:这其中的原因,估计就是在本篇博客一开头时的说明中写的那样,这就是在【深入理解业务,合理规划程序、模块结构】的基础上,不死板,灵活处理的表现;(好吧,我承认我在胡扯;但是,这条对于目前的自己来说,还是比较重要的;)
2.在MemberServiceImpl实现类中,去实现更新阅读状态的方法:updateMemberReadState()方法;
说明:
(1) 聪明如我,在前面我们就设置了MemberServiceImpl类的方法,默认开启事务了;
一个笔误的地方,上图中,我们写了两次setBookId(),忘记了serMemberId()方法;
(2) 这个方法的逻辑很简单,看下代码注释;
(3) 还是那个点,在啰嗦一遍:如下图
(插)把MemberServiceImpl类中的,selectMemberReadState()查询阅读状态的方法,设置为不开启事务;
这儿算是一个临时的补充;
3.在MemberController类中,创建更新阅读状态的方法:updateReadState();
说明:
(1) 一点说明;
(2) 接下来,我们就需要detail.ftl前端文件中编写逻辑了,具体是:在页面就绪函数中,编写点击【想看】,,待写………………
4.在detail.ftl前端页面中,向【MemberController类中的updateReadState()方法】,发起ajax请求;
有关jQuery中ajax请求的内容可以参考【jQuery中Ajax函数:.ajax()、.post()、$.get()的使用、区别;】
5.启动Tomcat,观察效果;
前端看起来是没问题的:无论是修改已有的图书状态,还是创建新的图书状态,都没问题;
写短评
说明:
(1) 本篇博客开发内容:前面我们实现了会员注册和登录功能;那么会员登录后,就开始介绍与会员交互有关的功能;本篇博客介绍第二个功能【为图书写短评】;
(2) 本篇博客的一个需要强调的【开发技巧】:比如我们Service层的一个方法,这个方法是向数据表插入数据的方法;虽然这个方法不是查询方法,但是这个方法最好还是返回我们插入的实体对象;万一有的地方,调用这个插入方法时,想得到插入的对象呐,是吧。这样做,让程序的更给力,更全面;
一:需求分析;
二:正式开发;
1.在detail.ftl前端编写:点击【写短评】按钮后,在页面上弹出一个写短评的对话框;
启动Tomcat,观察效果:没问题;
接下来就是,写完了短评,点击【提交】,会向后台发起ajax请求;但是,我们后端目前还没有处理写短评的逻辑;所以,接下来我们就去后端编写对应的逻辑;
2.在MemberService接口中,定义发布新短评的方法:evaluate()方法;
说明:
(1) 这儿有遇到了上篇博客提到的所谓的【灵活性】;
3.在MemberServiceImpl实现类中,实现发布新短评的方法:evaluate()方法;
说明:
(1) 这个方法很简单,就是根据传过来的参数,和数据表的要求,实例化Evaluation对象,然后调用Dao层方法,去插数据就行了;
(2) 自然要注入EvaluationMapper对象;同时,因为MemberServiceImpl类默认设置全部方法开启事务,所以我们不用再在evaluate()方法上设置事务了;
4.在MemberController类中,增加前后端交互的方法:evaluate()方法;(Controller中的方法可以随便起,但为了见名知意,也起做evaluate了)
说明:
(1) 这个方法很简单,没什么好说的;
(2) Service层返回值的一点说明:插入更新类的方法,最好也要返回【插入、更新】的数据;
5.在detail.ftl前端编写:点击【写短评】按钮后,向后端发起ajax请求;
启动Tomcat,观察效果:没问题;
在数据库中,也有对应的数据;
至此,写短评的功能就完成了;下篇博客将介绍会员给短评点赞的功能;
给短评点赞
说明:
(1) 本篇博客开发内容:前面我们实现了会员注册和登录功能;那么会员登录后,就开始介绍与会员交互有关的功能;本篇博客介绍第三个功能【为喜欢的短评点赞】;
(2) 本篇博客的功能比较简单;遇到的很多开发技巧,在前几篇博客也都遇到过;这儿就不重复啰嗦了; 只是,其中【防止重复点赞的功能】尚未完成……
一:需求分析;
某会员登录后,看到喜欢的短评,可以点赞这个短评;
其底层的数据表是evaluation表;
上面的一个问题:(这个问题挺重要的)
二:正式开发;
1.在MemberService接口中,定义点赞短评的方法:enjoy()方法;
说明:
(1) 是前面提到过的两点:【更新的方法,最好也返回更新的对象】;【操作evaluation表的逻辑方法,可以在MemberService中定义:就是前面提到过的所谓灵活性】;
2.在MemberServiceImpl实现类中,实现点赞短评的方法:enjoy()方法;
3.在MemberController类中,增加前后端交互的方法:enjoy()方法;(Controller中的方法可以随便起,但为了见名知意,也起做enjoy了)
接下来,我们就是在detail.ftl前端中编写:当点击点赞按钮后,向前端发起ajax请求了;
4.在detail.ftl中,点击【点赞按钮】后,向后端发起ajax请求;
启动Tomcat,观察效果:
5.补:一个会员,在一定的时间内,只能为某条评论,点赞一次;(这是个很常见的业务需求)(待完成……)
目前能想到的思路就是:
重新建一张表tt,这张表中主要有这几个字段:用户id,evaluationId,点赞时间;
每次用户点赞的时候,就去tt表中去查,看下这个用户在一定时间内是否给该条评论点过赞;;;如果没有,就顺利进行;如果有,不能点赞,给出提示;
同时,用户每次点赞时:如果是该用户第一次点赞,就在tt表中新增记录;如果是该用户的重复点赞,就更新tt表中记录的时间;
不过,上面的解决方案,会明显拖慢后台的处理速度;
这个问题,先搁置吧,等以后,遇到了或者想到了更好的思路~~
Spring-Task定时任务
说明:
(1) 本篇博客的内容说明:利用Spring-Task模块,实现对所有图书【更新评分、评价人数】的功能;
(2) 本篇博客的几个点:
● Mybatis-Plus可以帮助我们生成SQL语句;对于基本的增删改查,Mybatis-Plus是OK的;但是,对于复杂的SQL语句,Mybatis-Plus就比较吃力了;又因为,Mybatis-Plus只是扩展了Mybatis,其并没有修改Mybatis的任何内容;所以,我们在这儿,依旧可以使用Mybatis的内容;自然,这儿需要遵循Mybatis的开发规范,该对应的要对应好;
● 本篇博客的演示了Spring Task的基本使用;这是本篇博客的重点;
● 本篇博客的基本逻辑是:【分析业务逻辑,得到底层SQL】→【根据业务逻辑,编写业务代码】→【创建实现定时任务的类,调用业务逻辑代码,设置定时任务】;
一:【Spring-Task】定时任务模块,简介;
1.定时任务,简介;
定时任务概念:
**** 我们想在 [几点几分几秒] 或者 [一个固定的时间间隔内] ,执行Java中的某一段代码;
定时任务是一种非常常见的应用场景:
比如,开发一个闹钟应用,设置其每天早上6:30时,自动播放闹铃,此时就是定时任务;
再比如,金融业中:银行每天需要根据当天的业务情况,生成数据统计报表;又因为,银行每天的业务量可能百万千万,数据量如此庞大,计算这些业务,生成统计报表,是需要花费一定的时间;所以,我们不能在需要统计报表的时,即时先算;为此,银行就提出了一种延时处理的方案:通常在凌晨2:00-4:00,由银行的服务器自动执行处理数据的任务(这在银行的专业术语中,被称为日终处理),生成统计报表;第二天,当需要查看昨天的业务的统计报表时,只需要直接读取昨晚生成的数据就行了,而不需要现算;那么,如果保证在每天的凌晨2:00-4:00,去执行统计任务呐?其底层就是通过一些定时任务来处理的;
2.Spring-Task定时任务模块;
3.Cron表达式;
说明:
(1) Cron表达式,不是Java独有的技术;
(2) Cron表达式,是一个最多7位的字符串;这7位,分别对应秒、分、小时、日、月、星期、年;
(3) 其中,第7位年,可以省略;日和星期是互斥的,即,写了日,星期就只能是?;写了星期,日就只能是?;(如果星期哪儿是?,就表示忽略星期)
(4) 如上图中的第一个例子:表示:在任意年的、任意月、任意日、任意小时、任意分、0的时候,执行一次任务;;;;其实,也就是每分钟执行一次任务;
(5) 上图的第二个例子:表示:在2000年、任意月、任意日、任意小时、每小时的前五分钟、 第0和第30秒
,执行任务;(这儿可以看到,我们可以【0,30】这样写,来指定分散的时间点)
(6) 上图的第三个例子:表示:在任意年、任意月、星期三、第9-18小时、第0分、第0秒,执行任务;;;;其实,也就是每周三,上午9点到下午6点,整点的时候,执行任务;(这儿可以看到,当我们写了星期,就不能写日了;)
二:编写【更新评分、评价人数】的逻辑代码;
0.情况说明;
在前面,会员可以对图书进行评价;
那么,在新增评分的时候,需要重新计算一下该图书的评分;
此时,我们就可以使用【Spring-Task】,来实现;每分钟,来重新计算图书的平均评分;
1.【更新评分、评价人数】的:SQL分析;
这儿的业务主要是:查询某本书的【平均评分】和【评价数量】;下面的SQL,就可以完成这个功能;
说明:
(1) 上面的SQL很简单,其中涉及到了AVG,COUNT聚合函数(如有需要,可以参考【AVG函数】);子查询(如有需要,可以参考【子查询】);上面的SQL,很简单,没什么好说的;
(2) 但是,上面有一个需要注意的地方:
(3) 目前,暂时不要过于考虑数据库优化的内容;
至此,自动计算评分和评价人数的SQL语句就OK了;但是,我们希望,每分钟去执行一次这个SQL;在Spring-Task模块中,如何使用嘞?
2.在BookMapper接口中,定义操作底层数据库,实现【更新评分、评价人数】的方法:updateEvaluation()方法;
说明:
(1) 因为【更新评分、评价人数】的SQL逻辑比较复杂;Mybatis的BaseMapper接口中,没有对应的可以处理这个业务的方法;所以,我们需要自己定义一个方法
3.在book.xml中,编写【更新评分、评价人数】的SQL;
说明:
(1) Mybatis-Plus可以帮助我们生成SQL语句;对于基本的增删改查,Mybatis-Plus是OK的;但是,对于上面的那种复杂的SQL语句,Mybatis-Plus就比较吃力了;
(2) 又因为,Mybatis-Plus只是扩展了Mybatis,其并没有修改Mybatis的任何内容;所以,我们在这儿,依旧可以使用Mybatis的内容;
(3) 自然,这儿需要遵循Mybatis的开发规范,该对应的要对应好:
4.在BookService接口中,定义【更新评分、评价人数】的方法:updateEvaluation()方法;(PS:Service中的可以起其他名字,无需和Mapper中的保持一致)
5.在BookServiceImpl实现类中,去实现【更新评分、评价人数】的方法:updateEvaluation()方法;
【更新评分、评价人数】逻辑代码写完了;那么我们如何每分钟,执行Service层中定义的updateEvaluation()方法呐?
这就需要Spring-Task模块了;
三:【Spring-Task】定时任务模块:在本项目上的应用;
1.不需要额外引入依赖;
2.在applicationContext.xml配置文件中,配置:去开始Spring Task定时任务注解模式;
说明:
(1) 聪明如我,在前面我们开发的时候,就引入了task命名空间;
3.创建task包,创建ComputeTask类:调用Service中的逻辑,设置定时任务;
ComputeTask:
说明:
(1) 因为这个类不是Service,不是Controller;而我们又想让IoC容器管理这个类的对象,所以我们使用了@Component注解;有关@Component这种组件注解,如果有需要,可以参考【Compoment注解】;
(1.2) 由(1)联想到,在SSM项目中;Mapper接口没有使用@Repository注解;对于这个问题,可以参考【彻底搞懂使用MyBatis时为什么Dao层不需要@Repository】;这篇文章写的很清楚;
(3) 方法说明;
4.启动Tomcat,观察效果;
经过在页面上实操,发现【评分和评价人数】,实现了在每分钟的第0秒实现更新的功能;
而且在控制台这儿,也可以看到每分钟的第0秒,也输出了【已经更新所有图书的评分、评价人数】;
至此,【MK书评网】的前台都已经完成了;(前台是给用户用的)
接下来,我们要开发【MK书评网】的后台了;(后台是给管理用的)