博客系统实现

一.准备工作

1.创建项目,把前端写好的博客静态页面拷贝到webapp目录中

2.引入依赖,这里主要用到servlet,mysql5.1.47,jacson2.15.0

3.找到右上角的edit configurations->smartTomcat->进行配置

4.数据库设计:设计对应的表结构,并把数据库相关代码进行封装。如何设计表结构?首先确定实体,其次确认实体之间的关系。这里的实体就是博客和用户,所以就是一个blog表,一个user表。两者是一对多的关系->一个博客只能属于一个用户,然而一个用户可以拥有多篇博客。所以应在博客表中引入userid。

1.对数据库的操作

先把数据库创建好,并创建好表,为了方便创建,先在webapp下面新建一个文件db.sql,然后再这里面把sql语句编辑好,然后复制粘贴到MySQL中。这样方便修改语法错误(其实直接在MySQL中创建也可以)

然后我们把对数据库进行操作的代码进行封装。我们将对数据库的操作都放到model包中。所以在java目录下新建一个model包

建立连接等通用操作

然后再在model包中新建一个DButil类,用来封装数据库建立连接的代码。这是由于接下来的代码中有很多个servlet都需要使用库,所以就需要有单独的地方把DataSourse的操作进行封装,而不是只放到某个servlet的init中

