文章目录

  • 实现一个 web 表白墙
    • 准备工作
    • 约定前后端交互接口
    • 实现服务器端代码
    • 调整前端页面代码
    • 数据存入数据库
    • 实现效果
  • 上传文件
    • 核心方法
    • 代码示例——通过网页提交一个图片到服务器上

实现一个 web 表白墙

由于博主之前学前端的时候写的表白墙数据不能保存😅😅,所以此次是把之前实现的表白墙程序修改成服务器版本, 这样即使页面关闭, 表白墙的内容也不会丢失,那么为实现数据做到"持久化"存储,因此我这里用数据库来存储信息。

准备工作

  1. 创建 maven 项目
  2. 创建必要的目录webapp,WEB-INF,web.xml
<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun/dtd/web-app_2_3.dtd" >

<web-app>
    <display-name>Archetype Created Web Application</display-name>
</web-app>
  1. 调整 pom.xml
    引入依赖, 配置生成 war 包, 以及 war 包名字
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache/POM/4.0.0"
         xmlns:xsi="http://www.w3/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache/POM/4.0.0 http://maven.apache/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>message_Wall</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
<dependencies>
 <!-- 加入 servlet 依赖 -->
    <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <!-- servlet 版本和 tomcat 版本有对应关系,切记 -->
        <version>3.1.0</version>
        <!-- 这个意思是我们只在开发阶段需要这个依赖,部署到 tomcat 上时就不需要了 -->
        <scope>provided</scope>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.12.6.1</version>
    </dependency>
    <!--引入mysql驱动包-->
    <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
</dependencies>
</project>
  1. 把之前实现的表白墙前端页面拷贝到 webapp 目录中
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>表白墙</title>
</head>
<body>
	<style>
	* {
		margin: 0;
		padding: 0;
		box-sizing: border-box;
	}
	.container {
		width: 100%;
	}
	h3 {
		text-align: center;
		padding: 30px 0;
		font-size: 24px;
	}
	p {
		text-align: center;
		color: #999;
		padding: 10px 0;
	}
	.row {
		width: 400px;
		height: 50px;
		margin: 0 auto;
		display: flex;
		justify-content: center;
		align-items: center;
	}
	.row span {
		width: 70px;
	}
	.row input {
		width: 300px;
		height: 30px;
		font-size: 15px;
		text-indent: 0.5em;
		outline:none ;
	}
	.row #btn {
		width:300px;
		height: 40px;
		font-size: 20px;
		line-height: 40px; 
		margin: 0 auto;
		color: white;
		background-color: orange;
		border:none;
		border-radius: 10px;
	}
	.row #btn:active { 
		background-color: gray;
	}
</style>
	<div class="container">
		<h3>表白墙</h3>
	<p>输入后点击提交,会将信息显示在表格中</p>
	<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 id="btn">提交</button>
	</div>
	</div>
	<script>
		//当用户点击submit,就会获取到input中的内容,从而把内容构成一个div,插入到页面末尾
		let submitBtn=document.querySelector('#btn');
		submitBtn.onclick=function(){
		    // 1. 获取到 3 个 input 中的内容
			let input=document.querySelectorAll('input');
			let from=input[0].value;
			let to=input[1].value;
			let msg=input[2].value;
			if(from==''||to==''||msg==''){
			 // 用户还没填写完, 暂时先不提交数据
				return ;
			}
			//2.生成一个新的div,内容就是input里面的内容,把这个新的div加到页面中
			let newDiv=document.createElement('div');
			newDiv.innerHTML=from+'对'+to+'说:'+msg;
			newDiv.className='row';
			let container=document.querySelector('.container');
			container.appendChild(newDiv);
			//3.清空之前输入框的内容
			for (let i = 0; i < input.length; i++) {
				input[i].value='';
			}
		}
	</script>
</body>
</html>

约定前后端交互接口

所谓 “前后端交互接口”: 是进行 Web 开发中的关键环节
具体来说:就是允许页面给服务器发送哪些 HTTP 请求,并且每种请求预期获取什么样的 HTTP 响 应。

  1. 获取全部留言

请求:

GET /message

响应: JSON 格式

[
   {
        from: "易烊千玺",
        to: "鹤儿",
        message: "很高兴遇见你们"
   },
   {
        from: "玺子哥",
        to: "肥鹤",
        message: "努力来见我"
   },
    ......
]

我们期望浏览器给服务器发送一个 GET /message 这样的请求, 就能返回当前一共有哪些留言记
录, 结果以 json 的格式返回过来

  1. 发表新留言
    请求: body 也为 JSON 格式
POST /message
{
    from: "大佬",
    to: "鹤子",
    message: "要开心"
}

响应: JSON 格式

{
    ok: true
}

我们期望浏览器给服务器发送一个 POST /message 这样的请求, 就能把当前的留言提交给服务器

实现服务器端代码

创建 Message 类

class Message{
    public String from;
    public String to;
    public String message;
}

