很早以前就想要写一个本身的博客了,趁着如今学校安排的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实现,事务的实现,自动配置类的加载,动态代理等等)都用到反射技术。