文章目录
- 一. 创建项目 引入依赖
- 二. 设计数据库
- 三. 编写数据库代码
- 四. 创建实体类
- 五. 封装数据库的增删查改
- 六. 具体功能书写
- 1. 博客列表页
- 2. 博客详情页
- 3. 博客登录页
- 4. 检测登录状态
- 5. 实现显示用户信息的功能
- 6. 退出登录状态
- 7. 发布博客
一. 创建项目 引入依赖
创建blog_system项目.将之前写的博客系统前端代码复制到webapp目录下.
在pom.xml
中引入Servlet mysql jackson
三个依赖:
pom.xml
:
<?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>
</properties>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.14.2</version>
</dependency>
</dependencies>
</project>
二. 设计数据库
结合之前的需求,在当前博客系统中,主要涉及到两个实体.即 用户和博客.
经过分析我们可以得到,用户和博客之间是一对多的关系.即一个用户可以拥有多篇博客.
所以我们可以创建两张表,来表示用户和博客.
blog(blogId,title,content,postTime,userId)
user(userId,userName,password)
三. 编写数据库代码
把一些基本的数据库操作,先封装好.
需要手动粘贴至MySQL
-- 这个文件主要用来写 建库建表 语句
create database if not exists javaee_blog_system;
use javaee_blog_system;
-- 删除旧表,重新建表
drop table if exists user;
drop table if exists blog;
-- 创建表
create table blog(
blogId int primary key auto_increment,
title varchar(128),
content varchar(4096),
postTime datetime,
userId int
);
create table user(
userId int primary key auto_increment,
username varchar(20) unique,-- 要求用户名和别人的不重复
password varchar(20)
);
接下来是封装数据库的连接操作:
DBUtil
:
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import javax.sql.DataSource;
import java.sql.*;
public class DBUtil {
private static DataSource dataSource = new MysqlDataSource();
static {
((MysqlDataSource)dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/javaee_blog_system?characterEncoding=utf8&useSSL=false");
((MysqlDataSource)dataSource).setUser("root");
((MysqlDataSource)dataSource).setPassword("0828");
}
public static Connection getConnection() throws SQLException {
return dataSource.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();
}
}
}
}
四. 创建实体类
实体类就是和表中的记录对应的类:
blog表 =>Blog类对应Blog的一个对象,就对应表中的一条记录.
user表 =>User类对应User的一个对象,就对应表中的一个记录.
实体类有哪些属性,都是和当前表中的列是密切相关的.
User
:
public class User {
private int userId;
private String username;
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;
}
private String password;
}
Blog
:
import java.sql.Timestamp;
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 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;
}
}
五. 封装数据库的增删查改
针对博客表,创建BlogDao
;(Data Access Object)
针对用户表,创建UserDao
.
上述Dao用来提供一些方法.来进行增删查改.
BlogDao
:
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 {
//1.新增博客
public void add(Blog blog){
Connection connection = null;
PreparedStatement statement = null;
try {
//1.和数据库建立连接
connection = DBUtil.getConnection();
//2.构造SQL
String sql = "insert into blog values(null,?,?,?,?)";
statement = connection.prepareStatement(sql);
statement.setString(1,blog.getTitle());
statement.setString(2, blog.getContent());
statement.setTimestamp(3,blog.getPostTime());
statement.setInt(4,blog.getUserId());
//3.执行SQL
statement.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
DBUtil.close(connection,statement,null);
}
}
//2.根据博客id来查询指定博客(博客详情页)
public Blog selectById(int blogId){
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
//1.与数据库建立连接
connection = DBUtil.getConnection();
//2.构造sql
String sql = "select * from blog where blogId = ?";
statement = connection.prepareStatement(sql);
statement.setInt(1,blogId);
//3.执行sql
resultSet = statement.executeQuery();
//4.遍历结果集合
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"));
}
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
//5.释放资源
DBUtil.close(connection,statement,resultSet);
}
return null;
}
//3.查询数据库中所有的博客列表(用于博客列表页)
public List<Blog> selectAll(){
List<Blog> blogs = new ArrayList<>();
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
//1.与数据库建立连接
connection = DBUtil.getConnection();
//2.构造sql语句
String sql = "select * from blog";
statement = connection.prepareStatement(sql);
//3.执行sql
resultSet = statement.executeQuery();
//4.遍历结果集合
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(0,100) +"...";
}
blog.setContent(content);
blog.setPostTime(resultSet.getTimestamp("postTime"));
blog.setUserId(resultSet.getInt("userId"));
blogs.add(blog);
}
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
DBUtil.close(connection,statement,resultSet);
}
return blogs;
}
//4.删除指定博客
public void delect(int blogId){
Connection connection = null;
PreparedStatement statement = null;
try {
//1.与数据库建立连接
connection = DBUtil.getConnection();
//2.构造sql语句
String sql = "delete from blog where bolgId = ?";
statement = connection.prepareStatement(sql);
statement.setInt(1,blogId);
//3.执行sql
statement.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
//4.关闭资源
DBUtil.close(connection,statement,null);
}
}
}
UserDao
:
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
//针对用户表提供的基本操作
public class UserDao {
//1.根据 userId 来查用户信息
public User selectById(int userId){
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
//1.与数据库建立连接
connection = DBUtil.getConnection();
//2.构造sql
String sql = "select * from user where userId = ?";
statement = connection.prepareStatement(sql);
statement.setInt(1,userId);
//3.执行SQL
resultSet = statement.executeQuery();
//4.遍历结果集合
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) {
throw new RuntimeException(e);
}finally {
//5.释放资源
DBUtil.close(connection,statement,resultSet);
}
return null;
}
//2.根据username来查找用户信息.
public User selectByUsername(int username){
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
//1.与数据库建立连接
connection = DBUtil.getConnection();
//2.构造sql
String sql = "select * from user where userId = ?";
statement = connection.prepareStatement(sql);
statement.setInt(1,username);
//3.执行SQL
resultSet = statement.executeQuery();
//4.遍历结果集合
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) {
throw new RuntimeException(e);
}finally {
//5.释放资源
DBUtil.close(connection,statement,resultSet);
}
return null;
}
}
六. 具体功能书写
1. 博客列表页
当前博客列表页上的数据都是写死的.正确的做法,应该是通过数据库读取数据显示到页面上.
此处就需要打通前后端交互的操作.
让博客列表页,在加载的时候,通过ajax给服务器发一个请求服务器查数据库获取到博客列表数据,返回给浏览器,浏览器再根据数据构造页面内容.这样的交互过程,也称为“前后端分离"
前端只向后端请求数据,而不请求具体的页面,后端也仅仅是返回数据.这样的设定的目的就是为了前端和后端更加解耦,由浏览器进行具体的页面渲染.减少了服务器的工作量.
上述进行实现博客列表页的基本思路
接下来需要:
- 约定前后端交互接口
- 开发后端代码
- 开发前端代码
- 约定前后端交互接口
请求:GET /blog
响应:使用json格式的数据来组织
[
{
blogId:1,
title:"这是第一篇博客",
content:"从今天开始,我要认真学习",
postTime:"2023-08-01 09:40:45",
userId:1
}
{
blogId:1,
title:"这是第一篇博客",
content:"从今天开始,我要认真学习",
postTime:"2023-08-01 09:40:45",
userId:1
}
{
blogId:1,
title:"这是第一篇博客",
content:"从今天开始,我要认真学习",
postTime:"2023-08-01 09:40:45",
userId:1
}
]
- 开发后端代码
BlogServlet
:
package api;
import com.fasterxml.jackson.databind.ObjectMapper;
import model.Blog;
import model.BlogDao;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
public class BlogServlet extends HttpServlet {
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
BlogDao blogDao = new BlogDao();
List<Blog> blogs = blogDao.selectAll();
//需要把blogs转成符合要求的 json 格式字符串
String respJson = objectMapper.writeValueAsString(blogs);
resp.setContentType("application/json;charset = utf8");
resp.getWriter().write(respJson);
}
}
此处注意文件所在位置.model
是管理数据/操作数据的部分.
- 开发前端代码
在博客列表页加载过程中,触发ajax,访问服务器中的数据.再把拿到的数据构造到页面中.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>博客列表页</title>
<link rel="stylesheet" href="css/common.css">
<link rel="stylesheet" href="css/blog_list.css">
</head>
<body>
<!-- 导航栏 -->
<div class="nav">
<img src="image/logo2.jpg" alt="">
<span class="title">我的博客系统</span>
<!-- 用于占位,将a标签挤到右侧去 -->
<div class="spacer"></div>
<a href="#">主页</a>
<a href="#">写博客</a>
<a href="#">注销</a>
</div>
<!-- 页面主体部分 -->
<div class="container">
<!-- 左侧信息 -->
<div class="container-left">
<!-- 使用.card表示用户信息 -->
<div class="card">
<!-- 用户头像 -->
<img src="image/touxiang.jpg" alt="">
<!-- 用户名 -->
<h3>xxxflower</h3>
<a href="#">github地址</a>
<div class="counter">
<span>文章</span>
<span>分类</span>
</div>
<div class="counter">
<span>2</span>
<span>1</span>
</div>
</div>
</div>
<!-- 右侧信息 -->
<div class="container-right">
</div>
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.3/jquery.min.js"></script>
<script>
// 在页面加载时, 向服务器发起请求, 获取博客列表数据
function getBlogs() {
$.ajax({
type: 'get',
url: 'blog',
success: function(body) {
// 响应的正文 是一个 json 字符串, 此处已经被 jquery 自动解析成 js 对象数组了.
// 直接 for 循环遍历即可.
let containerRight = document.querySelector('.container-right');
for (let blog of body) {
// 构造页面内容, 参考之前写好的 html 代码
// 构造整个博客 div
let blogDiv = document.createElement('div');
blogDiv.className = 'blog';
// 构造标题
let titleDiv = document.createElement('div');
titleDiv.className = 'title';
titleDiv.innerHTML = blog.title;
blogDiv.appendChild(titleDiv);
// 构造发布时间
let dateDiv = document.createElement('div');
dateDiv.className = 'date';
dateDiv.innerHTML = blog.postTime;
blogDiv.appendChild(dateDiv);
// 构造 博客 摘要
let descDiv = document.createElement('div');
descDiv.className = 'desc';
descDiv.innerHTML = blog.content;
blogDiv.appendChild(descDiv);
// 构造查看全文按钮
let a = document.createElement('a');
a.innerHTML = '查看全文 >>';
// 期望点击之后能跳转到博客详情页. 为了让博客详情页知道是点了哪个博客, 把 blogId 给传过去
a.href = 'blog_detail.html?blogId=' + blog.blogId;
blogDiv.appendChild(a);
// 把 blogDiv 加到父元素中
containerRight.appendChild(blogDiv);
}
}
});
}
// 要记得调用
getBlogs();
</script>
</body>
</html>
效果如下图所示:
但是我们发现这里的时间显示有问题:
我们需要使用SimpleDateFormat
类.注意该类使用时的书写格式!
2. 博客详情页
关于博客详情页,点击查看全文按钮,就能跳转到博客详情页中.跳转过去之后,在博客详情页中发起一个ajax,从服务器获取到当前的博客的具体内容.再进行显示.
分为以下几个步骤:
- 约定前后端交互接口
- 实现后端代码
- 实现前端代码
-
约定前后端交互接口
请求:GET/blog?blogId = 1
响应:
HTTP/1.1 200 OK
{
blogId:1,
title:“这是第一篇博客”,
content:“从今天开始我要认真学习”,
postTime:“2023-08-01 17:00:00”,
userId:1
} -
实现后端代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>博客详情页</title>
<link rel="stylesheet" href="css/common.css">
<link rel="stylesheet" href="css/blog_detail.css">
</head>
<body>
<!-- 导航栏 -->
<div class="nav">
<img src="image/logo2.jpg" alt="">
<span class="title">我的博客系统</span>
<!-- 用于占位,将a标签挤到右侧去 -->
<div class="spacer"></div>
<a href="#">主页</a>
<a href="#">写博客</a>
<a href="#">注销</a>
</div>
<!-- 页面主体部分 -->
<div class="container">
<!-- 左侧信息 -->
<div class="container-left">
<!-- 使用.card表示用户信息 -->
<div class="card">
<!-- 用户头像 -->
<img src="image/touxiang.jpg" alt="">
<!-- 用户名 -->
<h3>xxxflower</h3>
<a href="#">github地址</a>
<div class="counter">
<span>文章</span>
<span>分类</span>
</div>
<div class="counter">
<span>2</span>
<span>1</span>
</div>
</div>
</div>
<!-- 右侧信息 -->
<div class="container-right">
<!-- 博客标题 -->
<h3 class="title"></h3>
<!-- 博客发布时间 -->
<div class="date"></div>
<!-- 博客正文, 为了配合 editormd 进行格式转换, 此处务必改成 id -->
<div id="content">
</div>
</div>
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.3/jquery.min.js"></script>
<!-- 要保证这几个 js 的加载在 jquery 之后. editor.md 依赖了 jquery -->
<script src="editor.md/lib/marked.min.js"></script>
<script src="editor.md/lib/prettify.min.js"></script>
<script src="editor.md/editormd.js"></script>
<script>
$.ajax({
type: 'get',
url: 'blog' + location.search,
success: function(body) {
// 处理响应结果, 此处的 body 就是表示一个博客的 js 对象.
// 1. 更新标题
let titleDiv = document.querySelector('.container-right .title');
titleDiv.innerHTML = body.title;
// 2. 更新日期
let dateDiv = document.querySelector('.date');
dateDiv.innerHTML = body.postTime;
// 3. 更新博客正文
// 此处不应该直接把博客正文填充到这个标签里
editormd.markdownToHTML('content', { markdown: body.content });
}
})
</script>
</body>
</html>
- 开发后端代码
package api;
import com.fasterxml.jackson.databind.ObjectMapper;
import model.Blog;
import model.BlogDao;
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;
import java.util.List;
@WebServlet("/blog")
public class BlogServlet extends HttpServlet {
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
BlogDao blogDao = new BlogDao();
String blogId = req.getParameter("blogId");
if(blogId == null){
List<Blog> blogs = blogDao.selectAll();
//需要把blogs转成符合要求的 json 格式字符串
String respJson = objectMapper.writeValueAsString(blogs);
resp.setContentType("application/json;charset = utf8");
resp.getWriter().write(respJson);
}else{
// queryString 存在, 说明本次请求获取的是指定 id 的博客.
Blog blog = blogDao.selectById(Integer.parseInt(blogId));
if (blog == null) {
System.out.println("当前 blogId = " + blogId + " 对应的博客不存在!");
}
String respJson = objectMapper.writeValueAsString(blog);
resp.setContentType("application/json; charset=utf8");
resp.getWriter().write(respJson);
}
}
}
这是因为浏览器自身也有缓存机制.
浏览器从服务器这边获取页面,这个操作是通过网络传输完成的,速度是比较慢的.浏览器就会把页面给缓存到本地(客户端电脑的硬盘上).后续再访问同一个页面,就直接读缓存.但是这样做有一些问题,即客户端命中缓存之后就不一定能及时感知到变化.
解决方法:强制刷新.(无视缓存,100%重新访问服务器)Ctrl + f5
3. 博客登录页
在此处输入用户名和密码,点击登录,就会触发一个http请求.
服务器验证用户名和密码,然后就可以根据结果,判定是否登录成功.
分为以下几个步骤:
- 约定前后端交互接口
- 实现前端代码
- 实现后端代码
- 约定前后端交互接口
请求:
POST/login
username = zhangsan&password = 123
响应:
HTTP/1.1 302
Location:blog_list.html
注意:
此处的响应应是from表单(本身就会触发页面跳转),响应是302才能够进行页面跳转.
如果是ajax请求(本身不会触发),响应是302,此时是无法进行跳转的.
- 实现前端代码
往页面上加入from表单,使得点击登录操作能够触发请求.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>博客登录页</title>
<link rel="stylesheet" href="css/common.css">
<link rel="stylesheet" href="css/login.css">
</head>
<body>
<!-- 导航栏 -->
<div class="nav">
<img src="image/logo2.jpg" alt="">
<span class="title">我的博客系统</span>
<!-- 用于占位,将a标签挤到右侧去 -->
<div class="spacer"></div>
<a href="#">主页</a>
<a href="#">写博客</a>
</div>
<!-- 正文部分 -->
<!-- 贯穿整个页面的容器 -->
<div class="login-container">
<!-- 垂直水平登录的对话框 -->
<div class="login-dialog">
<form action="login" method="post">
<h3>登录</h3>
<div class="row">
<span>用户名:</span>
<input type="text" id="username" placeholder="手机号/邮箱号" name="username">
</div>
<div class="row">
<span>密码:</span>
<input type="password" id="password" placeholder="请输入密码" name="password">
</div>
<div class="row">
<input type="submit" id="submit" value="登录">
</div>
</form>
</div>
</div>
</body>
</html>
注意此处对应的关系:
3. 修改后端代码
package api;
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 {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置请求的编码,使用 utf8 理解请求
req.setCharacterEncoding("utf8");
//设置响应的编码,使用 utf8 构造响应
//resp.setCharacterEncoding("utf8");
resp.setContentType("text/html;charset=utf8");
//1.读取参数中的用户名和密码
String username = req.getParameter("username");
String password = req.getParameter("password");
if(username == null ||"".equals(username) || password == null ||"".equals(password)){
//登录失败
String html = "<h3>登录失败!用户名/密码为空!</h3>";
resp.getWriter().write(html);
return;
}
//2.读取数据库,查看用户名是否存在.密码是否匹配
UserDao userDao = new UserDao();
User user =userDao.selectByUsername(username);
if(user == null){
//用户不存在
String html = "<h3>登录失败!用户名或密码错误</h3>";
resp.getWriter().write(html);
return;
}
if(!password.equals(user.getPassword())){
String html = "<h3>登录失败!用户名或密码错误</h3>";
resp.getWriter().write(html);
return;
}
//3.用户名密码验证通过,登录成功,接下来就创建会话,使用该会话保存用户信息
HttpSession session = req.getSession();
session.setAttribute("user",user);
//4.重定向,跳转到博客列表页
resp.sendRedirect("blog_list.html");
}
}
4. 检测登录状态
实现让页面强制要求登录,
当用户访问博客列表页/详情页/编辑页,要求用户必须是已经登录的状态.如果用户还没登录,就会强制跳转到登录页面.
实现思路:
在页面加载的时候,专门发起一个新的 ajax . (一个页面里可以发N个ajax请求)以博客列表页为例,先会发一个请求获取博客列表,再发个ajax获取用户的登录状态,如果用户已登录,相安无事.如果未登录,则页面跳转到登录页.
- 约定前后端交互接口
- 实现前端代码
- 实现后端代码
- 约定前后端交互接口
请求:GET/login
响应:
HTTP/1.1 200 OK
{
userId:1,
username:‘xxxflower’
}
响应就把当前登录的用户信息返回回来了,如果未登录,就返回一个userld
为0
的user
对象
- 实现后端代码
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("application/json; charset=utf8");
// 使用这个方法来获取到用户的登录状态.
// 如果用户未登录, 这里的会话就拿不到!!
HttpSession session = req.getSession(false);
if (session == null) {
// 未登录, 返回一个空的 user 对象
User user = new User();
String respJson = objectMapper.writeValueAsString(user);
resp.getWriter().write(respJson);
return;
}
User user = (User) session.getAttribute("user");
if (user == null) {
user = new User();
String respJson = objectMapper.writeValueAsString(user);
resp.getWriter().write(respJson);
return;
}
// 确实成功取出了 user 对象, 就直接返回即可.
String respJson = objectMapper.writeValueAsString(user);
resp.getWriter().write(respJson);
}
- 开发后端代码:
function checkLogin() {
$.ajax({
type: 'get',
url: 'login',
success: function(body) {
if (body.userId && body.userId > 0) {
// 登录成功!!
console.log("当前用户已经登录!!");
h3.innerHTML = body.username;
} else {
// 当前未登录
// 强制跳转到登录页.
location.assign('login.html');
}
}
});
}
checkLogin();
将此代码粘贴至博客列表页
则效果如下:
5. 实现显示用户信息的功能
此处我们是写死的,我们希望这个地方可以动态生成.
- 如果是博客列表页,此处显示登陆用户的信息
- 如果此处是博客详情页,此时显示的是该文章的作者
- 约定前后端接口
博客列表页:(复用监测登录状态的接口)
请求:
GET /login
响应:
HTTP/1.1 200 OK
{
userId:1,
username:‘huang’,
password:‘234’
}
由于是在监测登录的接口中直接写,所以后端代码不变,只需要微调前端代码即可
登录可以看到:
博客详情页: - 前后端交互接口
请求:
GET/author?blogId = 1
响应:
HTTP/1.1 200 OK
{
userId:1,
username:‘huang’,
password:‘234’
} - 后端代码:
package api;
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("/author")
public class AuthorServlet extends HttpServlet {
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String blogId = req.getParameter("blogId");
if(blogId == null){
resp.setContentType("text/html;charset = utf8");
resp.getWriter().write("参数非法,缺少blogId!");
return;
}
//根据 blog 查询Blog对象
BlogDao blogDao = new BlogDao();
Blog blog = blogDao.selectById(Integer.parseInt("blogId"));
if(blog == null){
resp.setContentType("text/html;charset = utf8");
resp.getWriter().write("没有找到指定博客");
return;
}
//根据BLog中UserId 获取到对应的用户信息
UserDao userDao = new UserDao();
User author = userDao.selectById(blog.getUserId());
String respJson = objectMapper.writeValueAsString(author);
resp.setContentType("application/json;charset=utf8");
resp.getWriter().write(respJson);
}
}
- 前端代码:
function getAuthor(){
$.ajax({
type:'get',
url:'author' + location.search,
success: function(body){
//将username设置到页面上
let h3 = document.querySelector('.container-left .card h3');
h3.innerHTML = body.userId;
}
})
}
getAuthor();
6. 退出登录状态
判断登录状态:
- 看是否能查到
http session
对象- 看
session
对象里面有没有user
实现退出登录:
- 处理
HttpSession
getSession
能够创建/获取会话,但是没有删除会话的方法.但是我们可以通过设置过期时间来达到类似效果- 处理user(推荐) 可以通过
removeAttribute
来处理
-
约定前后端接口
请求:
GET/logout
响应:
HTTP/1.1 302
Location:login.html -
实现后端代码:
package api;
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("/logout")
public class LogoutServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession httpSession = req.getSession(false);
if(httpSession == null){
//未登录,直接出错
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("当前未登录!");
return;
}
httpSession.removeAttribute("user");
resp.sendRedirect("login.html");
}
}
- 实现前端代码
修改博客列表页 ,博客编辑页的前端代码:
7. 发布博客
- 约定前后端交互接口
使用form
表单:页面中更多了form
标签,同时让form
里面能够感知到博客的内容.
请求:POST/blog
title=标题&content=正文
响应:HTTP/1.1 302
Location:blog_list.html - 编写服务器代码
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 发布博客
// 读取请求, 构造 Blog 对象, 插入数据库中即可!!
HttpSession httpSession = req.getSession(false);
if (httpSession == null) {
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("当前未登录, 无法发布博客!");
return;
}
User user = (User) httpSession.getAttribute("user");
if (user == null) {
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("当前未登录, 无法发布博客!");
return;
}
// 确保登录之后, 就可以把作者给拿到了.
// 获取博客标题和正文
req.setCharacterEncoding("utf8");
String title = req.getParameter("title");
String content = req.getParameter("content");
if (title == null || "".equals(title) || content == null || "".equals(content)) {
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("当前提交数据有误! 标题或者正文为空!");
return;
}
// 构造 Blog 对象
Blog blog = new Blog();
blog.setTitle(title);
blog.setContent(content);
blog.setUserId(user.getUserId());
// 发布时间, 在 java 中生成 / 数据库中生成 都行
blog.setPostTime(new Timestamp(System.currentTimeMillis()));
// 插入数据库
BlogDao blogDao = new BlogDao();
blogDao.add(blog);
// 跳转到博客列表页
resp.sendRedirect("blog_list.html");
}
- 客户端代码:
<!-- 编辑区的容器 -->
<div class="blog-edit-container">
<form action="blog" method="post" style="height: 100%;">
<!-- 博客标题编辑区 -->
<div class="title">
<input type="text" id="title" placeholder="输入文章标题" name="title">
<input type="submit" id="submit" value="发布文章">
</div>
<!-- 博客编辑器, 这里用 id 是为了和 markdown 编辑器对接, 而设置的 -->
<div id="editor">
<textarea name="content" style="display:none"></textarea>
</div>
</form>
</div>
后续点击submit就能自动提交
display:none
是隐藏元素.