实训——个人博客

这次实训项目——个人博客,主要采用SpringBoot+semantic-ui
视频链接


@


一.页面编写

  页面采用Semantic-ui框架编写
  包括前台博客展示页面后台博客管理页面两部分

  • 前台博客展示:
    在这里插入图片描述
  • 后台博客管理页面
    在这里插入图片描述
    同时也引入了以下插件:
  • animate :引入动画
  • editotmd : Markdown插件
  • prism : 代码高亮
  • qrcode : 网址二维码生成
  • tocbot : 文章目录生成

部分截图:

首页
在这里插入图片描述

博客管理在这里插入图片描述
博客发布
在这里插入图片描述

二.项目搭建

1.创建项目

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

2.配置项目

修改thymeleaf:

 <properties>
    <thymeleaf.version>3.0.11.RELEASE</thymeleaf.version>
    <thymeleaf-layout-dialect.version>2.1.1</thymeleaf-layout-dialect.version>
</properties>

application.yml中更改模式:

  thymeleaf:
    mode: HTML  

配置数据库:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/blog?serverTimezone=UTC
    username: root
    password: guan37
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true

异常处理:

新建异常页面
在这里插入图片描述

package net.ty.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;

/**
 * @date 2021年06月16日 10:08
 */
@ControllerAdvice
public class ControllerExceptionHandler {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @ExceptionHandler(Exception.class)
    public ModelAndView exceptionHandler(HttpServletRequest request, Exception e) throws Exception {
        logger.error("Requst URL : {}, Exception : {}", request.getRequestURI(),e);

        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;
    }
}

在这里插入图片描述

3.页面设置

  将页面引入thymeleaf模板
  同时博客页面的头部和尾部都是一样的,这些共同的部分设置为_framents模板