创建 MessageServlet 类

@WebServlet("/message")
public class MessageServlet extends HttpServlet {
    // 用于转换 JSON 字符串
    private ObjectMapper objectMapper=new ObjectMapper();
    //用于保存所有的留言
    private List<Message>messages=new ArrayList<>();
    // 获取所有留言
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //处理提交信息请求
        Message message=objectMapper.readValue(req.getInputStream(),Message.class);
        //最简单的保存方法就是保存在内存中
        messages.add(message);
        //通过ContentType来告知页面,返回的数据是json格式
        //有了这样的声明,此时jQuery Ajax就会自动的帮我们把字符串转成js对象
        //如果没有,jQuery Ajax就只是把当成字符串来处理
        resp.setContentType("application/json;charset=utf8");
        resp.getWriter().write("{\"ok\":true}");
    }
    // 新增留言
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取到消息列表,只要把消息列表中的内容整个的返回给客户端即可
        //此处需要使用ObjectMapper把Java对象,转换成json格式字符串
        String jsonString=objectMapper.writeValueAsString(messages);
        System.out.println("jsonString:"+jsonString);
        resp.setContentType("application/json;charset=utf8");
        resp.getWriter().write(jsonString);
    }

ObjectMapper 的 readValue方法也能直接从一个 InputStream 对象读取数据
ObjectMapper 的 writeValueAsString方法也能把一个对象数组直接转成 JSON 格式的字符串

调整前端页面代码

<script src="https://cdn.bootcdn/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
	<script>
		//加入Ajax的代码,此处要加入的逻辑有两个部分
		//点击按钮提交的时候,Ajax要构造数据发送给服务器
		//页面加载的时候,从服务器获取消息列表,并在界面上直接显示
		function getMessages(){
			$.ajax({
				type:'get',
				url:'message',
				success:function(body){
					//当前body已经是一个js对象数组了,Ajax会根据响应的contentType来自动进行解析
					//如果服务器返回的contentType已经是application/json了,Ajax就会把body自动转成js的对象
					//如果客户端没有自动转,也可以通过JSON.parse()这个函数来手动转换
					//依次来取数组中的每个元素
					let container=document.querySelector('.container');
					for(let message of body){
						let div=document.createElement('div');
						div.innerHTML=message.from+'对'+message.to+'说:'+message.message;
						div.className='row';
						container.appendChild(div);
					}
				}
			});
		}
		//加上函数调用
		getMessages();
		//当用户点击submit,就会获取到input中的内容,从而把内容构成一个div,插入到页面末尾
		let submitBtn=document.querySelector('#btn');
		submitBtn.onclick=function(){
			//1.获取到3个input中的内容
			let input=document.querySelectorAll('input');
			let from=input[0].value;
			let to=input[1].value;
			let msg=input[2].value;
			if(from==''||to==''||msg==''){
				//用户还没填写完,暂时先不提交数据
				return ;
			}
			//2.生成一个新的div,内容就是input里面的内容,把这个新的div加到页面中
			let newDiv=document.createElement('div');
			newDiv.innerHTML=from+'对'+to+'说:'+msg;
			newDiv.className='row';
			let container=document.querySelector('.container');
			container.appendChild(newDiv);
			//3.清空之前输入框的内容
			for (let i = 0; i < input.length; i++) {
				input[i].value='';
			}
			//4.把当前获取到的输入框的内容,构造成一个HTTP POST请求,通过Ajax发给服务器
			let body={
				from:from,
				to:to,
				message:msg
			};
			$.ajax({
				type:"post",
				url:"message",
				contentType:"application/json;charset=utf8",
				data:JSON.stringify(body),
				success:function(body){
					alert("消息提交成功!");
				},
				error:function(){
					alert("消息提交失败!");
				}
			});
		}
	</script>

此时我们每次提交的数据都会发送给服务器, 每次打开页面的时候页面都会从服务器加载数据, 因此即使关闭页面, 数据也不会丢失了。
但是数据此时是存储在服务器的内存中 ( private List messages = new ArrayList(); ), 一旦服务器重启, 数据仍然会丢失。

数据存入数据库

针对上面的问题, 如果把数据保存在数据库中, 那么重启服务器也不会丢失数据了。

  1. 在 pom.xml 中引入 mysql 的依赖(上面已经引入过了😊)
  2. 创建数据库, 创建 message 表
create table message(`from` varchar(1000), to varchar(1000), message varchar(3000));
ps:由于from是sql语句中的关键字,因此要用反引号``包围
  1. 创建 DBUtil 类
    DBUtil 类主要实现以下功能:
  • 创建 MysqlDataSource 实例,设置 URL, username,password 等属性
  • 提供 getConnection 方法, 和 MySQL 服务器建立连接
  • 提供 close 方法, 用来释放必要的资源
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 final String URL="jdbc:mysql://127.0.0.1:3306/wall?characterEncoding=utf8&useSSL=false";
    private static final String USERNAME="root";
    private static final String PASSWORD="root";
    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(URL);
                    ((MysqlDataSource)dataSource).setUser(USERNAME);
                    ((MysqlDataSource)dataSource).setPassword(PASSWORD);
                }
            }
        }
        return dataSource;
    }
    public static Connection getConnection() throws SQLException {
        return getDataSource().getConnection();
    }
    public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet){
        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();
            }
        }
    }
}
  1. 添加 load 和 save 方法,操作数据库
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;

