Java EE 博客系统(Servlet版)

文章目录

  • 1. 基本情况
  • 2. 准备工作
  • 3. 博客列表页
  • 4. 博客详情页
  • 5. 实现登录
  • 6. 强制要求登录
  • 7. 显示用户信息
  • 8. 退出登录
  • 9. 发布博客
  • 10. 如果程序出现问题怎么办?

1. 基本情况

这里的博客系统主要是四个界面

  1. 博客列表页
    显示出当前网站上都有哪些博客
  2. 博客详情页
    点击列表上的某个博客,就能进入对应详情页(显示出博客的具体内容)
  3. 博客编辑页
    让用户输入博客内容,并且发送到服务器
  4. 登录页

这里主要来写后端的代码,前端代码已经准备就绪,直接导入即可


任务:
基于上述的页面,编写服务器/前后端交互代码

通过这些代码,完成博客系统,完整的功能

  1. 实现博客列表页
    让页面从服务器拿到博客数据(数据库)
  2. 实现博客详情页
    点击博客详情的时候,可以从服务器拿到博客的完整数据
  3. 实现登录功能
  4. 实现强制要求登录
    (当前处于未登录的情况下,其他的界面,博客列表、博客详情、博客编辑…就会强制跳转到登录页)
    要求用户登录后才能使用
  5. 实现显示用户信息
    从服务器获取到
    博客列表页,拿到的是当前登录的用户的信息
    博客详情页,拿到的是文章作者的信息
  6. 实现退出登录
  7. 发布博客
    博客编辑页,输入文章标题和内容之后,点击发布,就能把这个数据上传到 服务器上并保存

在这些功能搞定之后,一个功能相对完整的博客网站,就初具规模了

2. 准备工作

  1. 创建项目,引入依赖,把当前的前端界面引入到项目中

  1. 数据库设计

设计好对应的表结构,并且把数据库相关代码,也进行封装

  • 找到实体
    博客(blog 表)
    用户(user 表)
  • 确认实体之间的关系
    一对多
    一个博客,只属于一个用户
    一个用户,可以发布多个博客
    这样就应该在博客列表中,引入一个 userId 这样的属性

blog (blogId, title, content, postTime, userId)
user (userId, username, password)
在这里插入图片描述
在这里插入图片描述

接下来,把数据库的代码进行一些封装
在这里插入图片描述

在进行网站开发的工程中,一种常见的代码组织结构,MVC
M model:操作数据的代码
V view:操作/构造界面的代码
C controler:业务逻辑,处理前端请求

由于这套组织结构比较古老,在现在写的过程中,也不会完全遵守

在这里插入图片描述
DBUtil 这个类,封装数据建立连接的操作

在这里插入图片描述
当前这个懒汉模式是不安全的
当前 servlet 本身就是在多线程环境下执行的
tomcat 收到多个请求的时候,就会使用 多线程 的方式,执行不同的 servlet 代码
这里就可能有现成不安全的问题

因此要加锁
在这里插入图片描述

package model;import com.mysql.jdbc.Connection;
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;import javax.sql.DataSource;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;// 通过这个类, 封装数据库建立连接的操作.
// 由于接下来代码中, 有多个 Servlet 都需要使用数据库. 就需要有一个单独的地方来把 DataSource 这里的操作进行封装.
// 而不能只是放到某个 Servlet 的 init 中了.
// 此处可以使用 单例模式 来表示 dataSource
public class DBUtil {private volatile static 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/servlet_blog_system?characterEncoding=utf8&useSSL=false");((MysqlDataSource) dataSource).setUser("root");((MysqlDataSource) dataSource).setPassword("123456");}}}return dataSource;}public static Connection getConnection() throws SQLException {return (Connection) getDataSource().getConnection();}public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet) {if (resultSet != null) {try {resultSet.close();} catch (SQLException e) {throw new RuntimeException(e);}}if (statement != null) {try {statement.close();} catch (SQLException e) {throw new RuntimeException(e);}}if (connection != null) {try {connection.close();} catch (SQLException e) {throw new RuntimeException(e);}}}
}

DBUtil 完成对于数据库建立连接和关闭连接的实现


  1. 创建实体类

大部分表,都需要搞一个专门的类来表示
表里的一条数据,就会对应到这个类的一个对象

这样就可以把数据库中的数据和代码联系起来了


  1. 针对博客表和用户表操作

