博客系统的具体实现
文章目录
- 博客系统的具体实现
- 软件开发的基本流程
- 具体实现的八大功能
- 数据库设计
- 创建数据库
- 操作数据库
- 引入依赖
- 封装DataSource
- 创建实体类
- 将JDBC增删改查封装起来
- 实现博客列表页
- web.xml的配置文件
- 实现博客系统的展示功能
- 登录功能
- 强制要求用户登录
- 显示用户信息
- 退出登录状态
- 发布博客
- 删除博客
- 在Linux上部署博客系统
- 安装jdk
- 安装tomcat
- 安装mysql
- 使用yum安装必要的包
- 启动
- 正式部署
在正式写后端程序之前,我已经将博客系统的前端最基本的页面写好,详情可以见我的gitee
https://gitee.com/dengchuanfei/vscode_demo/tree/master/blog_system
软件开发的基本流程
- 可行性分析
- 需求分析
- 概要设计
- 详细设计
- 编码
- 测试
- 发布
具体实现的八大功能
- 实现博客列表的展示功能
- 实现博客详情的展示功能
- 登录功能
- 强制用户登录
- 显示用户的信息
- 实现注销
- 发布博客
- 删除博客
数据库设计
写之前首先要进行规划,要做到“谋定而后动”
首先进行“数据库设计”,也就是想清楚需要几个库,几张表,每个表长啥样(属性是干什么的,是什么类型)
需要找到实体,然后分析实体之间的关联关系,再思考表的属性
这里业务比较简单,所以只需要两张表
博客表blog (blogId, tittle, content, postTime, userId)
用户表user (userId username password)
创建数据库
先建一个数据库,往里面添加数据
-- 拿到的数据库很可能并不干净,所以创建之前要确保之前没有同名的,还要删除同名的,注意:这是十分危险的操作,所以务必谨慎使用!!!
create database if not exists java_blog_system;
use java_blog_system;
drop table if exists blog;
-- 注意这里添加属性是使用()
create table blog(
blogId int primary key auto_increment,
title varchar(256),
content text,
postTime datetime,
--userId是文章作者的ID
userId int
);
drop table if exists user;
create table user
(
userId int primary key auto_increment,
username varchar(50),
password varchar(50)
);
-- 添加几个数据,测试一下
insert into blog values(null,"这是第一篇博客","从今天开始我要好好写代码,好好上课",now(),1);
insert into blog values(null,"这是第二篇博客","我要好好写代码,好好上课",now(),1);
insert into user values(null, "zhangsan","123");
insert into user values(null, "lisi","123");
这里设计的content是text类型的,text能放64KB的内容,一般是够博客使用的了,博客中的截图和博客文字不是存储在一起的,所以不用担心截图放不下
注意: 在SQL中的注释是 “-- ”,在–后面还有一个空格!
操作数据库
引入依赖
首先要先引入maven依赖,在.xml的中引入依赖
去中央仓库中,搜索servlet API(3.1.0),mysql connect Java(5.1.49), jackson Databind(2.13.4.2)(将JSON格式进行转换)
封装DataSource
由于DataSource只有一份,所以使用单例模式来实现会比较好
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import javax.sql.DataSource;
import javax.xml.crypto.Data;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
//使用这个类来封装DataSource,使用单例模式(懒汉模式 + 多线程判断)
//实现数据库的连接和断开
public class DBUtil {
private static volatile DataSource dataSource = null;
public static DataSource getDataSource() {
//第一次判断是否需要加锁
if (dataSource == null) {
synchronized (DBUtil.class) { //针对类对象加锁
//第二次判断是会否需要new对象
if (dataSource == null) {
dataSource = new MysqlDataSource();
((MysqlDataSource)dataSource).setURL("jdbc:mysql://127.0.0.1:3306/java_blog_system?characterEncoding=utf8&&useSSL=false");
((MysqlDataSource)dataSource).setUser("root");
((MysqlDataSource)dataSource).setPassword("1111");
}
}
}
return dataSource;
}
//建立连接
private static Connection getConnection() throws SQLException {
return getDataSource().getConnection();
}
//关闭连接
//建立连接的顺序是connection statement resultSet,所以关闭的顺序是反着的
private static void close(Connection connection, Statement 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();
}
}
}
}
在关闭资源的时候, 要是像这样直接使用依次try catch就会导致一旦上面的抛出异常,下面的就不会被执行到了,此时就会导致资源泄漏,很严重
要是使用的是throws相当于上面的情况,后面的代码还是不会执行,导致资源泄漏
所以还是应该 细分一下,多用几个try catch,来保证3个对象都关闭了
创建实体类
一个实体类对象就对应表中的一条记录
表中的属性怎么写,实体类就这么写
这里需要创建2个实体类 User 和 Blog
里面需要有属性和getter和setter方法, IDEA快捷键是alt + fn + insert
import java.security.Timestamp;
public class Blog {
private int blogId;
private String tittle;
private String content;
//mysql中的datetime和timestamp类型在java中都是使用Timestamp表示的
private Timestamp postTime;
private int userId;
public int getBlogId() {
return blogId;
}
public void setBlogId(int blogId) {
this.blogId = blogId;
}
public String getTittle() {
return tittle;
}
public void setTittle(String tittle) {
this.tittle = tittle;
}
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;
}
}
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;
}
}
将JDBC增删改查封装起来
这里创建的是BlogDao和UserDao类,这里的DAO是 Data Acess Object 数据访问对象
也就是说访问数据库的操作就可以使用这几个DAO对象来进行
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
//封装关于博客的相关操作
public class BlogDao {
//插入博客--发布博客
public void insert(Blog blog) {
Connection connection = null;
PreparedStatement statement = null;
//此处只要判断改变的行数是不是1就行了,所以没有resultSet
try {
//1.建立连接
connection = DBUtil.getConnection();
//2.构造SQL
//sql对应着blog的属性
//blogId tittle content postTime userId
String sql = "insert into blog values (null, ? , ?, now(), ?)";
statement = connection.prepareStatement(sql);
statement.setString(1, blog.getTittle());
statement.setString(2, blog.getContent());
statement.setInt(3, blog.getUserId());
//3.执行sql
int ret = statement.executeUpdate();//executeUpdate的返回值是修改的行数
if (ret != 1) {
System.out.println("博客插入失败!");
} else {
System.out.println("博客插入成功!");
}
//4.释放相关的资源--但是这里还是不适合,要是上面代码抛异常了,这里就会导致资源没有释放,资源泄露
} catch (SQLException e) {
e.printStackTrace();
} finally{
//方法哦finally就一定会执行到了,但是connection和statement是局部变量,所以就将这两个放到最外面,先置为null
DBUtil.close(connection, statement, null);//这里没有涉及到resultSet,所以填null
}
}
//查询一个博客--博客详情页
public Blog selectOne(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();
//遍历结果集合
if (resultSet.next()) {
Blog blog = new Blog();
blog.setBlogId(resultSet.getInt("blogId"));
blog.setTittle(resultSet.getString("tittle"));
blog.setContent(resultSet.getString("content"));
blog.setPostTime(resultSet.getTimestamp("postTime"));
blog.setUserId(resultSet.getInt("userId"));
return blog;
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
//关闭资源
DBUtil.close(connection,statement,resultSet);
}
return null;//要是没有找到直接返回null就行
}
//查询所有博客--博客展示页
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();
//遍历结果集合,这里使用的是while循环来寻找
while(resultSet.next()) {
Blog blog = new Blog();
blog.setBlogId(resultSet.getInt("blogId"));
blog.setTittle(resultSet.getString("tittle"));
blog.setContent(resultSet.getString("content"));
blog.setPostTime(resultSet.getTimestamp("postTime"));
blog.setUserId(resultSet.getInt("userId"));
blogs.add(blog);//将所有搜到的blog都添加到blogs中
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
//4.关闭资源
DBUtil.close(connection,statement,resultSet);
}
return blogs;
}
//删除博客
public void delete(int blogId) {
Connection connection = null;
PreparedStatement statement = null;
try {
//1.建立连接
connection = DBUtil.getConnection();
//2.构造SQL
String sql = "delete from blog where blogId = ?";
statement = connection.prepareStatement(sql);
statement.setInt(1, blogId);
//3.执行sql
int ret = statement.executeUpdate();//executeUpdate的返回值是修改的行数
if (ret != 1) {
System.out.println("博客删除失败!");
} else {
System.out.println("博客删除成功!");
}
//4.释放相关的资源--但是这里还是不适合,要是上面代码抛异常了,这里就会导致资源没有释放,资源泄露
} catch (SQLException e) {
e.printStackTrace();
} finally{
//方法哦finally就一定会执行到了,但是connection和statement是局部变量,所以就将这两个放到最外面,先置为null
DBUtil.close(connection, statement, null);//这里没有涉及到resultSet,所以填null
}
}
}
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class UserDao {
//根据用户名来查询用户--登录模块
//隐含条件:用户名必须要是唯一的
public User selectByName(String username) {
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
//1.建立连接
connection = DBUtil.getConnection();
//2.构造SQL
String sql = "select * from blog where username = ?";
statement = connection.prepareStatement(sql);
statement.setString(1, username);
//3.执行SQL
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;
}
//根据用户ID来查询用户--在获取用户信息的时候会用到
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 blog where userId = ?";
statement = connection.prepareStatement(sql);
statement.setInt(1, userId);
//3.执行
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 {
//4.关闭资源
DBUtil.close(connection,statement,resultSet);
}
return null;
}
}
将上面的几个函数的实现都看明白,其实JDBC的操作都是差不多的
下面主要是服务端和客户端的代码实现,由于服务端的代码比较长,就只有贴出核心功能的客户端代码
详细的客户端 服务端代码将会在文章最后给出
实现博客列表页
将之前写的前端代码复制到webapp目录下
web.xml的配置文件
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
</web-app>
实现博客展示,是从数据库中读取数据,然后写到前端网页上
在博客列表页,需要做一个很重要的事情,页面在加载的时候通过ajax发起HTTP请求,从服务端获取到博客列表的数据
所以需要实现想好发什么样的请求,返回什么样的响---->约定前后端接口
[请求]
GET /blog
[响应]
[
{
blogId: 1,
title: "第一篇博客",
content: "博客正文",
userId: 1,
postTime: "2021-07-07 12:00:00"
},
{
blogId: 2,
title: "第二篇博客",
content: "博客正文",
userId: 1,
postTime: "2021-07-07 12:10:00"
},
...
]
由页面发起请求,后服务端进行响应
import com.fasterxml.jackson.databind.ObjectMapper;
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();
List<Blog> blogs = blogDao.selectAll();
resp.setContentType("application/json;charset=utf8");
resp.getWriter().write(objectMapper.writeValueAsString(blogs));
}
}
在之前已经写过blog_list.html页面上进行修改
<!-- 发送ajax从服务端上获取数据 -->
<script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>
<script>
//在页面加载的时候,通过ajax给服务端发送消息,获取到博客列表信息,并且显示在页面上
function getBlog(){
$.ajax({
type:'get',
url:'blog',
success:function(body){
//获取到的body就是一个js对象数组,每个元素就是一个js对象,根据这个对象来构造一个div
//1.把原来.right里面原有的内容清空,替换成从数据库服务端拿到的
let rightDiv=document.querySelector('.container-right');
rightDiv.innerHTML='';//清空原有数据
//2.遍历body,构造出一个个blogDiv
for(let blog of body){
let blogDiv=document.createElement('div');
//针对blogDiv设置一个属性,类名设为blog
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);
//链接 查看全文(这里用的是a标签)
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("获取博客列表失败");
}
});
}
getBlog();
</script>
这里的时间很明显就是一个时间戳,并不直观,所以还是要改的,通过fiddler可以看到返回的响应就是时间戳的格式,所以也就是要将get方法的返回值变成String类型的时间格式
此处就要使用SimpleDateFormat类了
public String getPostTime() {
//使用IDEA提供的原生的,返回的是时间戳,所以需要改一下返回值
//使用SimpleDateFormat来将时间戳转换成指定的时间格式
//这里的参数标准格式建议查一下,因为在不同的语言中表示放方式是不一样的
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return simpleDateFormat.format(postTime);
}
时间的格式确实改好了,但是发现最上面的博客确实比较老的博客,不符合常规的博客展示思路
此时只要在BlogDao中将selectAll的搜索改成select * from blog order by postTime desc 按时间降序排列就行了
要是正文很长,在博客展示页就应该显示一部分文章,此时就对内容进行截断
在BlogDao 中进行content的判断和截断:
String content = resultSet.getString("content");
if(content.length() > 100){
content = content.substring(0,100) + "......";
}
blog.setContent(content);
实现博客系统的展示功能
在展示的时候,发送ajax请求来访问服务端,获取到服务端返回的响应之后,填充到博客的详情页面中
@WebServlet("/blog")
public class BlogServlet extends HttpServlet {
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("application/json;charset=utf8");
BlogDao blogDao = new BlogDao();
String blogId = req.getParameter("blogId");
if(blogId == null){
//说明是博客列表页发起的请求
List<Blog> blogs = blogDao.selectAll();
resp.getWriter().write(objectMapper.writeValueAsString(blogs));
}else{
//说明是博客详情页发起的请求
Blog blog = blogDao.selectOne(Integer.parseInt(blogId));
resp.getWriter().write(objectMapper.writeValueAsString(blog));
}
}
}
客户端核心代码:
function getBlog(){
$.ajax({
type:'get',
url:'blog' + location.search,
success : function(body){
let h3 = document.querySelector('.blog-detail>h3');
h3.innerHTML = body.title;
let dateDiv = document.querySelector('.blog-detail>.blog-date');
dateDiv.innerHTML = body.postTime;
//方法1:
//let contentDiv= document.querySelector('#content');
//contentDiv.innerHTML = body.content;
//方法2:
//此处使用editor.md来进行渲染,主要是后面实现博客编辑的时候使用
editormd.markdownToHTML('content', {markdown: body.content});
}
});
}
getBlog();
关于前端代码:
- 要是想显示具体的哪篇博客,就要知道具体的博客id,所以这里写URL的时候将location.search添加上去了
- 为什么这里要使用方法2,而不是方法1,主要是因为博客是以markdown的形式来写的,所以渲染的时候也要以markdown格式来渲染
登录功能
所谓的登录功能就是在登录之后跳转到博客列表页
服务端代码:
其实登录操作就是先根据请求获取到用户输入的账号密码,之后进行判断,看看用户输入的账号密码是不是符合要求,要是符合的话,再查询数据库,要是账号密码都是正确的,就创建出一个会话,重定向到博客列表页
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.获取用户名和密码
req.setCharacterEncoding("utf-8");
String username = req.getParameter("username");
String password = req.getParameter("password");
if (username == null || username.equals("") || password == null || password.equals("")) {
//用户名或者密码不存在(为空)
resp.setContentType("text/html;charset= utf8");
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);
//在会话中保存user,方便后面知道当前的user是谁
session.setAttribute("user", user);
//4.构造302响应报文(重定向)
resp.sendRedirect("blog_list.html");
}
}
客户端核心代码:
<div class="login-container">
<form action="login" method="post">
<!-- 实现登录对话框 -->
<div class="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">
<input class="login-btn" value="登录" type = "submit">
</div>
</div>
</form>
</div>
强制要求用户登录
在博客系统中要求要先登录之后才能查看博客和编辑博客,所以要检查用户的登录状况,要是没有登录的话就自动跳转到登录页面
服务端核心代码:
首先要判断当前有没有创建出会话,要是当前存在session并且user也存在,说明已经登录了,就不进行操作,返回一个状态码200就行了,要是没有session或者user就说明当前没有登录,重定向到登录页面
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.首先先获取一下会话
//getSession的参数是false,就是说要是没有会话,也不用创建会话
HttpSession session =req.getSession(false);
if(session == null){
//当前没有会话,说明没有登录
resp.setStatus(403);
return;
}
//为什么要判断user是否存在,其实还是会存在"有session但是没有user对象"这种情况的,这种情况会在后面退出登录的时候出现,所以此处一定要判断user对象是否存在
User user = (User) session.getAttribute("user");
if (user == null) {
//说明此时虽然有会话,但是没有user对象,还是不行
resp.setStatus(403);
return;
}
//当前已经是登录状态,其实这行代码不写也行,但是写上更清楚
resp.setStatus(200);
}
客户端核心代码 :
function checkLogin() {
$.ajax({
type:'get',
url:'login',
success: function(body) {
// 成功不做处理
},
error: function() {
// 失败就会强行跳转到登录页面
location.assign('login.html');
}
})
}
显示用户信息
当李四登录进去之后看到张三的文章,之后点了进去,此时就要修改一下用户的信息
这里的服务端逻辑要分成两种情况:
- 要是当前用户是在博客列表页,登录的信息就在session中
- 要是当前用户是在博客详情页中,就要求数据库中查询文章作者的信息
import com.fasterxml.jackson.databind.ObjectMapper;
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 {
//首先获取用户信息
String blogId = req.getParameter("blogId");//getParameter是用来获取表单中的值的,返回值是String,所以此时的blogId就变成了String类型
if (blogId == null) {
//博客ID为0说明当前是在列表页,登录的用户信息在session里面,所以从session中拿
getUserInfoFromSession(req,resp);
}else{
//存在博客ID说明当前是在博客展示页,要获取作者信息,就要查询数据库,不能从session中拿,因为文章作者可能不是自己,所以要从数据库中拿
getUserInfoFromDB(req,resp, Integer.parseInt(blogId));//将blogId转换成int类型
}
}
private void getUserInfoFromDB(HttpServletRequest req, HttpServletResponse resp, int blogId) throws IOException {
//先根据blogId查询blog对象,获取到userId(作者是谁)
// 根据user查询对应的User对象即可
BlogDao blogDao = new BlogDao();
Blog blog = blogDao.selectOne(blogId);
if (blog == null) {
//这种情况是: blogId是随便写的,数据库中查不到,是不符合要求的
resp.setStatus(404);
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("blogId不存在");
return;
}
UserDao userDao = new UserDao();
User user = userDao.selectById(blog.getUserId());
if (user == null) {
resp.setStatus(404);
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("user不存在");
return;
}
//排除了上面两种错误情况,接下来只有将user以JSON的形式返回就行了
user.setPassword("");
resp.setContentType("application/json;charset=utf8");
resp.getWriter().write(objectMapper.writeValueAsString(user));
}
private void getUserInfoFromSession(HttpServletRequest req, HttpServletResponse resp) throws IOException {
//首先先获取会话
HttpSession session = req.getSession(false);
if (session == null) {
resp.setStatus(403);
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("当前未登录!");
return;
}
//获取user对象
User user = (User) session.getAttribute("user");
if (user == null) {
resp.setStatus(403);
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("当前未登录!");
return;
}
//在获取到user对象之后为了安全起见,将密码设置为空
//以JSON的形式返回响应,所以要提前创建objectMapper
user.setPassword("");
resp.setContentType("application/json;charset=utf8");
resp.getWriter().write(objectMapper.writeValueAsString(user));
}
}
服务端核心代码:
//针对博客列表页,获取到当前用户的信息
function getUserInfo(){
$.ajax({
type:'get',
url:'userInfo',
success : function(body){
//修改头像
let h3 = document.querySelector('.container-left>.card>h3')
h3.innerHTML = body.username;
}
})
}
getUserInfo();
退出登录状态
在页面上一个注销按钮,要求实现点击一下按钮就能退出当前的账号
服务端代码:
后端服务端在收到请求之后,将当前会话的信息删除,然后重定向到登录页面即可
//实现注销功能(退出登录状态)
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 session = req.getSession(false);
if (session == null) {
resp.setStatus(403);
return;
}
//删除user对象,并重定向到登录页面
session.removeAttribute("user");
resp.sendRedirect("login.html");
}
}
这里需要注意的是: 在登录状态下,既有session又有user属性,要是想要删除用户的信息,只要删除其中一个就行了,但是这里选择的是删除user属性,因为没有提供删除session的api,并不是很好删除session
服务端代码:
<a href="logout">注销</a>
只要将注销按钮变成一个链接形式即可
发布博客
发布博客的意思是在用户写完博客之后,点击提交按钮,服务端收到请求之后,在数据库中添加一条记录,并且在最后还会跳转到博客列表页
有一个前提: 用户要写上了标题和正文之后点击提交按钮才能提交
所以还要判断一下是否用户写了标题和正文
服务端代码:
接着在原本的BlogServlet类中写
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//使用这个post方法来实现创建新的博客
//1.检查一下用户的登录状态和用户信息,要是未登录就不能创建新的博客
HttpSession session = req.getSession(false);
if (session == null) {
resp.setStatus(403);
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("当前未登录,不能创建博客!");
return;
}
//检查是否存在user对象
User user = (User) session.getAttribute("user");
if (user == null) {
resp.setStatus(403);
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("当前未登录,不能创建博客!");
return;
}
//2.当前已经登录,获取请求的参数(博客的标题和内容)
req.setCharacterEncoding("utf8");//将请求变成utf8的格式,防止乱码
String title = req.getParameter("title");
String content = req.getParameter("content");
//3.构造Blog对象,并插入到数据库中
Blog blog = new Blog();
blog.setTitle(title);
blog.setContent(content);
//博客的blogId是自增主键,所以不用专门指定设置,postTime是now()函数,所以也不用指定
blog.setUserId(user.getUserId());
BlogDao blogDao = new BlogDao();
blogDao.insert(blog);
//4.插入新的博客之后重定向到博客列表页
resp.sendRedirect("blog_list.html");
//有一个疑问:前面已经实现了未登录状态下是不能进入博客编辑页的,那么此时为什么还要先判断一下是否登录呢?
//1.要是有人使用postman直接构造post请求,直接绕开了登录,插入数据怎么办?
//2.在上面的代码中,要想给新的博客设置userId需要使用user.getUserId(),所以前面要保证存在user对象
//结合上面的两点理由,加上登录判断是很有必要的
}
客户端核心代码:
使用form表单来提交请求
<!-- 这个编辑页的版心 -->
<div class="blog-edit-container" style="height: 100%">
<!-- 套上一个form标签 -->
<form action="blog" method="post" style="height: 100%">
<!-- 标题的编辑区 -->
<div class="title">
<input type="text" id = 'blog-title' placeholder="请在这里输入文章的标题" name = "title">
<input id = "submit" value="发布文章" type="submit">
</div>
<!-- 正文的编辑区 -->
<div id = "editor">
<!-- editor.md规定,要想使用form表单来提交数据,就要按照下面的标准来写 -->
<textarea name="content" style="display: none"></textarea>
</div>
</form>
</div>
删除博客
要是想要删除博客,在不考虑管理员的情况下,只能由博客的作者自己来删除
所以就要判断当前登录的用户是不是博客的作者,要是不是的话,他是没有权限删除别人的博客的
在删除之后还要重定向到博客列表页
服务端代码:
主要的思路就是看看博客作者id和登陆者id是不是一样的,要是一样的,就有权限去删除博客
但是在此之前,还有很多要考虑的
- 当前用户是否已经登录?(这个问题是要考虑的,前面已经解释过)
- 客户端传过来的blogId是否可能是null(前端没有传过来blogId)
- 传过来的blogId是否可能不存在(客户端传了blogId,但是在服务端这边根本就没有)
在经历了上面的错误情况之后,就是客户端传来正确的blogId,服务端也有对应的博客作者的id,此时只要对比一下登录用户id和博客作者id即可
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("/blog_delete")
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.setStatus(403);
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("您当前未登录,不能删除博客!");
return;
}
User user = (User) session.getAttribute("user");
if (user == null) {
resp.setStatus(403);
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("您当前未登录,不能删除博客 !");
return;
}
//能走到这一步,说明已经是登录状态了
//2.获取到blogId
String blogId = req.getParameter("blogId");
if (blogId == null) {
//说明这个blogId为空,这种情况是前端没传blogId
resp.setStatus(403);
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("您当前要删除的blogId有误!");
return;
}
//3.查询这个blogId对应的blog作者
BlogDao blogDao = new BlogDao();
Blog blog = blogDao.selectOne(Integer.parseInt(blogId));
if (blog == null) {
//此时是存在一个blogId,但是它并没有对应的文章,这种情况是前端传了blogId,但是后端没有对应的数据
resp.setStatus(404);
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("您当前要删除的博客不存在! blogId = " + blogId);
return;
}
//观察上面的四个if可以感受到,这就是在排错,先将错误的情况都写出来,起到过滤的作用.
//4.判断登录用户是不是文章作者
if (blog.getUserId() != user.getUserId()) {
//blog.getUserId是当前文章的作者
//user.getUserId是从session中拿的登录用户信息
//要是不一样,直接返回403,并且提示
resp.setStatus(403);
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("您不能删除别人的博客!");
return;
}
//5.真正执行删除操作
//将String类型的blogId转换成int类型的,准备删除
blogDao.delete(Integer.parseInt(blogId));
//6.返回302,重定向到博客列表页,要是失败就提示失败
resp.sendRedirect("blog_list.html");
}
}
客户端核心代码:
function upDateDeleteURL(){
// 这里的location.search就是'?blogId=?'
//可以在网页开发者工具的console中输入查看
let deleteBtn = document.querySelector('#delete-btn');
deleteBtn.href = 'blog_delete' + location.search;
}
upDateDeleteURL();
在Linux上部署博客系统
在完成了博客系统之后,只是可以在自己的电脑上查看博客系统, 别人还是看不到的,所以要借助公网ip来让所有人都能访问博客系统
要是想要部署java web程序,首先要配置环境
jdk tomcat mysql
安装jdk
推荐的方法是使用yum直接安装openjdk(开源的,与官方的jdk功能差不多),目前使用的最多的就是jdk8系列
yum list | grep jdk
在源上搜索所有关于jdk的文件
devel表示development的意思,就是软件开发包
后面的x86_64的意思是支持64位的系统
选中之后按ctrl + insert来复制,之后使用yum install 粘贴(shift + insert ) 就下载好了
安装tomcat
当前的程序使用的是tomcat8.5的版本,但是使用yum list | grep tomcat 并没有8.5版本(当前的centos7系统有点老)
此时有一个好方法,找到之前的tomcat压缩包,直接拖到Linux上就能安装,主要是因为tomcat是java写的,能跨平台
注意: 在拖压缩包之前,要确认压缩包是不是zip格式的,因为Linux默认不支持rar格式的压缩包,只支持zip格式
可能会出现拖不过去的情况,此时就要安装一个lrzsz, yum insatll lrzsz
之后就能将tomcat的压缩包拖过去了
之后进行解压,需要使用unzip ,所以要先安装unzip, yum insatll unzip
之后进行解压 unzip apache apache-tomcat-8.5.85.zip(后面的文件可以用TAB来补全)
进入到tomcat的bin目录下,就可以看到启动的脚本
在Windows上使用的是startup.bat,但是在Linux上使用的是startup.sh
要想启动这里的程序,首先要赋予他们可执行权限,也就是chmod + x *.sh, 之后后面的文件就会变成绿色,表示可以执行
启动tomcat的过程:
- 下载安装包(要是zip格式的压缩包)
- 上传压缩包到Linux上(要使用到 lszrz命令)
- 解压缩(要使用到unzip 命令)
- 进入Tomcat目录的bin目录
- 给启动脚本增加可执行权限(chmod +x *.sh) --此时.sh的文件就会变成绿色
- 使用 sh startup.sh来启动 tomcat
验证tomcat是否启动成功
-
通过ps aux | grep tomcat来查看进程
-
通过nststat -anp | grep 8080 查看端口号是否被绑定
tomcat也是用java写的,所以此时8080已经被tomcat绑定了
还有一种验证tomcat是否启动的方式,就是直接访问tomcat的欢迎页面 公网IP地址:8080
但是大概率是不能访问的,这是因为云服务器的防火墙或者安全组没有对8080允许访问,所以要去云服务器那边手动添加一个8080端口号的规则
添加完8080端口号的规则之后就能正常访问tomcat的欢迎页面
但是在平时的时候还是不要开启端口号规则,防止服务器被攻击
安装mysql
linux安装mysql有很多的方式,最简单的一种安装方式是使用yum 安装mariadb(开源的,是mysql的孪生兄弟)
使用yum安装必要的包
直接执行这几个命令就行了
yum install -y mariadb-server
yum install -y mariadb
yum install -y mariadb-libs
yum install -y mariadb-devel
启动
启动mariadb服务
systemctl start mariadb
设置服务自启动
systemctl enable mariadb
查看服务的状态
systemctl status mariadb
验证是否连接上
mysql -uroot
这样子就是连接成功了
创建数据库的时候为了支持中文,这里统一使用utf8mb4字符集
create database demo_db charset utf8mb4;
此时就将mariadb安装连接好了
正式部署
在安装好了jdk tomcat mysql之后,就可以开始将博客系统部署到云服务器上
-
首先要理清楚 博客系统的依赖,先将依赖的内容打通
-
将博客系统的程序打包, 将war包上传到云服务器的webapps目录下即可
在我的博客系统中的依赖就是mysql,必须要知道的是,本地的电脑上的数据库与云服务器上的数据库是两台不一样的电脑,所以本地的数据库有数据,但是云服务器上的数据库还没有数据
所以此时要将SQL指令在云服务器上输入(粘贴)一下
进入mariadb 的命令: mysql -uroot,退出mariadb: ctrl + c
在开始打war包之前要先调整一下DBUtil的代码,主要就是要调整一下连接数据库的代码
需要将这里的密码设置成云服务器的数据库的密码,要是没有单独设置过云服务器上的密码,那么就是空字符串
这里的setURL也基本上不用改,因为云服务器也是在我主机上的,所以还是可以使用环回IP, 后面的3306端口号也是不用改的
打war包的时候
改好之后双击maven的package就好了
打好war包之后找到war包
在Linux上进入到apache tomcat目录中,在进入webapps目录中
将之前打好的war包拖到webapp下面
之后就会自动进行解压缩和部署,变成蓝色的了
要是之后修改了代码,务必要重新打包,重新上传
部署(也叫上线)是一个很重要,很有仪式感的事情,所以一定要小心谨慎!
此时就已经是部署完毕了!
注意: 要是发现网页打不卡,先检测一下tomcat的欢迎页面能不能打开,要是不能打开说明是tomcat8080端口被阻止了,就要去云服务器那边将防火墙新增8080端口的规则
要知道本地电脑上能运行,不能说明部署到云服务器上就能成功
此时我出现了一个问题,那就是mariadb上的中文乱码,但是我在建库的时候已经指定了utf8mb4字符集,最后我发现还要在建表的时候也要指定字符集
要是修改了任何代码都要重新使用maven双击package重新打war包
再把之前Linux上的war删除,再把新的war包拖进去,就能重新部署
此时,就正式完成了博客系统的简单功能实现和部署
博客系统的源码(服务端+客户端)