我的博客開發筆記

 

 

1. 選題背景

1.1 選題概述

很早以前就想要寫一個本身的博客了,趁着如今學校安排的web課設選題,決定把它給作出來,也順便複習一下曾經學過的一些web技術和框架。css

只有動手作完以後,才能發現不足之處,好比一些細節的處理,大致的表設計,業務邏輯接口的編寫,以及一些bug的存在,還可讓本身更熟練的開發各類功能的網頁。html

1.2 技術選型

後端技術:Springboot + Spring + Mybatis + Druid + Swagger + 熱部署 + Mysql前端

前端技術:html+css+js+Jquery+bootstrap+vue.jsvue

2. 整體系統功能模塊

2.1 需求分析

前端需求分析:web

簡潔/美觀——我的很喜歡像Mac那樣的簡潔風,越簡單越好,固然也得好看;(首頁輪播圖+分類左右排版+導航欄+博文詳情頁)sql

最好是單頁面——單頁面的目的一方面是爲了簡潔,另外一方面也是爲了實現起來比較簡單;(單頁面就不用vue.js作SPA了,仍是經過a標籤原地跳轉的方式模擬單頁面)數據庫

自適應——至少能適配常見的手機分辨率吧,我可不但願本身的博客存在顯示差別性的問題;(Bootstrap的柵格系統+CSS媒體查詢+配合JS實現)bootstrap

 

可能出現的頁面如圖:後端

 

 

圖1api

 

PS:留言頁和關於頁,簡歷頁之後再實現

 

 

 

圖2

 

PS:評論功能暫未實現,只實現了博文分類(CURD)和文章管理(常見的CURD)的功能

 

 

 

 

圖3

 

PS:數據統計模塊暫未實現

 

 

2.2 表結構設計

我的博客系統數據結構設計:

 

 

表1

 

PS:此圖用Navicat 的表逆向模型 的功能實現

2.3 表結構分析

1)分類信息表(tbl_category_content):

CREATE TABLE `tbl_category_info` (
`id` bigint(40) NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL COMMENT '分類名稱',
`number` tinyint(10) NOT NULL DEFAULT '0' COMMENT '該分類下的文章數量',
`create_by` datetime NOT NULL COMMENT '分類建立時間',
`modified_by` datetime NOT NULL COMMENT '分類修改時間',
`is_effective` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否有效,默認爲1有效,爲0無效',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

表2

2)文章內容表(tbl_article_content):

CREATE TABLE `tbl_article_content` (
`id` bigint(40) NOT NULL AUTO_INCREMENT,
`content` text NOT NULL,
`article_id` bigint(40) NOT NULL COMMENT '對應文章ID',
`create_by` datetime NOT NULL COMMENT '建立時間',
`modifield_by` datetime NOT NULL COMMENT '更新時間',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

表2

PS: 文章內容單獨分一個表是由於要把MD格式的文章直接從後臺添加到數據庫中,屬於大文本類型,不放在文章基礎信息表中,是爲了查詢效率,不須要索引大文本域

 

3)文章信息表(tbl_article_info):

CREATE TABLE `tbl_article_info` (
`id` bigint(40) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
`title` varchar(50) NOT NULL DEFAULT '' COMMENT '文章標題',
`summary` varchar(300) NOT NULL DEFAULT '' COMMENT '文章簡介,默認100個漢字之內',
`is_top` tinyint(1) NOT NULL DEFAULT '0' COMMENT '文章是否置頂,0爲否,1爲是',
`traffic` int(10) NOT NULL DEFAULT '0' COMMENT '文章訪問量',
`create_by` datetime NOT NULL COMMENT '建立時間',
`modified_by` datetime NOT NULL COMMENT '修改日期',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

 

表3

下面爲關聯表

PS:用關聯表,是爲了避免讓後端作多表鏈接查詢,影響查詢效率,因此也不須要創建外鍵,

讓後端在Service層手動完成外鍵的功能,大大減小了數據庫的壓力。


4)文章分類表(tbl_article_category):

