课程设计基于Spring Boot+Semantic ui的个人博客系统的设计与实现

项目背景:

本项目是基于互联网软件开发技术搭建的一款个人博客系统,因为自己十分喜欢创作,比如写一些开源项目,写一些博客笔记,虽然网络上有许多大型博客系统,但是一直想写一个属于自己专有的博客系统,比如可以上传一些自己的知识笔记,上传一些自己的生活照片等,于是便花了三周的时间设计了这款个人博客,该博客的前端部分用的是一款比较轻量级的框架Semantic ui,后端用的SpringBoot,数据库用的是mysql。
关键词:个人博客系统;Spring Boot;Semantic ui;

1 项目展示

1.1前端展示

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.2后台管理展示

在这里插入图片描述
在这里插入图片描述
41 5儆

2 技术背景

2.1 框架与架构技术

本系统后端采用的是一款主流的轻量级框架Spring Boot,Spring Boot使用“约定优于配置”的理念,这使得开发人员只需要进行一些简单的配置即可快速的构建一个基础的项目,而且更加方便管理依赖,功能更加丰富,性能更加优越,使开发者更加专注于业务的开发。
本系统后端持久层采用的是MyBatis。MyBatis是一款优秀的持久层框架,Mybatis是一个半自动的ORM持久层框架,内部封装了JDBC。作为开发者只需要关注sql语句本身。Mybatis是通过xml或注解的方式将需要执行的各种statement配置起来。通过Java对象和statement中的sql动态参数映射生成最终执行的sql语句,最终由Mabtais框架执行sql并将结果映射为Java对象并返回,提高开发效率。
本系统前端用的是一款Semantic UI,Semantic UI是一款语义化的UI框架,代码可读性与可理解性很强,界面简洁美观,与bootstrap风格接近,基于jquery,适用响应式布局,提供一些基本模板,兼容Firefox、Chrome、safari,IE 10+等浏览器。

2.2 数据库技术

本系统数据存储方面采用的是关系型数据库MySQL,由于MySQL数据库体积小、速度快、总体拥有成本低、开放源代码,其有着广泛的应用,一般中小型网站的开发都选择MySQL作为数据库。

2.3 开发环境及版本

本系统的后端项目开发环境基于Java 1.8.0_91;构建工具IDEA版本:2019.03;Spring Boot项目版本:2.2.6.RELEASE;Mybatis版本:2.2.2;数据库MySQL版本:8.0;

3 项目设计

3.1 设计思路

该系统共分为两部分:第一部分为前端展示部分,也就是所有用户可以浏览观看的界面,对于前端的处理分类,标签,归档,可以方便的检索需要的知识博客,因为在写博客的时候需要进行插入各种图片信息,为了方便管理,我单独的维护了图片模块,对于首页的处理,我做的相当于一个集成的页面,中间部分按时间的降序排列分页展示所有对博客,左侧边栏有分类和标签以及其对应的文章数量。在首页头部导航栏中有可以进行模糊查询的搜索框。
在这里插入图片描述
第二部分为后台管理部分,后台管理部分需要账号和密码,管理部分分为博客内容管理,标签管理,分类管理,图片管理,以及访问着对文章进行评论的评论管理和用于文章图片的图片管理,对每一部分的管理都涉及了增,删,改等功能。
在这里插入图片描述

3.2 数据库设计

3.2.1 实体类设计

对于一篇博客包含的内容应该有文章内容,发布时间,文章的类型和包含着哪一些标签内容,文章的首图展示,以及是否可以评论,赞赏,是否为原创,浏览量,文章的标题和概述等,文章类型的处理我设置了四个字段分别是一个自增的主键id和类型名称,该类型下的文章书数量和类型简介标签和类型字段大致一样只是一篇文章中会包含着多个类型。对于文章评论的处理我做了一个单独的处理,其中每一条评论的内容应该包含着评论人的信息比如姓名,邮箱等标记评论人身份的信息和评论的时间和内容,又因为评论内容是单独维护的所以每条评论中应该有对应者的博客id,为了区分博主评论和回复评论我又增设了两个字段,父评论和管理员评论。
在这里插入图片描述

3.2.2 数据库关系设计