这里再创建两个类,来完成准对博客表和用户表的增删改查操作

这两个类,叫做BlogDao 和 UserDao

DAO(Data Access Object):数据访问对象
通过这两个类的对象,来完成针对数据库表的操作

写一个复杂一些的代码,往往需要先理清楚思路
相比于细节来说,理清思路是更复杂的
(为了实现这个代码,要写哪些类,有哪些方法)
在这里插入图片描述
在这里插入图片描述

package model;import com.mysql.jdbc.Connection;
import com.mysql.jdbc.JDBC4PreparedStatement;import javax.xml.stream.events.DTD;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;// 通过 BlogDao 来完成针对 blog 表的操作
public class BlogDao {// 1. 新增操作 (提交博客就会用到)public void insert(Blog blog) {Connection connection = null;PreparedStatement statement = null;try {//1. 建立连接connection = DBUtil.getConnection();//2. 构造 SQLString 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. 执行 SQLstatement.executeUpdate();} catch (SQLException e) {e.printStackTrace();} finally {DBUtil.close(connection, statement, null);}}// 2. 查询博客列表 (博客列表页)//    把数据库里所有的博客都拿到.public List<Blog> getBlogs() {List<Blog> blogList = 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 做一个简单截断. 这个截断长度 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"));blogList.add(blog);}} catch (SQLException e) {e.printStackTrace();}finally {DBUtil.close(connection, statement, resultSet);}return blogList;}// 3. 根据博客 id 查询指定的博客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 作为主键, 是唯一的.// 查询结果非 0 即 1 , 不需要使用 while 来进行遍历if (resultSet.next()) {Blog blog = new Blog();blog.setBlogId(resultSet.getInt("bligId"));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;}// 4. 根据博客 id, 删除博客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 e) {e.printStackTrace();}finally {DBUtil.close(connection, statement, null);}}
}

这里的 JDBC 代码,大同小异
因此,后面就会有一些数据库的框架
(MyBatis,MyBatisPlus,JPA…)
这里封装了 JDBC 的代码
这些框架 本质上 就是帮我们自动生成 JDBC 的代码

3. 博客列表页

在博客列表页加载的时候,通过 ajax 给服务器发起请求
从服务器(数据库)拿到博客列表数据,并求显示到页面上


  1. 约定前后端交互接口
    请求
    GET /blog
    响应
    HTTP/1.1 200 OK
    Content-Type: applicantion/json
    在这里插入图片描述

  1. 让浏览器给服务器发起请求
    在这里插入图片描述

  1. 服务器处理上述请求,返回响应数据(查询数据库)
    在这里插入图片描述由于是 list,Jackson 就会把结果转成 数组
    每个元素又是一个 Blog 对象

  1. 让前端代码,处理上述数据
    构造成 html 片段,显示在页面上
    形如:
    在这里插入图片描述

这里构造页面的过程,还是之前的 api
(1)querySelector:获取页面已有的元素
(2)createElement:创建新的元素
(3).innerHtml:设置元素里的内容
(4).className:设置元素的 class 属性
(5)appendChild:把这个元素添加到另一个元素的末尾

html 中
显示 >:
需要使用转义字符 &gt;
显示 <:
也需要使用转义字符 &lt;
在这里插入图片描述

a 标签在 html 中称为“超链接”
点击之后能够跳转到新的页面
在这里插入图片描述

这个代码,和前面的 jdbc 的感觉类似
在这里插入图片描述

对于前端页面来说
生成页面的方式其实有很多种
此处使用的比较朴素的方式(基于 dom api 的方式)
dom api 就是属于是浏览器提供的标准的 api(不属于任何的第三方框架和库)

定位就类似于 jdbc api

前端也有一些框架和库,是把 dom api 又进行了封装,用起来更简单一些


这里有一个问题,就是返回的时间,是时间戳
在这里插入图片描述

在这里,Jackson 在进行主要的工作
(1)Jackson 发现 blogs 是一个 list,于是就会循环遍历里面的每个元素
(2)针对每个元素(Blog 对象),通过反射的方式,获取到都是哪些属性,属性的名字,属性的值
在获取属性值的时候,就是通过调取 get 方法

在这里插入图片描述
这个时候,我们就要对代码进行改变
修改 getPostTime 方法,让其直接返回一个“格式化时间”
在这里插入图片描述
制定了一个格式化字符串
描述了当前时间日期具体的格式
各种语言,表示格式化时间都有这样的字符串
但是不同语言,表示的含义是不同的

