1. 程式人生 > 實用技巧 >個人部落格專案完工

個人部落格專案完工

//20201202 寫在前面:歷經大半個月,個人部落格專案終於從零到落地了!!雖然難度不是很大,可能都比不上一個本科生的畢設,但是很久之前就想自己動手搭一個部落格系統了,一直因為各種原因沒有開張,所以今天的完成還是很有成就感的~

本文主要總結一下專案的流程、技術點、難點,希望對有想搭建個人部落格的朋友有所幫助

ps:部落格還是初期版本_暫定版本0.0.1趴(麻雀雖小但五臟俱全~),之後會根據功能和體驗改進(說不定會開放多使用者嘞)

正文部分


一、使用技術

  • 前端:Semantic UI

  • 後端:Spring Boot

  • 前後端聯結:Thymeleaf

  • 資料庫:MySQL8

  • 開發環境:IDEA(Win7下)

  • 生產環境:CentOS 7


二、模組組成

前端展示部分:

公共部分:

  • 導航欄模組

  • 底部資訊模組

部落格展示系統:

  • 首頁

    • 主體部分

      • 部落格展示模組

      • 部分分類展示模組

      • 部分標籤展示模組

      • 最新推薦部落格展示模組

  • 分類頁面

    • 主體部分:

      • 分類展示模組

      • 部落格內容展示模組

  • 標籤頁面

    • 主體部分:

      • 標籤展示模組

      • 部落格內容展示模組

  • 歸檔頁面

    • 歸檔內容模組

  • 搜尋頁面

    • 搜尋結果展示模組

  • 關於我頁面

    • 資訊展示主體部分

部落格管理系統

公共部分:
  • 二級導航欄

各個頁面不同部分
  • 登入頁面

    • 資訊輸入框

  • 歡迎頁面

  • 部落格管理頁面

    • 部落格模糊搜尋模組

    • 部落格狀態展示&&編輯模組

  • 標籤管理頁面

    • 標籤展示&&編輯模組

  • 標籤釋出頁面

    • 標籤輸入模組

  • 分類管理頁面

    • 分類展示&&編輯模組

  • 分類釋出頁面

    • 分類輸入模組

  • 部落格釋出頁面

    • 部落格輸入模組

      • 標題輸入

      • 型別選擇

      • 主體輸入

      • 分類選擇模組

      • 標籤選擇模組

      • 首圖連結選擇模組

      • 摘要輸入模組

      • 功能選擇模組

後端邏輯部分:

SpringBoot大致框架如下:

  • model層(面相物件的各個實體類,用於連線資料庫)

  • dao層(結合model層,連線資料庫)

  • service層(實現各實體類操作介面,結合dao層獲取並處理資料)

  • view層(對接前端,給前端頁面渲染髮送所需的資料)

  • 配置:

    • 分為兩個配置,在統一的application.yml檔案中進行切換,開發環境使用的dev配置,可以改動資料庫結構,生產環境使用pro配置,不可以改動資料庫結構,只能進行增刪改查

      • dev配置

      • pro配置

    • 專案配置:儲存在pom.xml檔案中

  • model層:

    • Blog實體類

    • Comment實體類

    • Tag實體類

    • Type實體類

    • User實體類

  • dao層(使用JPA對接資料庫):

    • Blog倉庫

    • Comment倉庫

    • Tag倉庫

    • Type倉庫

    • User倉庫

  • service層:

    • 部落格服務介面

    • 評論服務介面

    • 標籤服務介面

    • 分類服務介面

    • 使用者服務介面

  • view層

    • 管理系統View層:

      • Blog控制器

      • 登入控制器

      • 標籤處理控制器

      • 分類處理控制器

    • 展示系統View層:

      • 關於我頁面控制器

      • 歸檔頁面控制器

      • 評論功能控制器

      • 首頁控制器

      • 標籤展示控制器

      • 分類展示控制器

  • 工具類:

    • markdown文字處理(每次載入渲染html標籤)類

    • MD5加密工具(用於登入使用者密碼加密,後臺資料庫中儲存的是密文)

    • Bean工具類(用於比對更新資料與原有資料中不同的部分)

  • 攔截器:攔截未經許可訪問管理頁面的請求

  • 日誌輸出模組:

    • 自動寫入日誌模組

    • 異常攔截模組(防止控制器異常發生之後直接阻塞服務導致其他使用者無法訪問,將異常全部寫入日誌檔案)


三、具體實現