数据模型是对现实世界数据特征的抽象。概念模型常用的一种表示方法:实体-联系法,而实体是客观存在并可相互区别的事物。本项目中用到的实体如下:
(1) 博客实体包括博客ID,博客标题,博客摘要,发布时间等内容,如下图所示:
在这里插入图片描述
(2) 分类实体包括分类ID,分类名,对应的博客数量和分类简介如下图所示:

在这里插入图片描述
(3) 标签实体包括标签ID,标签名,标签对应的博客数量,实体关系如下图:
在这里插入图片描述
(4) 评论实体包括评论ID,评论内容,评论者的姓名,评论者的邮箱,评论时间,父级评论(是否是回复评论),是否是管理员评论。实体图如下:
在这里插入图片描述
(5) 图片实体包括图片的图片ID,图片的路径,图片的描述,图片的发布时间,实体图如下:
在这里插入图片描述
(6) E-R图是用来描述现实世界的概念模型的,E-R图主要由实体、属性、联系三部分组成。本系统的E-R图如图所示:
在这里插入图片描述

3.2.3 数据库表设计

表1 博客信息表
在这里插入图片描述
表2 类型信息表
在这里插入图片描述
表3 标签信息表
在这里插入图片描述
表4 评论信息表
在这里插入图片描述

3.3 系统架构设计

本系统是基于Spring Boot + semantic UI + thymeleaf模板基于MVC架构的一个个人博客系统,前端使用semantic UI进行数据渲染,后端基于Spring Boot构建的项目,用于提供数据接口,安全方面使用了MD5加密对身份进行验证(MD5加密手段主要是为了防止数据库信息的泄露或者非法的访问等一些数据库安全问题)。
后台管理系统在session域中没有检测到已有登录中的用户时会对非法的请求进行拦截,对拦截的请求进行处理,然后跳转到登录界面,用户登录成功之后每一次发起的post或者get请求都会经过拦截器的放行然后跳转到相对应的controller层进行相应的处理@RequestMapping()为映射的路径,Controller层为控制层,负责业务流程控制,Controller层再调用Service层接口中的方法来获取数据,然后将封装好的数据以JSON的形式返回给前端。Service层为业务层,负责业务的实现,事务的控制,Service层再调用Mapper层接口中的方法来获取数据。Mapper层为持久层,负责与数据库进行交互。系统的工作原理图如图4所示:
在这里插入图片描述

4系统详细设计

4.1 登录

登录功能的实现是通过拦截器+session的方式实现的,管理员进入登录的界面之后输入账号和密码,点击登录按钮请求来到后端的拦截器,拦截器不会对登录的请求进行拦截,通过拦截器之后进入对应的Controller层处理管理员登录,Controller会首先调用Service层的login方法检索数据库中是否有对应记录的账号和密码为了安全我们对密码进行一个MD5加密,如果查询结果为null说明账号密码不正确就向前端返回一个错误提示,然后将页面重定向到登录界面,如果查询结果不为空就将管理员信息存放到session域中,然后将页面跳转到后台系统首页,对于注销功能的实现,通过访问一个controller然后session域中的管理员信息进行remove然后将页面转向到登录页。如图所示。
在这里插入图片描述
在这里插入图片描述
拦截器核心代码:

/** 处理逻辑登录拦截 对每次的请求增加一个拦截判断session域中有没有id
 *  没有的话将界面进行重定向到登录界面*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    if(request.getSession().getAttribute("user") == null) {
        response.sendRedirect("/admin");
        return false;
    }
    return true;
}
Controller层的登录核心代码:
@PostMapping("/login")
public String login(@RequestParam String username,
                    @RequestParam String password,
                    HttpSession session,
                    RedirectAttributes attributes) {
    User user = userService.checkUser(username, password);
    if(user != null) {
        user.setPassword(null);
        session.setAttribute("user", user);
        return "admin/index";
    }
    else {
    //重定向之后跳转并携带参数
        attributes.addFlashAttribute("message", "用户名或密码错误!");
        return "redirect:/admin";
    }
}

在这里插入图片描述

4.2 博客管理

博客管理包括根据博客的关键信息进行搜索,关键信息包括标题分类和发布状态,可以对博客进行新增和修改以及删除功能,对于前端的数据我们用map集合进行接收因为可能存在着空字段所以我们可以做一个utils类写一个专用处理map集合的方法然后返回我们所需的信息。
新增博客核心代码:

@PostMapping("/blogs")
public String post(@RequestParam Map<String,Object> map, RedirectAttributes attributes){
    int integer=0;
    Integer id = TypeTrans.TransId(map);
    Blog blog = blogService.selectById(id);
    if (blog!=null){
        //执行更新操作
        Blog transBlog = TypeTrans.TransBlog(map);
        transBlog.setId(id);
        integer = blogService.updateBlogById(transBlog);
    }else {
         integer = blogService.saveBlog(TypeTrans.TransBlog(map));
    }
    if (integer==1){
        attributes.addFlashAttribute("message", "操作成功!");
    }else {
        attributes.addFlashAttribute("message", "操作失败");
    }
    return REDIRECT_LIST;
}

Map处理方法就不一一进行展示了核心思路就是进行数据转换将原数据换为我们所需要的数据。这里展示一个处理id的核心代码:

public static Integer TransId(Map<String,Object> map){
    Integer transId=null;
    String id = (String) map.get("id");
    if (id==null||"".equals(id)){
        return null;
    }else{
        return Integer.parseInt(id);
    }
}

将博客内容保存到数据库中使用的xml语句为:
在这里插入图片描述
后台博客的搜索为多条件查询,搜索的条件包括标题,类型,推荐状态,以及是否发布等条件进行单独搜索或者集合搜索,我们处理博客类型字段和标签字段的时候是将所对应的id保存进了这条信息中,但是我们在进行查询的时候应该同时将类型和标签的实体查询出来然后再封装进一个新的BlogCombination中,为了使系统在进行展示或者查询的时候能够显示出记录数,或者分页操作我们应该继续封装一个实体类,这个类中应该有当前的记录数,起始页,每一页的记录数以及封装的查询数据和当前页。
博客搜索的核心代码为如下:

@Override
public Page<BlogCombination> showListByCondition(Blog blog, int start, int limit) {
    PageTool<BlogCombination> tool = new PageTool<>();
    Page<BlogCombination> page = tool.list(start, limit, mapper.CountByCondition(blog));
    List<BlogCombination> blogCombinations = tranTypeInBlog(blog,(page.getCurrentPage()-1)*limit, limit);
    page.setList(blogCombinations);
    return page;
}

封装pageTool工具方法,这个方法接收三个参数前端传入的当前页和每一页限制的记录数以及从后端传来的总数据记录。

public Page<T> list(int start, int limit, int totalCount){
    if (start<=0){
        start=1;
    }
    Page<T> page = new Page<>();
    page.setTotalCount(totalCount);
    int totalPage=(totalCount%limit==0)?totalCount/limit:(totalCount/limit)+1;
    page.setTotalPage(totalPage);
    if (start>totalPage){
        start=totalPage;
        if (start<=0){
            start=1;
        }
    }
    /**获取list集合的方法*/
    page.setCurrentPage(start);
    return page;
}

类型转换的核心代码为:

public  List<BlogCombination> tranTypeInBlog(Blog blog,int start,int limit){
    List<Blog> blogs = mapper.selectListByCondition(blog, start, limit);
    List<BlogCombination> blogCombinationList=new ArrayList<>();
    Type type=null;
    for (Blog blog1 : blogs){
        type=typeMapper.getType(blog1.getType());
        BlogCombination blogCombination = new BlogCombination(blog1, type);
        blogCombinationList.add(blogCombination);
    }
    return blogCombinationList;
}

博客编辑功能的实现:根据id值去搜索这个blog然后将这个blog的数据进行回显,核心代码如下:

@GetMapping("/blogs/{id}/input")
public String editInput(@PathVariable String id, Model model){
    Blog blog = blogService.selectById(Integer.parseInt(id));
    model.addAttribute("types",typeService.getList());
    model.addAttribute("tags",tagService.showAllTagS());
    model.addAttribute("blog",blog);
    return INPUT;
}

在这里插入图片描述
前端展示首页中最主要的操作我认为类与类之间的整合操作即将分类和标签和评论内容封装在一个实体blog中。代码如下:

@Override
public Page<BlogCombination> showListBySearch(Map<String, Object> map, int start, int limit) {
    PageTool<BlogCombination> tool = new PageTool<>();
    List<BlogCombination> blogCombinationList=new ArrayList<>();
    Type type=null;
    Page<BlogCombination> page = tool.list(start, limit, mapper.countByVague(map));
    List<Blog> blogs = mapper.listByVague(map, (page.getCurrentPage() - 1) * limit, limit);
    for (Blog blog : blogs) {
        type=typeMapper.getType(blog.getType());
        List<Tag> tags = tagMapper.tagListByIds(TypeTrans.transTags(blog.getTags()));
        BlogCombination blogCombination = new BlogCombination(blog, type,tags);
        blogCombinationList.add(blogCombination);
    }
    page.setList(blogCombinationList);
    return page;
}