CREATE TABLE `tbl_article_category` (
`id` bigint(40) NOT NULL AUTO_INCREMENT,
`sort_id` bigint(40) NOT NULL COMMENT '分類id',
`article_id` bigint(40) NOT NULL COMMENT '文章id',
`create_by` datetime NOT NULL COMMENT '建立時間',
`modified_by` datetime NOT NULL COMMENT '更新時間',
`is_effective` tinyint(1) DEFAULT '1' COMMENT '表示當前數據是否有效,默認爲1有效,0則無效',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

 

表4

5)文章題圖表(tbl_article_picture):

CREATE TABLE `tbl_article_picture` (
`id` bigint(40) NOT NULL AUTO_INCREMENT,
`article_id` bigint(40) NOT NULL COMMENT '對應文章id',
`picture_url` varchar(100) NOT NULL DEFAULT '' COMMENT '圖片url',
`create_by` datetime NOT NULL COMMENT '建立時間',
`modified_by` datetime NOT NULL COMMENT '更新時間',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='這張表用來保存題圖url,每一篇文章都應該有題圖';

表5

PS:題圖用於前端首頁的輪播圖和文章詳情頁文章的配圖

3. 原型參考

3.1 前端原型參考

 

首頁:

 

 

圖4

 

文章分類頁:

 

 

圖5

 

 

文章詳情頁:

 

 

圖6

3.2 後端原型參考

 

 

圖7

4.項目搭建

4.1 Springboot項目配置

 

 

 

圖8

 

PS:上圖爲從Springboot官網Springboot initializer 後搭建的Springboot項目,在上面進行

二次開發後,最後的開發的目錄結構如上

 

Maven工程的依賴爲:Pom文件以下

 

 

 

圖9

 

對項目目錄結構進行簡要說明:

  • controller:控制器 (MVC的C模塊,用於處理url映射請求以及ResfulAPI的設計)
  • dao:實際上這個包能夠更名叫mapper,由於裏面放的應該是MyBatis逆向工程自動生成以後的mapper類。(就是數據訪問對象層,訪問數據庫的,增刪改查的方法都在這裏)
  • entity:實體類,(MVC中M模塊,Model,對應表的JavaBean)還會有一些MyBatis生成的example
  • generator:MyBatis逆向工程生成類
  • interceptor:SpringBoot 攔截器 (攔截後臺管理系統的請求,判斷有無管理員登錄的權限)
  • service:Service層,裏面還有一層impl目錄 (業務邏輯接口的開發都在這裏)
  • util:一些工具類能夠放在裏面 (Markdown格式轉html的工具類也在這裏)
  • mapper:用於存放MyBatis逆向工程生成的.xml映射文件
  • static:這個目錄存放一些靜態文件,簡單瞭解了一下Vue的先後端分離,前臺文件之後也須要放在這個目錄下面(放網頁和JS,CSS,image的地方)

 

4.2 Mybatis框架集成配置

1. Springboot繼承Mybatis是經過依賴starter來集成Mybatis框架的

Pom依賴以下:

 

 

圖10

 

2. Mybatis逆向工程:

 

 

圖11

 

PS:逆向工程用於自動根據配置的數據庫來生成Entity類,和mapper映射文件和mapper映射接口(用來操做數據庫的),至關於自動生成了一大堆的sql語句(增刪改查),上一層直接調用DAO層的接口便可訪問數據庫 (鬆耦合)

 

4.3 Restful設計與Swagger2配置

1.概要:RestfulAPI是一種HTTP請求的規範,能夠用到put請求表示更新數據,

Delete請求表示刪除數據,post請求表示添加數據,get請求表示查詢數據,合理的運用

HTTP方法來完成請求,避免了之前WEB開發只用get 和post請求的這種不規範設計

格式爲下圖:

 

 

圖12

 

2. Swaager文檔用於圖形化RestfulAPI風格的接口,效果以下圖:

 

 

圖13

4.4 數據庫鏈接池配置和日誌配置

1. 採用了Druid數據庫鏈接池(當今最實用,效率也很高的阿里巴巴的鏈接池)

 

 

圖14

 

 

2. 日誌配置: Springboot天生集成了logback日誌,因此不須要再從新導入新的日誌框架,

                     直接複製日誌配置文件便可,但注意名字要按格式來,才能被加載,如圖:

 

 

 

圖15

 

4.5 攔截器配置

登錄攔截器代碼以下:(還用Cookie實現了30分鐘有效期的自動登錄)

 

public class BackInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //經過session判斷是否已經登錄
        String username = (String)request.getSession().getAttribute("username");
        String name = "zhanp";


        //傳輸的加密先放一邊,後面再看下

        //先判斷Session不爲空,說明已經登錄了
        if(StringUtils.isEmpty(username)){
            Cookie[] cookies = request.getCookies();
            //判斷cookie中有沒有自動登錄的憑證
            if(cookies!=null){
                for(Cookie cookie:cookies){
                    if(!StringUtils.isEmpty(cookie)&&cookie.getName().equals(name)){
                        return true;
                    }
                }
            }else{
                return false;
            }
        }
        return true;
    }
}

 

 

