网站建设需要上税吗,海淀网站建设电话,网站内容建设运维服务,建材在线目录 一、准备工作
二、数据库的表设计
三、封装JDBC数据库操作
1、创建数据表对应的实体类
2、封装增删改查操作 四、前后端交互逻辑的实现
1、博客列表页
1.1、展示博客列表
1.2、博客详情页
1.3、登录页面
1.4、强制要求用户登录#xff0c;检查用户的登录状态
…目录 一、准备工作
二、数据库的表设计
三、封装JDBC数据库操作
1、创建数据表对应的实体类
2、封装增删改查操作 四、前后端交互逻辑的实现
1、博客列表页
1.1、展示博客列表
1.2、博客详情页
1.3、登录页面
1.4、强制要求用户登录检查用户的登录状态
1.5、实现显示用户信息的功能
1.5.1、针对列表页进行处理
1.5.2、针对详情页进行处理
1.6、用户退出登录功能
1.7、实现发布博客功能 一、准备工作 1️⃣创建新的maven项目
2️⃣引入依赖
我们需要使用的依赖有servlet、Jackson、MySQL。在中央仓库中搜索Java Servlet API选择3.1.0版本将maven中的代码复制到pom.xml中。 搜索点击Jackson Databind引入Jackson没有特定的版本随便选一个版本的maven代码复制到pom.xml的dependencies/dependencies标签中。 引入数据库搜索MySQL选择5.1版本的maven中的代码复制到pom.xml的dependencies/dependencies标签中。 引入依赖之后可能代码中会出现爆红这个时候点击刷新出发一下下载即可。
3️⃣创建必要的目录 目录创建好之后需要 给web.xml中写入指定配置。
!DOCTYPE web-app PUBLIC-//Sun Microsystems, Inc.//DTD Web Application 2.3//ENhttp://java.sun.com/dtd/web-app_2_3.dtd web-appdisplay-nameArchetype Created Web Application/display-name
/web-app 二、数据库的表设计
我们之前的设计的博客前端页面中有博客详情页博客编辑页博客列表页博客登录页。这些页面中需要使用数据存储的数据主要有两部分一个是编写的博客数据一个是用户数据。这里的建表操作和我们之前的直接在数据库中间表的方式有一些区别。首先我们在blog_system项目的main目录下创建一个.sql文件用来保存建表的过程。如果我们写的服务器需要部署到不同的机器上就需要在对应的主机上也将数据库建号。这个时候我们只需要将这里的代码拷贝到数据中就可以了。
-- 一般对于建表的sql都会单独用一个 .sql文件来保存
-- 后续程序可能需要在不同的主机上部署部署的时候就需要在对应的主机上把数据库也给建好。
-- 把建表sql保存好方便在不同的机器上进行建库建表。--表示当前电脑的数据库中不存在blog_sysyem这个库就创建存在就不创建了。
create database if not exists blog_system;use blog_system;--表示如果这个库中有blog表就先删除这个表
drop table if exists blog;
create table blog(blogId int primary key auto_increment,title varchar(128),content varchar(4096), --正文userId int,posTime datetime
);drop table if exists user;
create table user(userId int primary key auto_increment,username varchar(50) unique,password varchar(50)
);
上述我们在建表的时候先删除库中存在的我们要创建的表是为了清空之前残留的数据。
由于我们的博客系统并没有实现注册功能所以小编事先在数据库中存入两个用户信息。 三、封装JDBC数据库操作
JDBC中提供了简单的API但是我们写的类的太多如果每个类都需要初始化数据源、建立连接、关闭资源那就太麻烦了。所以这里我们将这些操作封装到一个类中需要使用的时候直接调用封装好的方法。
package model;import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;public class DBUtil {//这个类中提供DataSourceDataSource对于一个项目来说有一个就行了单例//DataSource是用来描述数据源的也就是用来描述数据库服务器在哪里。//实例并初始化数据源private static volatile DataSource dataSource null;private static DataSource getDataSource(){if(dataSource null){synchronized (DBUtil.class){if(dataSource null){dataSource new MysqlDataSource();((MysqlDataSource)dataSource).setUrl(jdbc:mysql://127.0.0.1:3306/blog_system?characterEncodingutf8useSSLfalse);((MysqlDataSource)dataSource).setUser(root);((MysqlDataSource)dataSource).setPassword(991218zf);}}}return dataSource;}//建立连接public static Connection getConnection() throws SQLException {//这里调用getDataSource方法的作用就是没有实例化数据源就会被实例先一下return getDataSource().getConnection();}//关闭资源public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet) {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();}}}
}需要了解的是我们实例并初始化数据源的时候使用了单例模型中的懒汉模式。懒汉模式在不加锁的情况下是多线程不安全的。
单例模型是指我们在一个项目中某个类只能有一个实例也就是我们这里DataSource它在我们的项目中只需要出现实例一次只需要描述一次数据库服务器所在位置。
这里使用懒汉模式有两个线程问题一个是保证创建MysqlDataSource实例时的原子性一个是保证内存可见性。 保证原子性就是让我们在实例化MysqlDataSource时不会出现创建多个MysqlDataSource对象针对DBUtil类对象进行加锁一个线程在创建对象的时候其他线程阻塞等待。当一个对象创建好之后下次需要使用的时候就不需要再创建这个对象了也就不需要进程加锁了所以这里在外层添加了一个if判断如果MysqlDataSource对象存在直接返回创建好的对象。因为加锁会导致程序执行速度变慢所以必要的时候加锁不必要的时候就不需要加锁。保证内存可见性使用volatile关键字防止在读取数据表中的数据时从内存中读数据的操作被编译器优化掉而另一个线程修改数据的时候读取数据的线程感知不到。 1、创建数据表对应的实体类
这里我们在数据库中已经创建好了两个表一个用来表示博客信息一个用来表示用户信息。但是我们还需要在Java代码中创建对应的类表示这两个实体比如创建Blog类Blog类的每个对象就代表数据库的一个记录。
1️⃣用户信息类(User)
package model;
/*
* 这个类表示数据库中user表的内容
* 每个user对象就对应user表中的一条记录
* */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;}
}2️⃣博客信息类Blog
package model;
/*
* 这里类表示数据库中Blog表的内容
* 没给Blog对象就对应blog表中的一条记录
* */import java.sql.Timestamp;
import java.text.SimpleDateFormat;public class Blog {private int bolgId;private String title;private String content;private int userId;private Timestamp postTime;public int getBolgId() {return bolgId;}public void setBolgId(int bolgId) {this.bolgId bolgId;}public String getTitle() {return title;}public void setTitle(String title) {this.title title;}public String getContent() {return content;}public void setContent(String content) {this.content content;}public int getUserId() {return userId;}public void setUserId(int userId) {this.userId userId;}public String getPostTime() {SimpleDateFormat format new SimpleDateFormat(yyyy-MM-dd HH:mm:ss);//format方法是用来转换时间戳为上述规定的格式。return format.format(postTime);}public void setPostTime(Timestamp postTime) {this.postTime postTime;}
}2、封装增删改查操作
这里我们创建两个类分别为BlogDao和UserDao这里的Dao是 Data Access Object的缩写表示的意思是数据访问对象通过这个类的对象来访问数据。所以这里我们通过在这两个类的方法中封装JDBC来操作数据库。
1️⃣BlogDao
package model;import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;public class BlogDao {//把一个Blog对象插入到数据库中public void insert(Blog blog){Connection connection null;PreparedStatement statement null;try{//1.建立连接connection DBUtil.getConnection();//2.构造sqlString sql insert into blog values(null,?,?,?,?);statement connection.prepareStatement(sql);statement.setString(1, blog.getTitle());statement.setString(2, blog.getContent());statement.setInt(3, blog.getUserId());//如果数据库表里面是datetime类型插入数据的时候按照TimeStamp来插入或者按照格式化时间来插入都是可以的statement.setString(4, blog.getPostTime());//3.执行sqlstatement.executeUpdate();}catch(SQLException e){e.printStackTrace();}finally{DBUtil.close(connection,statement,null);}}//查询blog表中所有的博客数据public ListBlog selectAll() {ListBlog blogs new ArrayList();Connection connection null;PreparedStatement statement null;ResultSet resultSet null;try {//1.建立连接connection DBUtil.getConnection();//2.构造并执行sqlString sql select * from blog order by postTime desc;statement connection.prepareStatement(sql);resultSet statement.executeQuery();//遍历结果while(resultSet.next()){//这里的resultSet是一个结果集Blog blog new Blog();blog.setBolgId(resultSet.getInt(blogId));blog.setTitle(resultSet.getString(title));//先把博客的正文取出来String content resultSet.getString(content);//判断如果正文超过了100从0到100截取出来。if(content.length() 100){content content.substring(0,100)...;}//最后将这个content放入到blog对象中。最后通过响应显示在博客简介中blog.setContent(content);blog.setUserId(resultSet.getInt(userId));blog.setPostTime(resultSet.getTimestamp(postTime));//将每次遍历到的结果都放在list中保存。blogs.add(blog);}} catch (SQLException e) {e.printStackTrace();}finally{DBUtil.close(connection,statement,resultSet);}return blogs;}//指定一个博客id来查询对应的博客public Blog selectOne(int blogId){Connection connection null;PreparedStatement statement null;ResultSet resultSet null;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 blog new Blog();blog.setBolgId(resultSet.getInt(blogId));blog.setTitle(resultSet.getString(title));blog.setContent(resultSet.getString(content));blog.setUserId(resultSet.getInt(userId));blog.setPostTime(resultSet.getTimestamp(postTime));return blog;}} catch (SQLException e) {e.printStackTrace();} finally{DBUtil.close(connection,statement,resultSet);}return null;}//指定一个博客id 来删除博客public void delete(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(connection,statement,null);}}
}2️⃣UserDao
package model;import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;public class UserDao {//根据用户id进行查询public User selectUserById(int userId){Connection connection null;PreparedStatement statement null;ResultSet resultSet null;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 user new User();user.setUserId(resultSet.getInt(userId));user.setUsername(resultSet.getString(username));user.setPassword(resultSet.getString(password));return user;}} catch (SQLException e) {e.printStackTrace();} finally{DBUtil.close(connection,statement,resultSet);}return null;}//根据用户名进行查询public User selectUserByName(String username){Connection connection null;PreparedStatement statement null;ResultSet resultSet null;try {connection DBUtil.getConnection();String sql select * from user where username ? ;statement connection.prepareStatement(sql);statement.setString(1,username);resultSet statement.executeQuery();if(resultSet.next()){User user new User();user.setUserId(resultSet.getInt(userId));user.setUsername(resultSet.getString(username));user.setPassword(resultSet.getString(password));return user;}} catch (SQLException e) {e.printStackTrace();} finally{DBUtil.close(connection,statement,resultSet);}return null;}
}✨这里需要注意的是执行sql语句时使用了两个方法executeQuery和executeUpdate方法这两个方法都表示执行sql语句但是在使用的时机上存在差异。 executeQuery方法用于执行从数据库中检索某些数据的SQL语句。例如selectexecuteUpdate方法用于执行更新或修改数据库的sql语句。例如insert into update 四、前后端交互逻辑的实现
1、博客列表页
1.1、展示博客列表
1️⃣约定前后端交互接口
请求GET/blog :这里的GET表示的是HTTP请求方法blog表示路径
响应由于博客列表页中存在多个博客所以使用数组来存放这个博客对象。
[{blogId1title这是标题content这是正文userId1,postTime2023-07-27 12:00:00},{blogId1title这是标题content这是正文userId1,postTime2023-07-27 12:00:00},{blogId1title这是标题content这是正文userId1,postTime2023-07-27 12:00:00},
]
2️⃣编写后端代码BlogServlet类
/*
*通过这个类来实现一些后端提供的接口
* */
WebServlet(/blog)
public class BlogServlet extends HttpServlet {//实例化一个ObjectMapper对象用来将数据转换为json格式private ObjectMapper objectMapper new ObjectMapper();Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {BlogDao blogDao new BlogDao();ListBlog blogs blogDao.selectAll();//将从数据库中查询到的数据转换为json格式String respString objectMapper.writeValueAsString(blogs);//使客户端浏览器区分不同种类的数据并根据不同的MIME调用浏览器内不同的程序嵌入模块来处理相应的数据。resp.setContentType(application/json;charsetutf8);resp.getWriter().write(respString);}
}
3️⃣修改前端代码
之前写的前端代码的博客列表页都是写死的现在我们的博客列表页使用数据库中获取数据所以就需要我们使用JavaScript根据之前写死的样式来编写标签以及其中的内容。
这是之前写死的博客列表页中的组成一个博客的标签。 根据上述的样式在回调函数中利用JavaScript编写博客列表页的样式现在只是对博客列表页的右半部分进行了修改。 !-- 右侧信息 --div classcontainer-right/div/divscript//通过ajax给服务器发请求获取到所有的博客数据并且构造到页面上。function getBlogs(){$.ajax({type:get, //这是请求的方法url:blog, //这是请求的路径success:function(body){//根据返回的响应数据构造出页面中对应的元素//由于返回的响应的数据是一个application/json,所以jQuery自动的将字符串转化为了数组对象let containerRight document.querySelector(.container-right)for(let blog of body){//拿到的body数据可以当作数组来使用的let blogDiv document.createElement(div);blogDiv.className blog;let titleDiv document.createElement(div);titleDiv.className title;titleDiv.innerHTML blog.title;let dateDiv document.createElement(div);dateDiv.className date;dateDiv.innerHTML blog.postTime;let descDiv document.createElement(div);descDiv.className desc;descDiv.innerHTML blog.content;let a document.createElement(a);a.href blog_detail.html?blogId blog.blogId;a.innerHTML 查看全文 gt;gt;;//把上述标签构造好了之后还需要组合起来blogDiv.appendChild(titleDiv);blogDiv.appendChild(dateDiv);blogDiv.appendChild(descDiv);blogDiv.appendChild(a);containerRight.appendChild(blogDiv);}}});}getBlogs();/script 我们博客显示的发布时间在页面上显示的是时间戳这里需要我们在Blog类中对getPostTime方法进行修改使用SimpleDateFormat类的format方法对时间戳转换成我们设置的格式。 public String getPostTime() {SimpleDateFormat format new SimpleDateFormat(yyyy-MM-dd HH:mm:ss);//format方法是用来转换时间戳为上述规定的格式。return format.format(postTime);}
这里的我们设置的格式中小时的HH一定要大写使用大写表示的是24小时制使用的hh表示的是12小时制。
我们写博客的时候看见的都是最近写的博客都在最上面所以我们这里设置在数据库中根据博客发布的时间倒序排序这样在页面中显示的时候就会将最近写的博客放在最上面。
select * from blog order by postTime desc
我们写的博客中摘要只出现正文的一部分所以给我们自己博客系统也实现一个在BlogDao类中selectAll进行一下改造让其先获取正文部分然后再对正文内容的长度进行判断长度超过100去正文的前100个字最为摘要。
//先把博客的正文取出来String content resultSet.getString(content);//判断如果正文超过了100从0到100截取出来。if(content.length() 100){content content.substring(0,100)...;}//最后将这个content放入到blog对象中。最后通过响应显示在博客简介中blog.setContent(content);
将这三个问题修改完成之后出现的页面就是这样的。 1.2、博客详情页
1️⃣约定前后端交互接口
请求GET /blog?blogId1这里后面添加blogId 1表示的意思就是指定获取某个博客内容
响应只获取一个博客内容。
{blogId:1,title:这是一篇博客,content:这是正文,userId:1,postTime:2023-07-27 12:00:00}
2️⃣编写后端代码
这里是否创建新的类是根据我们约定前后端接口的时候请求指定的路径来区分的由于这里我们约定的路径与博客列表页的请求路径是一样所以博客详情页的代码继续在BlogServlet类中来编写。
这里就是根据请求的query string中是否有blogId来区分是获取一个博客还是获取所有的博客。
mport java.util.List;/*
*通过这个类来实现一些后端提供的接口
* */
WebServlet(/blog)
public class BlogServlet extends HttpServlet {//实例化一个ObjectMapper对象用来将数据转换为json格式private ObjectMapper objectMapper new ObjectMapper();Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {String blogId req.getParameter(blogId);//从query string中查询一下看是否有blogId,如果有就认为是查询指定博客如果没有就是查询所有博客。BlogDao blogDao new BlogDao();if(blogId null){ListBlog blogs blogDao.selectAll();//将从数据库中查询到的数据转换为json格式String respString objectMapper.writeValueAsString(blogs);//使客户端浏览器区分不同种类的数据并根据不同的MIME调用浏览器内不同的程序嵌入模块来处理相应的数据。resp.setContentType(application/json;charsetutf8);resp.getWriter().write(respString);}else{//上述使用getParameter从query string中得到的blogId是一个字符串这里需要将其转为数字Blog blog blogDao.selectOne(Integer.parseInt(blogId));String respString objectMapper.writeValueAsString(blog);resp.setContentType(application/json;charsetutf8);resp.getWriter().write(respString);}}
}
3️⃣修改前端代码
这里需要在博客列表页点击查看全文跳转跳转后的博客详情页是由markdown格式构成的数据所以在前端页面显示的时候需要引入editor.md的依赖然后使用markdown官方提供的editormd.markdownToHTML方法来对正文进行渲染然后显示在页面上。
!DOCTYPE html
html langen
headmeta charsetUTF-8meta nameviewport contentwidthdevice-width, initial-scale1.0title博客详情页/titlelink relstylesheet hrefcss/common.csslink relstylesheet hrefcss/blog-detail.css!-- 引入jquery --script srcjquery.min.js/script!-- 引入editor.md依赖 --link relstylesheet hrefeditor.md/css/editormd.min.cssscript srceditor.md/lib/marked.min.js/scriptscript srceditor.md/lib/prettify.min.js/scriptscript srceditor.md/editormd.js/script
/head
body!-- 导航栏 nav 是导航整个次的缩写 --div classnav!-- logo --img srcimage/logo.png altdiv classtitle我的博客系统/div!-- 只是一个空白用来把后面的链接挤过去 --!-- 这是一个简单粗暴的写法 --div classspancer/diva hrefblog_list.html主页/aa hrefblog_edit.html写博客/a!-- 这里的地址回头在说 --a href#注销/a/div!-- 页面的主题部分 --div classcontainer!-- 左侧信息 --div classcontainer-left!-- 这个div表示整个用户信息的区域 --div classcard!-- 用户的头像 --img srcimage/head_portrait.jpg alt!-- 用户名 --h3小张学编程/h3!-- GitHub地址 --a hrefhttps://github.comgithub 地址/a!-- 统计信息 --div classcounterspan文章/spanspan分类/span/divdiv classcounterspan2/spanspan1/span/div/div/div!-- 右侧信息 --div classcontainer-righth3/h3div classdate/divdiv idcontent/div/div/divscriptfunction getBlog(){$.ajax({type:get,//location.searchurl:bloglocation.search,success:function(body){//设置博客标题let h3 document.querySelector(.container-right h3);h3.innerHTML body.title;//设置博客发布时间let dateDiv document.querySelector(.container-right .date);dateDiv.innerHTML body.postTime;//设置正文正文内容应该是markdown格式的数据//此处要显示的应该是渲染过的markdown的内容而不是markdown的原始字符串。//第一个参数是一个html元素的id接下来渲染的结果机会放到对应的元素中editormd.markdownToHTML(content,{markdown:body.content})}});}getBlog();/script
/body
/html
上述代码中需要注意的是location.search这个代码可以获取当前的页面的URL中的查询字符串内容。location与document一样是一个全局变量。 1.3、登录页面
1️⃣约定前后端交互接口
这里提交用户名和密码可以使用form也可以使用ajax但是form更简单一点所以我们这里使用form构造请求
请求 POST/login Content-Typeapplication/x-www-form-urlencoded 这种数据的组织类型就是form专属的类型 响应 登录成功直接跳转到主页302表示重定向 HTTP/1.1 302 Location:blog_list.html ✨注意 如果通过302来跳转页面。前端必须使用form不能使用ajax.如果使用ajax当收到302响应不会触发页面跳转。 2️⃣编写后端代码
这里的前端代码由于我们只是使用费form发送请求所以改动就非常小。 !-- 登录页的版心 --div classlogin-container!-- 登录对话框 --div classlogin-dialogh3登录/h3!-- 这里使用form包裹一下 下列内容便于后续给服务器提交数据 --form actionlogin methodpostdiv classrowspan用户名/spaninput typetext idusername nameusername/divdiv classrowspan密码/spaninput typepassword idpassword namepassword/divdiv classrowinput typesubmit idsubmit value登录/div/form/div/div
这里我们在每个input标签中添加一个name属性这个属性的值和id属性值相同。但是他们的作用大不相同。id属性只是针对html生效只是用来方便获取到该元素。name属性是针对form表单构造http请求的
3️⃣编写前端代码
由于我们约定的路径发生了变化所以我们在编写后端代码的时候重新创建一个类用来实现登录页面的后端程序。
package controller;import model.User;
import model.UserDao;import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;WebServlet(/login)
public class LoginServlet extends HttpServlet {Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//设置请求传过来的字符服务器以utf8字符集进行解析req.setCharacterEncoding(utf8);//1.从请求中获取到用户名和密码String username req.getParameter(username);String password req.getParameter(password);if(username null || username.equals() || password null ||password.equals()){//用户名或者密码不全登录必然失败String html h3登录失败缺少用户名或者密码/h3;resp.setContentType(text/html;charsetutf8);resp.getWriter().write(html);return;}//2.读取数据库看这里的用户名和密码是否和数据库中的匹配UserDao userDao new UserDao();User user userDao.selectUserByName(username);if(user null){//用户名不存在String html h3用户名或密码错误/h3;resp.setContentType(text/html;charsetutf8);resp.getWriter().write(html);return;}if(password.equals(user.getPassword())){//密码错误String html h3用户名或密码错误/h3;resp.setContentType(text/html;charsetutf8);resp.getWriter().write(html);return;}//3.用户名和密码都正确登录成功需要设置会话//先要创建一个会话HttpSession session req.getSession(true);//此处就把用户对象存储到session中了下次用户访问其他页面就可以直接拿到会话进一步就能拿到之前的user对象了session.setAttribute(user,user);//4.返回一个重定向响应能够跳转到博客列表页resp.sendRedirect(blog_list.html);}
}1.4、强制要求用户登录检查用户的登录状态
当用户想要访问博客列表页/详情页/编辑页的时候必须是登录状态如果是未登录状态则直接跳转到登录页要求用户登录。
在博客列表页/详情页/登录页页面加载的时候发起一个ajax请求通过这个请求访问服务器获取到当前的登录状态如果当前为登录则跳转到登录页面如果已登录则不做任何操作。
1️⃣约定前后端交互接口 请求 GET/login 响应 登录成功就返回一个200这样的响应。body可以不要登录失败未登录就返回403这样的响应。 2️⃣编写后端代码
我们可以直接在之前的LoginServlet类中写一个doGet判定当前的登录状态。 //通过这个方法判定用户的登录状态。已登录返回200.未登录返回403Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//看当前请求是否已经存在会话并且当前的会话是否包含user对象。HttpSession session req.getSession(false);if(session null){//会话不存在未登录resp.setStatus(403);return;}//因为当前的getAttribute返回的是一个Object类型的数值这里我们使用User将其强转User user (User)session.getAttribute(user);if(user null){//虽然会话对象存在但是用户对象没有也将其判定为未登录状态resp.setStatus(403);return;}//如果会话存在并且用户也存在这个时候就是已登陆状态//200是默认的状态这里的这句代码也可以不用写resp.setStatus(200);}
}
3️⃣编写前端代码
由于判定是否登录每个页面都需要所以这里我们将这个判定的方法单独取出来放到一个文件中其他的页面代码中引用这个外部代码即可。
function getLoginStatus(){$.ajax({type:get,url:login,success:function(body){//响应返回200的时候执行success回调函数//用户已经登录不用进行任何操作console.log(用户已经登录);},error:function(body){//只要返回的不是2开头的状态码都会触发error回调函数//assign方法用来跳转到login.html主页location.assign(login.html);}})
}
其他的页面使用这种样式引用即可但是需要注意的是引入的路径是否正确
script srcjs/app.js/script//引入完成之后调用这个方法
getLoginStatus(); 1.5、实现显示用户信息的功能
我们编写的页面中博客列表页和博客详情页的左边页面中都有显示用户信息但是这两个地方显示的不是一个用户信息在博客列表页显示的是登录用户的信息但是当用户点击到别人写的博客中也就是博客详情页这个时候就会显示本篇博客作者的信息。
所以实现显示用户信息的功能需要获取两部分的用户信息 一个是博客列表页此处显示的是登录的用户的信息一个是博客详情页显示的是文章作者的信息 所以针对这两部分的数据就需要分别进行获取 1.5.1、针对列表页进行处理 1️⃣约定前后端交互接口
在博客列表页要获取到登录用户的信息
前端部分可以直接复用getLoginStatus方法之前登录成功之后什么也没有做这里将其修改一下登录成功之后将响应返回的用户名设置到左侧的用户信息中。
后端代码之前的LoginServlet类中使用doGet方法判定用户的登录状态判定登录成功之后只返回了一个200的状态码这里我们登录成功之后的用户名返回给前端。
请求
GET/login
响应
HTTP/1.1 200Content-Type:application/json;{userId:1username:zhangsan,
} 2️⃣修改后端代码 3️⃣修改后端代码
只有博客列表页显示登录用户的信息所以这里我们将之间的getLoginStatus方法复制出来放在blog_list的代码中对齐进行修改之前的这个方法中当服务器返回200的时候这个代码只是打印了日志什么都没有做现在我们通过querySelector方法找到h3标签然后修改h3标签中的内容。 function getLoginStatus(){$.ajax({type:get,url:login,success:function(body){//响应返回200的时候执行success回调函数//用户已经登录不用进行任何操作console.log(用户已经登录);//把返回的用户名设置到页面中。let h3 document.querySelector(.card h3);//这里的body是一个js对象就是前面服务器返回的json格式的user对象h3.innerHTML body.username;},error:function(body){//只要返回的不是2开头的状态码都会触发error回调函数//assign方法用来跳转到login.html主页location.assign(login.html);}})}
1.5.2、针对详情页进行处理
这里的详情页用户信息这里显示的是当前文章的作者首先我们需要根据blogId查询到文章对象然后拿到文章作者的id在根据作者id查询对应的作者名字显示到页面上。
1️⃣约定前后端交互接口
请求 GET/user?blogId1 响应
HTTP/1.1 200
Content-Type:application/json
{userId:1,username:zhangsan
}
2️⃣编写后端代码
由于我们约定的时候指定了新的路径所以我们需要创建新的Servlet类来处理请求。 package controller;import com.fasterxml.jackson.databind.ObjectMapper;
import model.Blog;
import model.BlogDao;
import model.User;
import model.UserDao;import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;WebServlet(/user)
public class UserServlet extends HttpServlet {private ObjectMapper objectMapper new ObjectMapper();Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//1、先读取出blogId由于我们的blogId是通过query string 传递的所以我们使用getParameter获取String blogId req.getParameter(blogId);if(blogId null || blogId.equals()){//直接返回一个userId为0的对象因为最终返回的是一个Json数据//此时也是返回json格式比较好如果返回一个html,前端处理的时候还需要判断//这里new的user对象是一个空的对象也就满足了返回一个userId为0的对象String respJson objectMapper.writeValueAsString(new User());resp.setContentType(application/json;charsetutf8);resp.getWriter().write(respJson);System.out.println(参数给定的blog为空);return;}//2.查询数据库查询对应的Blog对象BlogDao blogDao new BlogDao();Blog blog blogDao.selectOne(Integer.parseInt(blogId));if(blog null){String respJson objectMapper.writeValueAsString(new User());resp.setContentType(application/json;charsetutf8);resp.getWriter().write(respJson);System.out.println(参数给定的blog不存在);return;}//3.根据blog中的userId查询作者信息UserDao userDao new UserDao();User user userDao.selectUserById(blog.getUserId());if(user null){String respJson objectMapper.writeValueAsString(new User());resp.setContentType(application/json;charsetutf8);resp.getWriter().write(respJson);System.out.println(该博客对应的作者不存在);}//4.把user对象返回给页面String respJson objectMapper.writeValueAsString(user);resp.getWriter().write(respJson);return;}
}3️⃣修改前端代码
在前端的博客详情页blog_detail中添加getAuthor方法。将之前左侧的用户信息框中的h3标签中写死的内容删除掉。 function getAuthor(){$.ajax({type:get,url:userlocation.search,success:function(body){//把响应中得到的user对象的数据构造到页面上if(body.userId 0){//表示服务器没有找到匹配的用户alert(当前未找到作者信息);return;}//找到了 没有进if表示这是一个合法的user对象let h3 document.querySelector(.card h3);h3.innerHTML body.username;}});}getAuthor(); 1.6、用户退出登录功能
之前我们在导航栏中写了注销这是一个a标签点击这个标签的时候就会触发一个GET请求服务器收到这个get请求就可以把当前用户的会话中的user对象给删除掉。这里为什么不直接删除会话session但是由于servlet中没有提供直接删除会话的操作只是提供了设置过期时间的方法在我们的现在的业务需求上不适用所以这里直接删除session中的user对象也就实现了退出登录。
1️⃣约定前后端交互接口 请求 GET/logout 响应 HTTP/1.1 302 Lcation:login.html 2️⃣编写后端代码
这里我们约定的请求使用新的路径所以我们需要创建新的Servlet来实现业务。由于之前的代码中用来判定用户是否登录的时候判断user对象是否存在所以这里我们直接将user对象删除并且重定向到登录页。
package controller;import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;WebServlet(/logout)
public class LogoutServlet extends HttpServlet {Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {HttpSession session req.getSession(false);if(session null){//session不存在说明用户本来就没有登录这里直接跳转到登录页//重定向resp.sendRedirect(login.html);return;}//拿到session之后 删除session中的user对象即可//这里也不要判断session中的user对象存在不存在存在删除不存在也不影响session.removeAttribute(user);//重定向resp.sendRedirect(login.html);}
}3️⃣编写后端代码
由于我们的a标签本身就可以发送http请求所以我们只需要在注销所在a标签的href属性设置为我们约定的路径即可 1.7、实现发布博客功能
1️⃣约定前后端交互接口
前端使用form提交博客 请求 POST/blog Content-Type:application/x-www-form-urlencoded body: title标题content...... 响应 HTTP/1.1 302 Location:blog_list.html 发布成功直接跳转到列表页 2️⃣编写后端代码
从请求中拿到标题和正文从会话中拿到用户的登录状态作者id、获取到系统时间。将这些进行拼接就构成了一个blog对象然后将这个对象插入到数据库中。这里由于我们约定的时候路径为blog所以我们在BlogServlet类中添加一个doPost方法来处理博客发布的请求。 Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {req.setCharacterEncoding(utf8);//1.先从请求中拿到标题和正文String title req.getParameter(title);String content req.getParameter(content);if(title null || title.equals() || content null || content.equals()){//只要其中有一个符合条件进入判断就认为内容是不全的。没有标题或者内容博客发布失败String html h3title 或者 content 为空新增博客是失败/h3;resp.setContentType(text/html;charsetuft8);resp.getWriter().write(html);return;}//2.从会话中拿到作者的idHttpSession session req.getSession(false);if(session null){String html h3当前用户未登录新增博客是失败/h3;resp.setContentType(text/html;charsetuft8);resp.getWriter().write(html);return;}User user (User)session.getAttribute(user);if(user null){String html h3当前用户未登录新增博客是失败/h3;resp.setContentType(text/html;charsetuft8);resp.getWriter().write(html);return;}//3.构造Blog对象Blog blog new Blog();blog.setUserId(user.getUserId());blog.setTitle(title);blog.setContent(content);blog.setPostTime(new Timestamp(System.currentTimeMillis()));//4.插入blog对象到数据库中BlogDao blogDao new BlogDao();blogDao.insert(blog);//5.跳转到博客列表页(重定向)resp.sendRedirect(blog_list.html);}
}
3️⃣编写前端代码
!DOCTYPE html
html langen
headmeta charsetUTF-8meta nameviewport contentwidthdevice-width, initial-scale1.0title博客编辑页/titlelink relstylesheet hrefcss/common.csslink relstylesheet hrefcss/blog-edit.css!-- 引入依赖 --script srcjquery.min.js/scriptlink relstylesheet hrefeditor.md/css/editormd.min.cssscript srceditor.md/lib/marked.min.js/scriptscript srceditor.md/lib/prettify.min.js/scriptscript srceditor.md/editormd.js/script
/head
body!-- 导航栏 nav 是导航整个次的缩写 --div classnav!-- logo --img srcimage/logo.png altdiv classtitle我的博客系统/div!-- 只是一个空白用来把后面的链接挤过去 --!-- 这是一个简单粗暴的写法 --div classspancer/diva hrefblog_list.html主页/aa hrefblog_edit.html写博客/a!-- 这里的地址回头在说 --a href#注销/a/div!-- 博客编辑页的版心 --div classblog-edit-containerform actionblog methodpost!-- 标题编辑区 --div classtitleinput typetext idtitle-input nametitleinput typesubmit idsubmit/div!-- 博客编辑器 --!-- 把 mackdown编辑器放到这个div中--div ideditortextarea namecontent systemdisplay:none;/textarea/div/form/divscript srcjs/app.js/script!-- 针对editor.md初始化创建一个编辑器对象并关联到页面的某个元素中 --scriptvar editor editormd(editor,{// 设置编辑器的宽度和高度width:100%,height:calc(100% - 50px),// 这是编辑器中的初始内容markdowm:# 在这里写下一篇博客,// 指定editor.md依赖的插件路径path:editor.md/lib/,saveHTMLToTextarea:true});getLoginStatus();/script
/body
/html