public class DButil {
    private static DataSource ds=null;
    private static DataSource getDataSourse(){
        if(ds==null){
            ds=new MysqlDataSource();
            ((MysqlDataSource)ds).setUrl("jdbc:mysql//127.0.0.1:3306/blog_system?charset=utf8&useSSL=false");
            ((MysqlDataSource)ds).setUser("root");
            ((MysqlDataSource)ds).setPassword("20050430zyh");
        }
        return ds;
    }
    public Connection getConnection() throws SQLException {
        return getDataSourse().getConnection();
    }
    public static void close(ResultSet resultSet, PreparedStatement statement,Connection connection){
        if(resultSet!=null){
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(statement!=null){
            try {
                statement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(connection!=null){
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

如上,对外只开放建立连接和关闭的操作,建立数据源的操作直接封装起来

两个类表示两张表

每个表都需要专门搞一个类来表示

public class Blog {
    private int blogid;
    private String content;
    private Timestamp postTime;
    private int userid;

    public int getBlogid() {
        return blogid;
    }

    public void setBlogid(int blogid) {
        this.blogid = blogid;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public Timestamp getPostTime() {
        return postTime;
    }

    public void setPostTime(Timestamp postTime) {
        this.postTime = postTime;
    }

    public int getUserid() {
        return userid;
    }

    public void setUserid(int userid) {
        this.userid = userid;
    }

    @Override
    public String toString() {
        return "Blog{" +
                "blogid=" + blogid +
                ", content='" + content + '\'' +
                ", postTime=" + postTime +
                ", userid=" + userid +
                '}';
    }
}
public class User {
    private int userid;
    private String username;
    private String password;

    public int getUserid() {
        return userid;
    }

    public void setUserid(int userid) {
        this.userid = userid;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "userid=" + userid +
                ", username='" + username + '\'' +
                '}';
    }
}

两个类完成对两张表的操作

就用BlogDao和UserDao来命名。Dao,就是Data Access Object(数据访问对象)通过这两个类的对象完成对数据的操作

public class BlogDao {
    //新增,提交博客
    public void insert(Blog blog){
        Connection connection=null;
        PreparedStatement statement=null;
        try {
            //建立连接
            connection=DButil.getConnection();
            //构造sql语句
            String sql="insert into blog values (null,?,?,now(),?)";
            statement=connection.prepareStatement(sql);
            statement.setString(1,blog.getTitle());
            statement.setString(2,blog.getContent());
            statement.setString(3,""+blog.getUserid());
            //执行sql
            statement.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            DButil.close(null,statement,connection);
        }
    }
    //查询博客列表(博客列表页),把库中所有博客都拿到
    public List<Blog> getBlogs(){
        Connection connection=null;
        PreparedStatement statement=null;
        ResultSet resultSet=null;
        List<Blog> blogList=new ArrayList<>();
        try {
            connection=DButil.getConnection();
            String sql="select * from blog";
            statement=connection.prepareStatement(sql);
            resultSet=statement.executeQuery();
            while(resultSet.next()){
                Blog blog=new Blog();
                blog.setBlogid(resultSet.getInt("blogid"));
                blog.setTitle(resultSet.getString("title"));
                String content=resultSet.getString("content");
                if(content.length()>100){
                    content=content.substring(100);
                }
                blog.setContent(content);
                blog.setPostTime(resultSet.getTimestamp("postTime"));
                blog.setUserid(resultSet.getInt("userid"));
                blogList.add(blog);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            DButil.close(resultSet,statement,connection);
        }
        return blogList;
    }
    //根据博客id查询指定博客
    public Blog getBlog(int blogid){
        Connection connection=null;
        PreparedStatement statement=null;
        ResultSet resultSet=null;
        Blog blog=new Blog();
        try {
            connection=DButil.getConnection();
            String sql="select * from blog where blogid=?";
            statement=connection.prepareStatement(sql);
            statement.setInt(1,blogid);
            resultSet=statement.executeQuery();
            if(resultSet.next()){
                blog.setBlogid(resultSet.getInt("blogid"));
                blog.setTitle(resultSet.getString("title"));
                blog.setContent(resultSet.getString("content"));
                blog.setPostTime(resultSet.getTimestamp("postTime"));
                blog.setUserid(resultSet.getInt("userid"));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            DButil.close(resultSet,statement,connection);
        }
        return blog;
    }
    //根据博客id删除指定博客
    public void deleteBlog(int blogid){
        Connection connection=null;
        PreparedStatement statement=null;
        try {
            connection=DButil.getConnection();
            String sql="delete from blog where blogid=?";
            statement=connection.prepareStatement(sql);
            statement.setInt(1,blogid);
            statement.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            DButil.close(null,statement,connection);
        }
    }
}

注意,在getBlogs这个方法中,获取正文时,不要把全部正文内容都拿出来显示到列表页上,只需要拿出一部分即可,所以会有一个截断操作

在getBlog操作中,由于blogid时主键,所以只能查询到一篇博客,所以不需要while循环,只需要一个if判断,同时,正文不用截断!!!

public class UserDao {
    //根据userid查询对应用户信息(获取用户信息)
    public User getUserByid(int userid){
        Connection connection=null;
        PreparedStatement statement=null;
        ResultSet resultSet=null;
        User user=new User();
        try {
            connection=DButil.getConnection();
            String sql="select * from user where userid=?";
            statement=connection.prepareStatement(sql);
            statement.setInt(1,userid);
            resultSet=statement.executeQuery();
            if(resultSet.next()){
                user.setUserid(resultSet.getInt("userid"));
                user.setUsername(resultSet.getString("usernsme"));
                user.setPassword(resultSet.getString("password"));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            DButil.close(resultSet,statement,connection);
        }
        return user;
    }
    //根据usernsme查询对应用户信息
    public User getUserByname(String username){
        Connection connection=null;
        PreparedStatement statement=null;
        ResultSet resultSet=null;
        User user=new User();
        try {
            connection=DButil.getConnection();
            String sql="select * from user where userid=?";
            statement=connection.prepareStatement(sql);
            statement.setString(1,username);
            resultSet=statement.executeQuery();
            if(resultSet.next()){
                user.setUserid(resultSet.getInt("userid"));
                user.setUsername(resultSet.getString("usernsme"));
                user.setPassword(resultSet.getString("password"));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            DButil.close(resultSet,statement,connection);
        }
        return user;
    }
}

上面这两方法就都很相似。

二.前后端交互,实现博客功能

1.获取博客列表页

在博客列表页加载时,通过ajax方式给服务器发送请求,从服务器数据库中拿到数据显示到页面上

约定前后端交互接口

请求

GET /blog

响应

HTTP/1.1 200 OK

Content-Type:application/json

[

     {

         blogid:1

         title:' '

         content:

         postTime:

         userid:

      }

……

]

让浏览器给服务器发送请求

我们要对前端的blog_list.html使用ajax进行修改,所以首先要引入jquery库:也就是在head标签内加一个script标签:<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"></script>

编写前把写死的那部分标签给注释掉,然后在</body>上方进行代码编写,加一个<script>标签,在开始和结束标签之间进行编写:

我们直接将代码封装到函数中,但一定要记住调用!!

服务器处理请求

我们将所有服务器的代码都放到一个包里面,也就是servlet包,然后在改包中线新建一个BlogServlet类。由于对于博客列表页发送的请求的处理方式是要将表中数据按照json格式字符串返回,所以肯定要用到jackson,所以要定义一个成员变量ObjectMapper,最终代码如下:

让前端代码处理响应

这里的关键是根据服务器返回的博客信息去构造博客列表页!!我们使用的是比较朴素的方式来构造博客列表页,也就是基于dom api。dom api是浏览器提供的标准的api,不属于任何第三方库/框架。

如上,我们要构建出类似于这样结构的标签,这些标签有着包含关系以及并列关系,最大的标签是containner-right,其他所有标签都是在它里面。它里面就是一个一个博客,其中每个博客标签里面又并列包含了标题,发布时间,摘要,查看全文按钮

如上。由于在像一个中设置了正文格式为json,所以当把响应返回给客户端后,body部分就自动被解析成了一个json对象,这里由于响应是一个对象列表转化成的json字符串数组,所以是被解析成了json对象数组,所以要通过for循环遍历数组,拿出每一个blog对象进行遍历。

其中,在创建查看全文按钮时,我们的目的是创建一个类似于”查看全文>>"这样的标签,但是大于号要想在html中写出,就必须使用转义字符&gt;小于号就是&lt。因为html标签就是<>构成的。

其次,一点击查看全文,就要进入博客详情页,所以要用到超链接标签,也就是a标签。其中的href属性就是在描述点击后会跳转到哪个页面。同时,点击a标签就会自动发送一个get请求!!!之前我们提到过。

解决出现的问题

到这里,博客列表页就构造完了。我们来测试一下,发现几个问题:1.时间显示不对,现在显示的是时间戳,但我们希望是某年某月某日几点。2.返回的数据的顺序问题,正常来说,最上面的博客应该是最新的博客,但是我们现在再插入一篇博客后,它会被显示到博客列表页的最下面,而不是最上面。

解决时间问题

抓包观察是哪里出现了问题,仔细观察发现在获取博客数据的响应中的postTime就是时间戳。所以是resp.getWriter().write(respJson)出问题了。所以关键就是respJson,它是怎么获取到的呢?

1.首先jackson发现,blogList是一个list,于是就循环遍历了

2.它针对每一个元素(Blog对象)通过反射的方式获取到属性名字,然后再用get方法拿到属性的值。

所以关键就在于修改postTime的get方法,如下:

java标准库中提供了SimpleDateFormet类,来完成时间戳到格式化时间的转化。此类的使用很复杂,不需要背,每次使用前都查一下就行。创建SDF对象,传入指定字符串,用来描述当前时间日期的具体格式,然后使用format方法,里面的参数可以是时间戳,也可以是date对象

解决顺序问题

我们是到,数据库查询到结果的顺序其实是随机的,所以要想固定顺序,就要使用orderby关键字,并按照发布时间降序排列,如下选中部分。

2.获取博客详情页

这个就是在点击查看全文后,就会发送get请求并跳转到了博客详情页(这是我们刚刚编写的a标签实现的逻辑),那我们现在要实现的就是根据请求中的blogid去查询到对应的博客并返回给前端

约定前后端交互接口

请求

GET /blog?blogid=1

响应

HTTP/1.1 200 OK

Content-Type:application/json

{

      blogid:1

      title:

      content:

      postTime:

      userid:

}

前端使用ajax发起请求

打开blog_list.html,在</div>下面添加script标签,进行代码编写:

url中的blogid是如何得到的呢?这里就是用location.search来拿到当前页面的url中的queryString(因为在设置a标签时,其中的href属性中就带有?blogid=……)。(在浏览器中,ctrl+shift+i就可以打开控制台,输入location.search就可以看到当前页面的queryString语句)注意,location.search拿到的是整个query String语句,包括了问号!!

服务器处理上述请求

上面请求的路径还是blog,所以还是在BlogServlet中处理get方法。但是在获取博客列表页的时候已经写了一个doGet方法了呀,这该怎么办?找两者的不同,发现上面的请求时没有blogid的,而当前这个请求是有blogid的。这就是区别。所以进行如下编辑:

前端将响应数据构造成html片段

首先把containner-right里面的内容给注释掉

然后按这个格式去构造片段:

测试一下,发现了两个问题:

1.写完代码之后,会发现点击某个博客,有点博客详情页里还是那些注释掉的内容。这个问题是浏览器缓存引起的。浏览器在加载页面时,是通过网络获取资源的,但是网络速度很慢,所以浏览器会把已经加载是页面在本地硬盘中保存一份,后续再次访问同一个页面时,就不通过网络加载,而是直接加载本地硬盘中的这一份。那这如何克服呢?前端有专业的解决方案,不过咱们不用关心,只需要ctrl+F5刷新一下即可。

当前的详情页,虽然能够显示正文了,但是显示的正文是markdown的原始数据。正常应该显示md渲染后的效果。这就需要通过引入第三方库来完成:

引入依赖:在blog_editor.html的</head>前面粘贴

然后在刚才的构造页面正文的代码进行修改:

这个editormd是editor.md官方文档上提供的一个全局变量。此方法的第一个参数必须是一个标签的id,但是content标签没有设置id,所以我们要在上面的content标签内部加上一个id属性。第二个参数就是一个js对象:

最终这个函数的效果就是把blog.content这里的原始md数据渲染成html放到content div的内容中

3.实现登录

在login.html点击登陆后,应该给服务器发起一个http请求。服务器处理上述请求,读取用户名和密码,在库中查询匹配,若正确,则成功登录,创建会话,跳转到博客列表页(所以登陆成功就直接进行重定向跳转

约定前后端交互接口

请求

POST /login

Content-Type:application/x-www-form-urlencoded

username= &password=

响应

HTTP/1.1 302

Location:blog_list.html

注意,若使用ajax发送请求,就要再写代码完成跳转;但是使用form表单的话,只要提交成功,就可以直接使用302完成页面跳转

前端发送请求

打开login.html,对下面的代码进行逻辑修改:

修改完就是:

服务器处理请求返回响应

先将请求中的用户名和密码获取到,判断是否为空。然后根据用户名去获取对应的用户,要是没获取到,就说明用户名错误了,若获取到,就说明用户名是对的。然后拿着刚刚获取到的用户的密码和请求中的密码对照一下,要是不一致,就说明密码填错了(不过,不管是密码写错买时用户名写错,我们都统一提示用户名或密码不正确)。最后创建会话,还是参数是true,并且设置会话中的属性user就是user对象。

4.强制要求登录

这个功能就是在博客的列表页,详情页,编辑页去判定当前用户是否已经登陆。若未登录,就强制要求跳转到登录页。所以要在这几个页面中,在页面加载时,给服务器发送ajax请求,从服务器获取到一个登录状态即可

约定前后端交互接口

请求

GET /login

响应

HTTP/1.1 200 OK ->表示成功登录

HTTP/1.1 403       ->表示登录失败

响应的格式可以有很多种,比如返回的都是200,但是正文不一样

前端发起ajax请求

打开blog_list.html,在function getBlogs()下面再写一个方法:

这里success是在返回2开头的状态码时会执行,error则是在返回除2以外的数字开头的状态码就hi执行。location.assign就是强制要求跳转到login.html,这是前端页面跳转的方式。那为啥不待会儿在服务器直接返回个302,直接跳转到登录页呢?因为302这样的响应回到ajax中的error中后,无法被ajax直接处理。除非通过提交form表单或者点击a标签这种触发的http请求,浏览器才可以直接跳转。

处理响应的过程比较简单,所以一起写了

服务器处理请求

注意,未登录状态的判定不单单是看会话是否存在,还有看该会话中是否存放着user(为什么?待会实现退出登录的时候就知道了)

测试一下:直接打开列表页,在列表页刷新一下,就发现跳转到了登录页。

但有个问题,虽然已经登陆了,但是一旦重启服务器,就会被判定为未登录状态。因为登陆状态是根据服务器的内存中的session确定的,重启服务器意味着之前的内存要释放。其实这一设定并不科学,如何解决?以后再说。

现在,博客编辑页和详情页也要执行上述逻辑,所以在vscode中创建一个新目录js,js中创建新文件app.js,将getLoginStqatus函数复制过去。

然后在每个页面的<script>上再来个<script src="js/app.js"></script>,然后在下面的script中调用getLoginStatus即可:

这就是把一些公共的js代码单独提取出来放到某个.js文件中,然后通过html中的script标签来引用这些文件内容,此时就可以在html中调用对应的公共代码了。

5.显示用户信息

在列表页显示当前登录的用户的信息,在详情页显示作者信息。在页面加载时,给服务器发送ajax请求,服务器返回对应的用户信息

约定前后端交互接口

请求

GET /userinfo(详情页则是authorid)

响应

HTTP/1.1 200 OK

Type:application/json

{

        userid:

        username:

}

前端发起ajax请求

这是博客列表页

这是博客详情页,通过location.search查到对应的博客id,从而查找对应的作者

服务器处理上述请求

首先是针对博客列表页的。最终要将user对象转成json格式的字符串,所以要有一个ObjectMapper。然后,先取出会话,看看会话是否为空,或者会话中是否存储了user,要是没有,就说明未登录,反之就是已经登录了。最后,在显示用户信息时,为保证安全,就应该将密码置为空!!

最后就是处理博客详情页的请求,首先看看请求中是否带有blogid,要不然没法查询。然后就是根据blogid查到对应的博客。再根据博客找到作者,最后返回作者信息

前端将响应构造成html片段

首先是列表页:

应该将用户名显示到<h3>标签处,所以代码如下:

注意,h3标签没有class属性,所以前面不用加点。

然后是详情页,也是要设置到h3标签:

6.注销/退出登录

在博客列表页/编辑页/详情页都有注销按钮,这就是一个a标签,其中有一个href属性,点击就能够触发一个http请求,就能引起浏览器跳转到另一个页面。

我们现在要做的就是修改a标签,让用户一点击它,就能触发一个http请求,服务器收到了就会把会话中的这个user删除掉并跳转到登录页。(那为啥不直接把会话给删除呢?因为servlet没有提供删除会话的操作!!!不过也可以给会话设置一个过期时间,但是不够优雅。session提供了removeAttribute的方法)

约定前后端交互接口

请求

GET /logout

响应

HTTP/1.1 302

location:login.html

前端发起请求

不用写ajax,直接给a标签添加一个href属性即可

服务器处理请求

7.发布博客

输入标题,正文,期望一点击提交就能够发到服务器这边进行保存

约定前后端交互接口

请求

POST /blog

content-Type:application/x-www-form-urlencoded

title= &content=

响应

HTTP/1.1 302

location:blog_list.html

前端发起请求

就是修改这一块,变成:

也就是给title标签加上name属性。那么如何给正文加上name属性?就是在 <div id="editor">这个标签下加上<textarea name="content" style="display:none"></textarea>,textarea就是一个多行编辑器容器,就把name属性加到它上面即可,然后在初始化editormd对象时加上一个对应的属性即可,也就是在下面的图片中的var editor 中加一个属性saveHTMLToTextarea并赋值为true:

上面这个图片修改为:

表示会把用户在编辑器里面的内容自动保存到textarea中,这样,一点提交,就会在form表单中有拷贝

服务器处理请求

相关推荐

  1. 技术提升实战:打造个人系统

    2024-04-09 22:16:03       42 阅读
  2. trpc-go 系统

    2024-04-09 22:16:03       41 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-09 22:16:03       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-09 22:16:03       100 阅读
  3. 在Django里面运行非项目文件

    2024-04-09 22:16:03       82 阅读
  4. Python语言-面向对象

    2024-04-09 22:16:03       91 阅读

热门阅读

  1. Redis是单线程,但为什么快

    2024-04-09 22:16:03       33 阅读
  2. vue-pdf只显示一页问题解决

    2024-04-09 22:16:03       35 阅读
  3. 数据驱动决策的秘密武器:一探FineBI的核心功能

    2024-04-09 22:16:03       40 阅读
  4. 边界框转化

    2024-04-09 22:16:03       38 阅读
  5. Istio-learning-note-about-Traffic Shifting(三)

    2024-04-09 22:16:03       40 阅读
  6. 从0开始复习python~

    2024-04-09 22:16:03       31 阅读
  7. InfluxDB2的数据查询示例

    2024-04-09 22:16:03       33 阅读
  8. Redis数据倾斜

    2024-04-09 22:16:03       36 阅读