ps:由於整體程式碼檔案過長,所以程式碼就放在github上——之後的更新也會在github上說明,本文只做簡略以及重點說明

原始碼在此:https://github.com/Lavender0423/blog

前端部分

管理系統:

技術點:
  • 使用thymeleaf模板中的fragment功能,設定公共fragment檔案,將所有頁面相同的部分放在fragment檔案中,然後使用thymeleaf模板裡的方法來獲取;因為篇幅不長,此處貼一下其原始碼(導航欄使用了選擇器來將當前頁面高亮——需要在每個頁面中傳入不同的值)

  • <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
    <head th:fragment="head(title)">
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/semantic.min.css">
    <link rel="stylesheet" href="../static/css/blog.css"th:href="@{/css/blog.css}">
    <link rel="stylesheet" href="../../static/lib/editormd/css/editormd.min.css" th:href="@{/lib/editormd/css/editormd.min.css}">
    <title th:replace="${title}"></title>
    </head>
    <body>
    <nav th:fragment="nav(n)" class="ui inverted attached segment m-padded-tb-mini m-shadow-small" >
    <div class="ui container">
    <div class="ui inverted secondary stackable menu">
    <h2 class="ui teal header item">部落格管理</h2>
    <a href="#" class="m-item m-mobile-hide item" th:href="@{/admin/blog_management}" th:classappend="${n==1}? 'active'"><i class="home icon"></i> 部落格</a>
    <a href="#" class="m-item m-mobile-hide item" th:href="@{/admin/types}" th:classappend="${n==2}? 'active'"><i class=" idea icon"></i> 分類</a>
    <a href="#" class="m-item m-mobile-hide item" th:href="@{/admin/tags}" th:classappend="${n==3}? 'active'"><i class=" tags icon"></i> 標籤</a>
    <div class="right m-item menu ">
    <div class="ui dropdown item">
    <div class="text">
    <img src="https://unsplash.it/100/100?image=456" alt="" class="ui avatar image">
    Lavender
    </div>
    <i class="dropdown icon"></i>
    <div class="menu">
    <a href="#" th:href="@{/admin/logout}" class="item">登出</a>
    </div>
    </div>
    </div>
    </div>
    </div>
    <a href="#" class="ui menu toggle button black icon 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">
    <img src="../static/img/info/we_chat_QC_code.png" th:src="@{/img/info/we_chat_QC_code.png}" class="ui rounded centered image" alt="" style="width:110px">

    </div>
    <div class = "three wide column">
    <h4 class="ui inverted header m-text-thin m-text-spaced m-opacity-mini">最新部落格</h4>
    <div class="ui inverted link list">
    <a href="#" class="item">使用者故事(User Story)</a>
    <a href="#" class="item">使用者故事</a>
    <a href="#" class="item">使用者故事</a>
    </div>
    </div>
    <div class = "three wide column">
    <h4 class="ui inverted header m-text-thin m-text-spaced m-opacity-mini">聯絡我</h4>
    <div class="ui inverted link list">
    <a href="#" class="item">Email:[email protected]</a>
    <a href="#" class="item">QQ:473243887</a>

    </div>
    </div>
    <div class = "seven wide column">
    <h4 class="ui inverted header m-text-thin m-text-spaced m-opacity-mini">Lavender</h4>
    <p class="m-text-thin m-text-spaced m-opacity-mini">這是我的個人部落格,會分享關於我生活、學習的點點滴滴,希望能給來到本站的你們有所幫助</p>
    </div>
    </div>
    <div class="ui inverted section divider"></div>
    <p class="m-text-thin m-text-spaced m-opacity-tiny">Copyright © 2012 - 2020 Lavender Designed by Pansy</p>
    </div>
    </footer>
    <th:block th:fragment="script">
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/semantic.min.js"></script>
    <script src="../../static/lib/editormd/editormd.min.js" th:src="@{/lib/editormd/editormd.min.js}"></script>
    </th:block>
    </body>
    </html>

  • 在有輸入框的地方均加上了前端第一層驗證(JQuery實現),如果不符合格式或者空白,則無法正常提交,會跳出提醒框,示例程式碼如下

  • $('.ui.form').form({
    fields :{
    title : {
    identifier:'title',
    rules:[{
    type:'empty',
    prompt:'標題:請輸入部落格標題'
    }]
    },
    content:{
    identifier:'content',
    rules:[{
    type:'empty',
    prompt:'內容:請輸入部落格內容'
    }]
    },
    type : {
    identifier:'type.id',
    rules:[{
    type:'empty',
    prompt:'分類:請選擇分類'
    }]
    },
    firstpicture : {
    identifier:'firstpicture',
    rules:[{
    type:'empty',
    prompt:'首圖:請輸入首圖地址連結'
    }]
    },
    description : {
    identifier:'description',
    rules:[{
    type:'empty',
    prompt:'部落格描述:請輸入部落格描述'
    }]
    },
    }
    });
  • 部落格輸入介面中,使用commonmark元件直接生成文字輸入框(可動態展示markdown文字)

    • 引入資源:

      • <link rel="stylesheet" href="../../static/lib/editormd/css/editormd.min.css">

      • <script src="../../static/lib/editormd/editormd.min.js"></script>

    • 元件生成程式碼

    • <div class="required field">
      <div id="md-content" style="z-index:1!important;">
      <textarea placeholder="Enjoy you coding now..."th:text="*{content}" name="content" style="display:none;"></textarea>
      </div>
      </div>

      <style>
      var contentEditor;

      $(function() {
      contentEditor = editormd("md-content", {
      width: "100%",
      height: 640,
      syncScrolling: "single",
      // path: "../static/lib/editormd/lib/"
      path: "/lib/editormd/lib/"
      });
      });
      </style>

