当前位置:   article > 正文

Servlet搭建博客系统

Servlet搭建博客系统

现在我们可以使用Servlet来搭建一个动态(前后端可以交互)的博客系统了(使用Hexo只能实现一个纯静态的网页,即只能在后台自己上传博客)。有一种"多年媳妇熬成婆"的感觉。

一、准备工作

首先创建好项目,引入相关依赖。具体过程在"Servlet的创建"中介绍了。

在这我们要引入servlet,mysql,jackson的相关依赖。

然后将相关web.xml配置好,将网站前端的代码也引入webapp中。

二、业务逻辑的实现

由于数据要进行持久化保存,在这我们使用mysql数据来存储。

首先我们先进行数据库的设计。

在这个博客系统中,会涉及到写博客和登陆的简单操作,因此需要创建两个表:用户表和博客表

因为数据库需要创建,当们换了一台机器的时候需要再一次创建,为了简便,可以将创建的sql语句保存下来,下次直接调用即可。

然后将上述代码复制到mysql的命令行执行即可。


封装数据库

为了简化后续对数据库的crud操作,在这对JDBC进行封装,后续代码就轻松许多了。

在这创建一个dao的文件夹,表示Data Access Object, 即数据访问对象,通过写一些类,然后通过类中的封装好的方法来间接访问数据库。

在dao文件下创建一个DBUtil的类,将连接数据库和释放资源操作进行封装。

  1. package dao;
  2. import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
  3. import javax.sql.DataSource;
  4. import java.sql.Connection;
  5. import java.sql.PreparedStatement;
  6. import java.sql.ResultSet;
  7. import java.sql.SQLException;
  8. public class DBUtil {
  9. //使用单例模式中的饿汉模式创建实例
  10. private static volatile DataSource dataSource = null;
  11. private static DataSource getDataSource(){
  12. //防止竞争太激烈
  13. if(dataSource == null){
  14. synchronized (DBUtil.class){
  15. if(dataSource == null){
  16. dataSource = new MysqlDataSource();
  17. ((MysqlDataSource)dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/blog_system?characterEncoding=utf8&useSSL=false");
  18. ((MysqlDataSource)dataSource).setUser("root");
  19. ((MysqlDataSource)dataSource).setPassword("root");
  20. }
  21. }
  22. }
  23. return dataSource;
  24. }
  25. //获取数据库连接
  26. public static Connection getConnection() {
  27. try {
  28. return getDataSource().getConnection();
  29. } catch (SQLException e) {
  30. e.printStackTrace();
  31. }
  32. return null;
  33. }
  34. //释放资源
  35. public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet){
  36. //一个一个释放,防止一个抛出异常,后续就不释放连接了
  37. if(resultSet != null){
  38. try {
  39. resultSet.close();
  40. } catch (SQLException e) {
  41. e.printStackTrace();
  42. }
  43. }
  44. if(statement != null){
  45. try {
  46. statement.close();
  47. } catch (SQLException e) {
  48. e.printStackTrace();
  49. }
  50. }
  51. if(connection != null){
  52. try {
  53. connection.close();
  54. } catch (SQLException e) {
  55. e.printStackTrace();
  56. }
  57. }
  58. }
  59. }

 创建实体类