_framents.html





    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title th:text="${title}">_fragments</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/semantic-ui/2.2.10/semantic.min.css">
    <link rel="stylesheet" href="../static/css/typo.css" th:href="@{/static/css/typo.css}">
    <link rel="stylesheet" href="../static/css/animate.css" th:href="@{/static/css/animate.css}">
    <link rel="stylesheet" href="../static/lib/prism/prism.css" th:href="@{/static/lib/prism/prism.css}">
    <link rel="stylesheet" href="./static/lib/prism/tocbot.css" th:href="@{/static/lib/tocbot/tocbot.css}">
    <link rel="stylesheet" href="../static/css/me.css" th:href="@{/static/css/me.css}">


    <!-- 导航 -->
    <nav th:fragment="menu(n)" class="ui inverted attached segment m-shadow-small m-padded-tb-mini">
        <div class="ui container">
            <div class="ui inverted secondary stackable menu">
                <h2 class="ui teal header item">Blog</h2>
                <a href="#" th:href="@{/}" class="m-item item m-mobile-hide" th:classappend="${n==1} ? 'active'"><i class="home icon"></i> 首页</a>
                <a href="#" th:href="@{/types/-1}" class="m-item item m-mobile-hide" th:classappend="${n==2} ? 'active'"><i class="idea icon"></i> 分类</a>
                <a href="#" th:href="@{/tags/-1}" class="m-item item m-mobile-hide" th:classappend="${n==3} ? 'active'"><i class="tags icon"></i> 标签</a>
                <a href="#" th:href="@{/archives}" class="m-item item m-mobile-hide" th:classappend="${n==4} ? 'active'"><i class="clone icon"></i> 归档</a>
                <a href="#" th:href="@{/about}" class="m-item item m-mobile-hide" th:classappend="${n==5} ? 'active'"><i class="info icon"></i> 关于</a>
                <div class="right m-item item m-mobile-hide">
                    <form name="search" action="#" th:action="@{/search}" method="post" target="_blank">
                        <div class="ui icon inverted transparent input m-margin-tb-tiny">
                            <input type="text" name="query" placeholder="Search...." th:value="${query}">
                            <i onclick="document.forms['search'].submit()" class="search link icon"></i>
                        </div>
                    </form>
                </div>
            </div>
        </div>
        <a href="#" class="ui menu toggle black icon button m-top-right m-mobile-show">
            <i class="sidebar icon"></i>
        </a>
    </nav>

    <!-- 底部 -->
    <footer th:fragment="footer" class="ui inverted vertical segment m-padded-tb-massive">
        <div class="ui center aligned container">
            <div class="ui inverted divided stackable grid">
                <div class="three wide column">
                    <div class="ui inverted link list">
                        <div class="item">
                            <img src="../static/images/QR_csdn.png" th:src="@{/static/images/QR_csdn.png}" class="ui rounded image" alt="" style="width: 110px">
                        </div>
                    </div>
                </div>
                <div class="three wide column">
                    <h4 class="ui inverted header">最新博客</h4>
                    <div id="newblog-container">
                        <div class="ui inverted link list" th:fragment="newblogList">
                            <a href="#" th:href="@{/blog/{id}(id=${blog.id})}" target="_blank" class="item m-text-thin" th:each="blog : ${newblogs}" th:text="${blog.title}">用户故事(User Story)</a>
                            <!--/*-->
                            <a href="#" class="item m-text-thin">用户故事(User Story)</a>
                            <a href="#" class="item m-text-thin">用户故事(User Story)</a>
                            <!--*/-->
                        </div>
                    </div>
                </div>
                <div class="three wide column">
                    <h4 class="ui inverted header">联系我</h4>
                    <div class="ui inverted link list">
                        <a href="#" class="item" th:text="#{index.email}">Email:tyaojoy@foxmail.com</a>
                        <a href="#" class="item" th:text="#{index.qq}">QQ:211163529</a>
                    </div>
                </div>
                <div class="seven wide column">
                    <h4 class="ui inverted header">Blog</h4>
                    <div class="ui inverted link list">
                        <p class="m-text-thin m-text-spaced m-opacity-mini">这是我的个人博客、会分享关于编程、写作、思考相关的任何内容,希望可以给来到这儿的人有所帮助...</p>
                    </div>
                </div>
            </div>
            <div class="ui inverted section divider"></div>
            <p class="m-text-thin m-text-spaced m-opacity-tiny">Copyright © 2021 Blog Designed by 观山奇</p>
        </div>
    </footer>

    <!-- script -->
    <th:block th:fragment="script">
        <script src="https://cdn.jsdelivr.net/npm/jquery@3.2/dist/jquery.min.js"></script>
        <script src="https://cdn.jsdelivr.net/semantic-ui/2.2.10/semantic.min.js"></script>
        <script src="../static/lib/prism/prism.js" th:src="@{/static/lib/prism/prism.js}"></script>
        <script src="../static/lib/qrcode/qrcode.min.js" th:src="@{/static/lib/qrcode/qrcode.min.js}"></script>
        <script src="../static/lib/tocbot/tocbot.min.js" th:src="@{/static/lib/tocbot/tocbot.min.js}"></script>
        <script src="https://cdn.jsdelivr.net/npm/jquery.scrollto@2.1.3/jquery.scrollTo.min.js"></script>
        <script>
            $('#newblog-container').load(/*[[@{/footer/newblog}]]*/"/footer/newblog");
        </script>
    </th:block>


注意:
  1.html标签需要加入xmlns:th="http://www.thymeleaf.org"
  2.静态文件需要加入th:src="@{/路径/文件名}"
  3.因为博客展示页面和后台管理页面需要引入的插件不一样,所有设置了两个模板

_fragment模板设置完成后,其他页面直接引用就可以了
在这里插入图片描述

4.实体类

根据老师设计的实体关系分析新建实体类生成数据库

新建实体类

在这里插入图片描述

jpa生成数据库

在这里插入图片描述

5.博客后台

 5.1 登录&首页页面

页面处理都一样分为:dao层,service层(接口,实现),控制器,页面处理

dao:dao/UserRepository

public interface UserRepository extends JpaRepository<user, long=""> {
    User findByUsernameAndPassword(String username, String password);
}

服务-接口:service/UserService

public interface UserService {
    User checkUser(String username, String password);
}

