目录
一.准备工作
二.实现数据库代码(JDBC)
1.创建数据库/表结构==>数据库设计
2.封装数据库(Model)
1>创建DBtil封装数据库连接操作
2>创建实体类-->表示一条记录
3>封装针对数据的增删改查
三.博客列表页
1.约定前后端
2.编写服务器代码
3.编写客户端代码
四.博客详情页
1.约定前后端交互接口
2.编写服务器代码
3.编写客户端代码
五.博客登录页
3.编写服务器代码
六.登录功能完成后,要调整一下,之前的两个页面
1.约定前后端交互接口
2.编写服务器代码
3.编写客户端代码
七.显示用户信息
1.博客列表页,显示用户信息,是当前登录的用户
2.在博客详情页,显示用户的信息是当前文章的作者信息
1.编写服务器代码
3.编写服务器代码
八:实现"注销功能"
1.约定前后端接口
2.编写服务器代码
步骤
3.编写客户端代码
九.发布博客
1.约定前后端交互接口
2.编写服务器代码
步骤
3.编写客户端代码
十.删除博客
1.约定前后端接口
2.编写客户端代码
步骤
3.编写服务器代码
步骤
前言
- 实现数据库代码,并进行封装;首先创建实现了数据库的初始化,然后建立了两个实体类,然后分别对User表进行了查找功能和Blog表的插入、查找和删除功能.
- 通过Servlet设计并实现具体功能,包括登录,浏览,发表,删除.
一.准备工作
1.创建maven项目~
2.引入依赖. servlet, jackson, mysql
3.创建必要的目录~
4.编写代码~
5/6.打包部署(直接基于smart tomcat)
7.在浏览器中验证
正式编写
把之前的前端页面引入到项目中,直接拷贝到webapp目录下
这些文件可以理解成静态资源
二.实现数据库代码(JDBC)
1.创建数据库/表结构==>数据库设计
前面写博客页面的时候,搞过这么几个页面.
1.博客列表页.显示博客的列表.
2.博客详情页.点击博客列表页, .上面列出的博客条目,跳转到的页面,显示博客的完整内容.
3.登录页
4.博客编辑页.基于editor.md搞了-个markdown编辑器~~基于这个页面来发布博客了.
==>
存储博客~当点击发布的时候,博客被发布到服务器上,就要被存起来获取博客~
在博客列表页和博客详情页,能够拿到博客的内容.
还能够进行登录校验~~
==>
设计表,就需要抓住需求中的实体.(关键性的名词)
博客表用来存储所有的博客数据~~
用户表,用户登录,就需要用到这个表~
创建dp.sql
创建java1_blog库
库中创建blog表(blogId , title, content , userId, postTime)和user表(userId ,username, password)
-- 编写建库建表的sql
create database if not exists java1_blog;
use java1_blog;
-- 创建一个博客表
drop table if exists blog;
create table blog(
blogId int primary key auto_increment,
title varchar(1024),
content mediumtext,
userId int,-- 文章作者的id
postTime datetime -- 发布时间
);
-- 创建用户表
drop table if exists user;
create table user(
userId int primary key auto_increment,
username varchar(128) unique,
password varchar(128)
);
insert into user values(null,'zhang','123');
insert into user values(null,'lisi','123');
把dp.sql中的代码,赋值到mysql中,并检查是否创建成功
2.封装数据库(Model)
1>创建DBtil封装数据库连接操作
1.创建数据源(懒汉模式,要解决线程安全问题)
2.建立连接
3.关闭
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;
/**
* @author KYF
* @create 2023-04-21
*/
//使用这个类和数据库建立连接
public class DBUtil {
private static final String URL="jdbc:mysql://127.0.0.1:3306/java1_blog?characterEncoding=utf8&useSSL=false";
private static final String USERNAME="root";
private static final String PASSWORD="123456";
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(URL);
((MysqlDataSource)dataSource).setUser(USERNAME);
((MysqlDataSource)dataSource).setPassword(PASSWORD);
}
}
}
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) {
e.printStackTrace();
}
}
if(statement!=null){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(connection!=null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
2>创建实体类-->表示一条记录
创建Blog和User类
//每个blog对象表示blog表中的一条记录
public class Blog {
private int blogId;
private String title;//标题
private String content;//正文
private int userId;//用户id
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;
// }
//不返回时间戳对象,返回一个String(格式化好的时间)
public String getPostTime(){
SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return simpleDateFormat.format(postTime);
}
public void setPostTime(Timestamp postTime) {
this.postTime = postTime;
}
}
//每个User对象表示User表中的一条记录
public class User {
private int userId;
private String username;
private String password;
public int getUserId(int userId) {
return this.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;
}
}
3>封装针对数据的增删改查
1.和数据库建立连接
2.构造SQL语句
3.执行SQL
4.关闭连接,释放资源
封装博客表的基本操作:增删查
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;
/**
* @author KYF
* @create 2023-04-21
*/
//封装博客表的基本操作
public class BlogDao {
//1.往博客表中,插入博客
public void insert(Blog blog){
//JDBC基本代码
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) {
e.printStackTrace();
}finally {
//4.关闭连接,释放资源
DBUtil.close(connection,statement,null);
}
}
//2.获取博客表中的所有博客信息(用于博客列表页)
public List<Blog> selectAll(){
Connection connection=null;
PreparedStatement statement=null;
ResultSet resultSet=null;
List<Blog> blogs=new ArrayList<>();
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"));
//要截断(太长要去掉后面)
String content=resultSet.getString("content");
if(content.length()>50){
content=content.substring(0,50)+"...";
}
blog.setContent(content);
blog.setUserId(resultSet.getShort("userId"));
blog.setPostTime(resultSet.getTimestamp("postTime"));
blogs.add(blog);
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
DBUtil.close(connection,statement,resultSet);
}
return blogs;
}
//3.根据博客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();
//因为是主键,查询结果要么是0,要么是1
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.getShort("userId"));
blog.setPostTime(resultSet.getTimestamp("postTime"));
return blog;
}
} catch (SQLException e) {
e.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 e) {
e.printStackTrace();
}finally {
DBUtil.close(connection,statement,null);
}
}
//没有改
}
用户表的基本操作:
1.根据用户名查找用户信息
登录中使用
2.根据用户Id用户信息
博客详情页,根据用户ID查询名字,把作者名字显示出来
package model;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* @author KYF
* @create 2023-04-21
*/
//提供了针对用户表的基本操作
public class UserDao {
//1.根据用户名查找用户信息
//登录中使用
public User selectByName(String username){
Connection connection=null;
PreparedStatement statement=null;
ResultSet resultSet=null;
try{
connection=DBUtil.getConnection();
String sql="select * from user blog 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;
}
//2.根据用户Id用户信息
//博客详情页,根据用户ID查询名字,把作者名字显示出来
public User selectById(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;
}
}
三.博客列表页
能展示数据库中的列表,页面加载,要通过ajax发起HTTP请求,从服务器获取到博客列表页数据
1.约定前后端
请求:
GET/blog
响应:(json数组)
[
{
blogId:1,
title:'这是第一篇博客',
content:'这是博客正文',(正文的摘要),要截取正文
userId:1,
postTime:'2023-04-20 15:00:00'
},
{
blogId:2,
title:'这是第一篇博客',
content:'这是博客正文',
userId:1,
postTime:'2023-04-20 15:00:00'
},
........
]
2.编写服务器代码
获取数据库中的博客列表:
- 从数据库中查询博客列表,转成json给格式返回
- 把blogs转成JSON格式
package controller;
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;
/**
* @author KYF
* @create 2023-04-21
*/
//处理/blog路径对应的请求
@WebServlet("/blog")
public class BlogServlet extends HttpServlet {
//获取数据库中的博客列表
private ObjectMapper objectMapper=new ObjectMapper();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//从数据库中查询博客列表,转成json给格式返回
BlogDao blogDao=new BlogDao();
List<Blog> blogs=blogDao.selectAll();
//把blogs转成JSON格式
String respJson=objectMapper.writeValueAsString(blogs);
//顺序不能颠倒-->先写body,设置的ContentType就不会生效
resp.setContentType("application/json;charset=utf8");
resp.getWriter().write(respJson);
}
}
3.编写客户端代码
在页面加载时,让页面通过ajax访问服务器,获取到数据库中的博客数据,并填到页面中
获取到的body是一个js对象数组,每个元素就是一个js对象
1.先把.right原有的内容清空
2.遍历body,构造出一个个blogDiv
3.在每一个blogDiv中构造所有信息
- 创建一个结点
- 命名
- 得到blog中的信息
- 插入到blogDiv中
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>博客列表</title>
<!-- 外部的css要通过link引入 -->
<link rel="stylesheet" href="CSS/commin.css">
<link rel="stylesheet" href="CSS/blog_list.css">
</head>
<body>
<!-- 这个是导航栏 -->
<div class="nav">
<img src="image/flower.jpg" alt="">
<span>我的博客系统</span>
<!-- 空白元素,用来占位置 -->
<div class="spacer"></div>
<a href="blog_list.html">主页</a>
<a href="blog_edit.html">写博客</a>
<a href="#">注销</a>
</div>
<!-- container作为版心 -->
<div class="container">
<!-- 左侧个人信息 -->
<div class="left">
<!-- 表示整个用户信息区域 -->
<div class="card">
<img src="image/girl.jpg" alt="">
<h3>沐晴</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="right">
<!-- 一个blog对应一个博客 -->
<div class="blog">
<!-- 博客标题 -->
<div class="title">
我的第一篇博客
</div>
<!-- 博客发布时间 -->
<div class="date">
2023-04-04 22:39:00
</div>
<!-- 博客摘要 -->
<div class="desc">
风羽,落花依依。独自行走在田野间的小路,氤氲在心上的离愁,始终带着些许迷离,些许感伤。记忆中,往昔的片段,总有太多不舍。故事的结局里,总是萦绕着心碎的痛楚。
</div>
<a href="blog_detail.html">查看全文> ></a>
</div>
<!-- 一个blog对应一个博客 -->
<div class="blog">
<!-- 博客标题 -->
<div class="title">
我的第二篇博客
</div>
<!-- 博客发布时间 -->
<div class="date">
2023-04-05 20:39:00
</div>
<!-- 博客摘要 -->
<div class="desc">
给我一滴水,我为你化成大海,让你游进幸福的港湾,给我一点阳光,我为你化成美丽的春天,让你飞到无忧的天地。亲爱的,周末愉快。
</div>
<a href="#">查看全文> ></a>
</div>
</div>
</div>
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
<script>
//在页面加载时,通过ajax给服务器发送数据,并获取到博客列表信息,并显示在页面上
function getBlogList(){
$.ajax({
type:'get',
url:'blog',
success:function(body){
//获取到的body是一个js对象数组,每个元素就是一个js对象
//1.先把.right原有的内容清空
let rightDiv=document.querySelector('.right');
rightDiv.innerHTML='';
//2.遍历body,构造出一个个blogDiv
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;
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='查看全文 >>';
//此处点击跳转博客详情页
//这个跳转过程要告知服务器要访问哪个博客详情页
//获取博客详情页发送的请求
a.href='blog_detail.html?blogId='+blog.blogId;
blogDiv.appendChild(a);
//把blogDiv挂到dom树上
rightDiv.appendChild(blogDiv);
}
},
error:function(){
alert("获取博客列表失败!");
}
});
}
getBlogList();
</script>
</body>
</html>
页面加载时,触发ajax请求来访问服务器,获取到博客内容再次填充到博客详情页
四.博客详情页
在博客列表页点击查看详情,跳转到详情页,看到详情页正文
1.约定前后端交互接口
请求:
GET/blog?blogId=1(请求有参数)
响应
HTTP/1.1 200 OK
Content-Type:application/json
(响应结果不是json数组,只是单一对象)
{
blogId:1,
title:'这是第一篇博客',
content:'这是博客正文',(不发生截断)
userId:1,
postTime:'2023-04-20 15:00:00'
}
2.编写服务器代码
后端代码实现和博客列表页的获取基本相同,就放在了一个方法中来实现,使用blogId参数来区分是获取博客列表还是详情
- 根据参数判断返回博客列表,还是详情页(有参数)
- 先尝试获取req的blogId参数,如果参数存在,说明是博客详情页
- 如果不存在,说明要请求博客的列表
-
- 得到blogId
- 通过blogId得到对应的博客
- 把Blog类型转换成Json类型
package controller;
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;
/**
* @author KYF
* @create 2023-04-21
*/
//处理/blog路径对应的请求
@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();
resp.setContentType("application/json;charset=utf8");
//根据参数判断返回博客列表,还是详情页(有参数)
//先尝试获取req的blogId参数,如果参数存在,说明是博客详情页
//如果不存在,说明要请求博客的列表
String param=req.getParameter("blogId");
if(param==null){
//没有参数,获取博客列表页
//从数据库中查询博客列表,转成json给格式返回
List<Blog> blogs=blogDao.selectAll();
//把blogs转成JSON格式
String respJson=objectMapper.writeValueAsString(blogs);
//顺序不能颠倒-->先写body,设置的ContentType就不会生效
resp.getWriter().write(respJson);
}else{
//有参数,获取博客详情页
int blogId=Integer.parseInt(param);
Blog blog=blogDao.selectOne(blogId);
String respJson=objectMapper.writeValueAsString(blog);
resp.getWriter().write(respJson);
}
}
}
3.编写客户端代码
修改blog_detail.html,在页面加载时,能调用上述接口,来从服务器获取博客数据
1.构造请求/blog?blogId=5
在博客详情页拿到博客的具体内容,要构造一个请求/blog?blogId=5(这个参数告诉服务器,要哪个博客)
这个请求是blog_detail.html通过ajax发送,就要构造blogId=5这个参数,5在来到这个页面,url就带上这个参数了,
通过location.search能拿到?blogId=5这段内容,构造/blog?blogId=5这个请求
内容来自博客列表页
这个按钮是一个a标签,href属性
a中带的href属性,有参数了
内容来自于博客列表页代码
let a=document.createElement('a');
a.innerHTML='查看全文 >>';
a.href='blog_detail.html?blogId='+blog.blogId;
blogDiv.appendChild(a);
在博客列表页从服务器获取博客列表时,拿到的数据中有构造"查看全文"a标签按钮,构造按钮时,就给href设置的内容中加上了当前博客的blogId
2.博客正文用的是有格式化的markdown数据
如果代码直接把content设为innerHTML,意味着界面最终效果是
而期望的是
处理:
要让markdown文本内容被渲染成有特定样式的html片段,要使用editor.md库来完成
这个库不仅提供了markdown编辑器也提供了渲染功能
加markdown渲染得到的效果
步骤
1.引入editor.md依赖
2.去掉原先的正文用div标签 使用id选择器
3.设置函数getBlogDetail()
通过ajax提交,得到标题,时间和正文
<!DOCTY3.PE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>博客详情页</title>
<link rel="stylesheet" href="CSS/commin.css">
<link rel="stylesheet" href="CSS/blog_detail.css">
<!-- 引入 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>
<!-- 这个是导航栏 -->
<div class="nav">
<img src="image/flower.jpg" alt="">
<span>我的博客系统</span>
<!-- 空白元素,用来占位置 -->
<div class="spacer"></div>
<a href="blog_list.html">主页</a>
<a href="blog_edit.html">写博客</a>
<a href="#">注销</a>
</div>
<!-- container作为版心 -->
<div class="container">
<!-- 左侧个人信息 -->
<div class="left">
<!-- 表示整个用户信息区域 -->
<div class="card">
<img src="image/girl.jpg" alt="">
<h3>沐晴</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="right">
<!-- 使用这个div,来包裹整个博客的详情内容 -->
<div class="blog-content">
<h3>我的第一篇博客</h3>
<div class="date">2023-04-04 22:39:00</div>
<!-- 正文 -->
<div id="content" style="opacity: 60%">
</div>
</div>
</div>
</div>
<script>
function getBlogDetail(){
$.ajax({
type:'get',
//location.search拿到了形如'?blogiD=5'这样一段内容
url:'blog'+location.search,
success:function(body){
//根据body中的内容来构造页面
//构造标题
let h3=document.querySelector(".blog-content>h3");
h3.innerHTML=body.title;
//构造博客发布时间
let dateDiv=document.querySelector('.date');
dateDiv.innerHTML=body.postTime;
//构造博客正文
//如果代码直接把content设为innerHTML,意味着界面最终效果是原始的markdown字符串
//需要的是渲染后的带有格式的效果
// let content=document.querySelector('#content');
// content.innerHTML=body.content;
//第一个参数对应id=content的html标签,渲染后得到的html片段会被放到这个标签下
editormd.markdownToHTML('content',{
markdown:body.content
});
}
});
}
getBlogDetail();
</script>
</body>
</html>
五.博客登录页
用户访问login.html,输入用户名和密码,点击登录按钮,发起一个请求,把用户名和密码交给服务器;服务器对身份进行验证,若验证成功,页面跳转到博客列表页,若失败,就返回错误
1.约定前后端交互接口
1.请求:点击按钮发送请求
POST/login
Content-type:application/x-www-form-urlencode
(如果要form表单,就要把按钮改成input type="submit")
usernmae=zhangsan&password=123
2.响应:验证身份,跳转到博客列表页
HTTP/1.1 302
Location:blog_list.html
(登录成功后,跳转到主页(博客列表页))
2.编写客户端代码
步骤:
1.给代码套上一层form标签(路径,方法) class标签改成div标签
2.input加上name属性,加上属性,就是后续提交数据的键值对key
name中的值是key,用户输入的值是value(例如:username=zhangsan&password=123)
3.把button按钮换成input标签
问题
前端页面开发,html页面结构很重要,一旦页面结构调整,可能导致css或者js失效
调整后
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>登录页面</title>
<link rel="stylesheet" href="CSS/commin.css">
<link rel="stylesheet" href="CSS/blog_login.css">
</head>
<body>
<!-- 这个是导航栏 -->
<div class="nav">
<img src="image/flower.jpg" alt="">
<span>我的博客系统</span>
<!-- 空白元素,用来占位置 -->
<div class="spacer"></div>
<a href="blog_list.html">主页</a>
<a href="blog_edit.html">写博客</a>
<!-- 注销没有必要在登录页面展示 -->
<!-- <a href="#">注销</a> -->
</div>
<div class="login-container">
<form action="login" method="post">
<div class="login-dialog">
<h3>登录</h3>
<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">
<!-- <button>提交</button> -->
<input type="submit"id="submit" value="提交">
</div>
</div>
</form>
</div>
</body>
</html>
3.编写服务器代码
1.body的参数要和写法对应
usernmae=zhangsan&password=123
String username=req.getParameter("username");
2.文字格式
前端已经告诉浏览器,格式是utf8
接下来浏览器在输入框中输入汉字,也会使用utf8编码,
但是Servlet默认不是utf8解析,就要告诉serlvet要按照utf8解析请求
req.setCharacterEncoding("utf8");
针对请求进行设置:使用utf8格式进行解析请求
resp.setCharacterEncoding("utf8");
针对响应进行设置:构造的数据是按照utf8构造
3.
List I=null; -->为空(连盒子都没有)
List I=new ArrayList<>(); -->非空,对应一个空的ArrayList(一个空的盒子)
4.步骤:
1.获取请求中的参数-->请求内容缺失,登录失败
2.和数据库中的内容进行比较-->用户名没查得到,或者和密码不匹配,登陆失败
3.如果比较通过,就会创建会话
4.返回一个重定向报文,跳转到博客主页
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;
/**
* @author KYF
* @create 2023-04-22
*/
@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 || "".equals(username) ||password==null || "".equals(password)){
//请求内容缺失,登录失败
resp.setContentType("text/html");
resp.getWriter().write("当前用户名或密码为空");
return;
}
//2.和数据库中的内容进行比较
UserDao userDao=new UserDao();
User user=userDao.selectByName(username);
if(user==null || !user.getPassword().equals(password)){
//用户名没查得到,或者和密码不匹配
//登陆失败
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");
}
}
六.登录功能完成后,要调整一下,之前的两个页面
让这两个页面登录后才能访问
进入博客列表页/详情页,要先检查用户的登录状态
如果当前用户已经是登录状态,才能使用,如果是未登录状态,则强制跳转到login页面
处理:
在博客列表页/详情页,通过ajax访问一下服务器,获取当前的登录状态
如果获取到说明,已经登录,此时可以留在这个页面,如果没有登录,要跳转到登录页面
1.约定前后端交互接口
请求:
GET/login
响应
HTTP/1.1 200 OK
Content-Type:application/json
{
userId:1,
username='zhangsan',
}
(登录了返回当前登录的用户信息;未登录,则直接返回一个userId为0的对象)
2.编写服务器代码
步骤
- 检测会话是否存在,不存在说明未登录
- 有会话,但是会话中没有user对象,也是未登录
- 已经登录的状态,返回
注:不要把密码返回给前端
//用来让前端检测登录状态
private ObjectMapper objectMapper=new ObjectMapper();
@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=new User();
resp.getWriter().write(objectMapper.writeValueAsString(user));
return;
}
User user=(User) session.getAttribute("user");
if(user==null){
//虽然有会话,但是会话中没有user对象,也是未登录
user=new User();
resp.getWriter().write(objectMapper.writeValueAsString(user));
return;
}
//已经登录的状态
//不要把密码返回给前端
user.setPassword("");
resp.getWriter().write(objectMapper.writeValueAsString(user));
}
3.编写客户端代码
js/common.js
//放页面公共的代码
//加上一个逻辑,通过GET/login这个接口获取登录状态
function getUserInfo(pageName){
$.ajax({
tupe:'get',
url:'login',
success:function(body){
if(body.userId && body.userId>0){
console.log("当前用户登录成功!用户名为:"+body.username);
}else{
//登录失败
//前端页面,跳转到login.html
alert("当前你未登录!请登录后访问博客列表!");
location.assign('blog_login.html');
}
},
error:function(){
alert("当前您未登录!请登录后访问博客列表!");
location.assign('blog_login.html');
}
});
}
blog_list.html
<!-- 引入s文件,执行登录状态的检测 -->
<script src="js/common.js"></script>
<script>
getUserInfo('blog_list.html');
</script>
blog_detail.html
</script>
<script src="js/common.js"></script>
<script>
getUserInfo(blog_detail.html);
</script>
七.显示用户信息
1.博客列表页,显示用户信息,是当前登录的用户
//放页面公共的代码
//加上一个逻辑,通过GET/login这个接口获取登录状态
function getUserInfo(pageName){
$.ajax({
type:'get',
url:'login',
success:function(body){
//判定此处的body是不是一个有效的user对象(userId是否非0)
if(body.userId &&body.userId>0){
//登录成功!
console.log("当前用户登录成功!用户名:"+body.username);
//根据当前用户登录情况,把当前用户名设置到界面上
if(pageName=='blog_list.html'){
changeUserName(body.username);
}
}else{
//登录失败
//让前端页面,跳转到login.html
alert("当前你未登录!请登录后在访问博客列表!");
location.assign('blog_login.html');
}
},
error:function(){
alert("当前您未登录!请登录后在访问博客列表!");
location.assign('blog_login.html');
}
});
}
function changeUserName(username){
let h3=document.querySelector('.card>h3');
h3.innerHTML=username;
}
2.在博客详情页,显示用户的信息是当前文章的作者信息
页面加载,获取用户信息,判定是否登录
博客详情页通过其他的API修改
1.约定接口
让服务器提供一个新的接口,让这个接口让客户端指定blogId,获取到指定blogId的作者信息
1.编写服务器代码
通过这个方法,获取到指定博客的作者信息
1.判断参数是否足够
2.在数据库中进行查找,找到对应的blog对象,根据blog,找到作者信息
blog==null 博客不存在
3.根据blog,查询到用户对象
author==null 用户不存在
4.返回信息(不返回密码)
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;
/**
* @author KYF
* @create 2023-04-22
*/
@WebServlet("/authorInfo")
public class AuthorServlet extends HttpServlet {
private ObjectMapper objectMapper=new ObjectMapper();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("application/json;charset=utf8");
//通过这个方法,获取到指定博客的作者信息
String param=req.getParameter("blogId");
if(param==null ||"".equals(param)){
//参数少了
resp.getWriter().write("{\"ok\":false,\"reason\":\"参数缺失!\"}");
return;
}
//根据当前blogId在数据库中进行查找,找到对应的blog对象,根据blog,找到作者信息
BlogDao blogDao=new BlogDao();
Blog blog=blogDao.selectOne(Integer.parseInt(param));
if(blog==null){
resp.getWriter().write("{\"ok\":false,\"reason\":\"要查询的博客不存在!\"}");
return;
}
//根据blog,查询到用户对象
UserDao userDao=new UserDao();
User author=userDao.selectById(blog.getUserId());
if(author==null){
resp.getWriter().write("{\"ok\":false,\"reason\":\"要查询的用户不存在!\"}");
return;
}
//把author返回给浏览器
author.setPassword("");
resp.getWriter().write(objectMapper.writeValueAsString(author));
}
}
3.编写服务器代码
blog_detail.html
// 加上一个逻辑, 通过 GET /login 这个接口来获取下当前的登录状态~
function getUserInfo(pageName) {
$.ajax({
type: 'get',
url: 'login',
success: function(body) {
// 判定此处的 body 是不是一个有效的 user 对象(userId 是否非 0)
if (body.userId && body.userId > 0) {
// 登录成功!
// 不做处理!
console.log("当前用户登录成功! 用户名: " + body.username);
// 在 getUserInfo 的回调函数中, 来调用获取作者信息
getAuthorInfo(body);
} else {
// 登录失败!
// 让前端页面, 跳转到 login.html
alert("当前您尚未登录! 请登录后再访问博客列表!");
location.assign('blog_login.html');
}
},
error: function() {
alert("当前您尚未登录! 请登录后再访问博客列表!");
location.assign('blog_login.html');
}
});
}
// 判定用户的登录状态
getUserInfo("blog_detail.html");
// 从服务器获取一下当前博客的作者信息, 并显示到界面上.
// 参数 user 就是刚才从服务器拿到的当前登录用户的信息
function getAuthorInfo(user) {
$.ajax({
type: 'get',
url: 'authorInfo' + location.search,
success: function(body) {
// 此处的 body, 就是服务器返回的 User 对象, 是文章的作者信息
if (body.username) {
// 如果响应中的 username 存在, 就把这个值设置到页面上.
changeUserName(body.username);
if (body.username == user.username) {
// 作者和登录的用户是一个人, 则显示 "删除按钮"
let navDiv = document.querySelector('.nav');
let a = document.createElement('a');
a.innerHTML = '删除';
// 期望点击删除, 构造一个形如 blogDelete?blogId=6 这样的请求
a.href = 'blogDelete' + location.search;
navDiv.appendChild(a);
}
} else {
console.log("获取作者信息失败! " + body.reason);
}
}
});
}
function changeUserName(username) {
let h3 = document.querySelector('.card>h3');
h3.innerHTML = username;
}
</script>
八:实现"注销功能"
点击注销后,在服务器取消登录状态,跳转到登录页面
注销按钮是a标签,点击会给服务器发送一个HTTP请求,发送的请求会告诉服务器要退出登录
服务器收到会话,判断是否为空,不为空删除信息,跳转到登录页面
1.约定前后端接口
请求
GET/logout
响应
HTTP/1.1 302
Location:login.html
2.编写服务器代码
用户有一个session,同时session有一个user属性~两者同时具备,才叫登录状态~~
注销,只要破坏掉上面的任意一个条件就行了~~
此处选择的是破坏第二个条件,把 user属性从session中删了.
user属性被删除后续读取的时候,得到的是null,因此会给客户端返回一个无效的User对象,客户端就认为是未登录状态
步骤
1.找到当前用户会话
2.会话为空,返回
3.不为空,把会话删除
4.跳转到登录页
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;
/**
* @author KYF
* @create 2023-04-22
*/
@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.getWriter().write("当前用户尚未登录!无法注销");
return;
}
//把用户的会话中的信息删掉
session.removeAttribute("user");
resp.sendRedirect("blog_login.html");
}
}
3.编写客户端代码
把博客列表页,博客详情页,博客编辑页中的导航栏的注销按钮的href属性都做出修改,改成logout这个路径
登录页没有注销按钮
<a href="logout">注销</a>
九.发布博客
在博客编辑页,当用户输入博客标题和正文后,点击发布,发起HTTP请求,会把博客数据提交到服务器,由服务器存储到数据库
1.约定前后端交互接口
请求:
POST/blog
Content-Type:application/x-www-form-urlencoded
title=这是标题&content=这是正文
响应:
HTTP/1.1 302
Location:blog_list.html
2.编写服务器代码
在BlogServlet中,添加doPost方法
读取请求中的标题和正文,构造Blog对象,插入数据库
步骤
1.得到会话,会话存在
2.得到用户,用户存在
3.从请求中,取出参数(博客的标题和正文)
4.构造blog对象,把当前的信息填进去(作者id就是当前提交这个博客的用户身份信息)
5.插入数据库中
6.重定向到博客列表页
@Override
protected void doPost(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;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对象,把当前的信息填进去,并插入数据库中
//postTime和blogId不用手动指定,都是插入数据库的时候自动生成
Blog blog=new Blog();
blog.setTitle(title);
blog.setContent(content);
//作者id就是当前提交这个博客的用户身份信息
blog.setUserId(user.getUserId());
BlogDao blogDao=new BlogDao();
blogDao.insert(blog);
//重定向到博客列表页
resp.sendRedirect("blog_list.html");
}
3.编写客户端代码
要用form表单,
1.加form表单
2.给标题的输入框加入name属性
3.发布按钮改成了input标签
4.创建一个隐藏的textarea为了后续的form提交
5.修改初始化代码
6.选择器调整,保证样式不丢失
<!-- 包裹整个博客编辑页内容的顶级容器 -->
<div class="blog-edit-container">
<form action="blog" method="post" style="height:100%">
<div class="title" >
<input type="text" placeholder="在此处输入标题" name="title" id="title">
<!-- <button>发表文章</button> -->
<input type="submit" value="发布文章" id="submit">
</div>
<!-- 放置md编辑器 -->
<div id="editor">
<!--为了form提交,创建一个textarea多行编辑框,借助这个编辑框实现表单提交 -->
<!-- 可以设置editor.md,让编辑器markdown内容同步保存到textarea中, 进行form提交-->
<textarea name="content" style="display:none"></textarea>
</div>
</form>
</div>
<script>
// 初始化编辑器
// 构造一个editor对象
let editor = editormd("editor", {
// 第二个参数是js对象
// 这里的尺寸必须在这里设置. 设置样式会被 editormd 自动覆盖掉.
width: "100%",
// 设定编辑器高度
height: "calc(100% - 50px)",
// 编辑器中的初始内容
markdown: "# 在这里写下一篇博客",
// 指定 editor.md 依赖的插件路径
path: "editor.md/lib/",
// editor.md会把用户在编辑器输入的内容同步保存到隐藏的textarea中
saveHTMLToTextatea:true,
});
</script>
十.删除博客
只有自己能删除自己的博客(当前用户登录信息和博客作者信息相同,则删除)
删除博客,肯定不是随便删除的~~
约定,只有,自己能删除自己的博客,不能删除别人的博客!!!(此处咱们暂时不考虑管理员)
界面上的处理
在博客详情页这里,就去进行判定,
判定看当前这个博客的作者,是否就是登录的用户.如果是,就在导航栏里显示一个“删除按钮”
如果不是,就不显示删除按钮~~
在博客详情页这块,其实既从服务器获取了当前用户的登录信息又获取到了博客的作者信息
主要看这两个接口返回的用户信息是不是一样的?
1.约定前后端接口
请求:
GET/login_delete?blogId=1
响应:
删除成功:
HTTP/1.1 302
Location:blog_list.html
2.编写客户端代码
这是关键代码,要在第一个ajax的回调中
执行第二个ajax,才能保证两个ajax之间获取数据的顺序先后~~
getAuthorInfo(user)放到getUserInfo(pageName)中调用
步骤
1.得到导航栏的信息
2.得到标签a的信息
3.a显示为删除
4.点击删除, 构造一个形如 blogDelete?blogId=6 这样的请求
// 加上一个逻辑, 通过 GET /login 这个接口来获取下当前的登录状态~
function getUserInfo(pageName) {
$.ajax({
type: 'get',
url: 'login',
success: function(body) {
// 判定此处的 body 是不是一个有效的 user 对象(userId 是否非 0)
if (body.userId && body.userId > 0) {
// 登录成功!
// 不做处理!
console.log("当前用户登录成功! 用户名: " + body.username);
// 在 getUserInfo 的回调函数中, 来调用获取作者信息
getAuthorInfo(body);
} else {
// 登录失败!
// 让前端页面, 跳转到 login.html
alert("当前您尚未登录! 请登录后再访问博客列表!");
location.assign('blog_login.html');
}
},
error: function() {
alert("当前您尚未登录! 请登录后再访问博客列表!");
location.assign('blog_login.html');
}
});
}
// 判定用户的登录状态
getUserInfo("blog_detail.html");
// 从服务器获取一下当前博客的作者信息, 并显示到界面上.
// 参数 user 就是刚才从服务器拿到的当前登录用户的信息
function getAuthorInfo(user) {
$.ajax({
type: 'get',
url: 'authorInfo' + location.search,
success: function(body) {
// 此处的 body, 就是服务器返回的 User 对象, 是文章的作者信息
if (body.username) {
// 如果响应中的 username 存在, 就把这个值设置到页面上.
changeUserName(body.username);
if (body.username == user.username) {
// 作者和登录的用户是一个人, 则显示 "删除按钮"
let navDiv = document.querySelector('.nav');
let a = document.createElement('a');
a.innerHTML = '删除';
// 期望点击删除, 构造一个形如 blogDelete?blogId=6 这样的请求
a.href = 'blogDelete' + location.search;
navDiv.appendChild(a);
}
} else {
console.log("获取作者信息失败! " + body.reason);
}
}
});
}
function changeUserName(username) {
let h3 = document.querySelector('.card>h3');
h3.innerHTML = username;
}
3.编写服务器代码
请求:GET/blogDelete?blogId=7
响应:直接跳转到博客列表页
步骤
1.检查当前用户是否登录(会话和用户)
2.获取到参数中的blogId
3.获取要删除的博客信息
4.再次校验,当前的用户是否就是博客作者
5.确认无误,开始删除
6.重定向到博客列表页
package controller;
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;
/**
* @author KYF
* @create 2023-04-23
*/
@WebServlet("/blogDelete")
public class BlogDeleteServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.检查当前用户是否登录
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;
}
//2.获取到参数中的blogId
String blogId=req.getParameter("blogId");
if(blogId==null || "".equals(blogId)){
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("当前blogId参数不对");
return;
}
//3.获取要删除的博客信息
BlogDao blogDao=new BlogDao();
Blog blog=blogDao.selectOne(Integer.parseInt(blogId));
if(blog==null){
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("当前要删除的博客不存在");
return;
}
//4.再次校验,当前的用户是否就是博客作者
if(user.getUserId()!=blog.getUserId()){
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("当前登录的用户不是作者,没有权限登录");
return;
}
//5.确认无误,开始删除
blogDao.delete(Integer.parseInt(blogId));
//6.重定向到博客列表页
resp.sendRedirect("blog_list.html");
}
}