对标签进行解析的核心代码如下:

/**
 *
 * @param tags 包含着tag id的字符串
 * @return 返回id的list集合
 */
public static List<Integer> transTags(String tags){
    ArrayList<Integer> list = new ArrayList<>();
    int trans_id;
    String[] tag_s = tags.split(",");
    //我们现在还需要一步操作将tag转化为int类型
    for (String tag : tag_s) {
        trans_id = Integer.parseInt(tag);
        list.add(trans_id);
    }
    return list;
}

Controller层代码:

@GetMapping("/search")
public String index_search(@RequestParam Map<String, Object> map,Model model,HttpSession session,String page){
    //将搜索数据封装在数据模型中
    model.addAttribute("page",blogCombinationService.showListBySearch(map,TypeTrans.TransPage(page),7));
    User user = (User) session.getAttribute("user");
    model.addAttribute("user",user);
    return "search";
}

在这里插入图片描述

4.3 分类管理

对于博客的分类管理其实是一个比较简单的模块,一篇博客只对应着一个类型而一个类型可以对应着多篇博客,博客和类型之间是一个一对多的关系,我们将类型单独管理更有利于系统维护,对分类管理我们可以进行的操作包括曾加一个分类,对分类名称进行修改,以及将错误的分类进行删除等一些简单的操作,在前端展示页中我们还可以向浏览者展示出该博客下的所有类型,以及在该类型下所对应的博客数量,点击某个类型我们展示出该类型下的所有博客。核心代码如下:
新增分类也就是相当于做了一个insert操作代码如下:

@PostMapping("/types")
public String post(@Valid Type type,BindingResult result, RedirectAttributes attributes) {
    /** 实现逻辑
     * 1 判断是否已经存在了该数据
     * 2 返回消息控制跳转
     * 3 插入失败/成功返回具体信息*/
    Type type1 = service.checkByName(type.getName());
    if(type1 != null) {
        result.rejectValue("name", "nameError", "不能添加重复的分类!");
    }
    if(result.hasErrors()) {
        return "admin/types-input";
    }
    Integer integer = service.saveType(type);
    if (integer!=1){
        attributes.addFlashAttribute("message", "新增分类失败!");
    }else{
        attributes.addFlashAttribute("message", "新增分类成功!");
    }
    return "redirect:/admin/types";
}

Update和操作和delete操作select操作较为简单就不进行一一展示了。
这里展示一种比较流行的数据传输风格restful风格,代码如下:

@GetMapping("/types/{id}/input")
public String editInput(@PathVariable int id, Model model) {
    model.addAttribute("type",service.getTypeById(id));
    return "admin/types-input";
}

在这里插入图片描述
分类管理的前端展示其实就是将全体的类型和对应着相关类型的博客同时展示出来,代码展示如下:

@GetMapping("/types/{id}")  //当前活跃的type这里的id为long类型
public String TypeShow(@PathVariable(value = "id") Long id, Model model, HttpSession session,String page){
    HashMap<String, Object> map = new HashMap<>();
    //这里有一个细节当转入type界面时 我们应该让type=-1
    //由于list集合是有序的所以我们需要将type排序输出 做一个激活效果
    List<Type> list = service.getList();
    if (id==-1){
        id=list.get(0).getId();
    }
    map.put("id", id);
    Page<BlogCombination> page_type = blogCombinationService.showListBySearch(map, TypeTrans.TransPage(page), 5);
    model.addAttribute("page_type",page_type);
    model.addAttribute("types",list);
    model.addAttribute("user",session.getAttribute("user"));
    model.addAttribute("activeTypeId", id);
    return "types";
}

在这里插入图片描述

4.4 标签管理

标签的管理过程其实和分类的管理差不多,只不过一篇博客可以有许多个标签而一个标签也可以对应着许多篇博客,所以博客和标签之间是多对多的关系,因为在存储博客的标签的时候是将标签的id存储进去的,存储的形式包含着多个标签的id的字符串,所以我们在进行处理的时候应该做一个方法来处理这个字符串。核心代码如下:

@GetMapping("/tags/{tag}")
public String showByTag(Model model, HttpSession session, @PathVariable(value = "tag") Long tag,String page){
    Map<String, Object> map = new HashMap<>();
    List<Tag> tags = tagService.showAllTagS();
    if (tag==-1){
        tag=tags.get(0).getId();
    }
    map.put("tag",tag);
    Page<BlogCombination> page_type = blogCombinationService.showListBySearch(map, TypeTrans.TransPage(page), 5);
    model.addAttribute("page_type",page_type);
    model.addAttribute("user",session.getAttribute("user"));
    model.addAttribute("tags",tags);
    model.addAttribute("activeTagId",tag);
    return "tags";
}

在这里插入图片描述

4.5 归档管理

博客的归档是按照博客的发布日期进行分组展示,这个模块还是比较简单的,比较繁琐的地方就是如何将时间段和这个时间段的文章组合起来,我的做法是通过Map集合首先对blog集合进行遍历操作,将解析了的日期和map中的key键值作比较,当前的blog于遍历到的map键值相等就将该该篇博客加进list集合中,如果不存在重新add一个map。核心代码如下:

@GetMapping("/archives")
public String archiveShow(Model model){
    model.addAttribute("blogCount",blogService.countAllBlog());
    model.addAttribute("archiveMap", ArchivesUtil.handlerArchives(blogService.showAllBlog()));
    return "archives";
}
public static Map<String, List<Blog>> handlerArchives(List<Blog> blogs){
    HashMap<String, List<Blog>> map = new HashMap<>();
    for (Blog blog : blogs) {
        //标记该blog的年份是否在map中存在
        boolean flag=false;
        for (String s : map.keySet()) {
            /**
             * 这里应该写一个工具类将日期的字符串转化为我们所需要的格式
             * 思路:增加一个新map和增加map中value 的list的数量 将时间进行转化之后再进行对比
             */
            if (TransDateFrom(blog.getDate()).equals(s)){
                map.get(s).add(blog);
                flag=true;
            }
        }
        if(!flag){
            List<Blog> blogList=new ArrayList<>();
            blogList.add(blog);
            map.put(TransDateFrom(blog.getDate()),blogList);
        }
    }
    return map;
}

下面这个代码是将该条记录的日期进行转换:

 Map<String,String> map=new HashMap<>();
    map.put("-01","年1月");
    map.put("-02","年2月");
    map.put("-03","年3月");
    map.put("-04","年4月");
    map.put("-05","年5月");
    map.put("-06","年6月");
    map.put("-07","年7月");
    map.put("-08","年8月");
    map.put("-09","年9月");
    map.put("-10","年10月");
    map.put("-11","年11月");
    map.put("-12","年12月");
    String transDate;
    String real_mouth = null;
    if (str==null||"".equals(str)){
        transDate="时间不详";
        return transDate;
    }
    //先对str进行一个截取操作; 字符串的拼接加连接
    //2022-04==>2022年4月
    //-01 年1月  -02 年2月  如果截取的片段在map中就将其转化输出
    String year=str.substring(0, 4);
    String mouth=str.substring(4,7);
    if (map.containsKey(mouth)){
        real_mouth=map.get(mouth);
    }
    return year+real_mouth;
}

在这里插入图片描述

4.6图片管理

为什么要做一个图片管理那?在一篇博客中有许多流程图和许多代码的展示图等,但是如果将图片直接存进数据库中会增加数据库的负担因为图片使以二进制流的形式存进数据库中的每次进行数据读取的时候都会涉及到数据渲染等问题,影响系统的效率,而且也不利于数据维护,所以我们专门维护一个图片管理模块,这个模块在数据库中存储的是图片的路径,在使用的时候我们只需要将图片的路径引入即可。在数据传输层面上因为图片是以文件形式进行传输的区别于其它的Json数据,所以我们在前端发送数据和在controller层接收数据的时候都应该声明这是一个文件。对于图片的存储还有个一个应该注意点前端传来的图片的图片名有时候是汉字有时候是一些特殊符号,但是这对于系统却并不友好所以我们需要对文件名进行重新解析。核心代码如下:

@RequestMapping("/savePicture")
public String savePicture(@RequestParam("file")MultipartFile file, RedirectAttributes attributes, String summary) throws IOException {
    try {
        if (file.isEmpty()||file.getSize()>MAX_SIZE){
            /**这里我们不妨做一个异常处理基类*/
            throw  new FileException("文件不可一为空");
        }
        if (!PICTURE_TYPE.contains(file.getContentType())){
            throw  new FileException("文件类型不对");
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    /**我们已经确定有这个文件了所以就不需要进行创建了*/
    File dir = new File(upLoadPath);
    if (!dir.isDirectory()) {
        //递归生成文件夹
        dir.mkdirs();
    }
    /**说白了后面的操作就是字符串的截取和重新拼接功能*/
    String suffix = "";
    //获取原来文件的名称
    String originalFilename = file.getOriginalFilename();
    assert originalFilename != null;//加一个断言就是保证传过来的字符串一定不为空
    int beginIndex = originalFilename.lastIndexOf( ".");
    if (beginIndex > 0) {
        suffix = originalFilename.substring(beginIndex);
    }
    String filename = UUID.randomUUID() + suffix;
    File dest = new File(dir, filename);// dir为父级目录 filename为子级目录
    file.transferTo(dest);//将上传的file中的文件中的数据放到dest指向的文件中
    /**调用service层传递参数*/
    Picture picture = new Picture();
    String path="/pictureLibrary/"+filename;
    picture.setPath(path);
    picture.setSummary(summary);
    Integer integer = pictureService.savePicture(picture);
    if (integer!=1){
        attributes.addFlashAttribute("message","请检查文件类型,不支持超过10M的高清图片");
    }
    attributes.addFlashAttribute("message","上传成功");
    return "redirect:/picture";
}

为了保证传过来的文件是一个图片的格式我们定义一个静态块,每次加载这个类的时候就会对文件类型进行检测,如果不是图片类型就抛出一个文件异常,代码如下:

static {
    PICTURE_TYPE.add("image/jpeg");
    PICTURE_TYPE.add("image/png");
    PICTURE_TYPE.add("image/bmp");
    PICTURE_TYPE.add("image/gif");
}

在这里插入图片描述
当已有管理员登录时,我们点开图片库会有删除操作,也会有对应的图片路径,为了图片的引用方便,但是当没有登录的时候只能看到的是图片和它的描述信息。
在这里插入图片描述
没有管理员登录时展示如下:
在这里插入图片描述
为了能加图片实时的在通过路径访问到对应目录下的图片内容我做了一个映射器。来实时展示图片代码如下:

@Configuration
public class BlogConfig implements WebMvcConfigurer {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        String property = PathUtil.transPath();
        StringBuilder builder = new StringBuilder(property);
        builder.replace(2,3,"/");
        //前端访问upload下的内容就会转到下面的目录下
        registry.addResourceHandler("/pictureLibrary/**")
                .addResourceLocations("file:G:/Guoblog/src/main/resources/static/pictureLibrary/");
//        registry.addResourceHandler("/tmp/tomcat.8080.8756523073464545020/work/Tomcat/localhost/ROOT/**")
//                .addResourceLocations("file:/home/www/pictureLibrary");
    }
}
@RequestMapping("/delete/{id}")
public String deletePicture(@PathVariable int id,RedirectAttributes attributes){
    //给前端一个提示信息
    Integer integer = pictureService.deletePicture(id);
    if (integer==1){
        attributes.addFlashAttribute("message", "删除成功!");
    }else {
        attributes.addFlashAttribute("message", "删除失败!");
    }
    return "redirect:/picture";
}

4.7 评论管理

一篇博客有多个评论,但是对于一个评论信息只对应着一篇博客,所以博客和评论内容之间的是一对多的关系,对于评论我的处理办法是用map集合来做,将该篇博客下的父级评论和子级评论分别存储在不同key的map中,然后将数据封装进model中发送给前端,然后前端根据子级评论的id找到其对应的父级评论然后将数据渲染在评论区。核心代码如下:

@Override
public Map<String, List<Comment>> findCommentsByBlog(Integer id) {
    HashMap<String, List<Comment>> map = new HashMap<>(2);
    map.put("parents",commentMapper.findParentCommentsByBlogId(id));
    map.put("sons",commentMapper.findSonCommentsByBlogId(id));
    return map;
}
@RequestMapping("/comment")
public String insertComment(Comment comment, HttpSession session){
    if (session.getAttribute("user")==null){
        comment.setIsAdmin(0);
    }else {
        comment.setIsAdmin(1);
    }
    service.savaComment(comment);
    return "redirect:/blog/"+comment.getBlog();
}

展示子评论:

<div class="comment"  th:each="son,iter:${sons}" th:if="${son.parent==parent.id}">

在这里插入图片描述

5 系统测试

软件测试主要有两种测试方法,即黑盒测试和白盒测试。黑盒测试就是不需要你弄清楚软件内部的逻辑结构,只需要按照测试规格说明书的要求来测试软件是否能够正确的输入数据,正确的输出数据。白盒测试主要是通过弄清楚软件内部的逻辑结构,对软件进行逻辑路径的覆盖测试。由于本次毕业设计所开发的系统不是属于大型分布式系统,所以软件测试方法只需采用黑盒测试方法就能很好的评估和检测出系统存在的问题。

6 异常处理

在系统中可能会遇到各种各样的错误,比如插入一条已经存在的数据,上传文件时文件类型不对等一些可能会发生的错误,但是我们不能就因为这些小的异常或者小错误就让整个系统的工作停滞,所以我们需要做一个异常处理将异常抛出去,然后继续执行下面的代码,这样便不会影响整个系统的工作。核心代码如下:

public class FileException extends RuntimeException{
    public FileException() {
        super();
    }
    public FileException(String message) {
        super(message);
    }
public class ServiceException extends RuntimeException{
    public ServiceException() {
    }

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

7 总结反思

经过为期两周的课程设计我学会了很多,比如提高了自己了自己处理问题的能力,提高了自己的工程能力,学会了如何从零开始自己构建一个完整的项目到发布上线,在开始这个项目之前我一直不相信自己可以完成这份课程设计,因为他的难度确实不低,springBoot项目的实际运行,前端框架的使用,这些都是自己之前没有尝试过的,但是经历了这个项目,我觉得给我最大的感慨便是做人,做事要自信,要坚持,不要因为事情感到繁琐,困难就放弃,就像这个项目,我在做的过程总也遇到了很多的麻烦,比如处理数据交互那一部分让我做了好几天,在好几天的时间里我翻阅了很多文档,看了很多的开源项目,然后再去处理这一部分的问题时就发现问题变得容易了,再比如处理归档的那一部分,就用到了日期转换这部分涉及到map集合的运用而这一部分我之前学的很一般所以也让我困扰了一两天,然后我又用一两天的时间去补习了集合这部分知识,然后发现问题又变得简单了好多,所以好多同学说自己不会写项目,遇到问题也找不到解决办法,再网上也找不到答案,其实很多时候并不是我们找不到答案,只是我们有时知识掌握的不好,自己理解不了罢了,所以不要小看老师再课堂上讲的基础知识,很重要的。这个项目虽然有好几个地方的功能模块没有实现,比如图片更新问题,标签修改和删除功能,但是我认为这不是主要问题,在这个项目中我认为最大的问题便是规范问题,代码写的有点乱,类名乱起,注释写的不规范,项目整体的结构不够调理,然后我自己也反思了一下,因为在编写代码的过程中没有规范自己,没有用一些软件记笔记,然后就只用了一个txt文本记录了一下实现思路,所以代码看起来不是很调理,再一点就是代码部分复用率较高,可能时代码量过少,有些虽然自己也抽取了公共代码然后封装成了方法,但总是感觉还是不够简洁。总之还是代码量和项目量较少,在一个反思的地方就是前端需要继续加强,因为前端页面自己只处理了thymeleaf那一部分整体的那一部分使用了模板,主要是自己的前端审美不够好,自己的布局和样式些许有些low,其实这个博客还是没能够十全十美的做完,虽然已经发布上线了,首页,但是还有部分bug没有处理,比如图片上传问题,因为linux操作系统和window系统文件的管理是不一样,其实这一部分自己慢慢调试几次也能够实现出来,但是太耗时间了,修改源码,clear ,package打包操作,然后将jar包放到远程的linux系统中,然后远程运行jar包,有些繁琐我就先不处理了,等之后有时间了学好了linux再来说吧,总之项目虽然有些小瑕疵,再比如项目没有实现前后端分离,当后端或者系统宕机时,前端界面也会因而无法访问,但是这个项目对我之后做项目或处理问题起到了很好的指导作用。
项目地址:gitee地址

  • 2
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值