前端展示系統:

  • 類似於管理系統,也使用了thymeleaf模板中的fragment功能,原始碼如下

  • <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
    <head th:fragment="head(title)">
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/semantic.min.css">
    <link rel="stylesheet" href="../static/css/typo.css" th:href="@{/css/typo.css}">
    <link rel="stylesheet" href="../static/css/animate.css" th:href="@{/css/animate.css}">
    <link rel="stylesheet" href="../static/lib/prism/prism.css" th:href="@{/lib/prism/prism.css}">
    <link rel="stylesheet" href="../static/lib/tocbot/tocbot.css"th:href="@{/lib/tocbot/tocbot.css}">
    <link rel="stylesheet" href="../static/css/blog.css"th:href="@{/css/blog.css}">
    <title th:replace="${title}"></title>
    </head>
    <body>
    <nav th:fragment="nav(n)" class="ui inverted attached segment m-padded-tb-mini m-shadow-small" >
    <div class="ui container">
    <div class="ui inverted secondary stackable menu">
    <h2 class="ui teal header item">Blog</h2>
    <a href="#" class="m-item m-mobile-hide item" th:href="@{/}" th:classappend="${n==1}? 'active'"><i class="home icon"></i> 首頁</a>
    <a href="#" class="m-item m-mobile-hide item" th:href="@{/types/-1}" th:classappend="${n==2}? 'active'"><i class=" idea icon"></i> 分類</a>
    <a href="#" class="m-item m-mobile-hide item" th:href="@{/tags/-1}" th:classappend="${n==3}? 'active'"><i class=" tag icon"></i> 標籤</a>
    <a href="#" class="m-item m-mobile-hide item" th:href="@{/archives}" th:classappend="${n==4}? 'active'"><i class=" clone icon"></i> 歸檔</a>
    <a href="#" class="m-item m-mobile-hide item" th:href="@{/about_me}" th:classappend="${n==5}? 'active'"><i class=" info icon"></i> 關於我</a>
    <div class="right item">
    <form name="search" method="post" action="#" th:action="@{/search}" target="_blank">
    <div class="ui icon inverted transparent input">
    <input name="query" type="text" 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 button black icon 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">
    <img src="../static/img/info/we_chat_QC_code.png" th:src="@{/img/info/we_chat_QC_code.png}" class="ui rounded centered image" alt="" style="width:110px">

    </div>
    <div class = "three wide column">
    <h4 class="ui inverted header m-text-thin m-text-spaced m-opacity-mini">最新部落格</h4>
    <div id="newblog-container">
    <div class="ui inverted link list" th:fragment="newblogList">
    <a href="#" class="item"th:href="@{/Blog/{id}(id=${blog.id})}" target="_blank"th:each="blog : ${newblogs}" th:text="${blog.title}">dh</a>

    </div>
    </div>
    </div>
    <div class = "three wide column">
    <h4 class="ui inverted header m-text-thin m-text-spaced m-opacity-mini">聯絡我</h4>
    <div class="ui inverted link list">
    <span class="item" th:text="|Email: #{index.email}|"></span>
    <span class="item" th:text="|QQ: #{index.qq}|"></span>

    </div>
    </div>
    <div class = "seven wide column">
    <h4 class="ui inverted header m-text-thin m-text-spaced m-opacity-mini">Lavender</h4>
    <p class="m-text-thin m-text-spaced m-opacity-mini">這是我的個人部落格,會分享關於我生活、學習的點點滴滴,希望能給來到本站的你們有所幫助</p>
    </div>
    </div>
    <div class="ui inverted section divider"></div>
    <p class="m-text-thin m-text-spaced m-opacity-tiny">Copyright © 2012 - 2020 Lavender Designed by Pansy</p>
    </div>
    </footer>
    <th:block th:fragment="script">
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/semantic.min.js"></script>
    <script src="../static/lib/prism/prism.js" th:src="@{/lib/prism/prism.js}"></script>
    <script src="../static/lib/tocbot/tocbot.js" th:src="@{/lib/tocbot/tocbot.js}"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/jquery.scrollTo.min.js"></script>
    <script src="../static/lib/qrcode/qrcode.js" th:src="@{/lib/qrcode/qrcode.js}"></script>
    <script src="../static/lib/waypoints/jquery.waypoints.js" th:src="@{/lib/waypoints/jquery.waypoints.js}"></script>
    <script>
    $('#newblog-container').load(/*[[@{/footer/newblog}]]*/"/footer/newblog");
    </script>
    </th:block>
    </body>
    </html>
  • 前端展示頁面中的個人介紹介面為靜態介面

  • 部落格展示頁面與個人介紹頁面中都有二維碼跳出元素

    • 部落格展示頁面:

      • 讚賞按鈕點選之後支付寶和微信二維碼會跳出

      • 右方工具條中微信圖示點選時會產生該網址二維碼

      • 跳出二維碼實現如下程式碼塊

      • $('.button.wechat').popup({
        popup:$('#QR-code'),
        position:'left center'
        });

        $('#payButton').popup({
        popup:$('.payQR.popup'),
        on:'click' ,
        position:'bottom center'
        });
  • 前端所有頁面都做成了響應式的,可響應手機小螢幕,小螢幕會收起導航欄,變成選單按鈕,點選之後會蹦出所有圖示

  • 歸檔頁面的日期從新到舊,歸檔基於建立時間而非更新時間

  • 評論展示採用二級目錄格式(最多二級,超出二級全部扁平化為二級),原始碼放在後端介紹裡,評論裡博主id前面會有“博主”字樣tag,回覆評論會有@標記

  • 前端展示頁面均加上了animate樣式,頁面主體部分會延時出現

