Ssm随学札记
使用在使用ObjectMapper将json转对象,调用mapper.readValue(jsonStr, XwjUser.class)时,报如下错:
java.lang.NullPointerException at com.fasterxml.jackson.core.JsonFactory.createParser(JsonFactory.java:889) at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3004) at com.express.utils.JsonUtils.jsonToPojo(JsonUtils.java:48) at com.express.web.controller.backstage.ManagerBaseController.getCurrentUser(ManagerBaseController.java:29) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:189) at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) at org.springframework.web.method.annotation.ModelFactory.invokeModelAttributeMethods(ModelFactory.java:142) at org.springframework.web.method.annotation.ModelFactory.initModel(ModelFactory.java:111) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:872) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:800) at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1038) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942)
这个错误产生的原因有三个:
1. 是因为在使用ObjectMapper将json转为对象时,会默认调用该对象的无参构造函数,因此在该对象类中加入无参构造函数即可去掉该错误。
2. 期待转成的对象与json对应的属性不匹配。
3. 真正的空指针,既json为空。
ajax提交的几个坑
通过ajax提交数据,在后台是无法进行重定向的,只能通过前端进行重定向。具体如何实现如下:
* 判断满足需要重定向时,需要修改返回前端的Header以及Status,并设置拦截(当然这个可以不用)。
* 编写redirectUtil工具类,处理后台跳转,这里需要注意,设置 response.setStatus(HttpServletResponse.SC_FORBIDDEN);之后前端会被拦截,报403错误,代表服务端终止,前端进行操作。/** *@author fyzn12 *@version 1.0 *@date 2020/4/18 13:46 *主要作用于ajax请求,后台重定向 *提供日志接口 *提供获取具体日志对象的方法 */ @Slf4j public class RedirecUtil { public static void redirect(HttpServletRequest request,HttpServletResponse response,String redirectUrl){ try { /* *如果是ajax请求 **/ String ajax = "XMLHttpRequest"; String header = "X-Requested-With"; if(ajax.equals(request.getHeader(header))){ System.out.println("执行ajax请求"); sendRedirect(response,redirectUrl); } //如果是浏览器地址栏请求 else { System.out.println("执行普通请求"); log.debug("normal redirect "); response.sendRedirect(redirectUrl); } }catch (Exception e){ } } /** *功能描述 *@Description Ajax请求时重定向处理 *@param: *@return: */ private static void sendRedirect(HttpServletResponse response, String redirectUrl){ try { //这里并不是设置跳转页面,而是将重定向的地址发给前端,让前端执行重定向 //告诉ajax我是重定向 response.setHeader("REDIRECT", "redirect"); //设置跳转地址 response.setHeader("redirectUrl", redirectUrl); //设置跳转使能 response.setHeader("enableRedirect","true"); response.setStatus(HttpServletResponse.SC_FORBIDDEN); response.flushBuffer(); System.out.println("跳转"); } catch (IOException ex) { log.error("Could not redirect to: " + redirectUrl, ex); System.out.println(ex.getMessage()); } } }
前端进行处理,这里我推荐运用全局的ajax处理,这样就不用每一个ajax都运用complete进行处理。
$(document).ajaxComplete(function (event, xhr, settings) { console.log("ajaxComplete ") redirectHandle(xhr); }) function redirectHandle(xhr) { //获取后台返回的参数 var url = xhr.getResponseHeader("redirectUrl"); console.log("redirectUrl = " + url); var enable = xhr.getResponseHeader("enableRedirect"); if((enable == "true") && (url != "")){ var win = window; while(win != win.top){ win = win.top; } win.location.href = url; }
评论系统的开发(楼中楼的形式)
数据库的设计
数据结构的说明:在设计数据库的时候需要设置一个唯一标识的主建comment_id,其次需要一个字段用于评论查找的parent_comment_id,该字段主要作用标识子评论,这里我设计当parent_comment_id为 0时,表示该条评论为父级评论。 具体数据结构如下CREATE TABLE `comment` ( `id` int(11) NOT NULL COMMENT '评论id', `user_id` int(11) NOT NULL COMMENT '用户评论的id', `replyid` int(11) NOT NULL DEFAULT 0 COMMENT '该评论回复的评论id,没有则为0', `demandId` int(11) NOT NULL COMMENT '该评论的需求订单号', `content` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '评论内容', `time` char(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '评论时间', `pic` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '评论图片', `score` int(11) NULL DEFAULT NULL COMMENT '评分', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
编写dao层,这里主要运用逆向工程生成的文件,不做详细说明。
编写service层,楼中楼的评论模式主要有两种实现思路:
递归的思想实现一张表的检索
栈的思想实现,但其实代码实现差不多,这里主要运用递归的算法实现楼中楼的评论显示模式
改写逆向工程生成的Comment.java的pojo类,主要是添加三个属性,以便实现递归,具体更改如下:
@Data public class Comment implements Serializable { /** * 评论id */ private Integer commentId; /** * 用户评论的id */ private Integer userId; /** * 父级评论id */ private Integer parentCommitId; /** * 评论内容 */ private String content; /** * 评论时间 */ private String time; /** * 头像 */ private String avatar; /** * 评分 */ private Integer score; /** * 昵称 */ private String nickname; /** * 评论的需求订单号 */ private Integer demandId; /* * 回复评论 * 使用递归的方式 * */ private List<Comment> replyComments = new ArrayList<>(); private Comment parentComment; private String parentNickname; private static final long serialVersionUID = 1L; }
- 首先查询出所有的父级评论,也就是parent_comment_id为0的记录。
- 其次通过父级索引的commentid查询出所有的一级评论,就是当一级评论的parent_comment_id等于父级评论的commentid时即为一级评论。
最后检索出所有的二级及以上的评论。实现代码如下:
@Override public DataTableResult listComment(int page, int rows) { //1.初始化分页插件 PageHelper.startPage(page,rows); //2.执行查询,获取查询集合 CommentExample commentExample = new CommentExample(); /* * 查询所有父级节点并分页 * */ commentExample.createCriteria().andParentCommitIdEqualTo(0); commentExample.setDistinct(false); List<Comment> comments = commentMapper.selectByExample(commentExample); for (Comment c : comments){ String parentNickname1 = c.getNickname(); //根据父级节点的commentid查询出子评论 CommentExample commentExample1 = new CommentExample(); commentExample1.createCriteria().andParentCommitIdEqualTo(c.getCommentId()); commentExample1.setDistinct(false); List<Comment> childComments = commentMapper.selectByExample(commentExample1); //查询出子评论 queryChildComment(childComments,parentNickname1); c.setReplyComments(tempReplys); tempReplys = new ArrayList<>(); } /* * 查询出子评论 * 这里主要运用了分页,所以把检索出楼中楼的数据的主要代码列举出 * */ private void queryChildComment(List<Comment> childComments,String parentNickname1){ //判断是否有一级回复 if (childComments.size()>0){ //循环找出子评论的id for (Comment childComment:childComments){ String parentNickname = childComment.getNickname(); childComment.setParentNickname(parentNickname1); //添加子回复 tempReplys.add(childComment); //查询二级以及所有子集回复 recursively(childComment.getCommentId(), parentNickname); } } } /* * 查询一级评论一下的所有的评论 * */ private void recursively(Integer childId, String parentNickname1) { //根据子一级评论的id找到子二级评论 CommentExample commentExample = new CommentExample(); commentExample.createCriteria().andParentCommitIdEqualTo(childId); commentExample.setDistinct(false); List<Comment> replayComments = commentMapper.selectByExample(commentExample); if(replayComments.size() > 0){ for(Comment replayComment : replayComments){ String parentNickname = replayComment.getNickname(); replayComment.setParentNickname(parentNickname1); Integer replayId = replayComment.getCommentId(); tempReplys.add(replayComment); //循环迭代找出子集回复 //利用递归的方式 recursively(replayId,parentNickname); } } }
编写controller层 主要是将评论信息传回前端
@RequestMapping(value = "/aa") public String getComment(Model model){ DataTableResult dataTableResult = commentService.listComment(1,12); model.addAttribute("comments",dataTableResult.getData()); /* * 为了前端能显示当前评论回复者的头像,必须将当前用户的头像地址传回前端 * */ model.addAttribute("pic",user.getPic()); model.addAttribute("nickname",user.getNickname()); return "user/comment"; }
前端显示。
<!--显示评论--> <div class="container"> <input type="text" id="pic" style="display: none" value="${pic}"/> <input type="text" id="nickname" style="display: none" value="${nickname}"/> <div class="row"> <div class="col-md-10"> <c:forEach var="comment" items="${comments}"> <div class="comments" style="margin-top: 20px"> <a class="comments-avatar" style="float: left"> <img style="width: 40px;height: 40px" class="img-circle" src="${comment.avatar}" alt=""/> </a> <div class="content" style="margin-left: 60px"> <a class="author-name" style="float: left;"> <span>${comment.nickname}</span> </a> <div class="metadata" style="margin-left: 70px"> <span class="date">${comment.time}</span> </div> <div class="text"> <span style="float:left;">${comment.content}</span> <div style="float: right;margin-right:20px;"> <span><a href="javascript:reply('${comment.commentId}','${comment.nickname}')">回复</a></span> </div> </div> </div> <!-- 用于显示提交新评论的位置 --> <div id="${comment.commentId}"></div> <!--子级评论--> <c:forEach items="${comment.replyComments}" var="childComment"> <div class="comments" style="margin-left: 35px;margin-top: 30px;margin-bottom: 30px;"> <!-- 用于显示提交新评论的位置 --> <div id="child-${childComment.commentId}"></div> <!-- 父级评论 --> <div class="comment" style="margin-top: 20px;margin-bottom: 20px;"> <a class="comments-avatar" style="float: left"> <img style="width: 40px;height: 40px" class="img-circle" src="${childComment.avatar}"> </a> <div class="content" style="margin-left: 60px"> <a class="author-name" style="float: left;"> <span>${childComment.nickname}</span> </a> <a class="author-name" style="float: left;margin-right: 10px"> <span> 回复 <!-- 父级评论回复子集评论--> <c:if test="${childComment.commentId==comment.commentId}"> <c:forEach items="${comment.replyComments}" var="ch_comment"> <c:if test="${ch_comment.commentId==childComment.parentCommitId}"> ${ch_comment.nickname} </c:if> </c:forEach> </c:if> <!-- 子级评论回复父级评论或者子级评论之间的回复--> <c:if test="${childComment.commentId!=comment.commentId}"> <c:forEach items="${comment.replyComments}" var="ch_comment"> <!-- 子级评论之间的回复--> <c:if test="${ch_comment.commentId==childComment.parentCommitId}"> ${ch_comment.nickname} </c:if> </c:forEach> <!-- 子级评论回复父集评论 --> <c:if test="${childComment.parentCommitId==comment.commentId}"> ${comment.nickname} </c:if> </c:if> </span> </a> <div class="date-picker-popup" > <a class="date-cell">${childComment.time}</a> </div> <div class="text"> <span style="float: left"> ${childComment.content}</span> <div style="float: right;margin-right:20px;"> <span><a href="javascript:childrenReply('${childComment.commentId}','${childComment.nickname}')">回复</a></span> </div> </div> </div> </div> </div> </c:forEach> </div> </c:forEach> </div> </div>
js的实现
/**************************************时间格式化处理************************************/ function dateFtt(fmt,date) { var o = { "M+" : date.getMonth()+1, //月份 "d+" : date.getDate(), //日 "h+" : date.getHours(), //小时 "m+" : date.getMinutes(), //分 "s+" : date.getSeconds(), //秒 "q+" : Math.floor((date.getMonth()+3)/3), //季度 "S" : date.getMilliseconds() //毫秒 }; if(/(y+)/.test(fmt)) fmt=fmt.replace(RegExp.$1, (date.getFullYear()+"").substr(4 - RegExp.$1.length)); for(var k in o) if(new RegExp("("+ k +")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length==1) ? (o[k]) : (("00"+ o[k]).substr((""+ o[k]).length))); return fmt; } //创建时间格式化显示 function crtTimeFtt(date){ return top.dateFtt("yyyy-MM-dd hh:mm:ss",date);//直接调用公共JS里面的时间类处理的办法 } function reply(replyCommentId,nickname){ var commentId = replyCommentId; //弹出alert swal({ title:'评论回复', text:'请输入回复内容~', type:'input', showCancelButton:true, confirmButtonColor:'#33ccff', closeOnConfirm: false, closeOnCancel: false, confirmButtonText:'回复', cancelButtonText:'取消', inputPlaceholder:'已回复......', showLoaderOnConfirm:true },function(inputValue){ //console.debug(inputValue); if(jQuery.trim(inputValue)!=''&&inputValue==false){ swal("取消回复", "你没有进行回复!", "info"); return; } if(inputValue==''){ swal.showInputError('你没有填写任何回复信息!'); return; } //将父评论的commentId作为回复评论的 //发送ajax $.ajax({ type:"post", url:'user/comment', traditional: true, data:{"parentCommitId":commentId,"content":inputValue}, success:function (data) { if(data.status==200){ swal("回复成功!", "您已进行此用户评论的回复!", "success"); //修改显示评论的 var time = crtTimeFtt(new Date()); $("#"+commentId).html('<div class="comments" style="margin-left: 35px;margin-top: 30px;margin-bottom: 30px;"> ' + '<div class="comment" style="margin-top: 20px;margin-bottom: 20px;">' + ' <a class="comments-avatar" style="float: left">' + ' <img style="width: 40px;height: 40px" class="img-circle" src="'+$("#pic").val()+'">' + ' </a>' + ' <div class="content" style="margin-left: 60px">'+ ' <a class="author-name" style="float: left;"> '+ '<span>'+$("#nickname").val()+'</span>' + '</a>'+ '<a class="author-name" style="float: left;margin-right: 10px">' + ' <span> 回复 '+ nickname+ '</span>'+ '</a>'+ '<div class="date-picker-popup" >'+ '<a class="date-cell">'+time+'</a>'+ '</div>'+ ' <div class="text">'+ '<span style="float: left">'+ inputValue+'</span>'+ '</div>'+ '</div>'+ '</div>'+ '</div>') return ; }else{ swal("回复失败!", "回复失败,请检查网络配置后重试!", "error"); } }, error:function (data) { swal("回复失败!", "回复失败,请检查网络配置后重试!", "error"); } }); } ); } function childrenReply(replyCommentId,nickname){ var commentId = replyCommentId; //弹出alert swal({ title:'评论回复', text:'请输入回复内容~', type:'input', showCancelButton:true, confirmButtonColor:'#33ccff', closeOnConfirm: false, closeOnCancel: false, confirmButtonText:'回复', cancelButtonText:'取消', inputPlaceholder:'已回复......', showLoaderOnConfirm:true },function(inputValue){ //console.debug(inputValue); if(jQuery.trim(inputValue)!=''&&inputValue==false){ swal("取消回复", "你没有进行回复!", "info"); return; } if(inputValue==''){ swal.showInputError('你没有填写任何回复信息!'); return; } //将父评论的commentId作为回复评论的 //发送ajax $.ajax({ type:"post", url:'user/comment', traditional: true, data:{"parentCommitId":commentId,"content":inputValue}, success:function (data) { if(data.status==200){ swal("回复成功!", "您已进行此用户评论的回复!", "success"); //修改显示评论的 var time = crtTimeFtt(new Date()); $("#child-"+commentId).html( '<div class="comment" style="margin-top: 20px;margin-bottom: 20px;">' + ' <a class="comments-avatar" style="float: left">' + ' <img style="width: 40px;height: 40px" class="img-circle" src="'+$("#pic").val()+'">' + ' </a>' + ' <div class="content" style="margin-left: 60px">'+ ' <a class="author-name" style="float: left;"> '+ '<span>'+$("#nickname").val()+'</span>' + '</a>'+ '<a class="author-name" style="float: left;margin-right: 10px">' + ' <span> 回复 '+ nickname+ '</span>'+ '</a>'+ '<div class="date-picker-popup" >'+ '<a class="date-cell">'+time+'</a>'+ '</div>'+ ' <div class="text">'+ '<span style="float: left">'+ inputValue+'</span>'+ '</div>'+ '</div>'+ '</div>' ) return ; }else{ swal("回复失败!", "回复失败,请检查网络配置后重试!", "error"); } }, error:function (data) { swal("回复失败!", "回复失败,请检查网络配置后重试!", "error"); } }); } ); }
效果图显示
使用schema-base配置消息通知的aop
配置消息通知的类
<!-- 配置消息通知aop--> <bean id="myadvice" class="com.express.advice.advice"/>
配置切面
<!-- 切面 --> <aop:config> </aop:config>
指定通知类
<!-- 切面 --> <aop:config> <!-- 指定通知类 <aop:aspect ref="myadvice"> </aop:aspect> </aop:config>
指定切点
<!-- 切面 --> <aop:config> <!-- 指定通知类 <aop:aspect ref="myadvice"> 指定切点 <aop:pointcut id="checkComment" expression="execution(* com.express.service.CommentService.addComment(com.express.domain.Comment)) and args(comment)"/> </aop:aspect> </aop:config>
切点织入
<!-- 切面 --> <aop:config> <!-- 指定通知类 <aop:aspect ref="myadvice"> 指定切点 <aop:pointcut id="checkComment" expression="execution(* com.express.service.CommentService.addComment(com.express.domain.Comment)) and args(comment)"/> 切点织入 <aop:after method="after" arg-names="comment" pointcut-ref="checkComment"/> </aop:aspect> </aop:config>
- 上一篇: Mybatis-的配置文件
- 下一篇: Mybatis之动态SQL