服务-实现:service/UserServiceImpl 实现

@Service
public class UserServiceImpl implements UserService{

    @Autowired
    private UserRepository userRepository;

    @Override
    public User checkUser(String username, String password) {
        User user = userRepository.findByUsernameAndPassword(username, MD5Utils.code(password));
        return user;
    }

控制器:web/admin/LoginController:

@Controller
@RequestMapping("/admin")
public class loginController {

    @Autowired
    private UserService userService;

    @GetMapping
    public String LoginPage() {
        return "admin/login";
    }

    @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";
        }
    }

    @GetMapping("/logout")
    public String logout(HttpSession session) {
        session.removeAttribute("user");
        return "redirect:/admin";
    }
}

修改页面:
添加表单验证:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

MD5加密:

正常情况密码应该以密文形式存储:

util/MD5Utils:

public class MD5Utils {

    /**
     * MD5加密类
     */
    public static String code(String str){
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(str.getBytes());
            byte[]byteDigest = md.digest();
            int i;
            StringBuilder buf = new StringBuilder();
            for (byte b : byteDigest) {
                i = b;
                if (i < 0)
                    i += 256;
                if (i < 16)
                    buf.append("0");
                buf.append(Integer.toHexString(i));
            }
            //32位加密
            return buf.toString();
            // 16位的加密
            //return buf.toString().substring(8, 24);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return null;
        }
    }

    public static void main(String[] args) {
        System.out.println(code("123456"));
    }
}

登录拦截:

为了防止用户未登录就进入后台管理页面,所以设置了登录拦截:

interceptor/LoginInterceptor:

public class LoginInterceptor implements HandlerInterceptor {

    @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;
    }
}

interceptor/WebConfig:

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/admin/**")
                .excludePathPatterns("/admin") //防止无限循环重定向进入admin
                .excludePathPatterns("/admin/login"); //表单提交不能被拦截

    }
}

在这里插入图片描述

 5.2 分类&标签管理页面

博客分类管理和标签管理基本一样,这里只展示标签

public interface TypeRepository extends JpaRepository<type,long> {
    Type findByName(String name);
}
public interface TypeService {

    Type saveType(Type type);

    Type getType(Long id);

    Type getTypeName(String name);

    Page<type> listType(Pageable pageable);

    List<type> listType();

    List<type> listTypeTop(Integer size);

    Type updateType(Long id, Type type);

    void deleteType(Long id);
}

sever实现:需要注意的是findone方法已经失效需要采用getbyid方法

@Service
public class TypeServiceImpl implements TypeService {

    @Autowired
    private TypeRepository typeRepository;

    @Transactional
    @Override
    public Type saveType(Type type) {
        return typeRepository.save(type);
    }

    @Transactional
    @Override
    public Type getType(Long id) {
        return typeRepository.getById(id);
    }

    @Override
    public Type getTypeName(String name) {
        return typeRepository.findByName(name);
    }

    @Transactional
    @Override
    public Page<type> listType(Pageable pageable) {
        return typeRepository.findAll(pageable);
    }

    @Override
    public List<type> listType() {
        return typeRepository.findAll();
    }

    @Override
    public List<type> listTypeTop(Integer size) {
        Sort sort = Sort.by(Sort.Direction.DESC, "blogs.size");
        Pageable pageable = PageRequest.of(0, size, sort);
        return typeRepository.findTop(pageable);
    }

    @Transactional
    @Override
    public Type updateType(Long id, Type type) {
        Type t = typeRepository.getById(id);
        if (t == null) {
            throw new NotFoundException("不存在该类型!");
        }
        BeanUtils.copyProperties(type,t);
        return typeRepository.save(t);
    }

    @Transactional
    @Override
    public void deleteType(Long id) {
        typeRepository.deleteById(id);
    }
}

控制器:页面处理步骤大致相同,只是每个页面功能不同,这其中设计重复验证,以及分页处理

@Controller
@RequestMapping("/admin")
public class TypeController {

    @Autowired
    private TypeService typeService;

    @GetMapping("/types")
    public String list(@PageableDefault(size = 5,sort = {"id"}, direction = Sort.Direction.DESC)
                                   Pageable pageable, Model model) {
        model.addAttribute("page", typeService.listType(pageable));
        typeService.listType(pageable);
        return "admin/types";
    }

    @GetMapping("/types/input")
    public String input(Model model) {
        model.addAttribute("type",new Type());
        return "admin/types-input";
    }

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

    @PostMapping("/types")
    public String post(@Valid Type type, BindingResult result , RedirectAttributes attributes) {

        /*重复验证*/
        Type t1 = typeService.getTypeName(type.getName());
        if (t1 != null) {
            result.rejectValue("name","nameError","不能添加重复分类");
        }

        /*非空验证*/
        if (result.hasErrors()) {
            return "admin/types-input";
        }

        Type t = typeService.saveType(type);
        if (t == null) {
            attributes.addFlashAttribute("message","新增失败");
        } else {
            attributes.addFlashAttribute("message","新增成功");
        }
        return "redirect:/admin/types";
    }

    @PostMapping("/types/{id}")
    public String editPost(@Valid Type type, BindingResult result, @PathVariable Long id, RedirectAttributes attributes) {

        /*重复验证*/
        Type t1 = typeService.getTypeName(type.getName());
        if (t1 != null) {
            result.rejectValue("name","nameError","不能添加重复分类");
        }

        /*非空验证*/
        if (result.hasErrors()) {
            return "admin/types-input";
        }

        Type t = typeService.updateType(id,type);
        if (t == null) {
            attributes.addFlashAttribute("message","更新失败");
        } else {
            attributes.addFlashAttribute("message","更新成功");
        }
        return "redirect:/admin/types";
    }

    @GetMapping("/types/{id}/delete")
    public String delete(@PathVariable Long id, RedirectAttributes attributes) {
        typeService.deleteType(id);
        attributes.addFlashAttribute("message","删除成功");
        return "redirect:/admin/types";
    }
}

