使用Spring Boot搭建个人博客全记录

使用Spring Boot搭建个人博客

简介

后端使用Spring Boot搭建的一个博客系统,前端使用的是thymeleaf + bootstrap,集成了editormd的markdown编辑器。

本项目适合Spring初学者作为练手项目,包含的主要技术点:
从开始到项目完成的全过程,篇幅可能较长

  1. spring data jpa的基本使用
  2. 数据绑定
  3. 拦截器
  4. spring boot 注解配置

地址

项目的思路以及前端代码来自他的博客:
一个JavaWeb搭建的开源Blog系统,整合SSM框架

项目GitHub地址:
https://github.com/wchstrife/blog

功能展示

把工程导入本地后,在mysql中添加一个叫做blog的database,然后在配置文件中把数据库的账号密码地址修改成自己的即可运行
主界面
主界面

详情
详情

登录
登录

后台管理
后台管理

写博客
写博客

下面我们的正式开始

一、项目搭建

我使用的是IDEA使用Maven构建一个Spring Boot工程,搭建过程请自行百度
建好工程之后手动添加一些目录,下面展示一下详细的目录

项目目录

目录结构

这里主要介绍一下resources目录

static目录是spring boot 默认的一个扫描的路径,所以我们要把引用的资源放在这里。

bootstrap是我们引进的一个样式控制的组件
editormd是引入的支持MarkDown语法的编辑器
css是一些全局的样式控制
jquery是bootstrap必要的

templates目录下放的是前端的HTML页面

admin是后台的管理
common是所有页面公用的部分
front是前台的展示界面

pom.xml

展示一下我们项目完整的依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.wchstrife</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>demo</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>

            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-thymeleaf -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
            <version>1.5.4.RELEASE</version>
        </dependency>

        <!--不严格检查-->
        <dependency>
            <groupId>net.sourceforge.nekohtml</groupId>
            <artifactId>nekohtml</artifactId>
            <version>1.9.22</version>
        </dependency>

        <!--热部署-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional><!-- optional=true,依赖不会传递,该项目依赖devtools;之后依赖myboot项目的项目如果想要使用devtools,需要重新引入 -->
        </dependency>

        <!--markdown-->
        <dependency>
            <groupId>org.tautua.markdownpapers</groupId>
            <artifactId>markdownpapers-core</artifactId>
            <version>1.4.1</version>
        </dependency>


    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>

实体映射

实体的映射是在entity包下
下面我们使用Spring data jpa 对我们项目需要的实体进行数据库的映射
通过这种方式,我们可以避免直接操作数据库,而是通过Spring data jpa来进行增删改查的操作
这里我们一共用到三张表:Aritcle Category User 分别对应博客,分类,用户
主键生成策略:
这里我采用的是UUID的生成方式,会生成一串字符串作为主键

@Id
    @GeneratedValue(generator = "uuid")
    @GenericGenerator(name = "uuid", strategy = "uuid")
    @Column(name = "id", columnDefinition = "varchar(64) binary")
    private String id;

外键约束
在Article表中,我们使用了ManyToOne的外键约束
在Article中有一个Categoriy的引用,代表一个博客有对应的一个分类,而一个分类下应该有多个博客

@ManyToOne
    private Category category;

数据访问层dao

在dao包下封装了一系列的对数据库增删改查的操作
Spring data jpa强大之处就是可以根据方法名进行解析。所以在dao层下的接口,大部分只有方法名和接收的参数。
如果需要自定义sql语句只需要加注解即可
这里演示一个自定义的模糊查询

@Query("from Article where title like %:title%")
    public List<Article> findByTitleLike(@Param("title") String title);

controller和service

划分这两层体现了很重要的分层的思想,即每一层只针对一层提供方法,不去了解其他层的方法,这样方便维护。
所以为了体现这种分层的思想,所以针对数据库的操作都放在service层进行封装
在controller层主要负责url地址的分配,对外提供的接口,部分简单的逻辑写在这里

二、front前台展示

在前端我们使用了Thymeleaf前端模板,里面有很多类似JSP标签的写法,进行对数据的遍历操作
在后端Control里面返回页面的时候,使用Model向其中添加属性,在前端页面上可以通过${}来获取这个属性值,并且用th:each来遍历
注意带th的语法表示交给thyme leaf模板解析的语法

例如前台index界面:
controller返回所有的博客列表

@RequestMapping("")
    public String list(Model model){
        List<Article> articles = articleService.list();
        model.addAttribute("articles", articles);

        return "front/index";
    }

在前台使用ty的语法进行遍历显示

<div th:each="articles:${articles}">                          
     <h3 th:text="${articles.title}"></h3>
     <span class="summary" th:text="${articles.summary}"></span><br/><br/>    
</div>

所以在前台展示只有三个页面,分别是列表显示所有博客,按类型显示博客,某个博客的全文
所以对应在controller里面只需要从数据库筛选全部的博客、某个类型的博客、取出某个博客(通过ID)的全文在页面上展示即可

管理员界面

