博客系统(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;当时…

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

一、引言 在当代中国&#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…

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

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

免费的八字软件

无敌八字排盘软件完全免费使用&#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 {…

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

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

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

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

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…

算法题1:电路开关(HW)

题目描述 实验室对一个设备进行通断测试,实验员可以操控开关进行通断,有两种情况: ps,图没记下来,凭印象画了类似的 初始时,3个开关的状态均为断开;现给定实验员操控记录的数组 records ,records[i] = [time, switchId],表示在时刻 time 更改了开关 switchId 的状态…

【大模型】 基于AI和全球化进程的权衡:开源大模型与闭源大模型

【大模型】 基于AI和全球化进程的权衡&#xff1a;开源大模型与闭源大模型 前言 实际上关于开源or闭源&#xff0c;一直以来都是颇有争议的话题&#xff0c;人们争执于数据的隐私性和共享性&#xff0c;到底哪一方能获得的收益更大。而对于开源与闭源哪个更好实际上也就是说是…

深度学习500问——Chapter09:图像分割(5)

文章目录 9.12 DenseNet 9.13 图像分割的数据集 9.13.1 PASCAL VOC 9.13.2 MS COCO 9.13.3 Cityscapes 9.14 全景分割 9.12 DenseNet 这篇论文是CVPR2017年的最佳论文。 卷积神经网络结构的设计主要朝着两个方向发展&#xff0c;一个是更宽的网络&#xff08;代表&#xff1a…

【算法例题】n元钱买n只鸡

题目描述&#xff1a;公鸡5元1只&#xff0c;母鸡3元1只&#xff0c;小鸡1元3只&#xff0c;问&#xff1a;n元钱买n只鸡&#xff0c;怎么买&#xff1f; 解题思路&#xff1a;这题要用枚举算法&#xff0c;枚举鸡的数量&#xff0c;代码如下&#xff1a; ​#include <bit…

初步学习pygame,使用pygame搭建简单的窗口效果

在VSCode上使用pygame 第一步&#xff1a;创建 Python 虚拟环境 打开 VSCode 中的 Terminal&#xff08;在菜单栏中选择 View > Terminal&#xff09;使用 cd 命令切换到你的项目文件夹输入以下命令来创建一个新的虚拟环境&#xff1a; python3 -m venv env这将在你的项目…

每天五分钟深度学习框架PyTorch:创建具有特殊值的tensor张量

本文重点 tensor张量是一个多维数组,本节课程我们将学习一些pytorch中已经封装好的方法,使用这些方法我们可以快速创建出具有特殊意义的tensor张量。 创建一个值为空的张量 import torch import numpy as np a=torch.empty(1) print(a) print(a.dim()) print(s.shape) 如图…

三菱机械手维修控制器故障

在工业自动化领域&#xff0c;三菱工业机器人凭借其高性能、高可靠性和易用性&#xff0c;受到了广泛应用。然而&#xff0c;随着时间的推移&#xff0c;可能会出现MITSUBISH工业机械臂控制器故障&#xff0c;需要进行三菱机械手维修。 一、MITSUBISH机械手控制器故障诊断 在进…

冷干机的日常维护

冷干机的日常维护保养。 观察记录 (一)每班观察记录仪表值4次 1、压缩空气进出口压差不超过0.035Mpa; 2、蒸发压力表0.4Mpa-0.5Mpa; 高压压力表1.2Mpa-1.6Mpa。&#xff08;冷媒R22&#xff09; 3、压缩机的运行电流、电压。 (二)经常观察冷却水系统、压缩空气系统的进口温度…

【Numpy】深入解析numpy.mgrid()函数

numpy.mgrid()&#xff1a;多维网格生成与数值计算的利器 &#x1f308; 欢迎莅临我的个人主页&#x1f448;这里是我深耕Python编程、机器学习和自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;并乐于分享知识与经验的小天地&#xff01;&#x1f387; &#x1f393…