前端
最终效果:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>表白墙</title>
</head>
<style>
/* *是选中所以元素;让盒子不被撑大 ;消除浏览器的默认样式*/
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* 这个对外层盒子操作 */
.container{
/* 水平方向左右auto外边距;就是居中状态 */
margin: 0 auto;
/* 宽度设置600px */
width:600px ;
}
/* 让h1标签的文字居中 */
h1{
text-align: center;
}
/* 让p标签的文字居中 */
p{
text-align: center;
/* 让这里的文字颜色浅一点 */
color: #666;
}
/* 把这里设置弹性布局 */
.row{
display: flex;
/* 让这里div高一点 */
height: 40px;
/* 水平方向居中 */
justify-content: center;
/* 垂直方向居中 */
align-items: center;
}
/* 针对输入框三个div */
.row span{
width: 80px;
}
.row input{
width:250px ;
height: 28px;
}
.row button{
width:100px ;
height: 20px;
color: black;
background-color: orange;
/* 设置圆角矩形 */
border-radius: 5px;
/* 按钮去边框 */
border: none;
/*有个问题; 点击后什么效果都没有;也不知道点了没 */
}
/* 伪类选择器;点击后有一个效果变化 */
.row button :active{
background-color: red;
}
</style>
<body>
<!-- 让整体水平居中 -->
<div class="container">
<h1>表白墙 </h1>
<p>提示:输入内容后点击提交,信息会显示到下方表格中</p>
<!-- 让这三个位置分别为一个div -->
<div class="row">
<span>谁:</span>
<input type="text">
</div>
<div class="row">
<span>对谁:</span>
<input type="text">
</div>
<div class="row">
<span>说:</span>
<input type="text">
</div>
<div class="row">
<button >
提交
</button>
</div>
</div>
</body>
</html>
js逻辑:希望点击提交后能够在下方显示
但是有个问题;我们希望提交后;这上面框的内容应该清空;好让下一个输入更方便。
<script>
// 实现提交操作;点击按钮;就能把用户输入的东西显示到页面显示
// 点击后获得三个输入框文本内容;把内容构造到一个新创建的div.row中
let containerDiv=document.querySelector('.container');
// 选中所有input标签
let inputs=document.querySelectorAll('input');
let button=document.querySelector('button');
// 对这个点击进行事件响应
button.onclick=function(){
//获取三个输入框的内容
let from =inputs[0].value;
let to =inputs[1].value;
let msg =inputs[2].value;
//构造一个新div ;并给它赋予row属性
let rowDiv =document.createElement('div');
rowDiv.className='row';
rowDiv.innerHTML=from+'对'+to+'说'+msg;
// 最后把这个div添加进去 let containerDiv=document.querySelector('.container');
containerDiv.appendChild(rowDiv);
// 清空之前的输入框内容
for(let input of inputs){
input.value=' ';
}
}
</script>
但是这里又有点问题;如果为空的时候点提交;会 对 、说也显示在下面
所以我们遇到任何一个为空就应该直接结束
if(from==''||to==''|| msg==''){
return;
}
增加撤销功能:
但是这又出现问题;当没有任何数据时点击撤销;连撤销按钮把自己撤销了。
修改逻辑:
整体代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>表白墙</title>
</head>
<style>
/* *是选中所以元素;让盒子不被撑大 ;消除浏览器的默认样式*/
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* 这个对外层盒子操作 */
.container{
/* 水平方向左右auto外边距;就是居中状态 */
margin: 0 auto;
/* 宽度设置600px */
width:600px ;
}
/* 让h1标签的文字居中 */
h1{
text-align: center;
}
/* 让p标签的文字居中 */
p{
text-align: center;
/* 让这里的文字颜色浅一点 */
color: #666;
}
/* 把这里设置弹性布局 */
.row{
display: flex;
/* 让这里div高一点 */
height: 40px;
/* 水平方向居中 */
justify-content: center;
/* 垂直方向居中 */
align-items: center;
}
/* 针对输入框三个div */
.row span{
width: 80px;
}
.row input{
width:250px ;
height: 28px;
}
.row button{
width:100px ;
height: 20px;
color: black;
background-color: orange;
/* 设置圆角矩形 */
border-radius: 5px;
/* 按钮去边框 */
border: none;
/*有个问题; 点击后什么效果都没有;也不知道点了没 */
}
/* 伪类选择器;点击后有一个效果变化;这里冒号之间还不能随便空格的;不然就没效果 */
.row button:active{
background-color: grey;
}
</style>
<body>
<!-- 让整体水平居中 -->
<div class="container">
<h1>表白墙 </h1>
<p>提示:输入内容后点击提交,信息会显示到下方表格中</p>
<!-- 让这三个位置分别为一个div -->
<div class="row">
<span>谁:</span>
<input type="text">
</div>
<div class="row">
<span>对谁:</span>
<input type="text">
</div>
<div class="row">
<span>说:</span>
<input type="text">
</div>
<!-- <div class="row">
<button ">
提交
</button>
</div> -->
<div class="row">
<button id="submit">
提交
</button>
</div>
<!-- 增加一个撤销按钮;上面的button就选择器得修改 -->
<div class="row">
<button id="revert" >
撤销
</button>
</div>
</div>
<script>
// 实现提交操作;点击按钮;就能把用户输入的东西显示到页面显示
// 点击后获得三个输入框文本内容;把内容构造到一个新创建的div.row中
let containerDiv=document.querySelector('.container');
// 选中所有input标签
let inputs=document.querySelectorAll('input');
// let button=document.querySelector('button');因为增加一个撤销按钮;就不能再是这么选择
let button=document.querySelector('#submit');
// 对这个点击进行事件响应
button.onclick=function(){
//获取三个输入框的内容
let from =inputs[0].value;
let to =inputs[1].value;
let msg =inputs[2].value;
//任何一个为空直接结束
// if(from == ''|| to == ''|| msg == ''){
// return;
// }
if (from == '' || to == '' || msg == '') {
return;
}
//构造一个新div ;并给它赋予row属性
let rowDiv =document.createElement('div');
// rowDiv.className='row ';
rowDiv.className='row mssage';
rowDiv.innerHTML=from+'对'+to+'说'+msg;
// 最后把这个div添加进去 let containerDiv=document.querySelector('.container');
containerDiv.appendChild(rowDiv);
// 清空之前的输入框内容
for(let input of inputs){
// 空字符串;什么都不能写;不能写个空格去
input.value='';
}
}
// 撤销逻辑
let revertButton = document.querySelector('#revert');
revertButton.onclick = function() {
// 点击事件;删除最后一条消息.
// 选中所有的 row, 找出最后一个 row, 然后进行删除
// let rows = document.querySelectorAll('.row');
let rows = document.querySelectorAll('.mssage');
// 加上个条件;没有的时候就不删除;不然会报错;直接结束了
if (rows == null || rows.length == 0) {
return;
}
containerDiv.removeChild(rows[rows.length - 1]);
}
</script>
</body>
</html>
后端
需求分析:我们上述页面有个问题;关闭或者刷新一下;数据就全没了。所以我们要实现能保存这些数据(提交一次保存一次到服务器);让其它用户也能看到这些数据(刷新或者打开页面就读取全部的数据)
前后端交互:约定好前后端的交互数据格式;请求是什么样;响应是什么样;浏览器啥时候发请求;浏览器按什么样格式解析。
设计:
1:点击提交;浏览器把表白信息发到服务器;数据格式约定是有很多种的。
请求:
响应:
这里并不需要响应什么特别东西给浏览器;我们要做的重点是把这里数据解析;保存下来;方便下次能取
2:页面加载;浏览器从服务器获取表白信息;
请求:
响应
代码实现:创建maven项目;7步走
1:创建maven项目;
2:导入依赖;maven中央仓库;搜索servlet;选择servlet API 3.1.0版本。复制粘贴到pomxl下
3:创建目录结构;在main下创建webapp;在webapp下创建WEB-INF;在WEB-INF下创建web.xml;在web.xml投名状加上;让tomcat带着项目跑
4:编写代码:继承HttpServlet类;如果没出现这个类那就刷新一下maven;
编写代码:注解得要和我们上面约定的请求路径是一样的
处理POST请求:我们先定义一个类;描述请求body内容;使用jackson进行json解析。因为我们上面的约定请求内容body是
导入jackson依赖:maven中央仓库;jackson;选择2.14.1版本;注意拷贝的位置是和Servlet 依赖是同级
使用jackson初始化我们的类对象:
我们这里先使用顺序表储存一下这些内容:
GET请求:
对POST请求处理写完成;现在GET请求处理:GET请求就是获取数据;把顺序表的数据返回给前端就好了
响应的数据也是json格式;那我们需要把这个List转成json格式:这里可以使用到writeValue:把java对象转成json格式字符串;
这个方法的作用先是把顺序表转成数组【】;里面的每一个对象是{};对象里面的值是键值对;json格式
现在后端部分已经完成:但是前端还没有发送请求的功能;一般后端开发在没有前端对接的情况下使用Postman测试是否能交互是很方便的。
现在我们打开Postman测试一下交互功能是否正常:
效果确实发送成功了;但是因为我们对响应并没有做过多的处理;我们send多几次试试看;然后获取get请求就能收到多个数据了。
这里如果出现输入中文;而响应的结果;英文是没有问题;中文却显示问号?代替;那就可能是没有设置响应的编码resp.setCharacterEncoding(“utf8”);。这个还得写在 resp.getWriter().write(json);上面才有效果
这里的发送;一次只能发送这样的一条json格式数据;只是跟我们服务器的处理代码是对应的;如果先一次发多条;像数组一样。那服务器的代码也得修改。
json演示:
如果没有请求参数Student没有的字段会怎么样;或者Student的字段比json的参数多出来的会怎么样???
难道是说 String s=“{“classId”:2,“studentId”:100}”; 的写法才能发挥出右边的效果 ? {“classId”:2,“StudentId”:100}
整体后端代码:
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 java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@WebServlet("/message")
public class MessageWall extends HttpServlet {
// 创建这个顺序用来储存传过来被jackson处理后的body
public List<Message> messagesList=new ArrayList<>();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// super.doGet(req, resp);
ObjectMapper objectMapper=new ObjectMapper();
// 转成json对象并且写到响应中;resp.getWriter()这个参数是表示转成json对象往哪写
// 分两步写效果是一样的
String json= objectMapper.writeValueAsString(messagesList);
System.out.println(json);
// resp.setCharacterEncoding("utf8");
resp.setContentType("application/json; charset=utf8");
resp.getWriter().write(json);
// objectMapper.writeValue(resp.getWriter(),messagesList);
// resp.setStatus(200);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// super.doPost(req, resp);
ObjectMapper objectMapper=new ObjectMapper();
Message message=objectMapper.readValue(req.getInputStream(),Message.class);
// System.out.println(message.from);证明这里传入的字符串是没问题;正确初始化到了
// System.out.println(message.to);
//每提交一次就发一次POST请求就执行这个方法一次;就把内容添加进顺序表一次
messagesList.add(message);
// 设置返回的状态码;默认也是返回200
resp.setStatus(200);
}
}
class Message{
// 处理请求的body;使用jackson;变量名一样它会根据body自动给这些初始化.所以我们得保证这些属性的名字一样
public String from;
public String to;
public String message;
}
5:打包
6:部署
7:使用
前端请求构建
编写前端代码;让页面来发起请求;并且解析响应;我们的需求;Post点击提交的时候发起;get是页面加载的时候发起。因为我们还是得要通过tomcat启动前端代码;所以先把前端代码粘贴到webapp目录下。
Ajax:
jQuery 选择 3.x minified 鼠标左键复制链接.然后粘贴到script标签
构建js对象;把文本框获取的内容赋值到这个对象里;再把这个对象转成字符串传输。js内置json转换库;这就比较方便;不像刚才java需要jackson的第三方库转换。
需要添加的代码:
这里得放这里面来;点击后的才产生这一系列的情况
处理get请求:页面刷新、加载就发送一个ajax构建get请求;实现读档操作。
我们后端对这个get请求响应的结果是读档;把格式转成字符串;到浏览器会自动帮我们解析成js数组body。我们只需要从这个数组取元素就好了。
找BUG:
提交数据后轻轻刷新一下;变成下面的效果:(先找到这个是什么意思;未定义的意思;极有可能:前端这个东西未定义)
当出现问题时;抓包;确认响应的数据是啥;如果数据没问题;那就说明是前端bug;如果响应数据有问题;说明是后端bug。
问题1:当输入URL时;这就是构建一个GET请求;然后服务器就会响应;把结果写到下面;但是我们什么都没有输入。
解决方法;我们可以判断一下;当链表为空时;也就是没有提交任何的数据;就不发送写回响应字符串。
问题2:当一直刷新时会一直出现;刚才的问题只解决了空的情况;空的情况随便你怎么刷新都行。现在是不空了;当我输入一次后一直刷新就出现这个bug。而且是我只刷新一次就弹满这个东西。当这个问题2解决后;问题1不处理也行;因为之前是对数据的错误处理导致的这两个bug。
先放一下;抓包的结果是后端的数据是没有问题的。(只能说当时还是太年轻;只顾看内容;内容是问题的;但是内容的数据类型不是json;得瞄一眼数据的类型是什么)
可能的情况:前端的处理方式不对;但是经过反复的检查和处理;发现前端并没有问题;后端发的数据打印出结果的格式也是没有问题;那就可能浏览器的识别不知道这是json格式的数据;所以没法处理;我们在设置字符集时并没有指定说这是json格式数据。所以浏览器就按照自己的方式认为这是什么数据;按什么方式解析。
整体代码:(注意:在写相关代码时一定得注意细节;稍有不慎可能结果就请求没法发送;没法响应;没法解析等;而且这里没达到预期效果往往是不显示错误的;需要自己去找提示;找错误)
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 java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@WebServlet("/message")
public class MessageWall extends HttpServlet {
// 创建这个顺序用来储存传过来被jackson处理后的body
public List<Message> messagesList=new ArrayList<>();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// super.doGet(req, resp);
ObjectMapper objectMapper=new ObjectMapper();
// 转成json对象并且写到响应中;resp.getWriter()这个参数是表示转成json对象往哪写
// 分两步写效果是一样的
// String json= objectMapper.writeValueAsString(messagesList);
// System.out.println(json);
// resp.setCharacterEncoding("utf8");
resp.setContentType("application/json; charset=utf8");
// resp.getWriter().write(json);
// if(messagesList.size()==0){
//
// return;
// }
objectMapper.writeValue(resp.getWriter(),messagesList);
// resp.setStatus(200);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// super.doPost(req, resp);
ObjectMapper objectMapper=new ObjectMapper();
Message message=objectMapper.readValue(req.getInputStream(),Message.class);
// System.out.println(message.from);证明这里传入的字符串是没问题;正确初始化到了
// System.out.println(message.to);
//每提交一次就发一次POST请求就执行这个方法一次;就把内容添加进顺序表一次
messagesList.add(message);
// 设置返回的状态码;默认也是返回200
resp.setStatus(200);
System.out.println("收到请求");
}
class Message{
// 处理请求的body;使用jackson;变量名一样它会根据body自动给这些初始化.所以我们得保证这些属性的名字一样
public String from;
public String to;
public String message;
}
数据库版本
我们需求想数据持久化保存:
1:使用流对象;写入文本文件;但是文本文件无法灵活增删查改等
2:借助数据库
创建数据表:里面内容是from,to,message。想使用关键字命名得加反引号;左上角。
连接数据库:
maven中央仓库搜索mysql;选择JDBC选择需要的版本8.0.32;导入mysql依赖;我们是maven项目;直接把修改配置它就会自动下载导入的;放与Servlet的配置同级下。
<!-- https://mvnrepository.com/artifact/com.mysql/mysql-connector-j -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.32</version>
</dependency>
数据库的连接
import com.mysql.cj.jdbc.MysqlDataSource;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
//数据库的连接;把连接的过程封装一下;作为工具类;提供一些static让我们使用更方便
public class DBlianjie {
// 静态成员相当于饿汉单例模式
public static DataSource dataSource=new MysqlDataSource();
//初始化DataSource
static {
((MysqlDataSource)dataSource).setURL("jdbc:mysql://127.0.0.1:3306/liao?characterEncoding=utf8&useSSL=false");
((MysqlDataSource)dataSource).setUser("root");
((MysqlDataSource)dataSource).setPassword("111111");
}
// 通过这个方法建立连接
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
//通过这个方法释放资源
public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet){
// 此处的三个 try catch 分开写更好, 避免前面的异常导致后面的代码不能执行.
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();
}
}
}
}
代码上添加两个方法:我们存元素和取元素就来自这两个方法
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 java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
//数据库版本表白墙程序
@WebServlet("/message")
public class MessageWallJDBC extends HttpServlet {
// 创建这个顺序用来储存传过来被jackson处理后的body
public List<Message> messagesList=new ArrayList<>();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// super.doGet(req, resp);
resp.setContentType("application/json; charset=utf8");
ObjectMapper objectMapper=new ObjectMapper();
// ObjectMapper对json的操作
// 这个方法返回的结果就是我们储存的全部数据
List<Message> messageList=getall();
System.out.println("收到get请求");
// String jsonResp = objectMapper.writeValueAsString(messageList);
// resp.getWriter().write(jsonResp);
// 写错单词我艹
objectMapper.writeValue(resp.getWriter(),messageList);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// super.doPost(req, resp);
ObjectMapper objectMapper=new ObjectMapper();
Message message=objectMapper.readValue(req.getInputStream(),Message.class);
//message是读取到提交的body
add(message);
// 设置返回的状态码;默认也是返回200
resp.setStatus(200);
System.out.println("收到post请求");
}
// 从数据库存一条消息
public void add(Message message){
// 用来提升变量作用域
Connection connection=null;
PreparedStatement statement=null;
//1建立连接
try {
connection=DBlianjie.getConnection();
// 2 构造sql语句;?占位符;后面可以通过set***替换占位符
String sql="insert into message values(?, ?,?)";
statement= connection.prepareStatement(sql);
// 这里替换的下标开始位置是1下标开始
statement.setString(1,message.from);
statement.setString(2,message.to);
statement.setString(3,message.message);
//3执行sql
statement.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
}
// 写finally保证能被执行到; 没有使用到查询;就用空代替。作用域不够得往外提
finally {
DBlianjie.close(connection,statement,null);
}
}
// 从数据库取所有消息 查询操作
public List<Message> getall(){
List<Message> messageList=new ArrayList<>();
Connection connection=null;
PreparedStatement statement=null;
ResultSet resultSet=null;
//1建立连接
try {
connection=DBlianjie.getConnection();
// 2构造sql语句
String sql="select *from message";
statement=connection.prepareStatement(sql);
// 3 执行sql
resultSet= statement.executeQuery();
//4遍历结果集;
while (resultSet.next()){
Message message=new Message();
// 这里的"from"是表的列名;给这个对象里的内容赋值;然后doget就继续把这个对象转成json;然后当body发给前端
message.from = resultSet.getString("from");
message.to = resultSet.getString("to");
message.message = resultSet.getString("message");
// 把全部的结果加到顺序表
messageList.add(message);
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
// 5释放资源;断开连接
DBlianjie.close(connection,statement,resultSet);
}
return messageList;
}
}
class Message{
// 处理请求的body;使用jackson;变量名一样它会根据body自动给这些初始化.所以我们得保证这些属性的名字一样
public String from;
public String to;
public String message;
}
代码的逻辑是不变的;只是储存变成数据库;读取也是如此;
以上代码大功告成;虽然差强人意;但是必须要刷新才能看到最新的消息;可以使用refresh或者websocket(应用层协议)实现服务器主动推送数据。