在这里插入图片描述


此时还有一个问题,就是希望新加入的博客在上面,以前写的博客在下面

这个时候,该如何做呢?
返回 list,把 list 先逆序一下?

此处的结果顺序,是从数据库里查询出来的
一个 sql 如果不加 order by,结果的顺序是不可预期的

此处科学的做法,应该是加上 order by,时间逆序
在这里插入图片描述

4. 博客详情页

点击查看全文,就可以跳转到 带有不同 blogId 的 query string
后续在博客详情页中,就可以给服务器发起 ajzx 请求,根据这里的 blogId ,查询数据库中,博客的具体内容再返回
前端还是把得到的数据给构造到页面上
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


  1. 约定前后端交互接口
    请求
    GET /blog?blogId=1
    请求
    HTTP/1.1 200 OK
    Content-Type: applivation/json
    在这里插入图片描述

这个请求,是希望在博客详情页的时候,通过 ajax 发给服务器


  1. 让前端代码,通过 ajax 发起请求

此处有个问题,发起 ajax 请求的时候要带有 blogId
blogId 当前处于 博客详情页 url 中
在这里插入图片描述
这里我们可以通过 location.search 方式拿到 页面 url 中的 query string
在这里插入图片描述


  1. 让服务器处理这个请求

这里依然使用 servlet 处理,一个路径对应到一个 servle

当前是使用一个 servlet 处理两种请求
博客列表页,不带 query string
博客详情页,带有 query string
就可以根据 query string 是否存在的情况,来区分是哪种请求
分别返回不同的数据即可

使用两个 servlet 处理这里的两个请求,也可以
就约定成不同的路径即可
使用一个 servlet 也可以,这里没有一个明确的标准

在这里插入图片描述


  1. 前端拿到响应之后,把响应数据,构成页面的 html 片段
    在这里插入图片描述
    在这里插入图片描述
    由于显影中,带有 Content-Type:application/json
    jquery 自动帮我们把字符串转成 js 对象了
    直接通过 . 的方式就能访问属性了
    在这里插入图片描述
    在博客列表页,需要循环遍历,构造的页面内容页更复杂
    此处就更简单一些,只需要设置三个内容即可

在这里插入图片描述
写完代码之后,再点击某个博客,就可以看到,有的博客里面的详情页,还是之前的旧的内容

这个问题是浏览器缓存引起的

浏览器加载页面的时候,是通过网络获取的(网络速度比较慢)
浏览器有时候就会把已经加载过的界面,在本地硬盘保存一份,后续再访问同一个界面
就不需要通过网络加载,直接加载本地硬盘的这一份

默认认为 html 出现修改的概率比较低
但是也不是完全不会修改

如何克服缓存的干扰,前端有专业的解决方案
我们在这里可以直接使用 ctrl + F5,强制刷新界面


当前博客详情页,虽然能用出博客的正文,但是显示的是正文的 md 原始数据
作为博客网站,正确的做法应该是显示出 md 渲染后的效果

此处仍然 通过 第三方库 (editor.md)
在这里插入图片描述

这个是 editormd 这个库给的一个全局变量
把依赖正确引入了,这个变量就能直接使用

这个方法的效果,就是把 blog.content 这里的 md 的原始数据,渲染成 html,放到 id 为 content 的 div 中
在这里插入图片描述
一个 html 标签,可以有很多的属性
class 属性,往往是用来和 css 样式配合的
id 属性,则是一个“身份标识”要求一个界面中,id 必须是唯一的
在这里插入图片描述

5. 实现登录

在这里插入图片描述

在登录界面,在输入框中填写 用户名和密码
点击登录,就会给服务器发起 http 请求(这里使用 form)

服务器处理登录请求,读取用户名密码,在数据库查询、匹配
如果正确,就登录成功,创建会话,跳转到博客列表页

由于这里,登录成功,直接进行重定向跳转,就不要浏览器额外写代码处理,直接浏览器自动跳转


  1. 约定前后段交换接口
    请求
    POST /login
    Content-Type: application/x-www-form-urlencoded
    username=zhangsan&password-123
    在这里插入图片描述

