SpringBoot博客笔记

SpringBoot开发的个人博客笔记 半个月(2020-11-05-2020-11-20)

一:需求分析

角色 商业 商业价值

1:角色

访客和管理员(我)

访客:具有查询.留言.赞赏的权限...

管理员:具有发布.编辑文章.分类.删除博客...的权限

二:页面的开发与设计

用纸和笔。‘

ps等软件设计页面

1.前端页面

首页:

详细页:

分类:

标签:

归档:

关于我:

2:后台管理

模板页面

三:构建框架的环境

1:在yml中配置连接所有的数据库和多种配置的环境开发和日常的日志

#开发环境
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/myblog
    username: root
    password: 123456
mybatis:
  type-aliases-package: com.sx.entity
  mapper-locations: classpath:mapper/*.xml
    #开启驼峰命名法
  configuration:
    map-underscore-to-camel-case: true
# 日志的级别
logging:
  level:
    root: info
    com.sx: debug
  file: log/blog-dev.log
server:
  port: 8080

#生产环境
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/myblog
    username: root
    password: 123456
mybatis:
  type-aliases-package: com.sx.entity
  mapper-locations: classpath:mapper/*.xml
  #开启驼峰命名法
  configuration:
    map-underscore-to-camel-case: true
# 日志的级别
logging:
  level:
    root: warn
    com.sx: info
  file: log/blog-pro.log
server:
  port: 8081

最大的权限application.yml

spring:
  thymeleaf:
    mode: HTML
    #选择你想选择的开发环境  
  profiles: 
    active: por

配置日志的信息

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <!--包含Spring boot对logback日志的默认配置-->
    <include resource="org/springframework/boot/logging/logback/defaults.xml" />
    <property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}/spring.log}"/>
    <include resource="org/springframework/boot/logging/logback/console-appender.xml" />

    <!--重写了Spring Boot框架 org/springframework/boot/logging/logback/file-appender.xml 配置-->
    <appender name="TIME_FILE"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <encoder>
            <pattern>${FILE_LOG_PATTERN}</pattern>
        </encoder>
        <file>${LOG_FILE}</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.%i</fileNamePattern>
            <!--保留历史日志一个月的时间-->
            <maxHistory>30</maxHistory>
            <!--
            Spring Boot默认情况下,日志文件10M时,会切分日志文件,这样设置日志文件会在100M时切分日志
            -->
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>10MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>

        </rollingPolicy>
    </appender>
<!--日志输出到哪里呢-->
    <root level="INFO">
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="TIME_FILE" />
    </root>

</configuration>
        <!--
            1、继承Spring boot logback设置(可以在appliaction.yml或者application.properties设置logging.*属性)
            2、重写了默认配置,设置日志文件大小在100MB时,按日期切分日志,切分后目录:

                my.2017-08-01.0   80MB
                my.2017-08-01.1   10MB
                my.2017-08-02.0   56MB
                my.2017-08-03.0   53MB
                ......
        -->

2:配置全局的异常处理

1:配置拦截异常处理的类:

package com.sx.config.handler;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;

/**
 * @author SX
 * @version 1.0
 * @date 2020/11/6 15:54
 */
@ControllerAdvice //拦截所有的Controller的注解
public class ControllerExceptionHandler {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @ExceptionHandler  //标识这个方法是做异常处理的
    public ModelAndView exceptionHandler(HttpServletRequest request, Exception e) throws Exception {
        //记录错误信息到日志中
        logger.error("Request URL : {},Exception : {}", request.getRequestURL(), e);
    //分析状态  如果不为空就交给springboot来处理
        if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) {
            throw e;
        }
        ModelAndView mv = new ModelAndView();
        mv.addObject("url", request.getRequestURL());
        mv.addObject("exception", e);
        mv.setViewName("error/error");
        return mv;
    }
}

配置error的页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>错误</title>
</head>
<body>
<h1>错误</h1>

<div>
    <div th:utext="'&lt;!--'" th:remove="tag"></div>
    <div th:utext="'Failed Request URL : ' + ${url}" th:remove="tag">
    </div>
    <div th:utext="'Exception message : ' + ${exception.message}"
         th:remove="tag"></div>
    <ul th:remove="tag">
        <li th:each="st : ${exception.stackTrace}" th:remove="tag"><span
                th:utext="${st}" th:remove="tag"></span></li>
    </ul>
    <div th:utext="'--&gt;'" th:remove="tag"></div>