後端部分

dao層

  • 除了各個介面普通的方法外,還有如下自定義方法及語句:

    • BlogRepository中:

    • @Query("select b from Blog b where b.recommend = true ")
      List<Blog> findNew(Pageable pageable);

      // 1 indicates parameter 1,2 indicates parameter 2
      @Query("select b from Blog b where b.title like ?1 or b.content like ?1")
      Page<Blog> findByQuery(String query, Pageable pageable);

      @Transactional
      @Modifying
      @Query("update Blog b set b.views = b.views+1 where b.id = ?1")
      int updateViews(Long id);

      @Query("select function('date_format',b.updateTime,'%Y') as year from Blog b group by function('date_format',b.updateTime,'%Y') order by function('date_format',b.updateTime,'%Y') desc ")
      List<String> findGroupByYear();

      @Query("select b from Blog b where function('date_format',b.createTime,'%Y') = ?1")
      List<Blog> findByYear(String year);
    • CommentRepository中

    • //符合Repository書寫規範,自動轉化為SQL語句
      List<Comment> findByBlogIdAndParentCommentNull(Long blogId, Sort sort);

      // only get one
      @Query(value="select * from t_comment c where c.nickname = ?1 limit 0,1",nativeQuery = true)
      Comment findByNickname(String nickname);
    • TagRepository中

    • //符合Repository書寫規範,自動轉化為SQL語句
      Tag findByName(String name);

      @Query("select t from Tag t")
      List<Tag> findTop(Pageable pageable);
    • TypeRepository中

    • //符合Repository書寫規範,自動轉化為SQL語句
       	Type findByName(String name);
       
         @Query("select t from Type t")
         List<Type> findTop(Pageable pageable);
    • UserRepository中

    •     User findByUsernameAndPassword(String username,String password);
  • service層

    • BlogService:主要工作有

      • 從dao層獲取部落格資料

      • 獲取符合查詢條件(模糊查詢)的部落格資料

      • 將markdown格式文字轉化成html格式的標籤化資料

      • 獲取固定數量的部落格(根據部落格推薦時間排序)

      • 刪除部落格資料

      • 更新部落格資料

      • 統計部落格數量

      • 儲存部落格資料

    • CommentService:主要工作有:

      • 獲取評論資料

      • 解析父子級評論關係並扁平化

    • 其餘Service無非增刪改查,就不再贅述,詳情請看原始碼

  • view層

    • 都是一些很尋常的操作,就在此說一下需要注意的點

    • 在type和tag的view層,初始化的時候傳回的id預設是-1,需要在該層判斷並返回預設值,否則會出錯

    • 在評論的view層,使用service層連線dao層進入資料庫根據名稱查詢使用者資訊,相同名稱的生成相同頭像(使用隨機影象網站連結,id使用random函式隨機生成)

    • 有些頁面中只需要部分渲染,此處使用fragments :: newblogList類似的格式來返回片段資訊,這樣就防止獲取部分資料而整個頁面重新整理(增強使用者體驗)

  • 攔截器:

    • 攔截未經許可訪問管理系統的請求,原始碼如下:

    • @Override
          public void addInterceptors(InterceptorRegistry registry) {
              //設定攔截所有admin路徑下的請求
              registry.addInterceptor(new LoginInterceptor())
              .addPathPatterns("/admin/**")
              .excludePathPatterns("/admin")
              .excludePathPatterns("/admin/login");
      
          }
    • 攔截view層產生的所有異常,並阻止其掛起伺服器而是將其寫入日誌檔案中,原始碼如下:

    • //    獲取日誌物件
          private final Logger logger = LoggerFactory.getLogger(this.getClass());
      
      //    表示此方法是用於異常處理的
          @ExceptionHandler(Exception.class)
          public ModelAndView exceptionHandler(HttpServletRequest request,Exception e) throws Exception{
      //        獲取並記錄異常
              logger.error("Request URL : {}.Exception : {}",request.getRequestURL(),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;
          }
  • 配置

    • 配置分為開發環境配置與生產環境配置

    • dev(開發環境)配置原始碼:

    • #開發環境
      #資料庫
      spring:
        datasource:
          driver-class-name: com.mysql.jdbc.Driver
          url: jdbc:mysql://localhost:3306/blog?useUnicode=true&charaterEncoding=utf-8&serverTimezone=UTC
          username: root
          password: 123456
        jpa:
      #    在開發環境可以更新資料庫
          hibernate:
            ddl-auto: update
          show-sql: true
      #日誌
      logging:
        level:
          root: info
          com.lavender.Blog: debug
        file:
          name: log/blog-dev.log
    • pro(生產環境配置):

    • #生產環境
      #資料庫
      spring:
        datasource:
          driver-class-name: com.mysql.jdbc.Driver
          url: jdbc:mysql://localhost:3306/blog?useUnicode=true&charaterEncoding=utf-8&serverTimezone=UTC
          username: root
          password: *******
          # 已打碼
        jpa:
      #    在生產環境禁止改動資料庫
          hibernate:
            ddl-auto: none
          show-sql: true
      #日誌
      logging:
        level:
          root: warn
          com.lavender.Blog: info
        file:
          name: log/blog-pro.log
      server:
        port: 8080
    • 二者區別:

      • 開發環境允許改動資料庫結構,生產環境不允許,只支援增刪改查

      • 輸出資訊不一樣,開發環境輸出debug級別資訊,而生產環境輸出info級別資訊(會少很多)

    • 另有一個總配置檔案來控制環境(active中填入的環境即為啟用環境),程式碼如下:

    • #資料庫
      spring:
        thymeleaf:
          mode: HTML
      #    指定使用配置
        profiles:
          active: dev

專案總依賴配置(maven)

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.4.0</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.lavender</groupId>
	<artifactId>Blog</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>Blog</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
		<thymeleaf.version>3.0.11.RELEASE</thymeleaf.version>
		<thymeleaf-layout-dialect.version>2.1.1</thymeleaf-layout-dialect.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-thymeleaf</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
			<optional>true</optional>
		</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-validation</artifactId>
		</dependency>
		<dependency>
			<groupId>com.atlassian.commonmark</groupId>
			<artifactId>commonmark</artifactId>
			<version>0.15.2</version>
		</dependency>
		<dependency>
			<groupId>com.atlassian.commonmark</groupId>
			<artifactId>commonmark-ext-gfm-tables</artifactId>
			<version>0.15.2</version>
		</dependency>
		<dependency>
			<groupId>com.atlassian.commonmark</groupId>
			<artifactId>commonmark-ext-heading-anchor</artifactId>
			<version>0.15.2</version>
		</dependency>

	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-surefire-plugin</artifactId>
				<version>2.22.2</version>
				<configuration>
					<skipTests>true</skipTests>
				</configuration>
			</plugin>

		</plugins>

		<resources>
			<resource>
				<!-- 指定resources外掛處理哪個目錄下的資原始檔 -->
				<directory>${basedir}/src/main/webapp</directory>
				<!-- 需要將資原始檔放到該目錄下才能訪問 -->
				<targetPath>META-INF/resources</targetPath>
				<includes>
					<include>**/**</include>
				</includes>
			</resource>

			<resource>
				<directory>${basedir}/src/main/resources</directory>
			</resource>
		</resources>
	</build>
	<packaging>jar</packaging>
	<repositories>
		<repository>
			<id>alimaven</id>
			<url>https://maven.aliyun.com/repository/public</url>
		</repository>
	</repositories>
	<pluginRepositories>
		<pluginRepository>
			<id>alimaven</id>
			<url>https://maven.aliyun.com/repository/public</url>
		</pluginRepository>
	</pluginRepositories>

</project>

四、遇到的問題&解決方法

前端篇:

  • 跳出二維碼被底部資訊欄擋住------->解決方法:設定跳出二維碼z-index,讓其比底部資訊欄配置高

  • 模板渲染一定要和view層中的引數一致,否則會報錯

後端篇:

  • 使用annotation @Query設定JPA自定義語句時,如果是MySQL專有語法,比如‘limit’,需要將語句賦值給value,再設定引數“nativeQuery ”為 true------------->意義為不在java中處理語句,將語句直接交給MySQL執行,然後解析對應返回物件,因為java中不會解析,所以選擇資料庫表需要使用原名而不是model層中的實體類名,後者將會報錯

  • Spring(2.1.1)下PageRequest、Sort類不再支援直接例項化物件,需要使用靜態方法獲取物件----------------->原始碼如下

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

配置篇:

  • 在將專案部署到雲端時,一定要將生產環境的資料庫配置成雲端資料庫的資料,且需要先在雲端設計好資料庫或者在雲端線性執行一遍開發環境建立好資料庫表,否則會報錯

部署篇:

  • 首先需要安裝和開發環境相同版本的資料庫(更加保險,避免改程式碼)

  • 雲端需要開放8080埠(安全組以及防火牆_此處使用的是阿里雲學生伺服器)

  • 設定指令碼檔案來控制伺服器的執行&停止&重啟(一共四個指令碼檔案),原始碼如下

    • 執行(在jar包所在目錄下執行):

    • java -jar Blog-0.0.1-SNAPSHOT.jar
    • 不間斷執行——直接執行startup.sh,在控制終端關閉之後,服務便會停止,此時需要使用nohup指令(不掛起)來不間斷執行以保持終端關閉之後伺服器狀態,原始碼如下

    • nohup ./startup.sh
    • 關閉伺服器_大致邏輯為:使用ps查詢所有程序---->使用管道輸出到grep來查詢帶有執行指令相關的程序----->使用管道輸出到grep -v來刪除帶有grep的程序---->最後通過awk來獲取程序pid------>最後使用kill -9殺死程序,原始碼如下

    • echo "---------find Blog process----------"
      process=`ps -ef | grep 'java -jar Blog-0.0.1-SNAPSHOT.jar' | grep -v 'grep'`
      echo "process is :"
      echo "$process"
      id=`ps -ef | grep 'java -jar Blog-0.0.1-SNAPSHOT.jar' | grep -v 'grep' | awk '{print $2}'`
      echo "this operation will kill the process which id is $id"
      kill -9 $id
      echo "done"
      echo "-----------opearation finished------"
    • 重啟伺服器:先執行shutdown再執行startup

    • ./shutdown.sh
      
      ./nohup-starup.sh
  • 使用nohup指令後,輸出會全部儲存到nohup.out檔案中,如果訪問量過大,會讓該檔案毫無邏輯且過於龐大,可以將nohup指令輸出注入到dev/null(黑洞)之中_未實施,目前訪問量較小,待更新...


五、 Summary

  • 部落格大概邏輯:

  • 前端展示---->傳送請求給view層----->view層呼叫Service層獲取資料----->Service層根據所需連線Repository獲取資料----->dao層直接從資料庫中取出資料------>返回前端頁面渲染(後期補上流程圖,此處略顯簡略)

以上

希望對大家有所幫助