创建实体类的Dao类,进一步封装数据库操作

  1. package dao;
  2. import java.net.ConnectException;
  3. import java.sql.Connection;
  4. import java.sql.PreparedStatement;
  5. import java.sql.ResultSet;
  6. import java.sql.SQLException;
  7. import java.util.ArrayList;
  8. import java.util.List;
  9. //通过这个类,封装针对 blog 表的增删改查操作
  10. public class BlogDao {
  11. //新增一个博客
  12. //使用try catch捕获异常,finally释放资源
  13. public void insert(Blog blog) {
  14. Connection connection = null;
  15. PreparedStatement statement = null;
  16. try{
  17. connection = DBUtil.getConnection();
  18. String sql = "insert into blog values(null, ?, ?, ?, now())";
  19. statement = connection.prepareStatement(sql);
  20. statement.setString(1, blog.getTitle());
  21. statement.setString(2, blog.getContent());
  22. statement.setInt(3, blog.getUserId());
  23. statement.executeUpdate();
  24. }catch (SQLException e){
  25. e.printStackTrace();
  26. }finally {
  27. DBUtil.close(connection, statement, null);
  28. }
  29. }
  30. public List<Blog> getBlogs(){
  31. Connection connection = null;
  32. PreparedStatement statement = null;
  33. ResultSet resultSet = null;
  34. List<Blog> blogs = new ArrayList<>();
  35. try{
  36. connection = DBUtil.getConnection();
  37. String sql = "select * from blog";
  38. statement = connection.prepareStatement(sql);
  39. resultSet = statement.executeQuery();
  40. while(resultSet.next()){
  41. Blog blog = new Blog();
  42. blog.setBlogId(resultSet.getInt("blogId"));
  43. blog.setTitle(resultSet.getString("title"));
  44. blog.setContent(resultSet.getString("content"));
  45. blog.setUserId(resultSet.getInt("userId"));
  46. blog.setPostTime(resultSet.getTimestamp("postTime"));
  47. blogs.add(blog);
  48. }
  49. }catch (SQLException e){
  50. e.printStackTrace();
  51. }finally {
  52. DBUtil.close(connection, statement, resultSet);
  53. }
  54. return blogs;
  55. }
  56. public Blog getBlog(){
  57. Connection connection = null;
  58. PreparedStatement statement = null;
  59. ResultSet resultSet = null;
  60. Blog blog = null;
  61. try{
  62. connection = DBUtil.getConnection();
  63. String sql = "select * from blog";
  64. statement = connection.prepareStatement(sql);
  65. resultSet = statement.executeQuery();
  66. if(resultSet.next()){
  67. blog = new Blog();
  68. blog.setBlogId(resultSet.getInt("blogId"));
  69. blog.setTitle(resultSet.getString("title"));
  70. blog.setContent(resultSet.getString("content"));
  71. blog.setUserId(resultSet.getInt("userId"));
  72. blog.setPostTime(resultSet.getTimestamp("postTime"));
  73. }
  74. } catch (SQLException e){
  75. e.printStackTrace();
  76. }finally {
  77. DBUtil.close(connection, statement, resultSet);
  78. }
  79. return blog;
  80. }
  81. //根据博客ID指定博客删除
  82. public void delete(int blogId){
  83. Connection connection = null;
  84. PreparedStatement statement = null;
  85. try{
  86. connection = DBUtil.getConnection();
  87. String sql = "delete from blog where blogId = ?";
  88. statement = connection.prepareStatement(sql);
  89. statement.setInt(1, blogId);
  90. statement.executeUpdate();
  91. }catch (SQLException e){
  92. e.printStackTrace();
  93. }finally {
  94. DBUtil.close(connection, statement, null);
  95. }
  96. }
  97. }

后面再处理数据库操作的时候就可以直接使用这些代码了。

通过观察这些代码,我们会发现非常多重复的东西,后期通过学习了一些高级的框架后就能将代码的结构再优化一下.

同理,UserDao类也是如此完成。

至此,数据方面的东西我们都已经写完了,后续只需要调用即可。

接来下就可以进行一些前后端交互逻辑的实现了。

在这以功能点为维度进行展开,针对每个功能点,进行"设计前后端交互接口","开发后端代码","开发前端代码","调试"

实现博客列表页

让博客列表页能够加载博客列表。

大致流程如下:

  1. 前端发起一个HTTP请求,向后端所要博客列表数据

  2. 后端收到请求之后查询数据库获取数据库中的 博客列表,将数据返回给前端

  3. 前端拿到响应后,根据内容构造出html片段,并显示。

在写代码前,需要进行约定,即规范双方发什么样的数据,发什么请求,如何解析数据等。

假设双方约定按照如下格式发送数据。

后端代码:

  1. @WebServlet("/html/blog")
  2. public class BlogServlet extends HttpServlet {
  3. private ObjectMapper objectMapper = new ObjectMapper();
  4. @Override
  5. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  6. //从数据库中获取数据
  7. BlogDao blogDao = new BlogDao();
  8. List<Blog> blogs = blogDao.getBlogs();
  9. //将数组转换成对象字符串
  10. String respJson = objectMapper.writeValueAsString(blogs);
  11. resp.setContentType("application/json; charset=utf8");
  12. //写回到响应中
  13. resp.getWriter().write(respJson);
  14. }
  15. }