</div>
</body>
</html>

3设置 自定义的异常类

package com.sx.config.exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

/**
 * @author SX
 * @version 1.0
 * @date 2020/11/6 16:21
 * 自定义异常
 */
//返回的状态
@ResponseStatus(HttpStatus.NOT_FOUND)
public class  NotFoundException extends RuntimeException {
    public NotFoundException() {
    }

    public NotFoundException(String message) {
        super(message);
    }

    public NotFoundException(String message, Throwable cause) {
        super(message, cause);
    }
}

package com.sx.controller;

import com.sx.config.exception.NotFoundException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

/**
 * @author SX
 * @version 1.0
 * @date 2020/11/6 15:48
 */
@Controller
public class IndexController {

    @GetMapping("/")
    public String index() {
        String blog = null;
        if (blog == null) {
            throw new NotFoundException("博客不存在");
        }
        return "index";
    }
}

3:日志的处理

3.1 写一个日志的类用aop记录(ip,url,method,参数)

package com.sx.config.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.condition.RequestConditionHolder;
import sun.misc.Request;

import javax.servlet.ServletRegistration;
import javax.servlet.ServletRequestAttributeEvent;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;

/**
 * @author SX
 * @version 1.0
 * @date 2020/11/6 16:43
 * 配置日志切面
 */
@Aspect
@Component
public class LogAspect {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Pointcut("execution(* com.sx.controller.*.*(..))")
    public void log() {
    }

    @Before("log()")
    public void doBefore(JoinPoint joinPoint) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String url = request.getRequestURL().toString();
        String ip = request.getRemoteAddr();
        String classMethod = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
        Object[] arges = joinPoint.getArgs();
        RequestLog requestLog = new RequestLog(url, ip, classMethod, arges);
        logger.info("Request : {}", requestLog);
    }

    @After("log()")
    public void doAfter() {

        logger.info("-------doBefore----------");

    }

    /**
     * 返回的内容
     *
     * @param result
     */
    @AfterReturning(returning = "result", pointcut = "log()")
    public void doAfterReturn(Object result) {

        logger.info("Result : {}", result);
    }

    /**
     * 获取到指定的信息用aop
     * 记录url、ip、classMethod、args
     */
    private class RequestLog {

        private String url;
        private String ip;
        private String classMethod;
        private Object[] args;

        public RequestLog(String url, String ip, String classMethod, Object[] args) {
            this.url = url;
            this.ip = ip;
            this.classMethod = classMethod;
            this.args = args;
        }

        @Override
        public String toString() {
            return "RequestLog{" +
                    "url='" + url + '\'' +
                    ", ip='" + ip + '\'' +
                    ", classMethod='" + classMethod + '\'' +
                    ", args=" + Arrays.toString(args) +
                    '}';
        }
    }
}

4 thymeleaf对公共部分进行处理

4.1关建语法:

新建common.html  这个页面存放公共的资源 关键词 fragment
th:fragment="footer"
在需要用的公共资源的时候用  关键词 replace
th:replace="common :: footer"

5:设计与规范

实体类

  • 博客 Blog
  • 博客分类 Type
  • 博客标签 Tag
  • 博客评论 Comment

一个对象有多个回复,将具有被回复性质的作为parentComment

//一对多
    List<Comment> replyComments=new ArrayList<>();
//多对一
    private Comment parentComment;	
  • 用户 User

5.1 命名约定

Service/DAO层命名约定:

  • 获取单个对象的方法用get做前缀。
  • 获取多个对象的方法用list做前缀。
  • 获取统计值的方法用count做前缀。
  • 插入的方法用save(推荐)或insert做前缀。
  • 删除的方法用remove(推荐)或delete做前缀。
  • 修改的方法用update做前缀。

四、后台管理功能实现

写一个配置类