页面处理 页面处理主要就是thymeleaf语法的使用,就不过多介绍,这是我找的一篇中文文档
在这里插入图片描述

 5.3 博客管理页面

博客页面管理也与标签分类类似,只是多了查询相关内容
在这里插入图片描述
在这里插入图片描述

6.博客前台展示

 6.1首页

  首页需要处理的就是页面的跳转,因为他全篇都是需要跳转的链接.
  同时还有数据的获取:标签,分类,文章……这些数据的获取在后面的页面展示也会使用到.
  还有就是博客点击后,浏览数要+1.
在这里插入图片描述

 6.2 博客展示

博客展示主要就是md-html的转换,然后还有评论

markdown转换为html

porm: 添加依赖

 <!--引入atlassian(将markdown形式转成html格式的)-->
        <dependency>
            <groupid>com.atlassian.commonmark</groupid>
            <artifactid>commonmark</artifactid>
            <version>0.14.0</version>
        </dependency>

        <dependency>
            <groupid>com.atlassian.commonmark</groupid>
            <artifactid>commonmark-ext-gfm-tables</artifactid>
            <version>0.14.0</version>
        </dependency>

        <dependency>
            <groupid>com.atlassian.commonmark</groupid>
            <artifactid>commonmark-ext-heading-anchor</artifactid>
            <version>0.14.0</version>
        </dependency>

markdownUtils

public class MarkdownUtils {

    /**
     * markdown格式转换成HTML格式
     */
    public static String markdownToHtml(String markdown) {
        Parser parser = Parser.builder().build();
        Node document = parser.parse(markdown);
        HtmlRenderer renderer = HtmlRenderer.builder().build();
        return renderer.render(document);
    }