class Message{
    public String from;
    public String to;
    public String message;
}
@WebServlet("/message")
public class MessageServlet extends HttpServlet {
    private ObjectMapper objectMapper=new ObjectMapper();
    //改成数据库,就不需要这个变量了
    //private List<Message>messages=new ArrayList<>();
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //处理提交信息请求
        Message message=objectMapper.readValue(req.getInputStream(),Message.class);
        //最简单的保存方法就是保存在内存中
        //messages.add(message);
        //通过ContentType来告知页面,返回的数据是json格式
        //有了这样的声明,此时jQuery Ajax就会自动的帮我们把字符串转成js对象
        //如果没有,jQuery Ajax就只是把当成字符串来处理
        save(message);
        resp.setContentType("application/json;charset=utf8");
        resp.getWriter().write("{\"ok\":true}");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取到消息列表,只要把消息列表中的内容整个的返回给客户端即可
        //此处需要使用ObjectMapper把Java对象,转换成json格式字符串
        List<Message>messages=load();
        String jsonString=objectMapper.writeValueAsString(messages);
        System.out.println("jsonString:"+jsonString);
        resp.setContentType("application/json;charset=utf8");
        resp.getWriter().write(jsonString);
    }
    private void save(Message message){
        //把一条消息保存到数据库
        Connection connection=null;
        PreparedStatement statement=null;
        try {
            //1.和数据库建立连接
            connection=DBUtil.getConnection();
            //2.构造sql语句
            String sql="insert into message values(?,?,?)";
            statement= connection.prepareStatement(sql);
            statement.setString(1,message.from);
            statement.setString(2,message.to);
            statement.setString(3,message.message);
            //3.执行SQL
            statement.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            DBUtil.close(connection,statement,null);
        }
    }
    private List<Message>load(){
        //从数据库中获取到所有的消息
        Connection connection=null;
        PreparedStatement statement=null;
        ResultSet resultSet=null;
        List<Message>messages=new ArrayList<>();
        try {
            connection=DBUtil.getConnection();
            String sql="select * from message";
            statement= connection.prepareStatement(sql);
            resultSet= statement.executeQuery();
            while(resultSet.next()){
                Message message=new Message();
                message.from=resultSet.getString("from");
                message.to= resultSet.getString("to");
                message.message= resultSet.getString("message");
                messages.add(message);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            DBUtil.close(connection,statement,resultSet);
        }
        return messages;
    }
}

实现效果


重新部署程序,此时使用数据库之后就可以保证即使服务器重启,数据也不丢失,达到了永久保存的效果。

上传文件

核心方法

HttpServletRequest 类方法:

方法描述
Part getPart(String name)获取请求中给定 name 的文件
Collection getParts()}获取所有的文件

Part类方法:

方法描述
String getSubmittedFileName()获取提交的文件名
String getContentType()获取提交的文件类型
long getSize()获取文件的大小
void write(String path)把提交的文件数据写入磁盘文件

代码示例——通过网页提交一个图片到服务器上

  1. 创建 upload.html,放到 webapp 目录中
<form action="upload" method="post" enctype="multipart/form-data">
        <input type="file" name="MyImage">
        <input type="submit" value="提交">
</form>
  • 上传文件一般通过 POST 请求的表单实现
  • 在 form 中要加上 multipart/form-data 字段
  1. 创建 UploadServlet 类
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.IOException;
@MultipartConfig
@WebServlet("/upload")
public class UpLoadServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Part part=req.getPart("MyImage");
        System.out.println(part.getSubmittedFileName());
        System.out.println(part.getContentType());
        System.out.println(part.getSize());
        part.write("e:/yyqx.jpg");
        resp.setContentType("text/html;charset=utf8");
        resp.getWriter().write("上传成功!");
    }
}
  • 需要给 UploadServlet 加上 @MultipartConfig 注解,来开启对于上传文件的支持, 否则服务器代码调用 getPart 方法时就会抛出异常
  • getPart 的 参数 需要和 form 中 input 标签的 name 属性对应
  • 客户端一次可以提交多个文件(使用多个 input 标签),此时服务器可以通过 getParts 获取所有的Part 对象。
  1. 部署程序

    选择文件后,点击提交, 则页面跳转到 /upload 页面

    此时可以看到服务器端的打印日志

    同时在 e 盘中生成了 yyqx.jpg

    上传图片请求的抓包结果为:

更多推荐

代码案例—— web版表白墙及文件上传