前面的学习中, 我们基于 HTML, CSS, JavaScript 实现了一个简单的博客系统的页面.
接下来我们基于博客系统页面来实现一个带服务器版本的博客程序.
1.准备工作
1.创建项目
2.引入依赖
<?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/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
</dependencies>
</project>
3.创建必要的目录
2.数据库设计
设计表结构,有几个表,每个表里面都有啥~~
要存储的主要是两个部分
1.博客数据
blog 博客id,标题,正文,userid,发布时间
2.用户存储
user userid,username,password[用户的头像,用户的github…暂时不搞]
--一般对于建表的SQL都会单独搞一个.sql文件来保存.
--后续程序可能需要在不同的主机上部署,部署的时候就需要在对应的主机上把数据库也给创建好.
--把建表sql保存好,方便在不同的机器上进行建库建表
create database if not exists blog_system;
use blog_system;
drop table if exists blog;
create table blog
(
blogId int primary key auto_increment, -- 博客 id
title varchar(128), -- 博客标题
content varchar(4096), -- 博客正文
userId int, -- 博客作者 id
postTime datetime
);
drop table if exists user;
-- 一般都会在建表的时候删一下,删一下的目的是为了清空之前残留的数据~~
create table user(
userId int primary key auto_increment,
username varchar(128),
password varchar(128)
);
insert into user values(null,'zhangsan','123'),(null,'lisi','123');
3.封装数据库操作
JDBC
1.封装数据库的连接操作
一个经典的 web 项目结构:MVC
M —> Model(和数据相关的部分)
V —> View(和界面相关的部分)
C —> COntroller(联系界面和数据之间的业务逻辑)
懒汉单例模式
真正使用的时候,才创建实例
如果不用,就不创建了
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;
/**
* @projectName: blog_system
* @package: model
* @className: DBUtil
* @author: 王嘉辉
* @description:
* @date: 2023/10/23 14:46
* @version: 1.0
*/
/*
* 通过这个类,封装数据库的连接操作
* */
public class DBUtil {
//这个类中要提供DataSource,DataSource 对于一个项目来说,有一个就行了(单例)
private 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/blog_system?characterEncoding=utf8&useSSL=false");
((MysqlDataSource) dataSource).setUser("root");
((MysqlDataSource) dataSource).setPassword("0126");
}
}
}
return dataSource;
}
public static Connection getConnection() throws SQLException {
return getDataSource().getConnection();
}
public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet) {
try {
if (resultSet != null) {
resultSet.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
2.创建两个表对应的实体类
package model;
/**
* @projectName: blog_system
* @package: model
* @className: User
* @author: 王嘉辉
* @description:
* @date: 2023/10/23 15:05
* @version: 1.0
*/
/*
* 这个类表示数据库中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;
}
}
package model;
import java.sql.Timestamp;
/**
* @projectName: blog_system
* @package: model
* @className: Blog
* @author: 王嘉辉
* @description:
* @date: 2023/10/23 15:05
* @version: 1.0
*/
/*
* 这个类表示数据库中blog表的内容
* 每个 blog 对象,就对应 blog 表中的一条记录
* */
public class Blog {
private int blogId;
private String title;
private String content;
private int userId;
private Timestamp postTime;
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 int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public Timestamp getPostTime() {
return postTime;
}
public void setPostTime(Timestamp postTime) {
this.postTime = postTime;
}
}
3.封装一些必要的增删改查操作
DAO —> Data Access Object
通过这样的对象来访问数据
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;
/**
* @projectName: blog_system
* @package: model
* @className: BlogDao
* @author: 王嘉辉
* @description:
* @date: 2023/10/23 15:14
* @version: 1.0
*/
public class BlogDao {
// 把一个博客对象插入到 blog 表中
public void insert(Blog blog) {
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
//建立连接
connection = DBUtil.getConnection();
//构造sql语句
String 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());
statement.setTimestamp(4, blog.getPostTime());
//执行sql
statement.executeUpdate();
}catch (SQLException e){
e.printStackTrace();
}finally {
//关闭
DBUtil.close(connection, statement, null);
}
}
// 从 blog 表中查找到所有的 blog 对象
public List<Blog> selectAll() {
List<Blog> blogs = new ArrayList<>();
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
connection = DBUtil.getConnection();
String sql = "select * from blog";
statement = connection.prepareStatement(sql);
while (resultSet.next()) {
Blog blog = new Blog();
blog.setBlogId(resultSet.getInt("blogId"));
blog.setTitle(resultSet.getString("title"));
blog.setContent(resultSet.getString("content"));
blog.setUserId(resultSet.getInt("userId"));
blog.setPostTime(resultSet.getTimestamp("postTime"));
blogs.add(blog);
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
DBUtil.close(connection, statement, resultSet);
}
return blogs;
}
// 从 blog 表中查找到指定的 blog 对象
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.setBlogId(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;
}
// 删除指定的 blog 对象
public void delete(int blogId) {
Connection connection = null;
PreparedStatement statement = null;
try {
connection = DBUtil.getConnection();
String sql = "delete from blog where blogId = ?";
statement.setInt(1, blogId);
statement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}finally {
DBUtil.close(connection, statement, null);
}
}
}
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;
/**
* @projectName: blog_system
* @package: model
* @className: BlogDao
* @author: 王嘉辉
* @description:
* @date: 2023/10/23 15:14
* @version: 1.0
*/
public class BlogDao {
// 把一个博客对象插入到 blog 表中
public void insert(Blog blog) {
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
//建立连接
connection = DBUtil.getConnection();
//构造sql语句
String 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());
statement.setTimestamp(4, blog.getPostTime());
//执行sql
statement.executeUpdate();
}catch (SQLException e){
e.printStackTrace();
}finally {
//关闭
DBUtil.close(connection, statement, null);
}
}
// 从 blog 表中查找到所有的 blog 对象
public List<Blog> selectAll() {
List<Blog> blogs = new ArrayList<>();
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
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"));
blog.setContent(resultSet.getString("content"));
blog.setUserId(resultSet.getInt("userId"));
blog.setPostTime(resultSet.getTimestamp("postTime"));
blogs.add(blog);
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
DBUtil.close(connection, statement, resultSet);
}
return blogs;
}
// 从 blog 表中查找到指定的 blog 对象
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.setBlogId(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;
}
// 删除指定的 blog 对象
public void delete(int blogId) {
Connection connection = null;
PreparedStatement statement = null;
try {
connection = DBUtil.getConnection();
String sql = "delete from blog where blogId = ?";
statement.setInt(1, blogId);
statement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}finally {
DBUtil.close(connection, statement, null);
}
}
}
4.前后端交互逻辑的实现
1.博客列表页
展示博客列表
1.约定前后端交互接口
请求:
GET/blog
响应:
2.写后端代码
创建一个Servlet来处理对应的请求
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;
/**
* @projectName: blog_system
* @package: api
* @className: BlogServlet
* @author: 王嘉辉
* @description:
* @date: 2023/10/24 11:10
* @version: 1.0
*/
@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();
List<Blog> blogs = blogDao.selectAll();
String respString = objectMapper.writeValueAsString(blogs);
resp.setContentType("application/json;charset=utf8");
resp.getWriter().write(respString);
}
}
3.写前端代码
构造ajax请求,按照上述约定,发送HTTP请求
此时得修改前端代码.(先把前端代码拷贝到咱们的项目中)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>博客列表页</title>
<link rel="stylesheet" href="css/common.css">
<link rel="stylesheet" href="css/list.css">
<!--引入jquery-->
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
</head>
<body>
<!--导航栏,nav是导航栏的缩写-->
<div class="nav">
<!--logo-->
<img src="image/1.jpg" alt="">
<!---->
<div class="title" >我的博客系统</div>
<!--只是一个空白,用来把后面的链接挤过去(这是一个简单粗暴地写法)-->
<div class="spacer"></div>
<a href="list.html">主页</a>
<a href="edit.html">写博客</a>
<!--注销 这里的地址后面提到-->
<a href="">注销</a>
</div>
<!--页面主体部分-->
<div class="container">
<!--左侧信息-->
<div class="container-left">
<!--这个div标识整个用户信息的区域-->
<div class = "card">
<!--用户头像-->
<img src = "image/3.jpg" alt = "">
<!--用户名-->
<h3>南北</h3>
<!-- Gitee 地址-->
<a href="https://gitee.com/D2814413094">Gitee 地址</a>
<!--统计信息-->
<div class="counter">
<span>文章</span>
<span>分类</span>
</div>
<div class="counter">
<span>3</span>
<span>1</span>
</div>
</div>
</div>
<!--右侧信息-->
<div class="container-right">
<!--<div class="blog">
<div class="title">我的第一篇博客</div>
<div class="data">2022-1-26 20:07:11</div>
<div class="desc">
清夜无尘,月色如银。酒斟时、须满十分。浮名浮利,虚苦劳神。叹隙中驹,石中火,梦中身。虽抱文章,开口谁亲。且陶陶、乐尽天真。几时归去,作个闲人。对一张琴,一壶酒,一溪云。
</div>
<a href="detail.html?blog=1">查看全文 >></a>
</div>-->
</div>
</div>
<script>
//通过ajax给服务器发请求,获取到所有的博客数据,并且构造到页面上
function getBlogs() {
$.ajax({
type:'get',
url:'blog',
success:function (body) {
//根据返回的数据,构造出页面中对应的元素
let containerRight = document.querySelector('.container-right');
for (let blog of 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 = 'data';
dateDiv.innerHTML = blog.postTime;
let descDiv = document.createElement("div");
descDiv.className = 'desc';
descDiv.innerHTML = blog.content;
let a = document.createElement("a");
a.href = 'detail.html?blogId=' + blog.blogId;
a.innerHTML = '查看全文 >>';
//把上述标签构造好了之后,还需要组合起来
blogDiv.appendChild(titleDiv);
blogDiv.appendChild(dateDiv);
blogDiv.appendChild(descDiv);
blogDiv.appendChild(a);
containerRight.appendChild(blogDiv);
}
}
});
}
//要记得在定义完函数以后调用函数.
getBlogs();
</script>
</body>
</html>
前端代码要做的事情,就是根据咱们当前这个页面的结构,来构造出内容~~
页面结构是啥,这里的内容就怎么构造~
我们在数据库中插入数据后发现当前发现有一些问题.
1.显示的时间不对
针对web程序的问题,首先要做的就是先明确问题是由前端还是后端引起的
通过抓包就可以知道问题是哪边的!
如果抓包结果返回的数据就是时间戳,不是格式化时间,说明是后端问题.
如果抓包的结果是格式化时间,说明是前端问题.
当前可以看到后端返回的就是时间戳了!!
后端返回的数据,是Jackson把blogs转成json字符串
直接返回的
jackson会遍历当前的blogs List,取到每个blog对象
去调用blog对象的每个getter方法,拿到对应的属性,构造json字符串
由于这个方法返回的是时间戳
导致最终构造json字符串的时候往里构造的内容就是时间戳了~
如果此处可以得到一个格式化时间,后续的json字符串里,也就是格式化时间了~~
具体如何生成格式化时间?
要用到SimpleDateFormat这个类~
咱们要生成格式化时间,这个时间具体是啥样的格式?需要描述一下~~
所以Blog里的方法要改为
public String getPostTime() {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
return format.format(postTime);
}
同时BlogDao里的数据库操作也要改成
statement.setString(4, blog.getPostTime());
2.博客文章的顺序应该是新的文章在上面
只需把BlogDao里的操作改为
String sql = "select * from blog order by postTime desc";
3.博客列表页,显示的是正文的一段摘要
我们就需要更改BlogDao中的Content
String content = resultSet.getString("content");
if (content.length() > 50) {
content = content.substring(0,50) + "...";
}
blog.setContent(content);
我们在数据库中在插入数据然后观察:
2.博客详情页
1.约定前后端交互接口
请求:
GET/blog?blogId=1
响应
{
blogId:1,
title:"第一篇博客",
content:"这是正文",
//这里要获取到完整的博客正文
userId:1,
postTime:"2023-01-26 12:00:00"
}
2.编写后端代码
每个Servlet类都是关联了一个路径的.
如果路径确定了,对应的Servlet也就明确了.
所以我们还是只需调整BlogServlet即可!
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//从 query string 中查询一下是否有blogId,如果有,就查询指定博客;如果没有就是查询所有博客
BlogDao blogDao = new BlogDao();
String blogId = req.getParameter("blogId");
if(blogId == null) {
List<Blog> blogs = blogDao.selectAll();
String respString = objectMapper.writeValueAsString(blogs);
resp.setContentType("application/json;charset=utf8");
resp.getWriter().write(respString);
}else {
Blog blog = blogDao.selectOne(Integer.parseInt(blogId));
String respString = objectMapper.writeValueAsString(blog);
resp.setContentType("application/json;charset=utf8");
resp.getWriter().write(respString);
}
}
此处的关键就是根据 blogId 这个query string是否存在,来分两个情况讨论~
3.编写前端代码
我们预期的url:blog?blogId=1
?blogId=1这一段就是从location,search来的
location是一个全局标量
相当于Document一样
由于当前响应的Content-Type 是 application/json
此时jquery就会直接把这个响应数据,转成js对象
blog.blogId
body.title
…
设置正文,正文内容应该是 markdown 格式的数据
此处要显示的应该是渲染过的markdown的内容,而不是markdown的原始字符串.
第一个参数,是一个html元素的id,接下来渲染的结果会放到对应的元素中.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>博客详情页</title>
<link rel = "stylesheet" href="css/common.css">
<link rel = "stylesheet" href="css/detail.css">
<!--引入jquery-->
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<!-- 引入 editor.md 的依赖 -->
<link rel="stylesheet" href="editor.md/css/editormd.min.css" />
<script src="js/jquery.min.js"></script>
<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>
</head>
<body>
<!--导航栏,nav是导航栏的缩写-->
<div class="nav">
<!--logo-->
<img src="image/1.jpg" alt="">
<!---->
<div class="title" >我的博客系统</div>
<!--只是一个空白,用来把后面的链接挤过去(这是一个简单粗暴地写法)-->
<div class="spacer"></div>
<a href="list.html">主页</a>
<a href="edit.html">写博客</a>
<!--注销 这里的地址后面提到-->
<a href="">注销</a>
</div>
<!--页面主体部分-->
<div class="container">
<!--左侧信息-->
<div class="container-left">
<!--这个div标识整个用户信息的区域-->
<div class = "card">
<!--用户头像-->
<img src = "image/3.jpg" alt = "">
<!--用户名-->
<h3>南北</h3>
<!-- Gitee 地址-->
<a href="https://gitee.com/D2814413094">Gitee 地址</a>
<!--统计信息-->
<div class="counter">
<span>文章</span>
<span>分类</span>
</div>
<div class="counter">
<span>3</span>
<span>1</span>
</div>
</div>
</div>
<!--右侧信息-->
<div class="container-right">
<h3></h3>
<div class="data"></div>
<div id="content">
</div>
</div>
<script>
function getBlog() {
$.ajax({
type:'get',
url:'blog'+location.search,
success:function (body) {
//设置博客标题
let h3 = document.querySelector('.container-right h3');
h3.innerHTML = body.title;
//设置发布时间
let dataDiv = document.querySelector('.container-right .data');
dataDiv.innerHTML = body.postTime;
//设置正文,正文内容应该是 markdown 格式的数据
//此处要显示的应该是渲染过的markdown的内容,而不是markdown的原始字符串.
//第一个参数,是一个html元素的id,接下来渲染的结果会放到对应的元素中.
editormd.markdownToHTML('content',{markdown:body.content});
}
});
}
getBlog();
</script>
</div>
</body>
</html>
当我们修改完代码以后,点击博客详情,如果发现内容还是之前的内容,就需要使用 Ctrl + f5 强制刷新,避免浏览器读取缓存~~
3.博客登录页
输入用户名,密码
点击登录,就会触发登录操作~~
期望,点击的时候,给后端发起一个http请求
后端做一些登录相关的逻辑(验证用户名密码之类的)
1.约定前后端交互接口
此处提交用户名密码,可以使用form也可以使用ajax
form是更简单的做法~
请求
POST/login
Content-Type:application/x-www-form-urlencoded
专属于form表单的类型
username=zhangsan&password=123
query string 就是这个格式,在服务器直接使用getParameter就能获取到内容
响应:
HTTP/1.1 302
Location:list.html
登陆成功,直接跳转主页
如果是通过302跳转
前端必须使用form,不能使用ajax
ajax的话,收到302响应,不会触发页面跳转~~
(必须使用js来跳转)
2.编写后端代码
需要创建一个Servlet来处理上述login的请求!!
如果前端传过来的用户名带有中文~~,此时这个代码拿到的username可能是乱码
需要明显的告诉Servlet按照啥样的编码方式来理解请求参数!!
会话,让服务器保存当前用户的一些数据!
会话可以理解成一个hash表~
每个用户(每个客户端)都会对应到这个表里的一个键值对
其中键是一个字符串,sessionId(唯一的字符串)
其中值是HttpSession对象了
这个对象,也可以按照键值对的方式来存储其他数据(getAttribute,setAttribute)
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;
/**
* @projectName: test-2023-10-27
* @package: api
* @className: LoginServlet
* @author: 王嘉辉
* @description:
* @date: 2023/10/27 17:36
* @version: 1.0
*/
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
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;charset=utf8");
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;charset=utf8");
resp.getWriter().write(html);
return;
}
if(!password.equals(user.getPassword())) {
String html = "<h3> 用户名或密码错误!</h3>";
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write(html);
return;
}
//3.需要设置会话
HttpSession session = req.getSession(true);
//此处就把用户对象存储到session中了,下次用户访问其他页面,就可以直接拿到会话,进一步拿到之前的user对象了.
session.setAttribute("user",user);
//4.返回一个重定向相应,能够跳转到博客列表页
resp.sendRedirect("list.html");
}
}
3,编写前端代码
id属性,只是针对html生效,只是用来方便获取到该元素.
name属性则是针对form表单构造http请求的~
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>博客登录页</title>
<link rel="stylesheet" href="css/common.css">
<link rel="stylesheet" href="css/login.css">
</head>
<body>
<!--导航栏,nav是导航栏的缩写-->
<div class="nav">
<!--logo-->
<img src="image/1.jpg" alt="">
<!---->
<div class="title" >我的博客系统</div>
<!--只是一个空白,用来把后面的链接挤过去(这是一个简单粗暴地写法)-->
<div class="spacer"></div>
<a href="list.html">主页</a>
<a href="edit.html">写博客</a>
</div>
<!--登录页的版心-->
<div class="login-container">
<!--登录对话框-->
<div class="login-dialog">
<h3>登录</h3>
<!--使用form包装一下下列内容,便于给后续服务器提交数据-->
<form action="login" method="post">
<div class="row">
<span>用户名</span>
<input type="text" id="username"name="username">
</div>
<div class="row">
<span>密码</span>
<input type="password" id="password"name="password">
</div>
<div class="row">
<input type="submit" id="submit" value="登录">
</div>
</form>
</div>
</div>
</body>
</html>
此时,输入正确的账号密码,就会跳转到博客列表页~
输入错误
4.实现强制要求登录
做出以下设定:
当用户访问博客列表页/详情页/编辑页的时候,必须是登录状态,如果未登录,则直接跳转到登录页要求用户登录
在博客列表页/详情页/登录页,页面加载的时候,发起一个ajax请求,通过这个请求,访问服务器,获取到当前的登录状态.
如果当前未登录,则跳转到登录页面
如果已登录,则不做任何操作~~
1.约定前后端交互接口
请求:
GET/login
响应:
登录成功,就返回200这样的响应
登录失败(未登录状态),返回403这样的响应
2.编写后端代码
在loginServlet写一个doGet判定当前的登录状态.
//通过这个方法,判定用户的登录状态,已登录,返回200,未登录,返回403
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//看当前请求是否存在会话,并且当前的会话是否包含user对象
//getSession(boolean create)意思是返回当前reqeust中的HttpSession ,如果当前reqeust中的HttpSession 为null,当create为true,就创建一个新的Session,否则返回null;
HttpSession session = req.getSession(false);
if (session == null) {
resp.setStatus(403);
return;
}
User user = (User) session.getAttribute("user");
if (user == null) {
//虽然会话对象存在,但是用户对象没有,未登录状态
resp.setStatus(403);
return;
}
//已经登陆了
//200 是默认的状态码,此处不写200也是可以的
resp.setStatus(200);
}
}
3.编写前端代码
需要在博客列表页/详情页加入
<script>
function getLoginStatus() {
$.ajax({
type:'get',
url:'login',
success:function (body) {
//响应200的时候,执行success回调
//用户已经登录啥都不用干
console.log("用户已经登录了.");
},
error:function (body) {
//响应403的时候,执行error回调(只要返回不是2开头的,都会触发error)
//跳转到login.html主页
location.assign("login.html");
}
});
}
getLoginStatus();
</script>
5.显示用户信息
在博客列表页,此处显现的是登录的用户信息!!
在博客详情页,显示的是文章作者的信息!!
针对这两个部分的数据,就需要分别进行获取~~
1.约定前后端交互接口
在博客列表页,要获取到登录用户的信息~~
可以直接复用刚刚的那个 getLoginStatus 方法.
让这个方法在登录成功之后,就把当前用户信息,给返回回来~~
请求:
GET/login
响应:
HTTP/1.1 200
Content-Type:application/json;
{
userId:1
username:zhangsan,
}
页面拿到这个数据就可以显示到界面上了~~
2.开发后端代码
基于现有代码进行修改
//200 是默认的状态码,此处不写200也是可以的
resp.setStatus(200);
resp.setContentType("application/json;charset=utf8");
user.setPassword("");//避免把密码也返回给前端,减少密码泄露的风险
String respJson = objectMapper.writeValueAsString(user);
resp.getWriter().write(respJson);
3.开发前端代码
基于现有代码进行修改
三个页面都在使用 getLoginStatus
实际上,只是需要在博客列表页,做出这样的调整,另外两个页面不变!!
<script>
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) {
//响应403的时候,执行error回调(只要返回不是2开头的,都会触发error)
//跳转到login.html主页
location.assign("login.html");
}
});
}
</script>
在针对详情页,进行处理.
详情页这里.显示的是当前文章的作者信息.
先根据blogId查询到文章对象,进一步拿到文章的作者的id
再根据作者id查询对应作者名字,显示到页面上~
1.约定前后端交互接口
请求:
GET/user?blogId = 1
响应:
HTTP/1.1 200
Content-Type:application/json;
{
userId:1
username:zhangsan,
}
2.编写后端代码
创建一个新的Servlet处理上述请求.
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;
/**
* @projectName: test-2023-10-30
* @package: api
* @className: UserServlet
* @author: 王嘉辉
* @description:
* @date: 2023/10/30 16:35
* @version: 1.0
*/
@WebServlet("/user")
public class UserServlet extends HttpServlet {
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("application/json;charset=utf8");
//1.先读取出blogId
String blogId = req.getParameter("blogId");
if (blogId == null || blogId.equals("")) {
//直接返回一个userId为0的对象,因为最终返回的是一个json数据
//此处也是返回 json 格式比较好,如果返回一个html,前端处理就会很麻烦
String respJson = objectMapper.writeValueAsString(new User());
resp.getWriter().write(respJson);
System.out.println("参数给定的 blogId 为空!!");
return;
}
//2.查询数据库,查询对应的Blog对象
BlogDao blogDao = new BlogDao();
Blog blog = blogDao.selectOne(Integer.parseInt(blogId));
if (blog == null) {
String respJson = objectMapper.writeValueAsString(new User());
resp.getWriter().write(respJson);
System.out.println("参数给定的 blogId 不存在!!");
return;
}
//根据 blog 中的 userId,查询作者信息
UserDao userDao = new UserDao();
User user = userDao.selectUserById(blog.getUserId());
if(user == null) {
String respJson = objectMapper.writeValueAsString(new User());
resp.getWriter().write(respJson);
System.out.println("该博客对应的作者不存在!!");
return;
}
//4.把user对象返回给页面
String respJson = objectMapper.writeValueAsString(user);
resp.getWriter().write(respJson);
}
}
3.编写前端代码
<script>
function getAuthor() {
$.ajax({
type:'get',
url:'user' + location.search,
success:function (body) {
//把响应中的得到的user对象的数据,给构造到页面上
if (body.userId == 0) {
//服务器没有找到匹配的用户
alert("当前未找到作者信息!")
return;
}
//body 是一个合法的 user 对象
let h3 = document.querySelector('.card h3');
h3.innerHTML = body.username;
}
});
}
getAuthor();
</script>
6.用户退出登录(注销)
这个注销,是一个a标签
这个a标签在点击的时候,就会触发一个GET请求
服务器收到这个get请求,就可以把当前用户的会话中的user对象给删掉即可!!
1.约定前后端交互接口
请求:
GET/logout
响应:
HTTP/1.1 302
Location:login.html
2.编写后端代码
创建新的Servlet
package api;
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;
/**
* @projectName: test-2023-10-30
* @package: api
* @className: LogoutServlet
* @author: 王嘉辉
* @description:
* @date: 2023/10/30 17:14
* @version: 1.0
*/
@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession(false);
if (session == null) {
//用户本来就没登录,直接跳转到登录页.
resp.sendRedirect("login.html");
return;
}
//user 存在,就删除了,不存在,也不会有副作用
session.removeAttribute("user");
resp.sendRedirect("login.html");
}
}
3.编写前端代码
只需给a标签写个href的值即可!
登录后
点击注销
7.实现发布博客
在博客编辑页进行相关操作~~
用户写的博客标题和正文,就可以随着点击按钮而进行上传
服务器就可以保存上述数据到数据库中
接下来,后续就可以在博客列表页和博客详情页中看到了刚才新的博客了~
1.约定前后端交互接口
使用form提交博客~~
请求:
POST/blog
Content-Type:application/x-www-form-urlencoded
title=标题&content=…
响应:
HTTP/1.1 302
Location:list.html
发布成功,直接跳转到列表页~
2.编写后端代码
package api;
import com.fasterxml.jackson.databind.ObjectMapper;
import model.Blog;
import model.BlogDao;
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;
import java.sql.Timestamp;
import java.util.List;
/**
* @projectName: blog_system
* @package: api
* @className: BlogServlet
* @author: 王嘉辉
* @description:
* @date: 2023/10/24 11:10
* @version: 1.0
*/
@WebServlet("/blog")
public class BlogServlet extends HttpServlet {
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//从 query string 中查询一下是否有blogId,如果有,就查询指定博客;如果没有就是查询所有博客
BlogDao blogDao = new BlogDao();
String blogId = req.getParameter("blogId");
if(blogId == null) {
List<Blog> blogs = blogDao.selectAll();
String respString = objectMapper.writeValueAsString(blogs);
resp.setContentType("application/json;charset=utf8");
resp.getWriter().write(respString);
}else {
Blog blog = blogDao.selectOne(Integer.parseInt(blogId));
String respString = objectMapper.writeValueAsString(blog);
resp.setContentType("application/json;charset=utf8");
resp.getWriter().write(respString);
}
}
@Override
protected 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 = "<h3>title 或者 content 为空!! 新增博客失败! </h3>";
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write(html);
return;
}
//2.从会话中拿到作者id
HttpSession session = req.getSession(false);
if (session == null) {
String html = "<h3>当前用户未登录 新增博客失败! </h3>";
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write(html);
return;
}
User user = (User) session.getAttribute("user");
if (user == null) {
String html = "<h3>当前用户未登录 新增博客失败! </h3>";
resp.setContentType("text/html;charset=utf8");
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("list.html");
}
}
3.编写前端代码
修改页面,加上form表单,同时让页面提交的时候按照咱们的约定,构造出HTTP请求.
这里是md编辑器
这个div内部的内容,其实是被editor.md做出了各种修改~~
如何才能把编辑框内部内容
作为form的一部份,提交给服务器?
editor.md的官方文档的Demo里给了答案~
(1)先给#editor div 中放一个隐藏的 textarea.后续编辑器输入框的内容就会被自动放到这个 textarea 中.
(2)在editor.md的初始化代码中,需要新增一个选项saveHTMLToTextarea:true