很早以前就想要寫一個本身的博客了,趁着如今學校安排的web課設選題,決定把它給作出來,也順便複習一下曾經學過的一些web技術和框架。css
只有動手作完以後,才能發現不足之處,好比一些細節的處理,大致的表設計,業務邏輯接口的編寫,以及一些bug的存在,還可讓本身更熟練的開發各類功能的網頁。html
後端技術:Springboot + Spring + Mybatis + Druid + Swagger + 熱部署 + Mysql前端
前端技術:html+css+js+Jquery+bootstrap+vue.jsvue
前端需求分析:web
①簡潔/美觀——我的很喜歡像Mac那樣的簡潔風,越簡單越好,固然也得好看;(首頁輪播圖+分類左右排版+導航欄+博文詳情頁)sql
②最好是單頁面——單頁面的目的一方面是爲了簡潔,另外一方面也是爲了實現起來比較簡單;(單頁面就不用vue.js作SPA了,仍是經過a標籤原地跳轉的方式模擬單頁面)數據庫
③自適應——至少能適配常見的手機分辨率吧,我可不但願本身的博客存在顯示差別性的問題;(Bootstrap的柵格系統+CSS媒體查詢+配合JS實現)bootstrap
可能出現的頁面如圖:後端
圖1api
PS:留言頁和關於頁,簡歷頁之後再實現
圖2
PS:評論功能暫未實現,只實現了博文分類(CURD)和文章管理(常見的CURD)的功能
圖3
PS:數據統計模塊暫未實現
我的博客系統數據結構設計:
表1
PS:此圖用Navicat 的表逆向模型 的功能實現
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:題圖用於前端首頁的輪播圖和文章詳情頁文章的配圖
首頁:
圖4
文章分類頁:
圖5
文章詳情頁:
圖6
圖7
圖8
PS:上圖爲從Springboot官網Springboot initializer 後搭建的Springboot項目,在上面進行
二次開發後,最後的開發的目錄結構如上
Maven工程的依賴爲:Pom文件以下
圖9
對項目目錄結構進行簡要說明:
1. Springboot繼承Mybatis是經過依賴starter來集成Mybatis框架的
Pom依賴以下:
圖10
2. Mybatis逆向工程:
圖11
PS:逆向工程用於自動根據配置的數據庫來生成Entity類,和mapper映射文件和mapper映射接口(用來操做數據庫的),至關於自動生成了一大堆的sql語句(增刪改查),上一層直接調用DAO層的接口便可訪問數據庫 (鬆耦合)
1.概要:RestfulAPI是一種HTTP請求的規範,能夠用到put請求表示更新數據,
Delete請求表示刪除數據,post請求表示添加數據,get請求表示查詢數據,合理的運用
HTTP方法來完成請求,避免了之前WEB開發只用get 和post請求的這種不規範設計
格式爲下圖:
圖12
2. Swaager文檔用於圖形化RestfulAPI風格的接口,效果以下圖:
圖13
1. 採用了Druid數據庫鏈接池(當今最實用,效率也很高的阿里巴巴的鏈接池)
圖14
2. 日誌配置: Springboot天生集成了logback日誌,因此不須要再從新導入新的日誌框架,
直接複製日誌配置文件便可,但注意名字要按格式來,才能被加載,如圖:
圖15
登錄攔截器代碼以下:(還用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;
}
}
圖16
圖17
這些實體類對應的是Mysql中創建的表的名字,屬性名字爲表的字段名
圖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:還有一系列的接口開發在源碼中查看吧
圖19
用於封裝了多個實體類的屬性,用於先後端交互的總體屬性封裝,便捷實用的進行
JSON數據交互
圖20
BaseController爲後臺控制器
ForeController爲前臺控制器
好比更新文章的Controller
圖21
圖22
圖23
Login.html
圖24
圖25
效果以下:
圖26
還用了輪播圖的形式
圖27
url:爲toLogin,代碼實現爲:
圖28
圖29
Category.html
圖30
圖31
圖32
效果以下:
圖33
圖34
圖35
效果以下:
圖36
圖37
PS:還用了動態的placeholder來保存更新前的數據,在此上面作修改。這個是模態框
圖38
PS:這些分類都是動態從數據庫里拉過來的,不是靜態的!!!
圖39
圖40
導航欄 + 最新幾篇文章的輪播圖(點擊可進入文章詳情頁)
代碼以下:
圖41
效果以下:
圖42
圖43
PS:這些都是動態的,非靜態頁面,靜態就沒有意義了
圖44
效果以下:
圖45
圖46
圖47
PS:還設置了動態的分類選中效果,根據不一樣的分類顯示不一樣的文章信息,點擊文章信息,便可進入文章詳情頁
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實現,事務的實現,自動配置類的加載,動態代理等等)都用到反射技術。