個人博客開發筆記


 

 

1. 選題背景

1.1 選題概述

很早之前就想要寫一個自己的博客了,趁着現在學校安排的web課設選題,決定把它給做出來,也順便復習一下曾經學過的一些web技術和框架。

只有動手做完之后,才能發現不足之處,比如一些細節的處理,大體的表設計,業務邏輯接口的編寫,以及一些bug的存在,還可以讓自己更熟練的開發各種功能的網頁。

1.2 技術選型

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

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

2. 總體系統功能模塊

2.1 需求分析

前端需求分析:

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

最好是單頁面——單頁面的目的一方面是為了簡潔,另一方面也是為了實現起來比較簡單;(單頁面就不用vue.js做SPA了,還是通過a標簽原地跳轉的方式模擬單頁面)

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

 

可能出現的頁面如圖:

 

 

圖1

 

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實現,事務的實現,自動配置類的加載,動態代理等等)都用到反射技術。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM