博客系统(Servlet实现)

目录

1.准备工作

2.数据库设计

2.1表设计

2.2封装数据库操作代码

2.3创建 Blog 类 和 User 类

2.4创建 BlogDao 类和 UserDao 类

3.读取博客列表功能

3.1约定前后端交互接口

3.2实现服务器代码

3.3实现客户端代码

4.实现博客详情

4.1约定前后端交互接口

4.2实现服务器代码

4.3实现客户端代码

 5.实现登录功能

 5.1约定前后端交互接口

5.2实现服务器代码

5.3实现客户端代码

6.实现强制要求登陆

6.1实现服务器代码

6.2实现服务器代码

7.实现显示用户信息

7.1约定前后端交互接口

7.2实现服务器代码

7.3实现客户端代码

8.实现注销登陆

8.1约定前后端交互接口

8.2实现服务器代码

9.实现发布博客

9.1约定前后端交互接口

9.2实现服务器代码

9.3实现客户端代码


如果想要源码可以私信作者

1.准备工作

1.1创建web项目

1.2创建目录结构

1.3配置pom.xml和web.xml

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>messageWall</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.14.2</version></dependency><!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.16</version></dependency></dependencies></project>

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>

2.数据库设计

2.1表设计

当前需要设计两张表, 文章表和用户表

文章表:

create table blog (blogId int primary key auto_increment,-- 博客的标题title varchar(256),-- 博客的正文content varchar(4096),-- 博客的作者userId int,-- 博客的发布时间postTime datetime
);

用户表:

create table user (userId int primary key auto_increment,-- 用户名, 约定用户名不能重复.username varchar(64) unique,-- 密码password varchar(64)-- user 里还可以增加很多别的属性. github 链接, 头像链接.....
);

完整SQL文件

-- 编写 SQL 完成建库建表操作.create database if not exists blog_system charset utf8;use blog_system;drop table if exists user;
drop table if exists blog;create table blog (blogId int primary key auto_increment,-- 博客的标题title varchar(256),-- 博客的正文content varchar(4096),-- 博客的作者userId int,-- 博客的发布时间postTime datetime
);create table user (userId int primary key auto_increment,-- 用户名, 约定用户名不能重复.username varchar(64) unique,-- 密码password varchar(64)-- user 里还可以增加很多别的属性. github 链接, 头像链接.....
);-- 构造一些初始数据, 方便后续的测试.
insert into user values(1, 'zhangsan', '123'), (2, 'lisi', '123');insert into blog values(1, '这是我的第一篇博客', '从今天开始我要好好敲代码', 1, '2023-09-23 19:00:00');
insert into blog values(2, '这是我的第二篇博客', '从昨天开始我要好好敲代码', 1, '2023-09-24 19:00:00');
insert into blog values(3, '这是我的第三篇博客', '从前天开始我要好好敲代码', 1, '2023-09-25 19:00:00');

2.2封装数据库操作代码

创建DBUtil类,通过单例模式来获取数据库连接

// 通过这个类, 把数据库建立连接的逻辑进行封装.
public class DBUtil {private static volatile 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?useSSL=false&characterEncoding=utf8");((MysqlDataSource)dataSource).setUser("root");((MysqlDataSource)dataSource).setPassword("123456");}}}return dataSource;}// 提供一个方法, 和数据库建立连接public static Connection getConnection() {try {return getDataSource().getConnection();} catch (SQLException e) {e.printStackTrace();}return null;}// 提供一个方法, 和数据库断开连接.public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet) {// 如果把 3 个 close 都放到同一个 try 中, 一旦前面的 close 出现异常, 就会导致后续的 close 执行不到了.// 相比之下, 还是分开写 try 比较好.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.3创建 Blog 类 和 User

Blog表示一篇博客,此处省略get、set和toString方法

public class Blog {private int blogId;private String title;private String content;private int userId;// SQL 里有 timestamp 类型, 还有 datetime 类型.// 使用 SQL 时, 推荐使用 datetime, 因为 timestamp 只有 4 字节, 2038 年就不够用了.// 但是 Java 代码中的 Timestamp 是可以使用的.private Timestamp postTime;
}

User表示一个用户,此处省略get、set和toString方法

public class User {private int userId;private String username;private String password;
}

2.4创建 BlogDao 类和 UserDao

理解 DAO

DAO 全称为 "data access object",主要的功能就是对于某个数据库表进行增删改查.   一般每张数据库表会对应一个 DAO . 这是一种给类命名的习惯做法, 并不是强制要求.

 创建BlogDao类,针对博客表进行操作

// 通过这个类, 封装针对 blog 表的增删改查操作
public class BlogDao {// 1. 新增一个博客//    调用 insert 的时候, 需要先构造一个 Blog 对象.//    作为 参数 传递给 insert. 再由 insert 内部完成数据库的插入操作.public void insert(Blog blog) {Connection connection = null;PreparedStatement statement = null;try {// 1. 和数据库建立连接.connection = DBUtil.getConnection();// 2. 构造 SQL 语句.//    此处的博客发布时间, 正好是执行 SQL 的时刻. 直接使用 SQL 里的 now() 库函数, 完成获取当前时间工作.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. 查询 blog 表里所有的博客//    正常开发中, 一般不会直接把整个表里的数据都查询出来, 一般都是要指定筛选条件/最大条数的.//    此处咱们先不考虑这么多, 就简单粗暴全都查询就行了.public List<Blog> getBlogs() {Connection connection = null;PreparedStatement statement = null;ResultSet resultSet = null;List<Blog> blogs = new ArrayList<>();try {// 1. 和数据库建立连接connection = DBUtil.getConnection();// 2. 构造 SQL 语句String sql = "select * from blog order by postTime desc";statement = connection.prepareStatement(sql);// 3. 执行 SQLresultSet = statement.executeQuery();// 4. 遍历结果集合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 throwables) {throwables.printStackTrace();} finally {DBUtil.close(connection, statement, resultSet);}// 如果前面的查询出现问题, blogs 就会得到空的 Listreturn blogs;// return null;}// 3. 指定 blogId, 查询某一个博客.public Blog getBlog(int blogId) {Connection connection = null;PreparedStatement statement = null;ResultSet resultSet = null;try {connection = DBUtil.getConnection();String sql = "select * from blog where blogId = ?";statement = connection.prepareStatement(sql);statement.setInt(1, blogId);resultSet = statement.executeQuery();// 由于此处是按照 blogId 来查询, blogId 又是主键.// 查询到的结果要么是 1 条记录, 要么是 0 条记录. 不会有别的情况.// 因此这里就没必要循环了, 直接条件判定即可.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 throwables) {throwables.printStackTrace();} finally {DBUtil.close(connection, statement, resultSet);}return null;}// 4. 指定博客进行删除public void delete(int blogId) {Connection connection = null;PreparedStatement statement = null;try {connection = DBUtil.getConnection();String sql = "delete from blog where blogId = ?";statement = connection.prepareStatement(sql);statement.setInt(1, blogId);statement.executeUpdate();} catch (SQLException throwables) {throwables.printStackTrace();} finally {DBUtil.close(connection, statement, null);}}// 像修改博客这样的操作, 此处暂时不涉及.// 同学们如果想自己实现, 代码和上述都差不多.
}

创建 UserDao , 实现对于用户表的增删改查.

// 使用这个类封装针对 user 表的增删改查
public class UserDao {// 对于新增 user, 主要就是需要实现一个 "注册" 功能. 但是当前不打算实现注册.// 对于删除 user, 主要就是需要实现一个 "注销" 功能. 但是当前也不打算实现注销.// 1. 根据 userId 来查询用户信息. (后续根据博客查询出作者详情)public User getUserById(int userId) {Connection connection = null;PreparedStatement statement = null;ResultSet resultSet = null;try {connection = DBUtil.getConnection();String sql = "select * from user where userId = ?";statement = connection.prepareStatement(sql);statement.setInt(1, userId);resultSet = statement.executeQuery();if (resultSet.next()) {User user = new User();user.setUserId(resultSet.getInt("userId"));user.setUsername(resultSet.getString("username"));user.setPassword(resultSet.getString("password"));return user;}} catch (SQLException throwables) {throwables.printStackTrace();} finally {DBUtil.close(connection, statement, resultSet);}return null;}// 2. 根据 username 来查询用户信息. (实现登陆效果)public User getUserByName(String username) {Connection connection = null;PreparedStatement statement = null;ResultSet resultSet = null;try {connection = DBUtil.getConnection();String sql = "select * from user where username = ?";statement = connection.prepareStatement(sql);statement.setString(1, username);resultSet = statement.executeQuery();if (resultSet.next()) {User user = new User();user.setUserId(resultSet.getInt("userId"));user.setUsername(resultSet.getString("username"));user.setPassword(resultSet.getString("password"));return user;}} catch (SQLException throwables) {throwables.printStackTrace();} finally {DBUtil.close(connection, statement, resultSet);}return null;}
}

3.读取博客列表功能

3.1约定前后端交互接口

[请求]
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" },...
]