在管理员界面要实现的功能比较多,最重要的是对博客的增删改
同时这里有一个登录的功能,只有在User表中有对应的账号密码才能登录,所以这里需要一层登陆拦截,这个稍后介绍。

引入Markdown组件

在编辑博客的时候我们支持使用markdown语法,我在网上找了一款叫做editormd的开源项目,放到static目录下
在我们write.html用如下的语法引入编辑器

<script th:src="@{/jquery-3.2.1.min.js}"></script>
    <script th:src="@{/editormd/editormd.js}"></script>
    <script th:src="@{/bootstrap/js/bootstrap.js}"></script>

    <script type="text/javascript" th:inline="javascript">
        //    调用编辑器
        var testEditor;
        $(function() {
            testEditor = editormd("test-editormd", {
                width   : "1000px",
                height  : 640,
                syncScrolling : "single",
                path    : [[@{/editormd/lib/}]]
            });
        });
    </script>

    <script th:inline="javascript">
        function selectCategory(obj) {
            var name = $(obj).attr("name");
            var displayName = $(obj).attr("abbr");
            console.log(name + "   " + displayName);
            $("#categoryBtn").html(displayName);
            $("#cateoryInput").val(name);
        }
    </script>

在需要编辑器的地方输入一个textarea即可

<div id="test-editormd">
            <textarea style="display:none;" name="content" th:field="*{content}" th:text="${target.content}"></textarea>
        </div>

深坑:
如果在js中要使用thymeleaf的语法,比如@{} ${}这种语法,一定要加上这句话th:inline="javascript
这样来使用该值[[@{/editormd/lib/}]]

write 操作

在写文章按钮上绑定好要提交的action,在controller里面对这个action进行处理,这里我们重点是要返回一个new出来的Article对象,因为要对对象进行数据的绑定,所以如果不传这个参数的话会报错

@RequestMapping("/write")
    public String write(Model model){
        List<Category> categories = categoryService.list();
        model.addAttribute("categories", categories);
        model.addAttribute("article", new Article());

        return "admin/write";
    }

在write.html中引入相关的编辑器组件以后,通过th:object绑定到Article对象上,然后Spring Boot会自动的帮我们把表单中的数据组合成一个ArtIicle对象,是不是很方便

<form method="post" th:action="@{/admin/save}" th:object="${article}">
    <input name="category" id="cateoryInput" type="hidden" th:field="*{category.name}"/>
    <input type="text" class="form-contorl" palceholder="标题" name="title" th:field="*{title}"/>
    <textarea style="display:none;" name="content" th:field="*{content}"></textarea>
</form>

save操作

前面html的表达提交之后,提交到save这个action中,在这里我们对提交的数据进行一个简单的处理,然后调用service里面封装的dao层的save方法即可
这里主要是对博客的日期,简介进行一个处理

@RequestMapping(value = "/save", method = RequestMethod.POST)
    public String save(Article article){
        //设置种类
        String name = article.getCategory().getName();
        Category category = categoryService.fingdByName(name);
        article.setCategory(category);
        //设置摘要,取前40个字
        if(article.getContent().length() > 40){
            article.setSummary(article.getContent().substring(0, 40));
        }else {
            article.setSummary(article.getContent().substring(0, article.getContent().length()));
        }
        article.setDate(sdf.format(new Date()));
        articleService.save(article);

        return "redirect:/admin";
    }

update操作

更新博客其实和写博客是一个道理,不过在更新的时候需要把id传给controller,然后根据id找到这个文章
把这个博客的内容、标题渲染在update.html中
然后在表单提交的字段中加一个隐藏表单,把博客的id传进去
调用save方法即可完成更新(根据id进行save,所以这时候会执行更新操作)

@RequestMapping("/update/{id}")
    public String update(@PathVariable("id") String id, Model model){
        Article article = articleService.getById(id);
        model.addAttribute("target", article);
        List<Category> categories = categoryService.list();
        model.addAttribute("categories", categories);
        model.addAttribute("article", new Article());

        return "admin/update";
    }

登陆拦截

对于后台的增删改操作,我们只对管理员开放,虽然我们增加了一个登录界面,但是别人还是可以通过直接输入对应url进行访问,所以我们要在这里增加一层登陆拦截,让没有登录的人不允许访问到我们后天的界面

在登录处理登录的doLogin方法中,我们在登录成功之后在cookie中加一个标志

Cookie cookie = new Cookie(WebSecurityConfig.SESSION_KEY, user.toString());
            response.addCookie(cookie);

在aspect包中建立一个拦截器

在WebSecurityConfig类中继承WebMvcConfigurerAdapter
重写addInterceptors方法,在里面配置要拦截的路径

在里面建一个内部类

SecurityInterceptor继承HandlerInterceptorAdapter
重写preHandle方法,表明在方法执行前执行拦截的动作
我们在这里对cookie的内容进行判断,如果有登录成功的标志,就进入后台管理界面,否则跳转到登录界面

注意:使用session的方法是不可以的,因为我们在登录的controller当中使用的是重定向(redirect),所以会导致session里面的值取不到

  • 10
    点赞
  • 88
    收藏
    觉得还不错? 一键收藏
  • 20
    评论
评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值