5. 後端開發過程

5.1 Entity層開發

 

 

圖16

 

 

圖17

 

這些實體類對應的是Mysql中創建的表的名字,屬性名字爲表的字段名

 

5.2 Service層開發

 

 

圖18

1. 好比文章的業務接口開發有

 1.1 添加文章->要填充文章內容表,文章-分類表,文章-題圖表,文章信息表,還要修改相應分類下的文章數目

 

    public void addArticle(ArticleDto articleDto) {
        //1.填充文章信息表----title/summary/isTop
//       前端不可能給你Id的,這是後端自動生產的,要在後端獲取Id
//        Long id = articleDto.getId();
        String title = articleDto.getTitle();
        String summary = articleDto.getSummary();
        Boolean isTop = articleDto.getIsTop();

        ArticleInfo articleInfo = new ArticleInfo();
        articleInfo.setTitle(title);
        articleInfo.setSummary(summary);
        articleInfo.setIsTop(isTop);

        //1.1 寫入文章信息表中
        //1.2 並查詢新增的文章Id。。。由於返回主鍵也須要select和插入處於同一事務,因此不會返回正確的插入後的主鍵
        articleInfoMapper.insertSelective(articleInfo) ;
        //從參數裏返回主鍵
        Long id = articleInfo.getId();

        //2. 填充文章-內容表----文章Id/content
        ArticleContent articleContent = new ArticleContent();
        articleContent.setArticleId(id);
        articleContent.setContent(articleDto.getContent());

        //2.1 寫入文章-內容表
         articleContentMapper.insertSelective(articleContent);

        //3. 填充文章 - 分類表---文章Id/分類Id
        ArticleCategory articleCategory = new ArticleCategory();
        articleCategory.setArticleId(id);
        articleCategory.setSortId(articleDto.getCategoryId());

        //3.1 寫入文章 - 分類表
        articleCategoryMapper.insertSelective(articleCategory);

        //3.2 分類下的文章信息 + 1
        Long sortId = articleCategory.getSortId();
        //查詢你源分類信息條目
        CategoryInfo categoryInfo = categoryInfoMapper.selectByPrimaryKey(sortId);
        //文章+1
        categoryInfo.setNumber((byte) (categoryInfo.getNumber()+1));
        categoryInfoMapper.updateByPrimaryKeySelective(categoryInfo);

        //4. 填充文章-題圖表 ---文章Id/圖片url
        ArticlePicture articlePicture = new ArticlePicture();
        articlePicture.setArticleId(id);
        articlePicture.setPictureUrl(articleDto.getPictureUrl());

        //4.1寫入 文章-題圖表
        articlePictureMapper.insertSelective(articlePicture);

    }

 

 1.2 更新文章:
 * 根據封裝的ArticleDto參數 選擇性的更新文章
 * warning: ArticleDto參數後臺按實際狀況應該只有文章基礎信息的Id,和圖片url,內容content,分類Id這種,
 *          而不會有從表的主鍵Id,因此除了文章信息表外,其餘從表須要根據文章Id關聯查詢出來
 * 好比更新文章基礎信息(title,summary,isTop)
 * 更新文章-分類表的信息
 * 更新文章-題圖表的信息
 *
 * 還有更新文章時分類信息改了的話,要調用分類文章-的api updateArticleCategory()去從新統計分類下的數目,這個寫漏了