form 表单,提交成功,可以直接使用 302 重定义跳转
如果使用 ajax,ajax 处理响应就需要写代码来完成跳转(不是浏览器自动完成了)


  1. 让前端发起请求
    form

username=zhangsan&password-123
这里的 input 标签,name 属性就是这里 body 中的 key

什么时候一个元素要有 id,什么时候没有呢?
看个人的需要,灵活处理

在这里插入图片描述


  1. 让服务器处理请求,并返回响应
    在这里插入图片描述

在这里插入图片描述

package servlet;import model.User;
import model.UserDao;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.time.temporal.Temporal;@WebServlet("/login")
public class LoginServlet extends HttpServlet {@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 1. 读取参数中的用户名和密码req.setCharacterEncoding("utf8");String username = req.getParameter("username");String password = req.getParameter("password");//    验证一下参数, 看下是否合理.if (username == null || username.length() == 0 || password == null || password.length() == 0) {resp.setContentType("text/html; charset=utf8");resp.getWriter().write("您输入的用户名或者密码为空!");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.setAttribute("user", user);// 4. 跳转到主页了.resp.sendRedirect("blog_list.html");}
}

6. 强制要求登录

在博客列表页、详情页、编辑页,判断当前用户是否已经登录
如果未登录,则强制跳转到登录页(要求用户必须登录后才能使用)

在上述的页面中,在页面加载中,给服务器发起 ajax
从服务器获取一下当前的登录状态


  1. 约定前后端交互接口

GET /login

登录成功:HTTP/1.1 200
登录失败:HTTP/1.1 403

也可以通过其他的方式,比如都是返回 200,但是在 body 中给不同的结果


  1. 让前端代码发起这个请求,并响应
    在这里插入图片描述

一个页面,触发的 ajax是可以有多个的
一个页面通常都会触发多个 ajax
这些 ajax 之间是“并发执行”这样的效果

js 中是没有“多线程”这样的机制
而 ajax 是一种特殊的情况,能够起到类似于“多线程”的效果

当页面中发起两个或者多个 ajax 的时候,这些 ajax 请求就相当于并发的发送出去
彼此之间不会相互干预
(不是 串行 执行,不是执行完一个 ajax,得到响应之后,再执行下一个)

同时发出去多个请求,谁的响应先回来了,就先执行谁的回调函数


  1. 让服务器处理上述请求

在这里插入图片描述
当前虽然等登录服了,一旦重启服务器,仍然会被判定为未登录状态

登录状态是通过服务器这里的 session 来存储的
session 这是服务器内存中的类似于 hashmap 这样的结构
一旦服务器重启了,hashmap 里面原有的内容就没了

但是这种设定,并不科学,相比支架,我们还有更好的解决方案

  1. 我们把会话进行持久化保存(文件,数据库,redis…)
  2. 使用令牌的方式保存(把用户信息,在服务器里面,还是保存在浏览器这里,相当于服务器没有在内存中存贮内存中的用户的身份)

这里我们需要让多个界面都有这样的机制,这里就可以把一些公共的代码,单独拿出来
放到某个 .js 文件中
通过 html 中的 script 标签,来引用这样的文件内容
此时,就可以在 html 中调用对应的公共代码了
在这里插入图片描述
在这里插入图片描述

7. 显示用户信息

博客列表页:显示的是当前登录的用户的信息

博客详情页:显示的是当前文章的作者信息

在页面加载的时候,给服务器发起 ajax 请求
在服务器返回对应的用户数据
根据发起请求不同的界面,服务器返回不同的信息即可


  1. 约定前后端交换接口

博客列表页,获取当前登录的用户信息
请求:
GET /userInfo
响应:
HTTP/1.1 200 OK
application/json
在这里插入图片描述
博客详情页,获取当前文章的作者信息
请求:
GET /authorInfo?blogId=1
响应:
HTTP/1.1 200 OK
application/json
在这里插入图片描述


  1. 先让前端代码,发起这样的请求

博客列表页:
在这里插入图片描述
博客详情页:
在这里插入图片描述


  1. 编写服务器代码,来处理上述请求

博客列表页:
在这里插入图片描述
博客详情页:
在这里插入图片描述
这里是通过两步 sql 分别查询的
先查 blog 表里面的 blog 对象
再查 user 表

其实也可以一步 sql 搞定
比如:可以使用联合查询,把 blog user 进行笛卡尔积,找出匹配的结果
也可以使用子查询,把两个 sql 合并在一个完成

