使用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裏面的值取不到

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章