@Override
public void updateArticle(ArticleDto articleDto) {

    Long id = articleDto.getId();

    //1.文章基礎信息表
    //1.1 填充ArticleInfo參數
    ArticleInfo articleInfo = new ArticleInfo();
    articleInfo.setId(id);
    articleInfo.setSummary(articleDto.getSummary());
    articleInfo.setIsTop(articleDto.getIsTop());
    articleInfo.setTitle(articleDto.getTitle());
    articleInfo.setTraffic(articleDto.getTraffic());

    articleInfoMapper.updateByPrimaryKeySelective(articleInfo);

    //2. 文章-分類表

    //根據文章Id----找出對應的文章分類表 的條目
    ArticleCategoryExample articleCategoryExample = new ArticleCategoryExample();
    ArticleCategoryExample.Criteria articleCategoryExampleCriteria = articleCategoryExample.createCriteria();
    articleCategoryExampleCriteria.andArticleIdEqualTo(id);
    List<ArticleCategory> articleCategoryList = articleCategoryMapper.selectByExample(articleCategoryExample);
    ArticleCategory category = articleCategoryList.get(0);

    //2.1 先檢查源分類Id與更新過來的分類Id是否相等
    // 若是分類被修改過了,那麼分類下的文章數目也要修改
    //前者是源Id,後者是更新過來的Id
    Long sourceSortId = category.getSortId();
    Long categoryId = articleDto.getCategoryId();
    if(!sourceSortId.equals(categoryId)){
        //2.3 更新分類下的文章信息
        updateArticleCategory(id,categoryId);
    }

    //3.文章-題圖表

    ArticlePictureExample articlePictureExample = new ArticlePictureExample();
    articlePictureExample.or().andArticleIdEqualTo(id);

    List<ArticlePicture> pictureList = articlePictureMapper.selectByExample(articlePictureExample);
    ArticlePicture articlePicture = pictureList.get(0);
    articlePicture.setPictureUrl(articleDto.getPictureUrl());
    articlePictureMapper.updateByPrimaryKeySelective(articlePicture);

    //4.文章-內容表
    ArticleContentExample articleContentExample = new ArticleContentExample();
    articleContentExample.or().andArticleIdEqualTo(id);
    List<ArticleContent> contentList = articleContentMapper.selectByExample(articleContentExample);
    ArticleContent articleContent = contentList.get(0);

    articleContent.setContent(articleDto.getContent());

    articleContentMapper.updateByPrimaryKeyWithBLOBs(articleContent);
}

1.3 獲取一篇文章(根據文章Id)

@Override