当然,一步完成是要付出代价的
联合查询来说,笛卡尔积,对于数据库是一个不小的开销
子查询来说,这样的 sql 可读性可能比较差


  1. 在前端代码中,处理响应
    把响应中的数据,给写到刚才页面的对应的位置上

在这里插入图片描述
在这里插入图片描述
以下这个请求从服务器拿到了当前用户的信息
进一步的就把用户的名字显示到页面上了
在这里插入图片描述
在这里插入图片描述

8. 退出登录

博客列表、博客详情、博客编辑 的导航栏中,都有一个“注销”按钮

在这里插入图片描述
注销 这个东西是 a 标签
可以有一个 href 属性
点击就会触发一个 http 请求
并且可能会引起浏览器跳转到另一个页面

让用户点击“注销”的时候,就能够触发一个 HTTP 请求(GET 请求)

服务器收到这个 GET 请求的时候,就会把会话里的 user 这个 Attribute 给删了
由于在判断用户是否是登录状态的逻辑中,需要同时验证,会话存在,且 这里的 user Attribute 也存在
只要破坏一个,就可以是登录状态发生改变了


为什么不直接删除 session 本身?
主要因为,sevlet 没有提供,删除 session 的方法
虽然有间接的方式(session 可以摄者国企时间,设置一个非常短的过期时间),也可以起到删除的效果,但是不太优雅

session 提供了 removeAttribute 这样的方法,可以把 user 这个 Attribute 给删了


  1. 约定前后端交互接口
    请求:
    GET /logout
    响应:
    直接重定向到登录页
    HTTP/1.1 302
    Location: login.html

  1. 编写前端代码,发送请求
    不用写 ajax,直接就给 a 标签设置 href 属性即可

在这里插入图片描述


  1. 编写后端代码,处理这个请求,完成退出登录的操作

在这里插入图片描述

9. 发布博客

当点击提交的时候,就需要构造 http 请求,把此时的页面中的标题和正文都传输到服务器这边
服务器把这个数据存入数据库即可

此处这里的 http 请求,可以使用 ajax,也可以使用 form
这种填写输入框,提交数据的场景,使用 form 会更方便


  1. 约定前后端交互接口

请求:
POST blog
Content-Type: x-www-form-urlencoded

title=这是标题&content=这是正文
(上面的中文 都是要 urlencode,form 表单直接就能完成这个操作)

响应:
HTTP/1.1 302
Location: blog_list.html


  1. 编写前端代码,构造请求

标题本身就是自己写的一个 input,给他加上 name 属性,很容易
但是博客正文,是由 editor md 构成的一个编辑器,这里如何添加 name 属性呢?

editor md 的开发者们,也考虑到了这种情况
在官方文档中也有这样的例子

在这里插入图片描述
这个 div 就是 editor.md 的编辑器的容器
在这个 div 里,搞一个隐藏的 textarea 标签(多行编辑框,把 name 属性加到 textarea 属性上)
并且在初始化 editormd 对象的时候,加上一个 对应的属性即可
在这里插入图片描述
name=“content”:是 form 中键值对的 key
“display: none;” :让这个 textarea 隐藏起来
在这里插入图片描述
这个代码是初始化 editormd 的编辑器的代码
在这里插入图片描述
在这里插入图片描述


  1. 编写服务器代码,处理刚才的请求
    在这里插入图片描述
    在这里插入图片描述

10. 如果程序出现问题怎么办?

这个时候,我们可以使用抓包来实现

抓包的目的,是为了先确定,在点击刷新这个过程中,浏览器和服务器之间有几次 http 交互
每一次交互,请求是什么样的,响应是什么样的

解下来就需要观察,抓包结果中,这几个 http 交互的请求是否都符合预期

  1. 先开请求发没有发
    如果你的浏览器都没发这个请求,说明前端代码有问题
    就需要检查你的前端带啊,ajax 是怎么写的

  2. 再看请求中各个部分是否正确,是否符合约定的接口要求
    如果请求不符合预期,说明还是前端代码有问题,检查 ajax 代码

  3. 如果请求没问题,需要再检查响应数据
    如果请求正常,相应数据不符合预期
    此时就下检查后端代码,是否你的后端代码
    没能正确的完成数据库查询操作等
    (尤其要注意服务器的控制套是否出现异常信息)

  4. 如果请求和响应都没有问题,说明服务器已经返回正确的数据了,但是页面没有把这些数据正确的显示出来
    此时还是要检查前端代码,尤其是检查前端处理响应的这里的逻辑
    (尤其要注意,浏览器控制台是否有报错)