package com.sx.config.interceptor;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author SX
 * @version 1.0
 * @date 2020/11/7 15:55
 * 过滤请求
 * 配置类
 */
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/admin/**")
                .excludePathPatterns("/admin/login").excludePathPatterns("/admin");

    }
}

期间遇见的问题:

1:WebMvcConfigurerAdapter过时的替换方法

Spring 5.0后,WebMvcConfigurerAdapter被废弃,取代的方法有两种:

①implements WebMvcConfigurer(官方推荐)

②extends WebMvcConfigurationSupport

使用第一种方法是实现了一个接口,可以任意实现里面的方法,不会影响到Spring Boot自身的@EnableAutoConfiguration,而使用第二种方法相当于覆盖了@EnableAutoConfiguration里的所有方法,每个方法都需要重写,比如,若不实现方法addResourceHandlers(),则会导致静态资源无法访问,实现的方法如下:

package com.sx.config.interceptor;

import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author SX
 * @version 1.0
 * @date 2020/11/7 15:55
 */
@Configurable
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/admin/**")
                .excludePathPatterns("/admin/login").excludePathPatterns("/admin");

    }
}

1:博客的业务

crud + 模糊查询

创建一个接受模糊查询dto

package com.sx.queryvo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author SX
 * @version 1.0
 * @date 2020/11/10 0:56
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SearchBlog {
    private String title;
    private Long typeId;
}

创建一个博客的信息的dto

package com.sx.queryvo;

import com.sx.entity.Type;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

/**
 * @author SX
 * @version 1.0
 * @date 2020/11/8 10:29
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class BlogQuery {
    private Long id;
    private String title;
    private Date updateTime;
    private Boolean recommend;
    private Boolean published;
    private Long typeId;
    private Type type;

}

删除文章的分类

    /**
     * 删除 分类 逻辑判断是否该id
     * 分类是否有文章有的话 就不让他删除
     * @param id
     * @param attributes
     * 重定向  携带数据
     * @return
     */
    @GetMapping("/types/{id}/delete")
    public String delete(@PathVariable Long id,RedirectAttributes attributes) {
        typeService.deleteType(id);
        attributes.addFlashAttribute("message", "删除成功");
        return "redirect:/admin/types";
    }
  • RedirectAttributes 重定向 携带数据 
    RedirectAttributes是SpringMVC3.1版本之后出来的一个新功能,专门用于重定向之后还能带参数跳转的的工具类。
    

2: 模糊查询实现 局部刷新(thymeleaf)

前端ajax

  function loaddata() {
      $("#table-container").load(/*[[@{/admin/blogs/search}]]*/"/admin/blogs/search",{
        title : $("[name='title']").val(),
        typeId : $("[name='typeId']").val(),
        page : $("[name='page']").val()
      });
    }

后端返回

admin/blogs :: blogList

异步刷新博客详细说明

