1. 使用在使用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提交的几个坑

  1. 通过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;
          }

评论系统的开发(楼中楼的形式)

  1. 数据库的设计
    数据结构的说明:在设计数据库的时候需要设置一个唯一标识的主建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;
  2. 编写dao层,这里主要运用逆向工程生成的文件,不做详细说明。

  3. 编写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);
                    }
                }
            }
  4. 编写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";
        }
  5. 前端显示。

            <!--显示评论-->
            <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>&nbsp;回复&nbsp;
                                            <!-- 父级评论回复子集评论-->
                                            <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>
  6. 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>&nbsp;回复&nbsp;'+
                                    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>&nbsp;回复&nbsp;'+
                                    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");
                        }
                    });
                   }
                );
    
             }
  7. 效果图显示

使用schema-base配置消息通知的aop

  1. 配置消息通知的类

        <!-- 配置消息通知aop-->
        <bean id="myadvice" class="com.express.advice.advice"/>
  2. 配置切面

        <!-- 切面 -->
         <aop:config>
    
        </aop:config>
  3. 指定通知类

         <!-- 切面 -->
         <aop:config>
            <!-- 指定通知类
            <aop:aspect ref="myadvice">
    
            </aop:aspect>
    
        </aop:config>
  4. 指定切点

         <!-- 切面 -->
         <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>
  5. 切点织入

          <!-- 切面 -->
         <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>