当确定范围之后,进一步排查问题,还需要在代码中,加入更多的日志
System.out.println
console.log

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

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

相关文章

浅析ARMv8体系结构:A64指令集

文章目录 A64指令编码格式加载与存储指令寻址模式变基模式前变基模式后变基模式 PC相对地址模式 伪指令加载与存储指令的变种不同位宽的加载与存储指令多字节内存加载和存储指令基地址偏移量模式前变基模式后变基模式 跳转指令返回指令比较并跳转指令 其它指令内存独占访问指令…

面试题:MySQL误删表数据,如何快速恢复丢失的数据?

相信后端研发的同学在开发过程经常会遇到产品临时修改线上数据的需求&#xff0c;如果手法很稳那么很庆幸可以很快完成任务&#xff0c;很不幸某一天突然手一抖把表里的数据修改错误或者误删了&#xff0c;这个时候你会发现各种问题反馈接踵而来。 如果身边有BDA或者有这方面经…

Kubernetes WebHook 入门 -- 入门案例: apiserver 接入 github

博客原文 文章目录 k8s 集群配置介绍Admission WebhookWebHook 入门实践: github 认证接入web 服务器Dockerfile 镜像制作amd64x86_64构造镜像检验镜像 Makefilewebhook 接入 apiserverwebhook.yamlapiserver 挂载 webconfig在 github 中创建认证 token将 token 添加到 kubecon…

AI绘画:Midjournety的使用体验

今天的时间少&#xff0c;没有给大家做一些教程&#xff0c;就单纯分享使用体验&#xff0c;还不错&#xff0c;体验感很好。 后需如果有需要&#xff0c;我可以出一些教程类的视频。 下面是一组复刻fated的saber的一组提示词&#xff0c;效果相当不错。我后续会分享一些学习经…

ai电话呼叫系统的功能有哪些,能帮到我们什么?呼叫系统

人工智能产品的研发&#xff0c;是为了帮助企业更好的生存&#xff0c;更好的利润放大&#xff0c;而不是用于不正规的工作&#xff0c;现在的电话呼叫中心软件让企业员工从简单重复的工作中得以解放&#xff0c;那电话呼叫系统的强大功能有哪些&#xff1f; 知识自学习&#x…

目标检测-One Stage-YOLOx

文章目录 前言一、YOLOx的网络结构和流程1.YOLOx的不同版本2.Yolox-Darknet53YOLOv3 baselineYolox-Darknet53 3.Yolox-s/Yolox-m/Yolox-l/Yolox-x4.Yolox-Nano/Yolox-Tiny 二、YOLOx的创新点总结 前言 根据前文CenterNet、YOLOv4等可以看出学界和工业界都在积极探索使用各种t…

【Proteus仿真】【Arduino单片机】汽车车窗除霜系统设计

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真Arduino单片机控制器&#xff0c;使用LCD1602显示模块、光线传感器、DS18B20温度传感器、PCF8691 ADC模块、继电器加热模块等。 主要功能&#xff1a; 系统运行后&#xff0c;LCD…

Linux系统与windows系统设置定时任务的具体操作方法,如数据库自动备份等

设置定时备份 要设置数据库定时备份&#xff0c;你可以使用操作系统的定时任务功能来自动执行 backup.sh 脚本(此脚本可关注文末公众号回复04获取)。不同的操作系统有不同的方法来设置定时任务&#xff0c;但一般来说&#xff0c;你可以按照以下步骤进行操作&#xff1a; 打开…

python_selenium零基础爬虫学习案例_知网文献信息

案例最终效果说明&#xff1a; 去做这个案例的话是因为看到那个博主的分享&#xff0c;最后通过努力&#xff0c;我基本实现了进行主题、关键词、更新时间的三个筛选条件去获取数据&#xff0c;并且遍历数据将其导出到一个CSV文件中&#xff0c;代码是很简单的&#xff0c;没有…

.NET Framework 与 .NET Core 与 .NET Standard 之间的差异

