目录
🚩部署页面需求
🚩准备工作
🚩获取博客列表页
🚩博客详情页
🚩实现登录页面
🎈强制要求登录
🎈显示用户信息
🚩退出登录
🚩发布博客
🚩部署页面需求
博客系统,基本情况,主要是四个页面
- 博客列表页,显示出当前网站上都有哪些博客
- 博客详情页,点击列表上的某个博客,就能进入对应详情页(显示出博客的具体内容)
- 博客编辑页,让用户输入博客内容,并且发送到服务器
这个部分是一个markdown编辑器,markdown(md)是程序员常用的一种用来写文档的语言。
- 登录页
当下要完成的任务:
基于上述页面,编写服务器/前后端交互代码l,通过这些代码,完成博客系统完整的功能。
📝实现博客列表页
让页面从服务器拿到博客数据(数据库)
📝实现博客详情页
让页面从服务器拿到博客数据(数据库)
📝实现登录功能
📝实现强制要求登录
(当前处于未登录的状态下,其他的页面,博客列表,博客详情,博客编辑.....就会强制跳转到登录页)要求用户登录之后才能使用
📝实现显示用户信息
从服务器获取到,博客列表页,拿到的是当前登录的用户的信息,博客详情页,拿到的是文章作者的信息。
📝实现退出登录
📝发布博客
博客编辑页,输入文章标题和内容之后,点击发布,就能把这个数据上传到服务器上并保存
这些功能搞定,就基本上的博客系统搞定
🚩准备工作
写一个复杂一些的代码,往往需要先理清楚思路,相对于细节来说,理清思路是更复杂的。(为了实现这个代码,要写哪些类,有哪些方法(方法的具体细节,我们先不用写))
1.创建项目,引入依赖,把当前的这些前端页面也导入到项目中
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>blog_system</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.15.0</version> </dependency> </dependencies> </project>
- 2.数据库设计
设计好对应的表结构,并且把数据库相关代码,也进行封装
a>找到实体
博客(blog表) 用户(user表)
b>确认实体之间的关系
一对多 一个博客,只能属于一个用户,一个用户,可以发布多个博客
create table blog ( blogId int primary key auto_increment, title varchar(1024), content varchar(4096), postTime datetime, userId int ); create table user ( userId int primary key auto_increment, username varchar(50) unique, -- 用户名也要求是不能重复的. password varchar(50) );
- 3.把数据操作的代码进行一些封装
进行网站开发的过程中,一种常见的代码组织结构,MVC结构
M model:操作数据的代码
V view:操作/构造界面的代码
C controller:业务逻辑,处理前端请求
- 📝DBUtil完成对于数据库建立连接和关闭连接的实现(这里需要用到懒汉模式)懒汉模式是不安全的,当前sevlet本身就是在多线程环境下执行的,tomcat收到多个请求的是,就会使用多线程的方式,执行不同的servlet代码
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; // 通过这个类, 封装数据库建立连接的操作. // 由于接下来代码中, 有多个 Servlet 都需要使用数据库. 就需要有一个单独的地方来把 DataSource 这里的操作进行封装. // 而不能只是放到某个 Servlet 的 init 中了. // 此处可以使用 单例模式 来表示 dataSource public class DBUtil { private volatile static 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/java109_blog_system?characterEncoding=utf8&useSSL=false"); ((MysqlDataSource) dataSource).setUser("root"); ((MysqlDataSource) dataSource).setPassword("105528clzyf."); } } } return dataSource; } public static Connection getConnection() throws SQLException { return getDataSource().getConnection(); } public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet) { if (resultSet != null) { try { resultSet.close(); } catch (SQLException e) { throw new RuntimeException(e); } } if (statement != null) { try { statement.close(); } catch (SQLException e) { throw new RuntimeException(e); } } if (connection != null) { try { connection.close(); } catch (SQLException e) { throw new RuntimeException(e); } } } }
- 📝创建实体类,每个表都需要搞一个专门的类来表示,表里的一条数据,就会对应到这个类的一个对象。这样数据库中的数据和代码联系在一起了
Blog类对应blog表,User类对应user表
package model; import java.sql.Timestamp; import java.text.SimpleDateFormat; // Blog 对象就是对应到 blog 表中的一条记录. // 表里有哪些列, 这个类里就应该有哪些属性 public class Blog { private int blogId; private String title; private String content; private Timestamp postTime; private int userId; public int getBlogId() { return blogId; } public void setBlogId(int blogId) { this.blogId = blogId; } 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 String getPostTime() { // Java 标准库提供了一个 SimpleDateFormat 类, 完成时间戳到格式化时间的转换. // 这个类的使用, 千万不要背!!! 都要去查一下!! 背大概率会背错!!! SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return simpleDateFormat.format(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 + ", title='" + title + '\'' + ", content='" + content + '\'' + ", postTime=" + postTime + ", userId=" + userId + '}'; } }
package model; // 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; } @Override public String toString() { return "User{" + "userId=" + userId + ", username='" + username + '\'' + ", password='" + password + '\'' + '}'; } }
- 📝还需要创建两个类,来完成针对博客表和用户表的增删改查操作 BlogDao UserDao(后续写)
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; // 通过 BlogDao 来完成针对 blog 表的操作 public class BlogDao { // 1. 新增操作 (提交博客就会用到) public void insert(Blog blog) { Connection connection = null; PreparedStatement statement = null; try { // 1. 建立连接 connection = DBUtil.getConnection(); // 2. 构造 SQL String sql = "insert into blog values (null, ?, ?, now(), ?)"; statement = connection.prepareStatement(sql); statement.setString(1, blog.getTitle()); statement.setString(2, blog.getContent()); statement.setInt(3, blog.getUserId()); // 3. 执行 SQL statement.executeUpdate(); } catch (SQLException e) { throw new RuntimeException(); } finally { DBUtil.close(connection, statement, null); } } // 2. 查询博客列表 (博客列表页) // 把数据库里所有的博客都拿到. public List<Blog> getBlogs() { List<Blog> blogList = new ArrayList<>(); Connection connection = null; PreparedStatement statement = null; ResultSet resultSet = null; try { connection = DBUtil.getConnection(); String sql = "select * from blog order by postTime desc"; statement = connection.prepareStatement(sql); resultSet = statement.executeQuery(); while (resultSet.next()) { Blog blog = new Blog(); blog.setBlogId(resultSet.getInt("blogId")); blog.setTitle(resultSet.getString("title")); // 此处读到的正文是整个文章内容. 太多了. 博客列表页, 只希望显示一小部分. (摘要) // 此处需要对 content 做一个简单截断. 这个截断长度 100 这是拍脑门出来的. 具体截取多少个字好看, 大家都可以灵活调整. String content = resultSet.getString("content"); if (content.length() > 100) { content = content.substring(0, 100) + "..."; } blog.setContent(content); blog.setPostTime(resultSet.getTimestamp("postTime")); blog.setUserId(resultSet.getInt("userId")); blogList.add(blog); } } catch (SQLException e) { throw new RuntimeException(e); } finally { DBUtil.close(connection, statement, resultSet); } return blogList; } // 3. 根据博客 id 查询指定的博客 public Blog getBlog(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(); // 由于此处是拿着 blogId 进行查询. blogId 作为主键, 是唯一的. // 查询结果非 0 即 1 , 不需要使用 while 来进行遍历 if (resultSet.next()) { Blog blog = new Blog(); 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")); return blog; } } catch (SQLException throwables) { throwables.printStackTrace(); } finally { DBUtil.close(connection, statement, resultSet); } return null; } // 4. 根据博客 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 throwables) { throwables.printStackTrace(); } finally { DBUtil.close(connection, statement, null); } } }
UserDao进行对用户表增删查改
package model; import java.lang.ref.PhantomReference; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; // 通过 UserDao 完成针对 user 表的操作 public class UserDao { // 新增暂时不需要. 不需要实现 "注册" 功能. // 删除暂时不需要. 不需要实现 "注销帐户" 功能. // 1. 根据 userId 来查到对应的用户信息 (获取用户信息) public User getUserById(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 throwables) { throwables.printStackTrace(); } finally { DBUtil.close(connection, statement, resultSet); } return null; } // 2. 根据 username 来查到对应的用户信息 (登录) public User getUserByName(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 throwables) { throwables.printStackTrace(); } finally { DBUtil.close(connection, statement, resultSet); } return null; } }
Dao=>Data Access Object 数据访问对象,通过这两个类的对象,来完成针对数据库表的操作
🚩获取博客列表页
实现逻辑
- 1>约定好前后端交互接口
- 2>编写前端代码,构造http请求(form/ajax)
- 3>编写后端代码,处理这个请求,返回响应
- 4>编写前端代码,解析http请求,构造页面
在博客列表页加载的时候,通过ajax给服务器发起请求,从服务器(数据库)拿到博客列表数据,并且显示在页面上。
📝约定前后端交互接口
请求GET /blog
响应
HTTP/1.1 200 OK
Content-Type:application/json
[
{
blogId:1,title:"这是标题",
content:"这是正文",
postTime:"2024-5-12 20:00:00",
userId:1
}
]
📝让浏览器给服务器发起请求了
<script> function getBlogs(){ $.ajax({ type:'get', url:'blog', success:function(body){ //服务器成功响应之后,调用回调函数 //TODO 根据返回的响应数据,构造页面的片段 } }) } //定义完函数,还需要调用,才能执行 getBlogs(); </script>
📝服务器处理上述请求,返回响应数据(查询数据库)
private ObjectMapper objectMapper=new ObjectMapper(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //查询数据库,得到博客列表 BlogDao blogDao=new BlogDao(); List<Blog>blogs=blogDao.getBlogs(); //把博客列表数据按照json格式返回回去 String respJson=objectMapper.writeValueAsString(blogs); System.out.println("respJson"+respJson); resp.setContentType("application/json;charset=utf8"); resp.getWriter().write(respJson); }
List<Blog>blogs,List,jackson就会把结果转成数组,每个元素又是一个Blog对象,转成的数据的每个元素也就是json构成的blog对象。
📝让前端代码处理上述响应数据并构造页面
构造html片段,显示到页面上。
我们在数据库中添加新的博客,看效果是什么样子的。
按照时间的降序排序.
🚩博客详情页
点不同的博客,跳转过去之后, 都会带有不同的blogId的query string,后续在博客详情页中,就可以也给服务器发起ajax请求,根据这里的blogId,查询数据库,博客的具体内容再返回,前端还是把得到的数据给构造到页面上。
📝约定前后端交互接口
请求:GET /blog?blogId=1
响应:
HTTP/1.1 200 OK
Content-Type:application/json
{
blogId:1
title:"这是第一篇博客”
content:"这是博客正文",
postTime:"2024-5-13 0:43:00",
userId:1
}
📝让前端代码,通过ajax发起请求
此处有个问题发起ajax请求的时候,要带有blogId,blogId当前就处于博客详情页的url中,这里可以通过location.search方式拿到页面url中的query string
<script> function getBlog(){ $.ajax({ type: 'get', url: 'blog' + location.search, success:function(blog){ } }) } </script>
url:'blog'+location.search一个路径,对应一个servlet,当前是使用一个servlet处理两种请求,博客列表页,不带query string,博客详情页,带有query string,就可以根据query string是否存在的请求,区分是哪种请求,分别返回不同的数据即可。使用两个servlet处理这里的两个请求,也可以,就约定成不同的路径即可,使用一个servlet也可以,没有明确的标准。
📝让服务器处理这个请求
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { BlogDao blogDao = new BlogDao(); String respJson = ""; String blogId = req.getParameter("blogId"); if (blogId == null) { // 请求中没有 query string, 请求来自博客列表页. // 查询数据库, 得到博客列表. List<Blog> blogs = blogDao.getBlogs(); // 把博客列表数据按照 json 格式返回回去. respJson = objectMapper.writeValueAsString(blogs); } else { // 请求中存在 query string, 请求来自博客详情页. Blog blog = blogDao.getBlog(Integer.parseInt(blogId)); respJson = objectMapper.writeValueAsString(blog); } System.out.println("respJson: " + respJson); resp.setContentType("application/json; charset=utf8"); resp.getWriter().write(respJson); }
📝前端拿到响应之后,把响应数据,构造成页面的html片段
function getBlog() { $.ajax({ type: 'get', url: 'blog' + location.search, // 这里需要带上 blogId 参数. success: function(blog) { // blog 就是返回的一篇博客的内容. // 形如 { blogId: 1, title: "这是标题", ..... } let h3 = document.querySelector('.container-right h3'); h3.innerHTML = blog.title; let dateDiv = document.querySelector('.container-right .date'); dateDiv.innerHTML = blog.postTime; // 这种设置方式, 页面显示的是 md 的原始内容. 希望对这个内容进行渲染成 html let contentDiv = document.querySelector('.container-right .content'); contentDiv.innerHTML = blog.content; } }) } getBlog();
博客列表页中,需要循环遍历,构造的页面内容也更复杂,此处就简单一些,只需要设置这三个内容即可。
当前博客详情页,虽然能够显示出博客的正文了,但是显示的是正文的md原始数据,作为博客网站,正确的做法应该是显示出md渲染后的效果。
此处的渲染,仍然是通过 第三方库(editor.md),editor.md官方文档上,给出了具体的例子,来完成上述的操作。
editormd.markdownToHTML('content', { markdown: blog.content });
是editormd这个库给的一个全局变量,把依赖正确引入了这个变量就能直接使用,这个方法的效果,就是把blog.content这里的md的原始数据,渲染成html,放到id为content的div中。
一个html标签,可以有很多的属性,class属性,往往是用来和css样式配合的。id属性,则是一个“身份标识”要求一个页面中,id必须是唯一的。
🚩实现登录页面
在登录页面中,在输入框中填写用户名和密码,点击登录,就会给服务器发起HTTP请求(可以使用ajax,也可以使用form)。
服务器处理登录请求,读取用户名密码,在数据库查询,匹配,如果正确就登录成功,创建会话,跳转到博客列表页。
由于这里,登录成功,直接进行重定向跳转,这种重定向操作,就不需要浏览器额外写代码处理,直接浏览器就自动跳转了。
📝约定前后端交互接口
请求:POST /login
Content-Type:application/x-www-form-urlencoded
username=zhangsan&password=123
input标签,name属性就是这里body的key
响应: Http/1.1 302
Location:blog_list.html
form表单,提交成功,可以直接使用302重定向跳转,如果使用ajax,ajax处理响应就需要写代码来完成跳转(不是浏览器自动完成了)
📝让前端发起请求
form
form方式比ajax表单使用简单,但是功能没有那么强大。
📝让服务器处理请求并做出响应
@Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //1.读取参数中的用户名和密码 req.setCharacterEncoding("utf8"); String username=req.getParameter("username"); String password=req.getParameter("password"); //验证一个参数,看下是否合理 if(username==null||username.length()==0||password==null||password.length()==0){ resp.setContentType("text/html;charset=utf8"); resp.getWriter().write("您输入用户名或者密码为空"); return; } //2.查询数据库,看看这里的用户名密码是否正确 UserDao userDao=new UserDao(); User user=userDao.getUserByName(username); if(user==null){ //用户名不存在 resp.setContentType("text/html;charset=utf8"); resp.getWriter().write("您输入的用户名或者密码不正确"); return; } if(!password.equals(user.getPassword())){ //密码不正确 resp.setContentType("text/html;charset=utf8"); resp.getWriter().write("您输入的用户名或者密码不正确!"); return; } //3.创建会话 HttpSession session=req.getSession(true); session.setAttribute("user",user); //4.跳转到主页了 resp.sendRedirect("blog_list.html"); }
点击登录,即可跳转到对应博客列表页。
🎈强制要求登录
在博客列表页,详情页,编辑页,判定当前用户是否已经登录,如果未登录,则强制跳转到登录页(要求用户必须登录后才能使用)
在上述几个页面中,页面加载时,给服务器发起ajax,从服务器获取一下当前的登录状态。
1>约定前后端交互接口
请求 GET /login
登录成功 HTTP/1.1 200
登录失败 HTTP/1.1 403
2>让前端代码发起这个请求
因为响应的处理也比较简单就顺便写了
由于我们每个页面进行访问的时候,如果没有登录,那么就都需要重新跳转到登录页面,所以我们创建一个js文件夹,包含js代码,在每个前端页面调用该函数
可以把公共的js代码,单独提取出来,放到某个.js文件中,然后通过html中的script标签,来引用这样的文件内容,此时就可以在html中调用对应的公共代码了。
// 定义新的函数, 获取登录状态 function getLoginStatus() { $.ajax({ type:'get', url:'login', success:function(body){ //已经登录的状态 console.log("已经登录了"); }, error:function(){ location.assign('login.html'); } }) } getLoginStatus();
3>让服务器处理上述请求
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 通过这个方法, 来反馈当前用户的登录状态. // 一个简单的判定方式, 直接看会话是否存在. // 此处使用一个更严格的方式. 不仅要求会话要存在, 还要求会话中要能保存 user 对象. // (之所以给出这种设定, 也是为了后面实现 "退出登录" 这样的功能来做个铺垫) HttpSession session=req.getSession(false); if(session==null){ //会话不存在,用户属于未登录状态 resp.setStatus(403); return; } User user=(User) session.getAttribute("user"); if(user==null){ //user对象不存在,同样也处于未登录状态 resp.setStatus(403); return; } //俩个都存在,返回200 //此处200不写也行,默认时200 resp.setStatus(200); }
当前虽然登录过了,一旦重新启动服务器,此时仍然会判定为未登录状态。
登录状态是通过服务器这里的session来存储的,session这里服务器内存中类似于hashmap这样的结构,一旦服务器重启了,hashmap里面原有的内容就没了
其实这种设定,严格的说,并不科学。相比之下有更好的解决方案
- 可以把会话进行持久性保存(文件数据库,redis...)
- 可以使用令牌的方式(把用户信息,在服务器加密,还是保存在浏览器这边)相当于服务器没有在内存中存储当前用户的身份
javaEE进阶会学到。
🎈显示用户信息
- 博客列表页:显示的是当前登录的用户的信息
- 博客详情页:显示的是当前文章的作者信息
在页面加载的时候,给服务器发起ajax请求,服务器返回对应的用户数据,根据发起请求的不同页面,服务器返回不同的信息即可。
📝约定前后端交互接口
📝前端代码发起请求
📝编写服务器代码,来处理上述请求
博客列表页
package servlet;
import com.fasterxml.jackson.databind.ObjectMapper;
import model.User;
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("/userInfo")
public class UserInfoServlet extends HttpServlet {
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 从会话中, 拿到用户的信息返回即可.
HttpSession session=req.getSession(false);
if (session==null){
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("当前用户未登录");
return;
}
User user=(User) session.getAttribute("user");
if(user==null){
resp.setContentType("text/html;cahrset=utf8");
resp.getWriter().write("当前用户未登录");
return;
}
//此时把user对象转成json,并返回给浏览器
resp.setContentType("application/json;charset=utf8");
//注意,user中还有password属性,把密码返回回去,不太合适
user.setPassword("");
String respJson=objectMapper.writeValueAsString(user);
resp.getWriter().write(respJson);
}
}
博客详情页
package servlet;
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("/getAuthorInfo")
public class AuthorInfoServlet extends HttpServlet {
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 先拿到请求中的 blogId
String blogId=req.getParameter("blogId");
if(blogId==null){
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("请求中缺少blogId");
return;
}
//2.blog表中查询对应的blog对象
BlogDao blogDao=new BlogDao();
Blog blog=blogDao.getBlog(Integer.parseInt(blogId));
if(blog==null){
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("blogId没有找到");
return;
}
//3.根据blog对象中的userId,从user表中查到对应的作者
UserDao userDao=new UserDao();
User user=userDao.getUserById(blog.getUserId());
if(user==null){
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("userId没有找到");
}
//把这个user对象,返回到浏览器中
user.setPassword("");
String respJson=objectMapper.writeValueAsString(user);
resp.setContentType("tapplication/json;charset=utf8");
resp.getWriter().write(respJson);
}
}
此处通过两步sql分别查询的,先查blog表里面的blog对象,再查user表。
📝前端代码处理响应
博客详情页
// 获取当前登录的用户信息
function getUserInfo() {
$.ajax({
type: 'get',
url: 'userInfo',
success: function(user) {
// 把拿到的响应数据, 取出其中的 username, 设置到页面的 h3 标签中!-->
let h3 = document.querySelector('.card h3');
h3.innerHTML = user.username;
}
});
}
getUserInfo();
</script>
博客列表页
function getAuthorInfo() {
$.ajax({
type: 'get',
url: 'getAuthorInfo' + location.search,
success: function(user) {
// 把拿到的 user 对象, 取出其中的 username, 设置到页面上.-->
let h3 = document.querySelector('.card h3');
h3.innerHTML = user.username;
}
});
}
getAuthorInfo();
🚩退出登录
博客列表/博客详情/博客编辑 导航栏中,都有一个“注销”按钮
让用户点击“注销”的时候,就能够触发一个HTTP请求(GET请求)
服务器收到这个GET请求的时候,就会把会话里的user这个Attribute给删了,由于判定用户是否是登录状态的逻辑中,需要同时验证,会话存在,且这里的user Attribute也存在,只要破坏一个,就可以使登录状态发生改变了。
为啥不直接删除session本身??主要因为,servlet没有提供,删除session方法。虽然有间接的方式(session可以设置过期时间,设置一个非常短的过期时间),也可以起到删除的效果。
session提供了removeAttribute这样的方法,可以把user这个Attribute给删了。
这个东西是a标签,可以有一个href属性,点击就会触发一个http请求,并且可能会引起浏览器跳转到另一个页面。
📝约定前后端交互接口
请求 GET /logout
响应 直接重定向到登录页 Http/1.1 302
Location:login.html
📝编写前端代码,发送请求
不用写ajax,直接给a标签设置href属性即可
📝编写后端代码,处理这个请求,完成退出登录的操作
@WebServlet("/logout")
public class LoginoutServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 先拿到会话对象
HttpSession session = req.getSession(false);
if (session == null) {
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("当前您尚未登录");
return;
}
// 再把会话中的 user 的属性给删除掉
session.removeAttribute("user");
// 跳转到博客登录页
resp.sendRedirect("login.html");
}
}
🚩发布博客
当点击提交的时候,就需要构造HTTP请求,把此时的页面中的标题和正文都传输到服务器这边,服务器把这个数据存入数据库即可。
此时这里的http请求,可以使用ajax,也可以使用form(这种填写输入框,提交数据的场景,使用form会更方便)
📝约定前后端交互接口
📝编写前端代码构造请求
标题,本身就是一个咱们自己写的input,给它加上name属性,很容易,但是博客正文,是由editor md构成的一个编译器,这里如何添加name属性呢?
这个div就是editor.md的编译器的容器,在这个div里,搞一个隐藏的textarea标签.
textarea多行编辑框,把name属性加到这个textarea上。并且在初始化editormd对象的时候,加上一个对应的属性即可。
display:none让这个textarea隐藏起来。
<script>
var editor = editormd("editor", {
// 这里的尺寸必须在这里设置. 设置样式会被 editormd 自动覆盖掉.
width: "100%",
// 设定编辑器高度
height: "calc(100% - 50px)",
// 编辑器中的初始内容
markdown: "# 在这里写下一篇博客",
// 指定 editor.md 依赖的插件路径
path: "editor.md/lib/",
saveHTMLToTextarea: true
});
getLoginStatus();
</script>
saveHTMLToTextarea:true 会把用户在编译器中输入内容,自动也保存到textarea里一份。
📝编写服务器代码,处理刚才的请求
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 读取请求中的参数
req.setCharacterEncoding("utf8");
String title = req.getParameter("title");
String content = req.getParameter("content");
if (title == null || title.length() == 0 || content == null || content.length() == 0) {
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("当前传过来的标题或正文为空! 无法新增博客!");
return;
}
// 2. 从会话中, 拿到当前登录的用户的 userId
HttpSession session = req.getSession(false);
if (session == null) {
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("当前用户未登录");
return;
}
User user = (User) session.getAttribute("user");
if (user == null) {
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("当前用户未登录");
return;
}
// 3. 构造 blog 对象
Blog blog = new Blog();
blog.setTitle(title);
blog.setContent(content);
// 从会话中拿到 当前正在登录的用户的 userId, 设置进去即可
blog.setUserId(user.getUserId());
// 4. 插入到数据库中
BlogDao blogDao = new BlogDao();
blogDao.insert(blog);
// 5. 返回一个 302 这样的重定向报文, 跳转到博客列表页.
resp.sendRedirect("blog_list.html");
}
输入标题,写内容,即可。显示出来。
我们不怕掉眼泪,但要值得。