前端代码:

让页面通过js的ajax的方式发起http请求。

  1. function getBlogs(){
  2. $.ajax({
  3. type: 'get',
  4. url: 'blog',
  5. success: function(body){
  6. let container = document.querySelector('.container-right');
  7. for(let blog of body){
  8. let blogDiv = document.createElement('div');
  9. blogDiv.className='blog';
  10. //构造标题
  11. let titleDiv = document.createElement('div');
  12. titleDiv.className = 'title';
  13. titleDiv.innerHTML = blog.title;
  14. blogDiv.appendChild(titleDiv);
  15. //构造发布时间
  16. let dateDiv = document.createElement('div');
  17. dateDiv.className = 'date';
  18. dateDiv.innerHTML = blog.postTime;
  19. blogDiv.appendChild(dateDiv);
  20. //构造博客摘要
  21. let descDiv = document.createElement('div');
  22. descDiv.className = 'desc';
  23. descDiv.innerHTML = blog.content;
  24. blogDiv.appendChild(descDiv);
  25. //构造查看全文按钮
  26. let a = document.createElement('a');
  27. a.href = 'blog_content.html?blogId=' + blog.blogId;
  28. a.innerHTML = '查看全文 &gt;&gt';
  29. blogDiv.appendChild(a);
  30. container.appendChild(blogDiv);
  31. }
  32. }
  33. });
  34. }
  35. getBlogs();

由于数据库中的数据为标明啥数据,我们还需要手动指定.

效果如下:

博客详情页

1.约定前后端交互接口

和前面博客列表页类似,不同的是我们只需要在请求中带上blogId的属性,以及后端代码的稍作修改即可。

后端代码:

我们可以通过对之前的后端代码稍作修改,就可以完成上述操作。

前端代码:

  1. <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
  2. <script>
  3. function getBlog(){
  4. $.ajax({
  5. type: 'get',
  6. url: 'blog'+location.search,
  7. success: function(body){
  8. let h3 = document.querySelector('.container-right h3');
  9. h3.innerHTML = body.title;
  10. let dateDiv = document.querySelector('.container-right .date');
  11. dateDiv.innerHTML = body.postTime;
  12. editormd.markdownToHTML('content', { markdown: body.content });
  13. }
  14. });
  15. }
  16. getBlog();
  17. </script>

登录功能

1.约定前后端交互接口

此处提交用户名和密码,可以使用form也可以使用ajax。在这使用form的形式(更简单一些)。

2.后端代码

  1. @WebServlet("/html/login")
  2. public class LoginServlet extends HttpServlet {
  3. @Override
  4. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  5. //首先获取请求中的查询字符串中的用户名和密码
  6. //需要手动告诉Servlet,使用什么样的编码方式来读取请求
  7. req.setCharacterEncoding("utf8");
  8. String username = req.getParameter("username");
  9. String password = req.getParameter("password");
  10. if(username == null || password == null || username.equals("") || password.equals("")){
  11. //用户提交的数据非法
  12. resp.setContentType("text/html; charset=utf8");
  13. resp.getWriter().write("当前的用户名或密码非法");
  14. return;
  15. }
  16. //再去数据库中比对
  17. UserDao userDao = new UserDao();
  18. User user = userDao.getUserByName(username);
  19. if(user == null){
  20. resp.setContentType("text/html; charset=utf8");
  21. resp.getWriter().write("当前的用户名或密码错误");
  22. return;
  23. }
  24. if(!user.getPassword().equals(password)){
  25. resp.setContentType("text/html; charset=utf8");
  26. resp.getWriter().write("当前的用户名或密码错误");
  27. }
  28. //创建会话关系
  29. HttpSession session = req.getSession(true);
  30. session.setAttribute("user", user);
  31. //发送重定向网页,跳转到列表页
  32. resp.sendRedirect("blog_list.html");
  33. }
  34. }

注意:

由于请求中可能带有中文字符,我们需要手动指定一下字符集utf8,防止读取请求的时候出现乱码。

使用一个会话,让服务器保存当前用户的一些数据。

3.前端代码

  1. <form action="login" method="post">
  2. <div class="row">
  3. <span>用户名</span>
  4. <input type="text" id="username">
  5. </div>
  6. <div class="row">
  7. <span>密码</span>
  8. <input type="password" id="password">
  9. </div>
  10. <div class="row">
  11. <input type="submit" id="submit" value="登录">
  12. </div>
  13. </form>

检查用户登录状态

强制用户登录,当用户直接去访问博客列表页或者其他页面的时候,如果是未登录过的状态,会强制跳转到登录页要求用户登录。

如何实现?

在其他页面中的前端代码,写一个ajax请求,通过这个请求,访问服务器来获取当前的登录状态。

如果当前未登录,则跳转登录页面,如果已经登录,就不进行操作。

1.约定前后端交互接口

2.后端代码

  1. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  2. HttpSession session = req.getSession(false);
  3. if(session == null){
  4. resp.setStatus(403);
  5. return;
  6. }
  7. User user = (User) session.getAttribute("user");
  8. if(user == null){
  9. resp.setStatus(403);
  10. return;
  11. }
  12. resp.setStatus(200);
  13. }

3.前端代码

  1. <div class="login-container">
  2. <!-- 登录对话框 -->
  3. <div class="login-dialog">
  4. <h3>登录</h3>
  5. <!-- 使用 form 包裹一下下列内容, 便于后续给服务器提交数据 -->
  6. <form action="login" method="post">
  7. <div class="row">
  8. <span>用户名</span>
  9. <input type="text" id="username" name="username">
  10. </div>
  11. <div class="row">
  12. <span>密码</span>
  13. <input type="password" id="password" name="password">
  14. </div>
  15. <div class="row">
  16. <input type="submit" id="submit" value="登录">
  17. </div>
  18. </form>
  19. </div>
  20. </div>

form表单中的action为请求中的url,method为请求中的方法类型,id属性时针对html获取元素,name属性则是针对form表单构造http请求。

显示用户信息

当我们进入博客列表页的时候,用户显示的内容应该是登录用户的信息,一旦我们进入到博客详情页的时候,显示的就应该是该博客作者的信息。

首先是博客列表页

1.约定前后端交互接口

2.后端代码

由于我们进入博客列表页,首先会去检查是否已经登录过,如果登录过就可以拿到用户的数据,此时我们可以将用户数据返回给前端,然后修改用户姓名的属性。此时我们也只要在前面代码的基础上稍加修改。

  1. //防止将密码传输回去
  2. user.setPassword("");
  3. String respJson = objectMapper.writeValueAsString(user);
  4. resp.setContentType("application/json; charset=utf8");
  5. resp.getWriter().write(respJson);

3.前端代码

前端代码也只需要在之前的基础上稍加修改就行

  1. function checkLogin(){
  2. $.ajax({
  3. type: 'get',
  4. url: 'login',
  5. success: function(body){
  6. let h3 = document.querySelector('h3');
  7. h3.innerHTML = body.username;
  8. },
  9. error: function(body){
  10. location.assign('blog_login.html');
  11. }
  12. }) ;
  13. }

然后是博客详情页

详情页这里显示的是当前文章的作者信息,由于我们知道blogId,就可以查询到userId然后就能查询到user的信息。最后再将信息显示出来即可。

1.约定前后端交互接口

