欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

个人博客开发笔记

程序员文章站 2022-04-08 13:58:10
1. 选题背景 1.1 选题概述 很早之前就想要写一个自己的博客了,趁着现在学校安排的web课设选题,决定把它给做出来,也顺便复习一下曾经学过的一些web技术和框架。 只有动手做完之后,才能发现不足之处,比如一些细节的处理,大体的表设计,业务逻辑接口的编写,以及一些bug的存在,还可以让自己更熟练的 ......

 

 

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实现,事务的实现,自动配置类的加载,动态代理等等)都用到反射技术。