public ArticleDto getOneById(Long id) {
    ArticleDto articleDto = new ArticleDto();

    //1. 文章信息表內的信息 填充 到 Dto
    ArticleInfo articleInfo = articleInfoMapper.selectByPrimaryKey(id);

    //1.1 增長瀏覽量 + 1
    ArticleInfo info = new ArticleInfo();
    info.setId(id);
    info.setTraffic(articleInfo.getTraffic()+1);
    articleInfoMapper.updateByPrimaryKeySelective(info);

    articleDto.setId(id);
    articleDto.setTitle(articleInfo.getTitle());
    articleDto.setSummary(articleInfo.getSummary());
    articleDto.setIsTop(articleInfo.getIsTop());
    //沒用到緩存,因此訪問量統計仍是在SQL操做這裏增長把(一個博客,作啥緩存啊)
    articleDto.setCreateBy(articleInfo.getCreateBy());
    articleDto.setTraffic(articleInfo.getTraffic()+1);

    //2. 文章內容表內的信息 填充 到 Dto
    ArticleContentExample articleContentExample = new ArticleContentExample();
    articleContentExample.or().andArticleIdEqualTo(id);
    List<ArticleContent> contentList = articleContentMapper.selectByExampleWithBLOBs(articleContentExample);
    ArticleContent articleContent = contentList.get(0);
    articleDto.setContent(articleContent.getContent());
    //填充關聯表的主鍵,其餘業務可能經過調用getOneById 拿到Dto裏的這個主鍵
    articleDto.setArticleContentId(articleContent.getId());

    //3.文章-分類表內的信息 填充 到 Dto
    ArticleCategoryExample articleCategoryExample = new ArticleCategoryExample();
    articleCategoryExample.or().andArticleIdEqualTo(id);
    List<ArticleCategory> articleCategories = articleCategoryMapper.selectByExample(articleCategoryExample);
    ArticleCategory articleCategory = articleCategories.get(0);

    //3.1設置文章所屬的分類Id+ 從表主鍵 --從表
    Long sortId = articleCategory.getSortId();
    articleDto.setCategoryId(sortId);
    articleDto.setArticleCategoryId(articleCategory.getId());

    //3.2找分類主表 --設置分類信息
    CategoryInfo categoryInfo = categoryInfoMapper.selectByPrimaryKey(sortId);
    articleDto.setCategoryName(categoryInfo.getName());
    articleDto.setCategoryNumber(categoryInfo.getNumber());

    //4.文章-題圖表
    ArticlePictureExample articlePictureExample = new ArticlePictureExample();
    articlePictureExample.or().andArticleIdEqualTo(id);
    List<ArticlePicture> articlePictures = articlePictureMapper.selectByExample(articlePictureExample);
    ArticlePicture picture = articlePictures.get(0);
    //4.1設置圖片Dto
    articleDto.setArticlePictureId(picture.getId());
    articleDto.setPictureUrl(picture.getPictureUrl());

    return articleDto;
}

1.4 找出分類下全部的文章信息

@Override
public List<ArticleWithPictureDto> listByCategoryId(Long id) {
    //1. 先找出分類下全部的文章
    ArticleCategoryExample articleCategoryExample = new ArticleCategoryExample();
    articleCategoryExample.or().andSortIdEqualTo(id);
    List<ArticleCategory> articleCategories = articleCategoryMapper.selectByExample(articleCategoryExample);

    ArrayList<ArticleWithPictureDto> list = new ArrayList<>();
    //1.1遍歷
    for(ArticleCategory articleCategory:articleCategories){
        ArticleWithPictureDto articleWithPictureDto = new ArticleWithPictureDto();

        //1.1.1 取出文章
        Long articleId = articleCategory.getArticleId();
        ArticleInfo articleInfo = articleInfoMapper.selectByPrimaryKey(articleId);

        //1.1.2 取出文章對應的圖片url
        ArticlePictureExample articlePictureExample = new ArticlePictureExample();
        articlePictureExample.or().andArticleIdEqualTo(articleId);
        List<ArticlePicture> articlePictures = articlePictureMapper.selectByExample(articlePictureExample);
        ArticlePicture picture = articlePictures.get(0);

        articleWithPictureDto.setId(articleId);
        articleWithPictureDto.setArticlePictureId(picture.getId());
        articleWithPictureDto.setTitle(articleInfo.getTitle());
        articleWithPictureDto.setSummary(articleInfo.getSummary());
        articleWithPictureDto.setIsTop(articleInfo.getIsTop());
        articleWithPictureDto.setTraffic(articleInfo.getTraffic());

        articleWithPictureDto.setPictureUrl(picture.getPictureUrl());

        list.add(articleWithPictureDto);
    }

    return list;
}

 

 