    /**
     * 增加扩展[标题锚点,表格生成]
     */
    public static String markdownToHtmlExtensions(String markdown) {
        //h标题生成id
        Set<extension> headingAnchorExtensions = Collections.singleton(HeadingAnchorExtension.create());
        //转换table的HTML
        List<extension> tableExtension = Arrays.asList(TablesExtension.create());
        Parser parser = Parser.builder()
                .extensions(tableExtension)
                .build();
        Node document = parser.parse(markdown);
        HtmlRenderer renderer = HtmlRenderer.builder()
                .extensions(headingAnchorExtensions)
                .extensions(tableExtension)
                .attributeProviderFactory(new AttributeProviderFactory() {
                    public AttributeProvider create(AttributeProviderContext context) {
                        return new CustomAttributeProvider();
                    }
                })
                .build();
        return renderer.render(document);
    }

    /**
     * 处理标签的属性
     */
    static class CustomAttributeProvider implements AttributeProvider {
        @Override
        public void setAttributes(Node node, String tagName, Map<string, string=""> attributes) {
            //改变a标签的target属性为_blank
            if (node instanceof Link) {
                attributes.put("target", "_blank");
            }
            if (node instanceof TableBlock) {
                attributes.put("class", "ui celled table");
            }
        }
    }
}

接口实现:

    @Transactional
    @Override
    public Blog getAndConvert(Long id) {
        Blog blog = blogRepository.getById(id);
        if (blog == null) {
            throw new NotFoundException("该博客不存在");
        }
        Blog b = new Blog();
        BeanUtils.copyProperties(blog,b);
        String content = b.getContent();
        b.setContent(MarkdownUtils.markdownToHtml(content));
        blogRepository.updateViews(id);
        return b;
    }

评论

评论可能是这个项目最难的一部分,其中还设计到了很多数据结构知识

dao

public interface CommentRepository extends JpaRepository<comment, long=""> {

    List<comment> findByBlogId(Long blogId, Sort sort);

    List<comment> findByBlogIdAndParentCommentNull(Long blogId, Sort sort);
}

服务-接口

public interface CommentService {

    List<comment> listCommentByBlogId(Long blogId);

    Comment saveComment(Comment comment);

}

服务-实现:

@Service
public class CommentServiceImpl implements CommentService {

    @Autowired
    private CommentRepository commentRepository;

    @Override
    public List<comment> listCommentByBlogId(Long blogId) {
        Sort sort = Sort.by("createTime");
        List<comment> comments = commentRepository.findByBlogIdAndParentCommentNull(blogId,sort);
        return eachComment(comments);
    }

    @Transactional
    @Override
    public Comment saveComment(Comment comment) {
        Long parentCommentId = comment.getParentComment().getId();
        if (parentCommentId != -1) {
            comment.setParentComment(commentRepository.getById(parentCommentId));
        } else {
            comment.setParentComment(null);
        }
        comment.setCreateTime(new Date());
        return commentRepository.save(comment);
    }

    /* 树 */

    private List<comment> eachComment(List<comment> comments) {
        List<comment> commentsView = new ArrayList<>();
        for (Comment comment : comments) {
            Comment c = new Comment();
            BeanUtils.copyProperties(comment,c);
            commentsView.add(c);
        }
        //合并评论的各层子代到第一级子代集合中
        combineChildren(commentsView);
        return commentsView;
    }

    private void combineChildren(List<comment> comments) {

        for (Comment comment : comments) {
            List<comment> replys1 = comment.getReplyComments();
            for(Comment reply1 : replys1) {
                //循环迭代,找出子代,存放在tempReplys中
                recursively(reply1);
            }
            //修改顶级节点的reply集合为迭代处理后的集合
            comment.setReplyComments(tempReplys);
            //清除临时存放区
            tempReplys = new ArrayList<>();
        }
    }

    //存放迭代找出的所有子代的集合
    private List<comment> tempReplys = new ArrayList<>();

    private void recursively(Comment comment) {
        tempReplys.add(comment);//顶节点添加到临时存放集合
        if (comment.getReplyComments().size()>0) {
            List<comment> replys = comment.getReplyComments();
            for (Comment reply : replys) {
                tempReplys.add(reply);
                if (reply.getReplyComments().size()>0) {
                    recursively(reply);
                }
            }
        }
    }
}

页面处理:

<div class="ui bottom attached segment" th:if="${blog.commentabled}">
                <!--评论列表-->
                <div id="comment-container" class="ui teal segment">
                    <div th:fragment="commentList">
                        <div class="ui threaded comments" style="max-width: 100%;">
                            <h3 class="ui dividing header">评论</h3>
                            <div class="comment" th:each="comment : ${comments}">
                                <a class="avatar">
                                    <img src="https://picsum.photos/id/1/100/100" th:src="@{${comment.avatar}}">
                                </a>
                                <div class="content">
                                    <a class="author">
                                        <span th:text="${comment.nickname}">G37</span>
                                        <div class="ui mini basic teal left pointing label m-padded-mini" th:if="${comment.adminComment}">博主</div>
                                    </a>
                                    <div class="metadata">
                                        <span class="date" th:text="${#dates.format(comment.createTime,'yyyy-MM-dd HH:mm')}">Today at 5:42PM</span>
                                    </div>
                                    <div class="text" th:text="${comment.content}">
                                        How are you
                                    </div>
                                    <div class="actions">
                                        <a class="reply" data-commentid="1" data-commentnickname="G37" th:attr="data-commentid=${comment.id},data-commentnickname=${comment.nickname}" onclick="reply(this)">回复</a>
                                    </div>
                                </div>
                                <div class="comments" th:if="${#arrays.length(comment.replyComments)}>0">
                                    <div class="comment" th:each="reply : ${comment.replyComments}">
                                        <a class="avatar">
                                            <img src="https://picsum.photos/id/1/100/100" th:src="@{${reply.avatar}}">
                                        </a>
                                        <div class="content">
                                            <a class="author">
                                                <span th:text="${reply.nickname}">小红</span>
                                                <div class="ui mini basic teal left pointing label m-padded-mini" th:if="${reply.adminComment}">博主</div>
                                                &nbsp;<span th:text="|@ ${reply.parentComment.nickname}|" class="m-teal">@ 小白</span>
                                            </a>
                                            <div class="metadata">
                                                <span class="date" th:text="${#dates.format(reply.createTime,'yyyy-MM-dd HH:mm')}">Today at 5:42PM</span>
                                            </div>
                                            <div class="text" th:text="${reply.content}">
                                                How artistic!
                                            </div>
                                            <div class="actions">
                                                <a class="reply" data-commentid="1" data-commentnickname="G37" th:attr="data-commentid=${reply.id},data-commentnickname=${reply.nickname}" onclick="reply(this)">回复</a>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
                <div id="comment-form" class="ui form">
                    <input type="hidden" name="blog.id" th:value="${blog.id}">
                    <input type="hidden" name="parentComment.id" value="-1">
                    <div class="field">
                        <textarea name="content" placeholder="请输入评论信息..."></textarea>
                    </div>
                    <div class="fields">
                        <div class="field m-mobile-wide m-margin-bottom-small">
                            <div class="ui left icon input">
                                <i class="user icon"></i>
                                <input type="text" name="nickname" placeholder="姓名" th:value="${session.user}!=null ? ${session.user.nickname}">
                            </div>
                        </div>
                        <div class="field m-mobile-wide m-margin-bottom-small">
                            <div class="ui left icon input">
                                <i class="mail icon"></i>
                                <input type="text" name="email" placeholder="邮箱" th:value="${session.user}!=null ? ${session.user.email}">
                            </div>
                        </div>
                        <div class="field  m-margin-bottom-small m-mobile-wide">
                            <button id="commentpost-btn" type="button" class="ui teal button m-mobile-wide"><i class="edit icon"></i>发布</button>
                        </div>
                    </div>

                </div>
            </div>

在这里插入图片描述

在这里插入图片描述

 6.3 分类&标签

在这里插入图片描述

 6.4 博客归档

因为添加的测试文档都是一年的,我修改了其中一遍为2020,已达到展示效果,实现只是新建了两个查询语句,能够实现按年份分组

在这里插入图片描述

 6. 关于

关于页面只是之前的静态页面

在这里插入图片描述

三.整体运行效果

posted @ 2021-06-30 17:54  浮世无忧  阅读(131)  评论(0)    收藏  举报