我们约定, 浏览器给服务器发送一个  GET /blog 这样的 HTTP 请求, 服务器给浏览器返回了一个 JSON 格式的数据. 

3.2实现服务器代码

创建 BlogServlet 、实现 doGet, 完成读取博客列表的功能.如果blogId为空则显示博客列表页面,如果点击了查看详情,则就会有blogId,则显示博客详情

@WebServlet("/blog")
public class BlogServlet extends HttpServlet {private ObjectMapper objectMapper = new ObjectMapper();@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 查询数据库, 获取到数据之后, 构造成要求的 json 格式并返回.// 先尝试获取下 blogId 这个参数, 看看能不能获取到.BlogDao blogDao = new BlogDao();String blogId = req.getParameter("blogId");if (blogId == null) {// 此时说明是获取博客列表. 没有 blogId 参数List<Blog> blogs = blogDao.getBlogs();String respJson = objectMapper.writeValueAsString(blogs);resp.setContentType("application/json; charset=utf8");resp.getWriter().write(respJson);} else {// 此时说明是获取博客详情. 有 blogId 参数.Blog blog = blogDao.getBlog(Integer.parseInt(blogId));if (blog == null) {// 返回一个 id 为 0 的 blog 对象. 前端再根据这里进行判定.blog = new Blog();}String respJson = objectMapper.writeValueAsString(blog);resp.setContentType("application/json; charset=utf8");resp.getWriter().write(respJson);}}

3.3实现客户端代码

使用 ajax 给服务器发送 HTTP 请求.

服务器返回的响应是一个 JSON 格式的数据, 根据这个响应数据使用 DOM API 构造页面内容. 

响应中的 postTime 字段为 ms级时间戳, 需要转成格式化日期.

列表页中拿到的 "content" 字段其实是已经裁剪过的摘要.

跳转到博客详情页的 url 形如 blog_content.html?blogId=1这样就可以让博客详情页知道当前是要访问哪篇博客.

function getBlogs() {$.ajax({type: 'get',url: 'blog',success: function (body) {// 就需要根据响应的内容, 构造出 html 片段, 展示到页面上.// 由于服务器响应中已经设置了 Content-Type 为 application/json, 此时// jQuery 就能够自动的把此处响应的内容解析成 js 对象数组.let containter = document.querySelector('.container-right');for (let blog of body) {// 根据当前这个 blog 构造出一个 html 片段.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.href = 'blog_detail.html?blogId=' + blog.blogId;a.innerHTML = '查看全文 &gt;&gt;';blogDiv.appendChild(a);// 最后把拼好的 blogDiv 添加到 container 的后面containter.appendChild(blogDiv);}}});}

运行结果:博客列表成功显示

 

理解数据交互过程

在刚才的页面访问过程中, 涉及两次 HTTP 请求-响应的交互. (不考虑从服务器下载 css, js, 图片等)

 

第一次请求: 浏览器从服务器下载 blog_list.html 页面.

第二次请求: blog_list.html 中触发了 ajax 请求, 获得到 博客列表 数据.

在前后端分离的模式中, 往往一个页面的显示需要多次 HTTP 交互过程.

4.实现博客详情

目前点击博客列表页的 "查看全文" , 能进入博客详情页, 但是这个博客详情页是写死的内容. 我们期望能够根据当前的 博客 id 从服务器动态获取博客内容.

4.1约定前后端交互接口

[请求]
GET /blog?blogId=1[响应] {blogId: 1,title: "第一篇博客", content: "博客正文", userId: 1,postTime: "2021-07-07 12:00:00" 
},

相比于博客列表页, 博客详情页的请求中多了一个 blogId 参数, 响应中只获取到一个博客的内容.

4.2实现服务器代码

在之前BlogServlet中,设置了参数blogId,如果是输入网站的话blogId就为空,点击博客列表中的查看详情,就会带一个blogId参数给当前页面,并放到Querystring中,

@WebServlet("/blog")
public class BlogServlet extends HttpServlet {private ObjectMapper objectMapper = new ObjectMapper();@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 查询数据库, 获取到数据之后, 构造成要求的 json 格式并返回.// 先尝试获取下 blogId 这个参数, 看看能不能获取到.BlogDao blogDao = new BlogDao();String blogId = req.getParameter("blogId");if (blogId == null) {// 此时说明是获取博客列表. 没有 blogId 参数List<Blog> blogs = blogDao.getBlogs();String respJson = objectMapper.writeValueAsString(blogs);resp.setContentType("application/json; charset=utf8");resp.getWriter().write(respJson);} else {// 此时说明是获取博客详情. 有 blogId 参数.Blog blog = blogDao.getBlog(Integer.parseInt(blogId));if (blog == null) {// 返回一个 id 为 0 的 blog 对象. 前端再根据这里进行判定.blog = new Blog();}String respJson = objectMapper.writeValueAsString(blog);resp.setContentType("application/json; charset=utf8");resp.getWriter().write(respJson);}}

4.3实现客户端代码

其中blog_list.html中有跳转到详情页的代码:

当用户点击这个按钮后会携带blogId参数进入blog_detail.html

修改 blog_detail.html:

其中要引入editor_md的依赖

    function getBlog() {$.ajax({url: 'blog' + location.search,type: 'get',success: function (body) {// 根据拿到的响应数据, 构造页面内容.let h3 = document.querySelector('.container-right h3');h3.innerHTML = body.title;let dateDiv = document.querySelector('.container-right .date');dateDiv.innerHTML = body.postTime;editormd.markdownToHTML('content', {markdown: body.content});}});}getBlog();

运行结果:

 5.实现登录功能

登陆页面提供一个 form 表单, 通过 form 的方式把用户名密码提交给服务器. 

服务器端验证用户名密码是否正确.

如果密码正确, 则在服务器端创建 Session ,并把 sessionId 通过 Cookie 返回给浏览器.

 5.1约定前后端交互接口

[请求]
POST /login
Content-Type: application/x-www-form-urlencoded 
username=test&password=123
[响应]
HTTP/1.1 302
Location: blog_list.html

5.2实现服务器代码

创建 LoginServlet


@WebServlet("/login")
public class LoginServlet extends HttpServlet {@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 1. 获取请求中的用户名和密码//    给请求对象设置字符集, 保证说请求中的 username 或者 password 是中文, 也能正确处理.req.setCharacterEncoding("utf8");String username = req.getParameter("username");String password = req.getParameter("password");if (username == null || password == null || "".equals(username) || "".equals(password)) {// 当前提交的用户名密码有误!resp.setContentType("text/html; charset=utf8");resp.getWriter().write("当前传过来的 username 或者 password 为空");return;}// 2. 和数据库进行验证. 看当前这样的用户名和密码是否匹配.UserDao userDao = new UserDao();User user = userDao.getUserByName(username);if (user == null) {// 当前提交的用户名密码有误!resp.setContentType("text/html; charset=utf8");resp.getWriter().write("您的用户名或者密码错误!");return;}if (!password.equals(user.getPassword())) {// 当前提交的用户名密码有误!resp.setContentType("text/html; charset=utf8");resp.getWriter().write("您的用户名或者密码错误!");return;}// 3. 创建会话HttpSession session = req.getSession(true);// 把当前登录的用户信息保存到 session 中, 方便后续进行获取.session.setAttribute("user", user);// 4. 跳转到博客列表页.resp.sendRedirect("blog_list.html");}

5.3实现客户端代码

修改login.html:

<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>

部署程序验证效果:

6.实现强制要求登陆

当用户访问 博客列表页 和 博客详情页 时, 如果用户当前尚未登陆, 就自动跳转到登陆页面.

6.1实现服务器代码

修改LoginServlet代码:添加方法检测登录状态

    // 通过这个方法, 来检测当前的登录状态.@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 会话不存在, 就认为是未登录.HttpSession session = req.getSession(false);if (session == null) {// 未登录resp.setStatus(403);return;}// 不仅仅是看 session 对象本身, 还需要看 user 对象存在. (为了后面实现 "退出登录" 功能)User user = (User) session.getAttribute("user");if (user == null) {resp.setStatus(403);return;}// 返回 200 表示已经登陆.resp.setStatus(200);}

6.2实现服务器代码

单独编写一个js文件,在每个页面中都加上登录检查机制

function checkLogin() {$.ajax({type: 'get',url: 'login',success: function(body) {},error: function(body) {location.assign('login.html');}});
}

7.实现显示用户信息

目前页面的用户信息部分是写死的. 形如:

我们期望这个信息可以随着用户登陆而发生改变.

如果当前页面是博客列表页, 则显示当前登陆用户的信息.

如果当前页面是博客详情页, 则显示该博客的作者用户信息.

7.1约定前后端交互接口

在博客列表页, 获取当前登陆的用户的用户信息.

[请求]
GET /user[响应] {userId: 1,username: test 
}

在博客详情页, 获取当前文章作者的用户信息

[请求]
GET /user?blogId=1[响应] {userId: 1,username: test 
}

7.2实现服务器代码

创建UserServlet:

@WebServlet("/user")
public class UserServlet extends HttpServlet {private ObjectMapper objectMapper = new ObjectMapper();@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {String blogId = req.getParameter("blogId");if (blogId == null) {// 博客列表页// 从 session 中拿到 user 对象.HttpSession session = req.getSession(false);if (session == null) {User user = new User();String respJson = objectMapper.writeValueAsString(user);resp.setContentType("application/json; charset=utf8");resp.getWriter().write(respJson);return;}User user = (User) session.getAttribute("user");if (user == null) {user = new User();String respJson = objectMapper.writeValueAsString(user);resp.setContentType("application/json; charset=utf8");resp.getWriter().write(respJson);return;}String respJson = objectMapper.writeValueAsString(user);resp.setContentType("application/json; charset=utf8");resp.getWriter().write(respJson);} else {// 博客详情页// 需要查询数据库了.BlogDao blogDao = new BlogDao();Blog blog = blogDao.getBlog(Integer.parseInt(blogId));if (blog == null) {User user = new User();String respJson = objectMapper.writeValueAsString(user);resp.setContentType("application/json; charset=utf8");resp.getWriter().write(respJson);return;}UserDao userDao = new UserDao();User user = userDao.getUserById(blog.getUserId());if (user == null) {user = new User();String respJson = objectMapper.writeValueAsString(user);resp.setContentType("application/json; charset=utf8");resp.getWriter().write(respJson);return;}String respJson = objectMapper.writeValueAsString(user);resp.setContentType("application/json; charset=utf8");resp.getWriter().write(respJson);}}
}

7.3实现客户端代码

1. 修改 blog_list.html和blog_detail.html,都要加上下列代码

    function getUser() {$.ajax({type: 'get',url: 'user',success: function (body) {// body 就是解析后的 user 对象了.let h3 = document.querySelector('.card h3');h3.innerHTML = body.username;}})}getUser();

8.实现注销登陆

8.1约定前后端交互接口

[请求]
GET /logout[响应]
HTTP/1.1 302
Location: login.html

8.2实现服务器代码

session 中删除掉保存的 User 对象

响应重定向到 login.html 页面.

创建 LogoutServlet:

@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {HttpSession session = req.getSession(false);if (session == null) {// 当前就是未登录状态, 谈不上退出登录!resp.sendRedirect("login.html");return;}// 之前在登录成功后, 就会给 session 中存储 user 这样的 Attribute .// 把这个删掉之后, 自然就会判定为 "未登录" 了.session.removeAttribute("user");resp.sendRedirect("login.html");}
}

9.实现发布博客

9.1约定前后端交互接口

[请求]
POST /blog
Content-Type: application/x-www-form-urlencoded
title=标题&content=正文 ...
[响应]
HTTP/1.1 302
Location: blog_list.html

9.2实现服务器代码

修改 BlogServlet, 新增 doPost 方法.

    @Overrideprotected void doPost(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. 获取到请求中传递过来的内容req.setCharacterEncoding("utf8");  // 这个操作不要忘, 否则遇到中文可能会乱码String title = req.getParameter("title");String content = req.getParameter("content");if (title == null || content == null || "".equals(title) || "".equals(content)) {resp.setContentType("text/html; charset=utf8");resp.getWriter().write("标题或者正文为空");return;}// 3. 构造 Blog 对象, 并且插入到数据库中.Blog blog = new Blog();blog.setTitle(title);blog.setContent(content);blog.setUserId(user.getUserId());// 由于在 sql 插入数据的时候, 已经使用 sql 自带的 now 获取当前时间, 不需要此处代码中手动设置时间了.blog.setPostTime(new Timestamp(System.currentTimeMillis()));BlogDao blogDao = new BlogDao();blogDao.insert(blog);// 4. 跳转到博客列表页resp.sendRedirect("blog_list.html");}

9.3实现客户端代码

修改 blog_edit.html 页面结构,

增加 form 标签, action  blog_edit , method   POST 

form 指定  height: 100%;  防止编辑器高度不能正确展开. .  给标题的 input 标签加上 name 属性

把提交按钮改成 <input type="submit" value="发布文章"> •   

 <div id="editor"> 里面加上一个隐藏的 textarea

<!-- 博客编辑页的版心 -->
<div class="blog-edit-container"><form action="blog" method="post"><!-- 标题编辑区 --><div class="title"><input type="text" id="title-input" name="title"><input type="submit" id="submit"></div><!-- 博客编辑器 --><!-- 把 md 编辑器放到这个 div 中 --><div id="editor"><textarea name="content" style="display: none;"></textarea></div></form>
</div><script src="js/app.js"></script>
<script>var editor = editormd("editor", {// 这里的尺寸必须在这里设置. 设置样式会被 editormd 自动覆盖掉.width: "100%",// 设定编辑器高度height: "calc(100% - 50px)",// 编辑器中的初始内容markdown: "# 在这里写下一篇博客",// 指定 editor.md 依赖的插件路径path: "editor.md/lib/"});checkLogin();
</script>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/840348.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AGI技术与原理浅析:曙光还是迷失?

前言&#xff1a;回顾以往博客文章&#xff0c;最近一次更新在2020-07&#xff0c;内容以机器学习、深度学习、CV、Slam为主&#xff0c;顺带夹杂个人感悟。笔者并非算法科班出身&#xff0c;本科学制药、研究生学金融&#xff0c;最原始的算法积累都来源于网络&#xff0c;当时…

Android android.os.DeadObjectException aidl通信异常分析及解决

问题描述 做一款音乐播放应用&#xff0c;播放服务是通过AIDL形式对外暴露&#xff0c;允许跨进程调用且多个App同时操作音乐播放&#xff0c;偶现android.os.DeadObjectException问题 12-15 09:28:12.371: W/System.err(5412): android.os.DeadObjectException 12-15 09:28:…

乡村振兴与乡村文化传承创新:保护和传承乡村文化,推动乡村文化创新发展,打造具有文化魅力的美丽乡村

一、引言 在当代中国&#xff0c;乡村振兴已成为国家发展的重要战略之一。乡村不仅是自然资源的富集地&#xff0c;更是中华优秀传统文化的发源地。保护和传承乡村文化&#xff0c;推动乡村文化创新发展&#xff0c;对于打造具有文化魅力的美丽乡村&#xff0c;实现乡村全面振…

SSMP整合案例第三步 业务层service开发及基于Mybatis的接口功能拓展

业务层开发 对于业务层的制作有个误区 Service层接口定义与数据层接口定义具有较大差别 不要混用 业务层接口关注的是业务名称 数据层接口关注的是数据层名称 操作是不难 但是有些东西还是要掌握的 业务层接口如果是业务方法 就按照业务名称来代替 如果是数据操作 直接用…

本地部署Whisper实现语言转文字

文章目录 本地部署Whisper实现语言转文字1.前置条件2.安装chocolatey3.安装ffmpeg4.安装whisper5.测试用例6.命令行用法7.本地硬件受限&#xff0c;借用hugging face资源进行转译 本地部署Whisper实现语言转文字 1.前置条件 环境windows10 64位 2.安装chocolatey 安装chocol…

【调试笔记(目录)】

调试笔记-系列文章目录 第一章 Windows 环境 [1001] 使用VS2019编译edk2&#xff08;上&#xff09; [1002] 使用VS2019编译edk2&#xff08;下&#xff09; [1003] 调试 ExdiGdbSvr [1004] WSL 修改已安装发行版名称 [1005] 配置 QEMU/x86_64 运行 OpenWrt-23.05 发行版并搭…

mysql驱动版本变更导致查询数据结果一直是空

1 引言 最近接手了一个已离职同事的java项目&#xff0c;这个项目中原来使用了自己的mysql驱动版本&#xff0c;并未使用公司公共依赖中的版本号。我想为了统一版本号&#xff0c;就将当前项目中pom文件中mysql的版本号verson给去除了。没怎么自测&#xff0c;就直接发到测试环…

零基础学Java第二十二天之IO流之内存流,打印流,随机流

IO流之内存流&#xff0c;打印流&#xff0c;随机流 1、内存流 1、理解 内存流"&#xff08;Memory Stream&#xff09;在计算机编程中通常指的是一种特殊的数据流&#xff0c;它在内存中存储和操作数据&#xff0c;而不是在外部存储&#xff08;如硬盘、网络等&#xf…

申请轻纺行业工程设计资乙级对企业有什么要求

注册资金&#xff1a;企业的注册资金应至少达到三百万&#xff0c;这是衡量企业经济实力和承担风险能力的重要指标。独立法人资格&#xff1a;企业应具备独立的法人资格&#xff0c;能够独立承担民事责任&#xff0c;并具备相应的经营自主权。专业技术人员配备&#xff1a;企业…

前端框架选择指南:React vs Vue vs Angular

选择前端框架时&#xff0c;React、Vue 和 Angular 都是流行的选择&#xff0c;各有优缺点。我们可以从各个维度进行比较和选择&#xff1a; React 核心理念&#xff1a; 组件化开发&#xff0c;专注于视图层。学习曲线&#xff1a; 相对平缓&#xff0c;因为重点在于JSX和组…

免费的八字软件

无敌八字排盘软件完全免费使用&#xff0c;即使用不需要付费且无任何限制。同时推出手机版电脑版&#xff0c;两版本数据互通互用&#xff0c;即电脑版的数据可以备份到手机版上导入&#xff0c;手机版的数据也可以备份到电脑版上恢复导入&#xff0c;方便手机和电脑共用的朋友…

Golang实现递归复制文件夹

代码 package zdpgo_fileimport ("errors""os""path/filepath""strings" )// CopyDir 复制文件夹 // param srcPath 源文件夹 // param desPath 目标文件夹 // return error 错误信息 func CopyDir(srcPath, desPath string) error {…

在Java中实现泛型(Generics)的深入解析

在Java中&#xff0c;泛型&#xff08;Generics&#xff09;是一个强大的工具&#xff0c;它允许我们在编译时定义类型参数&#xff0c;使代码更加灵活、可重用和类型安全。下面&#xff0c;我将从技术难点、面试官关注点、回答吸引力以及代码举例四个方面&#xff0c;详细解析…

Android-自定义三角形评分控件

效果图 序言 在移动应用开发中&#xff0c;显示数据的方式多种多样&#xff0c;直观的图形展示常常能带给用户更好的体验。本文将介绍如何使用Flutter创建一个自定义三角形纬度评分控件&#xff0c;该控件可以通过动画展示评分的变化&#xff0c;让应用界面更加生动。 实现思…

转行3年涨薪300%,我总结了一套产品经理快速入门指南!

想转行的产品小白&#xff0c;初期一定会遇到这个问题——我要如何 0 基础转行产品经理&#xff1f; 要想 0 基础快速转行产品经理&#xff0c;我通过个人实践总结了 5 个关键点&#xff0c;可以参考。 一、熟悉产品经理的工作全流程 转行的产品小白&#xff0c;首先要建立产…

【刷题日记】最长连续序列

题目描述 给定一个未排序的整数数组 nums &#xff0c;找出数字连续的最长序列&#xff08;不要求序列元素在原数组中连续&#xff09;的长度。 请你设计并实现时间复杂度为 O(n) 的算法解决此问题 实现思路 核心要素两点&#xff1a; 1、当前元素的前驱是否在这个数组中&a…

ABtest假设检验知识|配对检验|比率检验|单向表-列联表检验

文章目录 1 假设检验基础2 一般假设检验2.1 假设检验包2.2 sample - 点击转化率2.2.1 问题描述2.2.2 实验设计2.2.3 数据处理2.2.4 方差齐性检验2.2.5 假设检验2.2.6 结果分析 3 检验两个均值的差&#xff1a;配对3.1 大样本检验3.1.1 单侧检验3.1.2 双侧检验 3.2 小样本检验3.…

【大模型】(记一面试题)使用Streamlit和Ollama构建PDF文件处理与聊天机器人应用

【大模型】(记一面试题)使用Streamlit和Ollama构建PDF文件处理与聊天机器人应用 我在找工作的过程中&#xff0c;遇到一个面试题&#xff1a;搭建一个简易的利用大型 LLM 和 RAG 来实现用户与PDF文件的自然语言交互。 参考链接&#xff1a;https://medium.com/the-ai-forum/ra…

深入理解软件测试:单元测试、集成测试与系统测试

软件测试是软件开发过程中至关重要的一环&#xff0c;旨在确保软件产品的质量和可靠性。根据测试的范围和目标&#xff0c;软件测试可以分为不同的层次和类型&#xff0c;其中最常见的包括单元测试、集成测试和系统测试。下面详细介绍这三种测试类型。 ### 1. 单元测试&#x…

【Python】—— 高阶函数

目录 &#xff08;一&#xff09;体验高阶函数 &#xff08;二&#xff09;内置高阶函数 2.1 map(&#xff09; 2.2 reduce() 2.3 filter() Python中的高阶函数是指那些接受函数作为参数&#xff0c;或者返回函数作为结果的函数。这种特性让Python的函数编程能力非常强大&…