介绍 在本文中&#xff0c;我们将探讨 .NET Framework、.NET Core 和 .NET Standard 之间的差异。 .NET Framework 与 .NET Core .NET框架.NET核心 历史 .NET Framework 是 .NET 的第一个实现。 .NET Core 是 .NET 的最新实现。 开源 .NET Framework 的某些组件是开源的。 .N…

【python可视化大屏】使用python实现可拖拽数据可视化大屏

介绍&#xff1a; 我在前几期分享了关于爬取weibo评论的爬虫&#xff0c;同时也分享了如何去进行数据可视化的操作。但是之前的可视化都是单独的&#xff0c;没有办法在一个界面上展示的。这样一来呢&#xff0c;大家在看的时候其实是很不方便的&#xff0c;就是没有办法一目了…

vue项目完整搭建与启动

vue项目完整搭建与启动 一&#xff0c;安装node环境二&#xff0c;安装vue脚手架&#xff08;vue-cli&#xff09;1.cnpm(淘宝镜像安装&#xff09;2.npm安装3.yarn安装 三&#xff0c;创建vue项目四&#xff0c;cmd切换目录方式1方式2 一&#xff0c;安装node环境 1.下载地址…

uniapp微信小程序投票系统实战 (SpringBoot2+vue3.2+element plus ) -我创建的投票列表实现

锋哥原创的uniapp微信小程序投票系统实战&#xff1a; uniapp微信小程序投票系统实战课程 (SpringBoot2vue3.2element plus ) ( 火爆连载更新中... )_哔哩哔哩_bilibiliuniapp微信小程序投票系统实战课程 (SpringBoot2vue3.2element plus ) ( 火爆连载更新中... )共计21条视频…

GitLab clone 地址 不对

1丶问题描述 2丶解决方案 解决方案&#xff1a; 找到挂载到宿主机配置文件&#xff1a;gitlab.rb vi gitlab.rb 改成自己的ip 重启容器 docker restart gitlab 如果发现容器一直重启&#xff0c;可采用粗暴的方法&#xff0c;直接干掉当前容器&#xff0c;重新运行一个 …

SpringMVC-异常处理及常用组件

异常处理器 1.基于配置的异常处理 springmvc提供了一个处理控制器方法执行过程中所出现的异常的接口: HandlerExceptionResolver HandlerExceptionResolver接口的实现类有: DefaultHandlerExceptionResolver和SimpleMappingExceptionResolver springmvc提供了自定义的异常处…

Apache Doris (六十二): Spark Doris Connector - (2)-使用

🏡 个人主页:IT贫道-CSDN博客 🚩 私聊博主:私聊博主加WX好友,获取更多资料哦~ 🔔 博主个人B栈地址:豹哥教你学编程的个人空间-豹哥教你学编程个人主页-哔哩哔哩视频 目录 1. 将编译jar包加入本地Maven仓库

全版本Windows RCE漏洞复现CVE-2023-36025

漏洞简介 CVE-2023-36025是微软于11月补丁日发布的安全更新中修复Windows SmartScreen安全功能绕过漏洞。攻击者可以通过诱导用户单击特制的URL来利用该漏洞&#xff0c;对目标系统进行攻击。成功利用该漏洞的攻击者能够绕过Windows Defender SmartScreen检查及其相关提示。该漏…

2024年【R2移动式压力容器充装】考试资料及R2移动式压力容器充装理论考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 R2移动式压力容器充装考试资料根据新R2移动式压力容器充装考试大纲要求&#xff0c;安全生产模拟考试一点通将R2移动式压力容器充装模拟考试试题进行汇编&#xff0c;组成一套R2移动式压力容器充装全真模拟考试试题&a…

Node.js和npm

目录 01_Node.js01.什么是 Node.js目标讲解小结 02.fs模块-读写文件目标讲解小结 03.path模块-路径处理目标讲解小结 04.案例-压缩前端html目标讲解小结 05.认识URL中的端口号目标讲解小结 06.http模块-创建Web服务目标讲解小结 07.案例-浏览时钟目标讲解小结 02_Node.js模块化…

02-python的基础语法-01python字面量/注释/数据类型/数据类型转换

字面量 在代码中&#xff0c;被写下来的固定的值&#xff0c;被称为字面量。 python中哪些值是可以被写出来的呢?又该如何写呢&#xff1f; 字符串&#xff1a;又称文本&#xff0c;是由任意数量的字符如中文&#xff0c;英文&#xff0c;各类符号&#xff0c;数字组成。 这…