(Java高级教程)第三章Java网络编程-第八节:博客系统搭建(前后端分离)
文章目录
- 一:前端页面回顾
- 二:博客功能展示
- 三:数据库表设计
- (1)表设计
- (2)封装DataSource
- 四:实体类和数据访问对象
- (1)实体类
- (2)数据访问对象
- 五:各功能实现
- (1)登录功能
- (2)强制登录
- (3)博客列表页
- (4)博客详情页
- (5)显示用户信息和作者信息
- (6)注销
- (7)发布博客
- (8)删除博客
在(Java高级教程)第四章必备前端知识-最终节:博客系统搭建(页面设计部分)中我们实现了一个博客系统的前端页面。这一节我们会结合Servlet和之前的前端页面搭建一个完整的博客系统。该博客系统十分简单,虽然只包含下面最基础的功能,但足以对我们之前学习内容进行串联
- 博客列表页展示
- 博客详情页展示
- 博客登录页用户登录
- 在用户未登录并访问其他页面时强制要求登录
- 显示用户信息(作者信息)
- 注销登录
- 发布博客
- 删除博客
本文完整代码见Gihub仓库:点击跳转
一:前端页面回顾
博客登录页:BlogLoginPage.html
博客列表页:BlogListPage.html
博客详情页:BlogDetailPage.html
博客编辑页:BlogEditPage.html
二:博客功能展示
博客登录和列表页展示:
- 如果用户名或密码错误,则会弹出提示框
- 如果用户名和密码正确,则登录成功并跳转至博客列表页
在用户未登录并访问其他页面时强制要求登录:
博客列表页用户信息和博客详情页作者信息展示:
- 某用户登录后在博客列表中要显示该登录用户的信息。他可以查看到所有博客内容(部分博客是自己写的,部分博客是别人写的)
- 当点击进入一篇具体的博客的详情后,要展示该博客的作者信息
注销账户:
- 当用户点击“注销账户”后就会退出登录并跳转至博客登录页
发布博客:
- 当用户点击“开始创作”就会跳转至博客编辑页
- 待用户写好博客标题和内容并点击标题右侧的“发布文章”后,就会将该博客插入到数据库中
- 发布成功后自动跳转至博客列表页
删除博客:
- 当用户点击“删除文章”时就会将该博客删除,然后跳转至博客列表页
- 用户不能将别人的博客删除
三:数据库表设计
(1)表设计
博客数据,用户数据都是存储在数据库中的,因此数据库表设计在很大程度上决定或影响了整个程序的编写。为此,我们需要创建以下两张表
- 博客表:blog:
blogId
:博客Idtitle
:博客标题content
:博客内容postTime
:博客插入数据库的时间(发布时间)userId
:用户Id
- 用户表:user:
userId
:用户Idusername
:用户名password
:用户密码
SQL语句如下,你可以在IDEA中创建一个后缀名为.sql
的文件,然后在这里面编写,之后将所有SQL语句复制到终端即可
-- 创建数据库
create database if not exists BlogSystem2; -- 使用数据库
use BlogSystem2; drop table if exists blog; -- 创建博客表blog
create table blog ( blogId int primary key auto_increment, title varchar(256), content text, postTime datetime, userId int
); -- 创建用户表user
drop table if exists user;
create table user ( userId int primary key auto_increment, userName varchar(50) unique, passWord varchar(50)
); -- 插入数据测试
insert into blog values(null, "第一篇博客", "今天我们介绍博客系统的实现", now(), 1);
insert into blog values(null, "第二篇博客", "今天我们再次介绍博客系统的实现", now(), 2);
insert into blog values(null, "第三篇博客", "今天我们第三次次介绍博客系统的实现", now(), 1);
insert into blog values(null, "第四篇博客", "### 第一:任务内容 - asd1", now(), 2); insert into user values(1, "张三", "123");
insert into user values(2, "李四", "123");
效果如下
(2)封装DataSource
在进行数据插入、删除等操作时会频繁设计如“获取数据源”、“建立连接”、“释放资源”等等重复操作,所以我们可以将它们封装在一个DBUtil
中。它要符合单例模式的设计结构
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; 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/blogsystem2?characterEncoding=utf8&allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=UTC"); ((MysqlDataSource)dataSource).setUser("root"); ((MysqlDataSource)dataSource).setPassword("123456"); } } } return dataSource; } private DBUtil() {}; // 建立连接 public static Connection getConnection() throws SQLException { return getDataSource().getConnection(); } // 释放资源 public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet) throws SQLException { 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 { statement.close(); } catch (SQLException e) { e.printStackTrace(); } } }
}
四:实体类和数据访问对象
(1)实体类
数据库中有什么样的表,程序中就必须有对应的实体类。因此创建User类和Blog类,并设置对应的get
和set
方法
- 注意:Blog类中的
postTime
类型为时间戳(Timestamp),在获取时需要转换为对应的“年月日”等格式,这里借助了SimpleDateFormat
Blog类:
import java.sql.Timestamp;
import java.text.SimpleDateFormat; public class Blog { private int blogId; private String title; private String content; private Timestamp postTime; private int userId; 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 String getPostTime() { // 返回格式化好的字符串日期类型 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年-MM月-dd日 HH:mm:ss"); return simpleDateFormat.format(this.postTime); } public void setPostTime(Timestamp postTime) { this.postTime = postTime; } public int getUserId() { return userId; } public void setUserId(int userId) { this.userId = userId; }
}
User类:
public class User { private int userId; private String userName; private String passWord; public int getUserId() { return userId; } public void setUserId(int userId) { this.userId = userId; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassWord() { return passWord; } public void setPassWord(String passWord) { this.passWord = passWord; }
}
(2)数据访问对象
针对实体类肯定有各种各样的增删改查操作,十分繁琐,所以我们需要创建对应的数据访问对象(Data Access Object, DAO)。BlogDAO对应Blog类,UserDAO对应User类
BlogDAO:
import java.beans.PropertyEditorSupport;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List; // 针对博客表的一些操作
public class BlogDAO { // 插入博客到数据库中 public void insert(Blog blog) throws SQLException { Connection connection = null; PreparedStatement statement = null; try { // 获取数据源并连接 connection = DBUtil.getConnection(); // 构造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()); // 执行SQL int ret = statement.executeUpdate(); if (ret != 1) { System.out.println("插入失败!"); } else { System.out.println("插入成功"); } } catch (SQLException e) { e.printStackTrace(); } finally { // 释放资源 DBUtil.close(connection, statement, null); } } // 根据blogId查询指定博客 public Blog selectById(int blogId) throws SQLException { Connection connection = null; PreparedStatement statement = null; ResultSet resultSet = null; try { connection = DBUtil.getConnection(); String sql = "select * from blog where blogId = ?"; statement = connection.prepareStatement(sql); statement.setInt(1, blogId); resultSet = statement.executeQuery(); // 遍历结果,如果有查询结果则返回 if (resultSet.next()) { Blog blog = new Blog(); blog.setBlogId(resultSet.getInt("blogId")); blog.setTitle(resultSet.getString("title")); blog.setContent(resultSet.getString("content")); blog.setPostTime(resultSet.getTimestamp("postTime")); blog.setUserId(resultSet.getInt("userId")); return blog; } } catch (SQLException e) { e.printStackTrace(); } finally { DBUtil.close(connection, statement, resultSet); } return null; } // 查询所有博客 public List<Blog> selectAll() throws SQLException { List<Blog> blogs = new ArrayList<>(); Connection connection = null; PreparedStatement statement = null; ResultSet resultSet = null; 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")); // 注意:为了避免因博客content太多而导致博客列表页博客摘要显示过长 // 规定当content长度大于100时只显示部分内容,用户如果想要查看完整内容则需要点击按钮 String content = resultSet.getString("content"); if (content.length() > 100) { content = content.substring(0, 100) + "......"; } blog.setContent(content); blog.setPostTime(resultSet.getTimestamp("postTime")); blog.setUserId(resultSet.getInt("userId")); blogs.add(blog); } } catch (SQLException e) { e.printStackTrace(); } finally { DBUtil.close(connection, statement, resultSet); } return blogs; } // 根据blogId删除博客 (删除博客) public void delete (int blogId) throws SQLException { 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); int ret = statement.executeUpdate(); if (ret != 1) { System.out.println("删除失败!"); } else { System.out.println("删除成功!"); } } catch (SQLException e) { e.printStackTrace(); } finally { DBUtil.close(connection, statement, null); } }
}
UserDAO:
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException; // 针对用户表相关操作
public class UserDAO { // 根据用户名查询用户
public User selectByUserName(String userName) throws SQLException { 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 e) { e.printStackTrace(); } finally { DBUtil.close(connection, statement, resultSet); } return null; } // 根据userId查询用户 public User selectByUserId(int userId) throws SQLException { 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; }
}
五:各功能实现
这里是博客系统实现最复杂的地方,要搭建一个完整的博客系统,就要处理后前后端交互的逻辑。也即页面发起HTTP请求,然后服务器返回HTTP响应。我们只需要约定后请求和响应分别是什么样的,然后按照需求分别编写前端页面和后端服务即可
(1)登录功能
处理逻辑:当用户输入用户名和密码并点击登录后,就会发送请求给服务器,服务器负责验证。如果验证成功,则让页面跳转至博客列表页,否则弹出提示让其重新登录
- 请求
- POST/login
- Content-Type:application/x-www-form-urlencoded
- 响应
- HTTP/1.1 302
- Location:博客列表页
前端页面:BlogLoginPage.html
<!DOCTYPE html>
<html lang="en">
<head> <meta charset="UTF-8"> <title>登录页</title> <link rel="stylesheet" href="css/Common.css"> <link rel="stylesheet" href="css/Blog_login.css"> </head>
<body>
<!--导航栏-->
<div class="nav"> <img src="image/电子书.png" alt=""> <span class="title">博客之家·文行天下</span> </div> <!--登录框-->
<div class="container"> <!--登录对话框--> <form action="login" method="post"> <div class="dialog"> <h3>登录你的账户</h3> <div class="row"> <span>用户名</span> <input type="text" required name="username"> </div> <div class="row"> <span>密   码</span> <input type="password" required name="password"> </div> <div class="row"> <button id="login">登录</button> </div> </div> </form> </div> <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
<script> $(document).ready(function() { $("#login").click(function(event) { event.preventDefault(); // 阻止表单默认提交行为 var username = $('input[name="username"]').val(); var password = $('input[name="password"]').val(); $.ajax({ url: "login", type: "post", data: { username: username, password: password }, success: function(body) { alert("登录成功!"); // 重定向到博客列表页 location.assign("/BlogSystem2/BlogListPage.html"); }, error: function(body) { alert("用户名或密码为空,请重新输入!"); } }); }); });
</script> </body>
</html>
后端:LoginServlet.java
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;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map; @WebServlet("/login")
public class LoginServlet extends HttpServlet { @Override // 登录功能 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 获取用户名和密码 req.setCharacterEncoding("utf-8"); String username = req.getParameter("username"); String password = req.getParameter("password"); if (username == null || "".equals(username) || password == null || "".equals(password)) { resp.setContentType("text/html; charset=utf-8"); resp.getWriter().write("登录失败!用户名或密码为空"); resp.setStatus(403); return; } // 查询数据库,验证用户名和密码是否正确 UserDAO userDAO = new UserDAO(); User user = null; try { user = userDAO.selectByUserName(username); if (user == null || !user.getPassWord().equals(password)) { resp.setContentType("text/html; charset=utf-8"); resp.getWriter().write("登录失败!用户名或密码错误"); resp.setStatus(401); return; } } catch (SQLException e) { e.printStackTrace(); } // 如果正确则创建一个会话对象,保证在访问其他页面时可以直接判定是哪个用户在访问 HttpSession session = req.getSession(true); session.setAttribute("user", user); }
}
(2)强制登录
处理逻辑:在博客列表页、详情页和编辑页加载后,会发起一个ajax请求,从服务器获取登录状态,如果是未登录状态则提示并重定向到登录页面,如果已经登录则不做任何改变
- 请求
- GET/login
- 响应
- 如果已经登录:HTTP/1.1 200 OK
- 如果未登录:HTTP/1.1 403
前端页面:BlogDetailPage.html
、BlogEditPage.html
、BlogListPage.html
需要使用下面的js函数
function getLoginStatus() { $.ajax({ type: 'get', url: 'login', success: function (body) { // 已经在登录状态,不处理 }, error: function () { // 非登录或其他状态,则强行跳转 alert("未登录!请登录后再访问") location.assign('BlogLoginPage.html') } });
}
后端:LoginServlet.java
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;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map; @WebServlet("/login")
public class LoginServlet extends HttpServlet { @Override // 登录功能 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 获取用户名和密码 req.setCharacterEncoding("utf-8"); String username = req.getParameter("username"); String password = req.getParameter("password"); if (username == null || "".equals(username) || password == null || "".equals(password)) { resp.setContentType("text/html; charset=utf-8"); resp.getWriter().write("登录失败!用户名或密码为空"); resp.setStatus(403); return; } // 查询数据库,验证用户名和密码是否正确 UserDAO userDAO = new UserDAO(); User user = null; try { user = userDAO.selectByUserName(username); if (user == null || !user.getPassWord().equals(password)) { resp.setContentType("text/html; charset=utf-8"); resp.getWriter().write("登录失败!用户名或密码错误"); resp.setStatus(401); return; } } catch (SQLException e) { e.printStackTrace(); } // 如果正确则创建一个会话对象,保证在访问其他页面时可以直接判定是哪个用户在访问 HttpSession session = req.getSession(true); session.setAttribute("user", user); } @Override // 防止未登录直接访问 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 获取会话 HttpSession session = req.getSession(false); // 没有会话则为未登录 if (session == null) { resp.setStatus(403); return; } // 如果有会话则已经登录,获取用户 User user = (User)session.getAttribute("user"); // 这里是为了结合注销逻辑,注销时会直接删除user if (user == null) { resp.setStatus(403); return; } // 返回200 resp.setStatus(200); }
}
(3)博客列表页
处理逻辑:当博客列表页加载时就发起请求,从数据库中获取博客数据,然后展示
- 请求
- GET/blog
- 响应
- HTTP/1/1 200ok
- Content-Type:application
前端页面:BlogListPage.html
<!DOCTYPE html>
<html lang="en">
<head> <meta charset="UTF-8"> <title>博客列表</title> <link rel="stylesheet" href="css/Common.css"> <link rel="stylesheet" href="css/Blog_list.css"> </head>
<body> <!--导航栏-->
<div class="nav"> <img src="image/电子书.png" alt=""> <span class="title">博客之家·文行天下</span> <!-- 用于占位 --> <div class="space"></div> <a href="BlogListPage.html">博客主页</a> <a href="logout">注销账户</a> <div class="creating"><a href="BlogEditPage.html">开始创作</a></div> </div> <!--版心区域-->
<div class="container"> <!-- 用户信息 --> <div class = "left"> <div class="card"> <!-- 用户头像 --> <img src="image/头像.png" alt=""> <!-- 用户名字 --> <h3></h3> <!-- Github地址 --> <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-03-13 06:00:00</div> <div class="desc"> 在这个充满机遇和挑战的时代,我们需要不断地学习和成长。 只有不断地提升自己的能力才能够适应未来社会的发展趋势, 并且取得更好的成就。无论是在工作中还是生活中, 都需要具备一定的技能和知识储备,以便更好地解决问题并迎接新挑战 </div> <div class="click"><a href="BlogDetailPage.html">查看全文</a></div> </div>--> </div>
</div> <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
<script src="js/app.js"></script> <script> // 利用ajax发送请求从服务器获取博文数据 function getBlogs() { $.ajax({ type: 'get', url: 'blog', success: function(body) { // 如果成功,那么body就是一个json对象数组,每个元素为一个博客 let container = document.querySelector('.right') for (let blog of body) { // blogDiv let blogDiv = document.createElement('div'); blogDiv.className = 'blog'; // 博客标题 let titleDiv = document.createElement('div'); titleDiv.className = 'title'; titleDiv.innerHTML = blog.title; // 博客日期 let dateDiv = document.createElement('div'); dateDiv.className = 'title'; dateDiv.innerHTML = blog.postTime; // 博客摘要 let descDiv = document.createElement('div'); descDiv.className = 'desc'; descDiv.innerHTML = blog.content; // 查看全文按钮 let a = document.createElement('a'); a.href = 'BlogDetailPage.html?blogId=' + blog.blogId; a.innerHTML = '查看全文'; // 点击按钮 let clickdiv = document.createElement('div'); clickdiv.className = 'click'; // 拼接 blogDiv.appendChild(titleDiv); blogDiv.appendChild(dateDiv); blogDiv.appendChild(descDiv); clickdiv.appendChild(a); blogDiv.appendChild(clickdiv); container.appendChild(blogDiv); } } }); } getBlogs(); // 获取登录状态判断是否登录 getLoginStatus(); </script> </body>
</html>
后端:BlogServlet.java
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;
import java.sql.SQLException;
import java.util.List;// 博客列表页和博客详情页请求处理
@WebServlet("/blog")
public class BlogServlet extends HttpServlet {private ObjectMapper objectMapper = new ObjectMapper();@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 按照约定的接口格式返回数据resp.setContentType("application/json; charset=utf-8");BlogDAO blogDAO = new BlogDAO();String blogId = req.getParameter("blogId");try {List<Blog> blogs = blogDAO.selectAll();resp.getWriter().write(objectMapper.writeValueAsString(blogs));} catch (SQLException e) {e.printStackTrace();}}}
(4)博客详情页
处理逻辑:当在博客列表页中点击“查看全文”时就会跳转至该博客对应的详情页。在请求对应详情页面时,需要在请求的url
处加上query_string
,例如http://localhost:8080/BlogSystem2/BlogDetailPage.html?blogId=2
就表示请求的是blogId=2
的详情页
- 请求
- GET/blogId=1
- 响应
- HTTP/1.1 200 OK
- Content-Type:applocation/json
需要注意的是,在博客列表页中,我们已经使用了BlogServlet.doGet
方法了,如果博客列表页也想要使用的话就要作出区分。具体来说,如果请求带有queryString
,有blogId
这个参数,就认为这是博客详情页的请求,否则则认为是博客列表页的请求
前端页面:BlogDetailPage.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>博客详情</title><link rel="stylesheet" href="css/Common.css"><link rel="stylesheet" href="css/Blog_detail.css"><!-- 引入 editor.md 的依赖 --><link rel="stylesheet" href="editor.md/css/editormd.min.css" /><script src="https://apps.bdimg.com/libs/jquery/2.1.4/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/电子书.png" alt=""><span class="title">博客之家·文行天下</span><!-- 用于占位 --><div class="space"></div><a href="BlogListPage.html">博客主页</a><a href="logout">注销账户</a><div class="creating"><a href="BlogEditPage.html">开始创作</a></div><div class="deleting"><a href="blog_delete" id="delete-btn">删除文章</a></div></div><!--版心区域-->
<div class="container"><!-- 用户信息 --><div class = "left"><div class="card"><!-- 用户头像 --><img src="image/头像.png" alt=""><!-- 用户名字 --><h3></h3><!-- Github地址 --><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 class="blog_detail"><h3></h3><div class="date"></div><div id="content" style="background-color: transparent"></div></div></div><script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script><script src="js/app.js"></script><script><!-- 利用ajax服务器获取该博客数据-->function getBlog() {$.ajax({type: 'get',// location.search可以返回query-stringurl : 'blog' + location.search,success: function (body) {let h3 = document.querySelector('.blog_detail h3');h3.innerHTML = body.title;let dateDiv = document.querySelector('.date');dateDiv.innerHTML = body.postTime;// 此处应该使用editor.md对markdown内容进行渲染editormd.markdownToHTML('content', {markdown: body.content});}});}getBlog();getLoginStatus();</script></body>
</html>
后端:BlogServlet.java
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;
import java.sql.SQLException;
import java.util.List;// 博客列表页和博客详情页请求处理
@WebServlet("/blog")
public class BlogServlet extends HttpServlet {private ObjectMapper objectMapper = new ObjectMapper();@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 按照约定的接口格式返回数据resp.setContentType("application/json; charset=utf-8");BlogDAO blogDAO = new BlogDAO();String blogId = req.getParameter("blogId");if (blogId == null) {// 这是博客列表页请求try {List<Blog> blogs = blogDAO.selectAll();resp.getWriter().write(objectMapper.writeValueAsString(blogs));} catch (SQLException e) {e.printStackTrace();}} else {// 这是博客详情页请求try {Blog blog = blogDAO.selectById(Integer.parseInt(blogId));resp.getWriter().write(objectMapper.writeValueAsString(blog));} catch (SQLException e) {e.printStackTrace();}}}
}
(5)显示用户信息和作者信息
处理逻辑:
- 在博客列表页加载时,从服务器获取当前登录的用户信息,然后将信息展现在页面之上
- 请求
- GET/userinfo
- 响应
- HTTP/1.1 200OK
- content-Type:application/json
- 请求
- 在博客详情页加载时,从服务器获取博客的作者信息,然后展现在页面智商
- 请求
- GET/userinfo?blogId=1
- 响应
- Content-Type:application/json
- 请求
前端页面:BlogListPage.html
和BlogDetailPage.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>博客列表</title><link rel="stylesheet" href="css/Common.css"><link rel="stylesheet" href="css/Blog_list.css"></head>
<body><!--导航栏-->
<div class="nav"><img src="image/电子书.png" alt=""><span class="title">博客之家·文行天下</span><!-- 用于占位 --><div class="space"></div><a href="BlogListPage.html">博客主页</a><a href="logout">注销账户</a><div class="creating"><a href="BlogEditPage.html">开始创作</a></div></div><!--版心区域-->
<div class="container"><!-- 用户信息 --><div class = "left"><div class="card"><!-- 用户头像 --><img src="image/头像.png" alt=""><!-- 用户名字 --><h3></h3><!-- Github地址 --><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-03-13 06:00:00</div><div class="desc">在这个充满机遇和挑战的时代,我们需要不断地学习和成长。只有不断地提升自己的能力才能够适应未来社会的发展趋势,并且取得更好的成就。无论是在工作中还是生活中,都需要具备一定的技能和知识储备,以便更好地解决问题并迎接新挑战</div><div class="click"><a href="BlogDetailPage.html">查看全文</a></div></div>--></div>
</div><script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
<script src="js/app.js"></script><script>// 利用ajax发送请求从服务器获取博文数据function getBlogs() {...........}getBlogs();// 获取登录状态判断是否登录getLoginStatus();// 针对博客列表页获取当前用户的登录信息function getUserInfo() {$.ajax({type: 'get',url: 'userInfo',success: function(body) {let h3 = document.querySelector('.left>.card>h3');h3.innerHTML = body.userName;}});}getUserInfo();
</script></body>
</html>
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>博客详情</title><link rel="stylesheet" href="css/Common.css"><link rel="stylesheet" href="css/Blog_detail.css"><!-- 引入 editor.md 的依赖 --><link rel="stylesheet" href="editor.md/css/editormd.min.css" /><script src="https://apps.bdimg.com/libs/jquery/2.1.4/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/电子书.png" alt=""><span class="title">博客之家·文行天下</span><!-- 用于占位 --><div class="space"></div><a href="BlogListPage.html">博客主页</a><a href="logout">注销账户</a><div class="creating"><a href="BlogEditPage.html">开始创作</a></div><div class="deleting"><a href="blog_delete" id="delete-btn">删除文章</a></div></div><!--版心区域-->
<div class="container"><!-- 用户信息 --><div class = "left"><div class="card"><!-- 用户头像 --><img src="image/头像.png" alt=""><!-- 用户名字 --><h3></h3><!-- Github地址 --><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 class="blog_detail"><h3></h3><div class="date"></div><div id="content" style="background-color: transparent"></div></div></div><script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script><script src="js/app.js"></script><script><!-- 利用ajax服务器获取该博客数据-->function getBlog() {.........}getBlog();getLoginStatus();// 针对博客详情页获取当前用户信息function getUserInfo() {$.ajax({type: 'get',url: 'userInfo' + location.search,success: function(body) {let h3 = document.querySelector('.left>.card>h3');h3.innerHTML = body.userName;}});}getUserInfo();// 博文删除,为其赋上blogIdfunction updateDeleteURL() {let DeleteBtn = document.querySelector('#delete-btn');DeleteBtn.href = 'blog_delete' + location.search;}updateDeleteURL();</script></body>
</html>
后端:UserInfoServlet.java
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;
import java.sql.SQLException; @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"); if (blogId == null) { // 这是列表页在请求,直接从session中获取即可 getUserInfoFromSession(req, resp); } else { // 这是详情页在请求,查询数据库 try { getUserInfoFromDB(req, resp, Integer.parseInt(blogId)); } catch (SQLException e) { throw new RuntimeException(e); } } } private void getUserInfoFromDB(HttpServletRequest req, HttpServletResponse resp, int blogId) throws SQLException, IOException { // 根据blogId查询Blog对象,获取userId BlogDAO blogDAO = new BlogDAO(); Blog blog = blogDAO.selectById(blogId); if (blog == null) { // 未找到这样的blog resp.setStatus(404); resp.setContentType("text/html; charset=utf-8"); resp.getWriter().write("blogId不存在"); return; } // 根据userId查询对应的User对象 UserDAO userDAO = new UserDAO(); User user = userDAO.selectByUserId(blog.getUserId()); if (user == null) { resp.setStatus(404); resp.setContentType("text/html; charset=utf-8"); resp.getWriter().write("userId不存在"); return; } // 将user对象返回 user.setPassWord(""); resp.setContentType("application/json; charset=utf-8"); 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=utf-8"); resp.getWriter().write("当前未登录"); return; } User user = (User)session.getAttribute("user"); if (user == null) { resp.setStatus(403); resp.setContentType("text/html;charset=utf-8"); resp.getWriter().write("当前未登录"); return; } // 移除password,避免返回密码 user.setPassWord(""); resp.setContentType("application/json;charset=utf-8"); resp.getWriter().write(objectMapper.writeValueAsString(user)); }
}
(6)注销
处理逻辑:点击博客列表页、博客详情页和博客编辑页导航栏中的注销按钮后,会向服务器发送一个HTTP请求(不是ajax请求),告诉服务器准备退出登录,然后服务器会把会话中的user对象删除,同时重定向的登录页。需要注意的是这里删除的user对象而不是session对象,因为HttpSession没有一个直接用于删除的方法
- 请求
- GET/logout
- 响应
- HTTP/1.1 302
- Location:login.html
前端页面:BlogDetailPage.html
、BlogEditPage.html
、BlogListPage.html
,只需要再路径处填入logout
即可
<div class="nav"> <img src="image/电子书.png" alt=""> <span class="title">博客之家·文行天下</span> <!-- 用于占位 --> <div class="space"></div> <a href="BlogListPage.html">博客主页</a> <a href="logout">注销账户</a> <div class="creating"><a href="BlogEditPage.html">开始创作</a></div> <div class="deleting"><a href="blog_delete" id="delete-btn">删除文章</a></div> </div>
后端:LogOutServlet.java
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必须同时存在,所以在这里我们可以直接删除user即可 session.removeAttribute("user"); // 重定向到登录页面 resp.sendRedirect("BlogLoginPage.html"); }
}
(7)发布博客
处理逻辑:用户在博客编辑页中填写标题和内容后点击”发布文章“按钮,此时会发起一个HTTP请求。当服务器收到这些数据后,会构造一个blog对象,然后插入数据库。 发布成功后跳转至列表页
- 请求
- post/blog
- Content-Type:application.x-www-from-urlencoded
- 响应
- HTTP/1.1 032
- Location:blog_list.html
前端页面:BlogEditPage.html
<!DOCTYPE html>
<html lang="en">
<head> <meta charset="UTF-8"> <title>编辑博客</title> <link rel="stylesheet" href="css/Common.css"> <link rel="stylesheet" href="css/Blog_edit.css"> <!-- 引入 editor.md 的依赖 --> <link rel="stylesheet" href="editor.md/css/editormd.min.css" /> <script src="https://apps.bdimg.com/libs/jquery/2.1.4/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/电子书.png" alt=""> <span class="title">博客之家·文行天下</span> <!-- 用于占位 --> <div class="space"></div> <a href="BlogListPage.html">博客主页</a> <a href="logout">注销账户</a> <div class="creating"><a href="BlogEditPage.html">开始创作</a></div> </div> <!--markdown编辑器区域-->
<div class="container"> <!--标题编辑区--> <div class="title"> <input type="text" id="title-input" placeholder="在这里输入博客标题(100字以内)"> <button id="title-submit">发布文章</button> </div> <!--正文编辑区--> <div id="editor"> </div> </div> <script src="js/app.js"></script>
<script> // 初始化编辑器, 代码也是截取自 官方文档 . var editor = editormd("editor", { // 这里的尺寸必须在这里设置. 设置样式会被 editormd 自动覆盖掉. width: "100%", // 设定编辑器高度 height: "calc(100% - 50px)", // 编辑器中的初始内容 markdown: "## hello world", // 指定 editor.md 依赖的插件路径 path: "editor.md/lib/", // 发布文章 // saveHTMLToTextarea: true, }); getLoginStatus(); // 发布博客 $(document).ready(function() { // 当点击发布文章按钮时执行 $("#title-submit").click(function() { // 获取博客标题和内容 let title = $("#title-input").val(); let content = editor.getMarkdown(); // 假设你已经初始化了editor.md并赋给了变量editor console.log(content); // 创建一个包含标题和内容的对象 let postData = { title: title, content: content }; // 发送POST请求 $.ajax({ type: "POST", url: "blog", // 替换成你的服务器端处理请求的URL data: JSON.stringify(postData), // 将数据转换为JSON格式 contentType: "application/json", success: function(response) { // 请求成功处理 location.assign("/BlogSystem2/BlogListPage.html"); }, error: function(error) { // 请求失败处理 console.error("文章发布失败", error); } }); }); }); </script> </body>
</html>
后端:BlogServlet.java
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;
import java.sql.SQLException;
import java.util.List;// 博客列表页和博客详情页请求处理
@WebServlet("/blog")
public class BlogServlet extends HttpServlet {private ObjectMapper objectMapper = new ObjectMapper();@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 按照约定的接口格式返回数据............}//提交新博客@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 获取会话和用户信息/*按照道理来说,这里不需要在判定登录了。因为既然能发起Post请求说明已经登录了但是有人可能会利用postman等工具收到发起Post请求同时要构造博客对象,必须知道现在谁在登录,才能知道文章作者是谁*/HttpSession session = req.getSession(false);if (session == null) {resp.setStatus(403);resp.setContentType("text/html; charset=utf-8");resp.getWriter().write("未登录,请登录后再访问");return;}User user = (User)session.getAttribute("user");if (user == null) {resp.setStatus(403);resp.setContentType("text/html; charset=utf-8");resp.getWriter().write("未登录,请登录后再访问");return;}// 获取博客标题和正文req.setCharacterEncoding("utf-8");// 从请求体中获取JSON数据并解析为Blog对象ObjectMapper objectMapper = new ObjectMapper();Blog blog = objectMapper.readValue(req.getReader(), Blog.class);// 设置博客的用户ID为当前用户的IDblog.setUserId(user.getUserId());// 构造Blog对象并插入到数据库中BlogDAO blogDAO = new BlogDAO();try {blogDAO.insert(blog);} catch (SQLException e) {throw new RuntimeException(e);}// 发布成功后重定向到列表页resp.sendRedirect("BlogListPage.html");}
}
(8)删除博客
处理逻辑:当点击博客详情页上的“删除文章”后,服务器会做出一个判定,如果当前登录的用户就是文章的作者,才能真正删除
- 请求
- GET/bloh_delete?blogId=1
- 响应
- 删除成功
- HTTP/1.1 302
- Location:blog_list.html
- 删除失败
- HTTP/1.1 302
- 没有删除权限
- 删除成功
前端页面:BlogDetailPage.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>博客详情</title><link rel="stylesheet" href="css/Common.css"><link rel="stylesheet" href="css/Blog_detail.css"><!-- 引入 editor.md 的依赖 --><link rel="stylesheet" href="editor.md/css/editormd.min.css" /><script src="https://apps.bdimg.com/libs/jquery/2.1.4/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/电子书.png" alt=""><span class="title">博客之家·文行天下</span><!-- 用于占位 --><div class="space"></div><a href="BlogListPage.html">博客主页</a><a href="logout">注销账户</a><div class="creating"><a href="BlogEditPage.html">开始创作</a></div><div class="deleting"><a href="blog_delete" id="delete-btn">删除文章</a></div></div><!--版心区域-->
<div class="container"><!-- 用户信息 --><div class = "left"><div class="card"><!-- 用户头像 --><img src="image/头像.png" alt=""><!-- 用户名字 --><h3></h3><!-- Github地址 --><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 class="blog_detail"><h3></h3><div class="date"></div><div id="content" style="background-color: transparent"></div></div></div><script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script><script src="js/app.js"></script><script><!-- 利用ajax服务器获取该博客数据-->function getBlog() {......}getBlog();getLoginStatus();// 针对博客详情页获取当前用户信息function getUserInfo() {......}getUserInfo();// 博文删除,为其赋上blogIdfunction updateDeleteURL() {let DeleteBtn = document.querySelector('#delete-btn');DeleteBtn.href = 'blog_delete' + location.search;}updateDeleteURL();</script></body>
</html>
后端:BlogServlet.java
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.sql.SQLException; @WebServlet("/blog_delete")
public class BlogDeleteServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 判定用户登录状态 HttpSession session = req.getSession(false); if (session == null) { resp.setStatus(403); resp.setContentType("text/html; charset=utf-8"); resp.getWriter().write("未登录,请登录后再访问"); return; } User user = (User)session.getAttribute("user"); if (user == null) { resp.setStatus(403); resp.setContentType("text/html; charset=utf-8"); resp.getWriter().write("未登录,请登录后再访问"); return; } // 获取blogId String blogId = req.getParameter("blogId"); if (user == null) { // 没有这样的blogId resp.setStatus(404); resp.setContentType("text/html; charset=utf-8"); resp.getWriter().write("blogId有误"); return; } // 获取对应Blog BlogDAO blogDAO = new BlogDAO(); Blog blog = null; try { blog = blogDAO.selectById(Integer.parseInt(blogId)); if (blog == null) { // 没有这样的blog resp.setStatus(404); resp.setContentType("text/html; charset=utf-8"); resp.getWriter().write("当前删除的博客不存在"); return; } } catch (SQLException e) { throw new RuntimeException(e); } // 判断登录用户是否就是文章作者 if (blog.getUserId() != user.getUserId()) { // 不能删除别人的博客 resp.setStatus(404); resp.setContentType("text/html; charset=utf-8"); resp.getWriter().write("禁止删除他人博客"); return; } // 实际删除 try { blogDAO.delete(Integer.parseInt(blogId)); } catch (SQLException e) { throw new RuntimeException(e); } // 重定向 resp.sendRedirect("BlogListPage.html"); }
}
相关文章:
(Java高级教程)第三章Java网络编程-第八节:博客系统搭建(前后端分离)
文章目录 一:前端页面回顾二:博客功能展示三:数据库表设计(1)表设计(2)封装DataSource 四:实体类和数据访问对象(1)实体类(2)数据访问…...
901. 股票价格跨度
设计一个算法收集某些股票的每日报价,并返回该股票当日价格的 跨度 。 当日股票价格的 跨度 被定义为股票价格小于或等于今天价格的最大连续日数(从今天开始往回数,包括今天)。 例如,如果未来 7 天股票的价格是 [100,…...
JavaScript中的模块化编程,包括CommonJS和ES6模块的区别。
聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 模块化编程概述⭐ CommonJS 模块⭐ ES6 模块⭐ 区别⭐ 写在最后 ⭐ 专栏简介 前端入门之旅:探索Web开发的奇妙世界 欢迎来到前端入门之旅!感兴趣的可以订阅本专栏哦!这个专栏是为那些对Web开发感兴趣、…...
从零开始 Spring Cloud 13:分布式事务
从零开始 Spring Cloud 13:分布式事务 1.分布式事务问题 用一个示例项目演示在分布式系统中使用事务会产生的问题。 示例项目的 SQL:seata_demo.sql 示例项目代码:seata-demo.zip 这个示例项目中的微服务的互相调用依赖于 Nacos…...
2023Node.js零基础教程(小白友好型),nodejs新手到高手,(二)NodeJS入门——buffer模块、计算机基础、fs模块、path模块
就算步子乱了又如何,接着跳下去就好了。——《闻香识女人》 开始 011_Buffer_介绍与创建 hello,大家好,我们来学习一下buffer。首先来看看 buffer 是一个什么东东。buffer,中文译为缓冲区,是一个类似于数组的对象&am…...
lua如何调用C/C++
1 lua vs C/C lua是脚本语言,优点是门槛低,可以热更新,缺点当然就是性能。C/C是编译型语言,有点是性能高,但是相对的,门槛高,技术不好的人写的代码可能还没有lua的性能高,容易出现c…...
简单聊一聊公平锁和非公平锁,parallel并行流
目录 一、降低锁的粒度,将synchronized关键字不放在方法上了,改为synchronized代码块。二、先区分一下公平锁和非公平锁1、公平锁2、非公平锁3、公平锁的优缺点:4、非公平锁的优缺点: 三、是否对症下药四、IntStream.rangeClosed是…...
【SpringCloud】微服务技术栈入门4 - RabbitMQ初探
目录 RabbitMQ安装 rabbitmqSpringAMQP 基础队列WorkQueue路由发布订阅 FanoutExchangeDirectExchangeTopicExchange RabbitMQ 安装 rabbitmq 首先确保自己已经安装好了 docker 是 docker 拉取镜像文件:docker pull rabbitmq:3-management 拉取完毕,打…...
cefsharp(117.2.20)cef117.2.2最新体验版
一、下载nupkg https://www.nuget.org/packages/CefSharp.WinForms/ https://www.nuget.org/packages/CefSharp.Common/ https://www.nuget.org/packages/cef.redist.x64/ https://www.nuget.org/packages/cef.redist.x86/ 此版本暂时不支持H264。上一版本支持H264 cefsharp…...
layui在上传图片在前端处理图片压缩
有的人会遇到需要在前端代码处理图片压缩的问题,下面给大家分享怎么处理。 // 上传图片 var image_src var IsImgDealfalse; layui.upload.render({ elem: "#{tag}{id}", url: sessionStorage.getItem(httpUrlPrefix) /upload/uploadImage, // dataT…...
js 事件参考
事件参考 事件介绍 触发事件是为了通知代码可能影响代码执行的“有趣变化”。这些可能来自用户交互,例如使用鼠标或调整窗口大小,底层环境状态的变化(例如,低电量或来自操作系统的媒体事件)以及其他原因。 每个事件都由一个基于Event接口的…...
卷积网络的发展历史-LeNet
简介 LeNet是CNN结构的开山鼻祖,第一次定义了卷积神经网络的结构。 LeNet模型包含了多个卷积层和池化层,以及最后的全连接层用于分类。其中,每个卷积层都包含了一个卷积操作和一个非线性激活函数,用于提取输入图像的特征。池化层…...
(2023,GPT-4V,LLM,LMM,功能和应用)大型多模态模型的黎明:GPT-4V(ision) 的初步探索
The Dawn of LMMs: Preliminary Explorations with GPT-4V(ision) 公众号:EDPJ(添加 VX:CV_EDPJ 或直接进 Q 交流群:922230617 获取资料) 目录 0. 摘要 1. 简介 1.1 动机和概述 1.2 我们探索 GPT-4V 的方法 1.3…...
【C++设计模式之装饰模式:结构型】分析及示例
装饰模式(Decorator Pattern)是一种结构型设计模式,它允许在运行时动态地给一个对象添加额外的行为。 描述 装饰模式通过创建一个包装器(Wrapper)来包裹原始对象,并在原始对象的行为前后添加额外的功能。…...
绘制散点图、曲线图、折线图和环形图失败, 设置迭代次数和进度无法保存图片
错误❌ 分别input设置(我想知道微积分的力量) 设1个人,他有每天3种方案,每天进步千分之一,千分之一,十万分之一等到他们迭代 200,500,1000,2000,3000,5000,9000次 他们在图片什么位置画曲线图࿰…...
micro-ROS中对消息的内存管理
文章目录 1.背景2.答案2.1.基本类型及其数组,不需要2.1.序列类型(复合类型、复合序列类型),需要 3.内存申请方法3.1.手动申请(Manual allocation)3.1.工具辅助(micro-ROS utilities)…...
Springboot中使用拦截器、过滤器、监听器
一、Servlet、Filter(过滤器)、 Listener(监听器)、Interceptor(拦截器) Javaweb三大组件:servlet、Filter(过滤器)、 Listener(监听器) Spring…...
代码随想录二刷day45
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、力扣70. 爬楼梯二、力扣322. 零钱兑换三、力扣279. 完全平方数 前言 一、力扣70. 爬楼梯 class Solution {public int climbStairs(int n) {int[] dp new…...
泊车功能专题介绍 ———— AVP系统基础数据交互内容
文章目录 系统架构系统功能描述云端子系统车辆子系统场端子系统用户APP 工作流程基础数据交互内容AVP 系统基础数据交互服务车/用户 - 云基础数据交互内容车位查询工作流程技术要求数据交互要求 车位预约工作流程技术要求数据交互要求 取消预约工作流程技术要求数据交互要求 泊…...
蓝桥杯每日一题2023.10.6
题目描述 门牌制作 - 蓝桥云课 (lanqiao.cn) 题目分析 #include<bits/stdc.h> using namespace std; int ans; int main() {for(int i 1; i < 2020; i ){int x i;while(x){int a x % 10;if(a 2)ans ;x / 10;}}cout << ans;return 0; } 题目描述 既约分数…...
7、【Qlib】【主要组件】Data Layer:数据框架与使用
7、【主要组件】Data Layer:数据框架与使用 简介数据准备Qlib 格式数据Qlib 格式数据集自动更新日频率数据将 CSV 格式转换为 Qlib 格式股票池(市场)多股票模式 数据API数据检索特征过滤器 数据加载器QlibDataLoaderStaticDataLoaderInterfac…...
Kubernetes安装部署 1
本文主要描述kubernetes的安装部署,kubernetes的安装部署主要包括三个关键组件,其中,包括kubeadm、kubelet、kubectl,这三个组件的功能描述如下所示: Kubeadm 用于启动与管理kubernetes集群 Kubelet 运行在所有集群的…...
在VS Code中优雅地编辑csv文件
文章目录 Rainbow csv转表格CSV to Tablecsv2tableCSV to Markdown Table Edit csv 下面这些插件对csv/tsv/psv都有着不错的支持,这几种格式的主要区别是分隔符不同。 功能入口/使用方法Rainbow csv按列赋色右键菜单CSV to Table转为ASCII表格指令CSV to Markdown …...
LCR 128.库存管理 I
题目来源: leetcode题目,网址:LCR 128. 库存管理 I - 力扣(LeetCode) 解题思路: 数组可以分割成两段的升序连续子数组,找到两个子数组的开始元素并返回较小者即可。 解题代码: …...
eigen::Affine3d 转换
平移eigen::vector3d和四元数Eigen::Quaterniond 转 eigen::Affine3d Eigen::Vector3d t Eigen::Vector3d::Zero(); Eigen::Quaterniond q Eigen::Quaterniond ::Identity();Eigen::Affine3d affine3d t * q.toRotationMatrix(); Eigen::Matrix4d 转 eigen::Affine3d Eige…...
【Python从入门到进阶】38、selenium关于Chrome handless的基本使用
接上篇《37、selenium关于phantomjs的基本使用》 上一篇我们介绍了有关phantomjs的相关知识,但由于selenium已经放弃PhantomJS,本篇我们来学习Chrome的无头版浏览器Chrome Handless的使用。 一、Chrome Headless简介 Chrome Headless是一个无界面的浏览…...
给Python项目创建一个虚拟环境(enev)
给Python项目创建一个虚拟环境(enev) 为您的Python项目创建一个虚拟环境是一种良好的实践,可以隔离项目的依赖项,以确保它们不会干扰全局Python环境或其他项目。您可以使用venv模块来创建虚拟环境。以下是在Linux上创建虚拟环境的…...
【RK3588】YOLO V5在瑞芯微板子上部署问题记录汇总
YOLO V5训练模型部署到瑞芯微的板子上面,官方是有给出案例和转过详情的。并且也提供了Python版本的推理代码,以及C语言的代码。 但是,对于转换过程中的细节,哪些需要改?怎么改?如何改,和为什么…...
别人做的百度百科词条信息不全,如何更正自己的百度百科词条
很多人自己的百度百科词条是别人上传上去的,自己压根不知道,而且里面的信息内容要么不全,要么是有错漏的,但自己想要更正自己的百度百科词条又不知道如何更正,下面洛希爱做百科网和大家介绍一些百科经验知识。 首先百…...
[论文精读]U-Net: Convolutional Networks for BiomedicalImage Segmentation
论文原文:U-Net: Convolutional Networks for Biomedical Image Segmentation (arxiv.org) 英文是纯手打的!论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误,若有发现欢迎评论指正!文章偏向于笔…...
专业做熟女的网站/知名网络营销推广
一、基础知识点:1、所有请求最后加?pretty是表示格式化展示返回值,如果是手动查看信息建议带上,如果是程序去调用不建议带2、每个索引的主分片和副本分片的数量均是在建立索引的时候指定的,如果不指定,会按照elasticsearch.yml里…...
seo网站排名查询/百度ai助手入口
百度搜索url中存在很多参数,懂程序的SEO黑帽对此特别感兴趣。百度URL参数中,cl3 表示网页搜索,tn表示来源站点,word是关键词,ie表示编码方式,这里是utf-8编码. 想让百度没有广告吗? 想框架调用百度吗? 就来学学百度搜索参数吧。 百度URL命令中…...
如何将网站内容做chm/营销案例
类似问题答案郑州升达经贸管理学院计算机科学与技术专业2016年在福建理科高考录取最低分数线学校 地 区 专业 年份 批次 类型 分数 郑州升达经贸管理学院 福建 计算机科学与技术 2016 二批 理科 361 学校 地 区 专业 年份 批次 类型 分数 郑州升达经贸管理学院 福建 计算机科学…...
如何在淘宝开网站建设/seo网络排名优化
(1)js 中将json格式的字符串转换为json对象。 json eval(js字符串);//用eval函数for(var i0; i<json.length; i) { var idjson[i].id;//id为json字符串中有一个name为id的键值对。//TODO } (2) js 中将json对象转换为js…...
开发网站监控工具/草根seo博客
开发移动应用是一项非常复杂的工作,但作为开发者,我们就是来解决这个复杂的。状态机(state machine)是一个很好的工具,它可以帮助我们简化开发中的复杂问题。因此,在本篇基于Swift语言的Xcode教程中&#x…...
wordpress新建会员主页/常见的网络直接营销有哪些
什么是模板方法模式 所谓模板方法模式,其实很简单,可以从模板的角度考虑,就是一个对模板的应用,就好比老师出试卷,每个人的试卷都是一样的,即都是从老师的原版试卷复印来的,这个原版试卷就是一个模板,可每个人写在试卷上的答案都是不一样的,这就是模板方法模式,是不是很好理解…...