2.后端代码

  1. public class UserServlet extends HttpServlet {
  2. private ObjectMapper objectMapper = new ObjectMapper();
  3. @Override
  4. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  5. String blogId = req.getParameter("blogId");
  6. //如果用户直接访问博客详情页
  7. if(blogId == null){
  8. //从session中拿到user对象
  9. HttpSession session = req.getSession(false);
  10. if(session == null){
  11. //为了方便后续统一处理,返回空对象
  12. User user = new User();
  13. String respJson = objectMapper.writeValueAsString(user);
  14. resp.setContentType("application/json; charset=utf8");
  15. resp.getWriter().write(respJson);
  16. return;
  17. }
  18. User user = (User)session.getAttribute("user");
  19. String respJson = objectMapper.writeValueAsString(user);
  20. resp.setContentType("application/json; charset=utf8");
  21. resp.getWriter().write(respJson);
  22. }else{
  23. BlogDao blogDao = new BlogDao();
  24. Blog blog = blogDao.getBlog(Integer.parseInt(blogId));
  25. if(blog == null){
  26. User user = new User();
  27. String respJson = objectMapper.writeValueAsString(user);
  28. resp.setContentType("application/json; charset=utf8");
  29. resp.getWriter().write(respJson);
  30. return;
  31. }
  32. UserDao userDao = new UserDao();
  33. User user = userDao.getUserById(blog.getUserId());
  34. if(user == null){
  35. String respJson = objectMapper.writeValueAsString(user);
  36. resp.setContentType("application/json; charset=utf8");
  37. resp.getWriter().write(respJson);
  38. return;
  39. }
  40. String respJson = objectMapper.writeValueAsString(user);
  41. resp.setContentType("application/json; charset=utf8");
  42. resp.getWriter().write(respJson);
  43. }
  44. }
  45. }

3.前端代码

前端代码前面博客列表页类似

  1. function getUser(){
  2. $.ajax({
  3. type:'get',
  4. url:'user'+location.search,
  5. success: function(body){
  6. let h3 = document.querySelector('.card h3');
  7. h3.innerHTML = body.username;
  8. }
  9. });
  10. }
  11. getUser();

用户退出功能

当用户点击注销的时候,即点击了a标签,此时会触发一个get请求,服务器收到这个get请求,就可以把当前用户会话中的user对象删除。即通过代码删除之前的session对象(最好是删除映射关系,但是Servlet没有提供相应简单的API).

1.约定前后端交互接口

2.后端代码

  1. @WebServlet("/html/logout")
  2. public class LogoutServlet extends HttpServlet {
  3. @Override
  4. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  5. HttpSession session = req.getSession(false);
  6. if(session == null){
  7. resp.sendRedirect("blog_login.html");
  8. return;
  9. }
  10. session.removeAttribute("user");
  11. resp.sendRedirect("blog_login.html");
  12. }
  13. }

3.前端代码

只需要给a元素写个href即可。

发布博客

在写博客页中,用户可以写博客标题,正文,然后点击发布即可上传数据。

1.约定前后端交互接口

2.后端代码

  1. @Override
  2. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  3. HttpSession session = req.getSession(false);
  4. if(session == null){
  5. resp.setContentType("text/html; charset=utf8");
  6. resp.getWriter().write("用户未登录! 无法发布博客!");
  7. return;
  8. }
  9. User user = (User)session.getAttribute("user");
  10. if (user == null) {
  11. resp.setContentType("text/html; charset=utf8");
  12. resp.getWriter().write("用户未登录! 无法发布博客!");
  13. return;
  14. }
  15. resp.setCharacterEncoding("utf8");
  16. String title = req.getParameter("title");
  17. String content = req.getParameter("content");
  18. if (title == null || content == null || "".equals(title) || "".equals(content)) {
  19. resp.setContentType("text/html; charset=utf8");
  20. resp.getWriter().write("标题或者正文为空");
  21. return;
  22. }
  23. Blog blog = new Blog();
  24. blog.setTitle(title);
  25. blog.setContent(content);
  26. blog.setUserId(user.getUserId());
  27. BlogDao blogDao = new BlogDao();
  28. blogDao.insert(blog);
  29. resp.sendRedirect("blog_list.html");
  30. }

3.前端代码

  1. <div class="blog-edit-container">
  2. <form action="blog" method="post">
  3. <!-- 标题编辑区 -->
  4. <div class="title">
  5. <input type="text" id="title-input" name="title">
  6. <input type="submit" id="submit">
  7. </div>
  8. <!-- 博客编辑器 -->
  9. <!-- 把 md 编辑器放到这个 div 中 -->
  10. <div id="editor">
  11. <textarea name="content" style="display: none;"></textarea>
  12. </div>
  13. </form>
  14. </div>

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/675459
推荐阅读
相关标签
  

闽ICP备14008679号