PS:還有一系列的接口開發在源碼中查看吧

 

5.3 DTO層開發

 

 

圖19

 

用於封裝了多個實體類的屬性,用於先後端交互的總體屬性封裝,便捷實用的進行

JSON數據交互

 

 

 

5.4 Controller層開發

 

 

圖20

 

BaseController爲後臺控制器

ForeController爲前臺控制器

 

好比更新文章的Controller

 

 

圖21

 

 

 

 

圖22

 

 

  

 

圖23

 

6. 前端開發過程

6.1 登錄頁開發

Login.html

 

 

圖24

 

 

圖25

 

效果以下:

 

 

圖26

還用了輪播圖的形式

 

 

圖27

 

url:爲toLogin,代碼實現爲:

 

 

圖28

 

6.2 分類管理頁開發

 

 

圖29

 

 

Category.html

 

 

圖30

 

 

圖31

 

圖32

 

 

效果以下:

 

 

圖33

6.3博文管理頁開發

 

 

 

圖34

 

 

圖35

 

效果以下:

 

 

圖36

 

 

 

圖37

 

 

PS:還用了動態的placeholder來保存更新前的數據,在此上面作修改。這個是模態框

 

 

圖38

 

PS:這些分類都是動態從數據庫里拉過來的,不是靜態的!!!

 

6.4 博客首頁開發

 

 

圖39

 

 

圖40

導航欄 + 最新幾篇文章的輪播圖(點擊可進入文章詳情頁)

 

6.5 博客文章詳情頁開發

代碼以下:

 

 

圖41

 

效果以下:

 

 

圖42

 

 

 

圖43

 

PS:這些都是動態的,非靜態頁面,靜態就沒有意義了

 

6.6 博文分類頁面

 

 

圖44

效果以下:

 

 

 

 

 

 

圖45

 

 

 

圖46

 

 

圖47

 

PS:還設置了動態的分類選中效果,根據不一樣的分類顯示不一樣的文章信息,點擊文章信息,便可進入文章詳情頁

7. 項目心得總結

1. 之後要多加練習,多作項目來熟悉通常web項目的整個開發流程,好比搭建項目的環境,相應框架的配置。

2. 還要多總結開發過程當中遇到的bug和一些細節的處理,好比這個效果怎麼實現,這個功能用什麼方法實現,要寫個筆記好好記錄一下,方便之後的開發,

不須要再次查詢百度或谷歌。

3. 還要重視數據庫,不要覺得只會寫幾條增刪改查的sql語句便可,關鍵是

對數據庫的設計,對錶的編排,關聯表的運用,如何設計表結構讓程序跑的更快,開發更方便。還要重視數據庫的索引技術,分表分庫,之後均可以深造

4. 不要停留在只知道這個技術,而不去動手實踐,這些知識不實踐就會忘。

好比Mybatis配置文件和框架整合,或Spring的配置,或Springboot的錯誤處理頁面的定製,或者Thymeleaf模板引擎的熟練使用(雖然先後端分離以及不用這種相似JSP的模板引擎了),或者是事務的添加,又或者先後端密碼校驗的加密處理,以及前端CSS的佈局,樣式的熟練掌握,bootstrap經常使用的樣式的實現,vue.js的細節和bug等等。

5.可是又不能停留在只會用這些表面的框架和技術,而不懂其原理,基礎和原理是很重要的,對於後期的debug排查錯誤,對原理熟悉的,能夠很快的找尋出是哪方面致使的問題。並且Spring框架的IOC和AOP概念貫穿了整個Spring全家桶的產品,因此必定要深入理解和練習,還有對於Java基礎的提升,好比反射技術(對應於Spring中的AOP實現,事務的實現,自動配置類的加載,動態代理等等)都用到反射技術。

相關文章
相關標籤/搜索