文章的模糊查询动态sql:

 <select id="listSearchBlog" resultMap="IndexBlogMap" parameterType="com.sx.queryvo.SearchBlog">
        select b.id, b.title, b.recommend, b.published, b.update_time, t.id, t.name
        from myblog.t_blog b,
        myblog.t_type t
        <where>
            <if test="1==1">
                b.type_id=t.id
            </if>
            <if test="typeId != null">
                and type_id=#{typeId,jdbcType=BIGINT}
            </if>
            <if test="title != null">
                and title like concat('%',#{title,jdbcType=VARCHAR},'%')
            </if>
        </where>
    </select>

五:博客前端页面的显示:

1.首页的业务分析:

  • 最新文章:

创建一个dto用来接受最新文章的数据

package com.sx.queryvo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

/**
 * @author SX
 * @version 1.0
 * @date 2020/11/10 17:56
 * 最新博客信息返回的pojo
 * 显示博客信息外,还需要显示分类、作者等信息,所以还需要定义分类名称和用户名、用户头像属性
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LatestBlog {
    //博客信息
    private Long id;
    private String title;
    private String firstPicture;
    private Integer views;
    private Integer commentCount;
    private Date updateTime;
    private String description;
    //分类名称
    private String typeName;
    //用户名
    private String nickname;
    //用户头像
    private String avatar;


}

 <select id="listLatestBlog" resultType="com.sx.queryvo.LatestBlog">
        select b.id,
               b.title,
               b.first_picture,
               b.views,
               b.comment_count,
               b.update_time,
               b.description,
               t.`name` as typeName,
               u.nickname,
               u.avatar
        from myblog.t_blog b,
             myblog.t_user u,
             myblog.t_type t
        where b.type_id = t.id
          and b.user_id = u.id
    </select>
  • 推荐文章:

创建一个dto用来接受推荐文章的数据

package com.sx.queryvo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author SX
 * @version 1.0
 * @date 2020/11/10 17:59
 * 接受推荐博客的pojo
 * 推荐只要显示博客标题、首图信息
 * boolean类型的变量recommend  是否被推荐
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class RecommendedBlog {
    private Long id;
    private String title;
    private String firstPicture;
    private boolean recommend;

}

 <select id="listRecommendedBlog" resultType="com.sx.queryvo.RecommendedBlog">
        select b.id, b.title, b.first_picture, b.recommend
        from myblog.t_blog b
        where b.recommend = true
    </select>
  • 博客的信息的统计

    文章总数

    访问总数

    评论总数

    留言总数

  • 模糊查询 查询 文章

2:博客详情页显示

业务分析

当点进去一篇博客的时候

访问量+1

博客信息数据更新

3:评论

  • 创建entity
package com.sx.entity;

import com.sx.queryvo.DetailedBlog;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @author SX
 * @version 1.0
 * @date 2020/11/7 14:21
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Comment {
    private Long id;
    private String nickname;
    private String email;
    private String content;
    //头像
    private String avatar;
    private Date createTime;

    private Long blogId;
    //父节点的id
    private Long parentCommentId;
    //父节点的名字
    private String parentNickname;
    //回复评论
    private List<Comment> replyComments = new ArrayList<>();
    private Comment parentComment;
    private boolean adminComment;
    private DetailedBlog blog;
}

注意:

查询 所有评论的业务 逻辑

parentCommentId 当为-1时是父节点

然后根据id 循环 查询 出子节点

放到 replyComments集合中

  @Override
    public List<Comment> listCommentByBlogId(Long BlogId) {
        List<Comment> comments = iCommentMapper.findByBlogIdParentIdNull(BlogId, Long.parseLong("-1"));
        if (!comments.isEmpty()) {
            for (int i = 0; i < comments.size(); i++) {
                Long id = comments.get(i).getId();
                String Parentnickname1 = comments.get(i).getNickname();
                List<Comment> childComments = iCommentMapper.findByBlogIdParentIdNotNull(BlogId, id);
                //查询出子评论
                findTwoComment(BlogId, childComments, Parentnickname1);
                comments.get(i).setReplyComments(tempReplys);
                tempReplys = new ArrayList<>();
            }

        }
        return comments;
    }

    public void findTwoComment(Long BlogId, List<Comment> comments, String parentnickname1) {
        if (!comments.isEmpty()) {
            for (int i = 0; i < comments.size(); i++) {
                String parentnickname = comments.get(i).getNickname();
                comments.get(i).setParentNickname(parentnickname1);
                tempReplys.add(comments.get(i));
                Long childId = comments.get(i).getId();
//查询二级评论
                ReplyComment(BlogId, childId, parentnickname);
            }
        }
    }

    //查看回复
    public void ReplyComment(Long blogId, Long childId, String parentnickname1) {
        List<Comment> replyComment = iCommentMapper.findByBlogIdChildIdNotNull(blogId, childId);
        if (!replyComment.isEmpty()) {
            for (int i = 0; i < replyComment.size(); i++) {
                Long replyId = replyComment.get(i).getId();
                String parentnickname = replyComment.get(i).getNickname();
                replyComment.get(i).setParentNickname(parentnickname1);
                tempReplys.add(replyComment.get(i));
                ReplyComment(blogId, replyId, parentnickname);
            }
        }
    }

4:前端分类显示

    /**
     * 前端分类的显示
     *
     * @return
     */
    @GetMapping("/types/{id}")
    public String queryType(@RequestParam(defaultValue = "1", value = "pageNum") Integer pageNum, @PathVariable Long id, Model model) {
        List<Type> types = iTypeService.listAllType();
        //id为-1表示从首页导航栏点击进入分类页面
        if (id == -1) {
            id = types.get(0).getId();
        }
        model.addAttribute("types", types);
        List<LatestBlog> blogs = iBlogService.listGetBlogByTypeId(id);
        PageHelper.startPage(pageNum, 10000);
        PageInfo<LatestBlog> pageInfo = new PageInfo<>(blogs);
        model.addAttribute("pageInfo", pageInfo);
        model.addAttribute("activeTypeId", id);
        return "types";
    }

BUG

前端 页面 进行重构 大改造

级联删除 在 JPA中可以 级联删除

但是在 Mybatis中需要自己掌管业务逻辑

标签删除

博客删除

分类删除

业务是否都得放到service中呢?

mysql:的免安装问题

接受数据中文,插入到数据库就乱码

解决方法:
修改 安装文件中的 my.ini



[mysqld]
#安装目录
basedir=S:\environment\mysql-5.7.19-winx64
datadir=S:\environment\mysql-5.7.19-winx64\data\
port=3306
#skip-grant-tables
#basedir表示mysql安装路径
#datadir表示mysql数据文件存储路径
#port表示mysql端口
#skip-grant-tables表示忽略密码

character-set-server=utf8

# 设置mysql客户端默认字符
character-set-server=utf8

普通for循环

  @Override
    public List<Comment> listCommentByBlogId(Long BlogId) {
        List<Comment> comments = iCommentMapper.findByBlogIdParentIdNull(BlogId, Long.parseLong("-1"));
        if (!comments.isEmpty()) {
            for (int i = 0; i < comments.size(); i++) {
                Long id = comments.get(i).getId();
                String Parentnickname1 = comments.get(i).getNickname();
                List<Comment> childComments = iCommentMapper.findByBlogIdParentIdNotNull(BlogId, id);
                //查询出子评论
                findTwoComment(BlogId, childComments, Parentnickname1);
                comments.get(i).setReplyComments(tempReplys);
                tempReplys = new ArrayList<>();
            }

        }
        return comments;
    }

    public void findTwoComment(Long BlogId, List<Comment> comments, String parentnickname1) {
        if (!comments.isEmpty()) {
            for (int i = 0; i < comments.size(); i++) {
                String parentnickname = comments.get(i).getNickname();
                comments.get(i).setParentNickname(parentnickname1);
                tempReplys.add(comments.get(i));
                Long childId = comments.get(i).getId();
//查询二级评论
                ReplyComment(BlogId, childId, parentnickname);
            }
        }
    }

    //查看回复
    public void ReplyComment(Long blogId, Long childId, String parentnickname1) {
        List<Comment> replyComment = iCommentMapper.findByBlogIdChildIdNotNull(blogId, childId);
        if (!replyComment.isEmpty()) {
            for (int i = 0; i < replyComment.size(); i++) {
                Long replyId = replyComment.get(i).getId();
                String parentnickname = replyComment.get(i).getNickname();
                replyComment.get(i).setParentNickname(parentnickname1);
                tempReplys.add(replyComment.get(i));
                ReplyComment(blogId, replyId, parentnickname);
            }
        }
    }

增强for循环

@Override
    public List<Comment> listCommentByBlogId(Long blogId) {
        //查询出父节点
        List<Comment> comments = commentDao.findByBlogIdParentIdNull(blogId, Long.parseLong("-1"));
        for (Comment comment : comments) {
            Long id = comment.getId();
            String parentNickname1 = comment.getNickname();
            List<Comment> childComments = commentDao.findByBlogIdParentIdNotNull(blogId, id);
//            查询出子评论
            combineChildren(blogId, childComments, parentNickname1);
            comment.setReplyComments(tempReplys);
            tempReplys = new ArrayList<>();
        }
        return comments;
    }

    private void combineChildren(Long blogId, List<Comment> childComments, String parentNickname1) {
//        判断是否有一级子评论
        if (childComments.size() > 0) {
//                循环找出子评论的id
            for (Comment childComment : childComments) {
                String parentNickname = childComment.getNickname();
                childComment.setParentNickname(parentNickname1);
                tempReplys.add(childComment);
                Long childId = childComment.getId();
//                    查询出子二级评论
                recursively(blogId, childId, parentNickname);
            }
        }
    }

    private void recursively(Long blogId, Long childId, String parentNickname1) {
//        根据子一级评论的id找到子二级评论
        List<Comment> replayComments = commentDao.findByBlogIdAndReplayId(blogId, childId);

        if (replayComments.size() > 0) {
            for (Comment replayComment : replayComments) {
                String parentNickname = replayComment.getNickname();
                replayComment.setParentNickname(parentNickname1);
                Long replayId = replayComment.getId();
                tempReplys.add(replayComment);
                recursively(blogId, replayId, parentNickname);
            }
        }
    }

测试粗线

end

评论