项目源码地址:https://gitee.com/StoneLib/picture-server
该项目基于 web 下的 servlet 开发,具有简单的图片的上传、图片的查看和删除的功能,用户可以在这里上传自己的照片,进行存储与观看
JSON:一种数据组织的格式,格式为键值对的结构。此处有使用 JSON 完成数据的序列化,方便进行网络传输
// JSON 只是一个数据格式,和编程语言无关(JavaScript)
正式开始设计前后端交互 API:
请求:
POST/image
Content-Type:multipart/form-data
//正文内容 包含图片自身的一些信息图片正文的二进制内容
响应:
HTTP /1.1 200 OK
{
// 上传成功
“ok":true
// 上传失败
"ok":false
"reason":"具体失败原因"
}
请求:GET/image
响应:
HTTP /1.1 200 OK
[ // 响应成功
{
imageId: 1,
imageName: "a.png",
size: 10,
uploadTime: "20220815",
path: "./image/a.png",
md5:"dq2ew4"
},
{ ........ }
]
响应失败:
HTTP /1.1 200 OK
[
// 上传成功
“ok":true
// 上传失败
"ok":false
"reason":"具体失败原因"
]
1.API 具体的设定有很多方式,可以用200表示成功,404表示失败;
2.也可以使用 body 中的 OK 字段 ,true 表示成功,false 表示失败;
3.还可以使用 [] 有内容表示成功,为空表示失败。
请求:
GET / image?imageId = [具体的数值]
响应:
HTTP/1.1 200 OK
{ // 响应成功
imageId: 1,
imageName: "a.png",
size: 10,
uploadTime: "20220815",
path: "./image/a.png",
md5:"dq2ew4"
}
HTTP/1.1 200 OK
{ // 响应失败
"ok": false,
"reason":"具体出错的原因"
}
请求:
DELETE/image?imageId = [具体图片的ID]
响应:
HTTP/1.1 200 OK
{ // 响应成功
"ok": true,
}
HTTP/1.1 200 OK
{ // 响应失败
"ok": false,
"reason":"具体出错的原因"
}
服务器实现代码的时候就可以判定方法,如果是 DELETE 方法,就执行删除操作
删除也不一定非得用 DELETE 方法,
例如:GET / image?imageId=xxx&delete=1
请求:
GET / imageShow?imageId = [具体图片的id]
响应:
HTTP/1.1 200 OK
// 响应成功
Content-Type: image/png [图片的二进制内容]
HTTP/1.1 200 OK
{ // 响应失败
ok: false,
reason:"具体出错的原因"
}
1.先创建 DBUtil 封装一下获取数据库连接的过程, dao 数据访问层:这里的类围绕着数据操作展开
package dao;
import java.sql.Connection;
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import javax.sql.DataSource;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class DBUtil {
//3306后面跟的是数据库的名字
private static final String URL = "jdbc:mysql://127.0.0.1:3306/数据库?characterEncoding=utf8&useSSL=false";
private static final String USERNAME = "root";
private static final String PASSWORD = "数据库密码";
private static DataSource dataSource = null;
public static DataSource getDataSource(){
// 通过这个方法来创建DataSource的实例
// 保证线程安全:
// 1.先加锁 2.二次判断 3.使用volatile关键字
if(dataSource == null){
synchronized (DBUtil.class){
if(dataSource == null){
dataSource = new MysqlDataSource();
MysqlDataSource tempDataSource = (MysqlDataSource) dataSource;
tempDataSource.setURL(URL);
tempDataSource.setUser(USERNAME);
tempDataSource.setPassword(PASSWORD);
}
}
}
return dataSource;
}
public static Connection getConnection(){
try {
return getDataSource().getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet){
//注意关闭顺序,先连接的后关闭
try {
if(resultSet != null){
resultSet.close();
}
if(statement != null){
statement.close();
}
if(connection != null){
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
2.建立数据库中的表
create table image_table (imageId int not null primary key auto_increment,
imageName varchar(50),
size int,
uploadTime varchar(50),
contentType varchar(50),
path varchar(1024),
md5 varchar(1024));
public class Image {
private int imageId;
private String imageName;
private int size;
private String uploadTime;
private String contentType;
private String path;
private String md5;
// 再利用 IDEA 快捷键 alt+enter+insert 对上述属性进行 set 和 get 方法的创建
}
public void insert(Image image) {
// 1.获取数据库连接
Connection connection = DBUtil.getConnection();
// 2.创建片拼接SQL语句
PreparedStatement statement = null;
try {
String sql = "insert into image_table values(null, ?, ?, ?, ?, ?, ?)";
statement = connection.prepareStatement(sql);
statement.setString(1,image.getImageName());
statement.setInt(2,image.getSize());
statement.setString(3,image.getUploadTime());
statement.setString(4,image.getContentType());
statement.setString(5,image.getPath());
statement.setString(6,image.getMd5());
// 3.执行SQL语句
int ret = statement.executeUpdate();
if(ret != 1){
//程序出现问题,抛出一个异常
throw new ImageServerException("插入数据库错误");
}
} catch (SQLException | ImageServerException e) {
e.printStackTrace();
} finally {
// 4.关闭连接和statement对象
DBUtil.close(connection,statement,null);
}
}
// 查看所有图片
public List<Image> selectAll() {
List<Image> images = new ArrayList<>();
// 1.获取数据库连接
Connection connection = DBUtil.getConnection();
// 2.构造 SQL 语句
String sql = "select * from image_table";
PreparedStatement statement = null;
ResultSet resultSet = null;
// 3.执行 SQL 语句
try {
statement = connection.prepareStatement(sql);
resultSet = statement.executeQuery();
// 4.处理结果集
while (resultSet.next()) {
Image image = new Image();
image.setImageId(resultSet.getInt("imageId"));
image.setImageName(resultSet.getString("imageName"));
image.setSize(resultSet.getInt("size"));
image.setUploadTime(resultSet.getString("uploadTime"));
image.setContentType(resultSet.getString("contentType"));
image.setPath(resultSet.getString("path"));
image.setMd5(resultSet.getString("md5"));
images.add(image);
}
return images;
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 5.关闭连接和 statement 对象
DBUtil.close(connection,statement,resultSet);
}
return null;
}
// 根据 imageId 查看指定图片
public Image selectOne(int imageId) {
// 1.获取数据库连接
Connection connection = DBUtil.getConnection();
// 2.构造 SQL 语句
String sql = "select * from image_table where imageId = ?";
PreparedStatement statement = null;
ResultSet resultSet = null;
// 3.执行 SQL 语句
try {
statement = connection.prepareStatement(sql);
statement.setInt(1,imageId);
resultSet = statement.executeQuery();
// 4.处理结果集
if (resultSet.next()) {
Image image = new Image();
image.setImageId(resultSet.getInt("imageId"));
image.setImageName(resultSet.getString("imageName"));
image.setSize(resultSet.getInt("size"));
image.setUploadTime(resultSet.getString("uploadTime"));
image.setContentType(resultSet.getString("contentType"));
image.setPath(resultSet.getString("path"));
image.setMd5(resultSet.getString("md5"));
return image;
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 5.关闭连接和 statement 对象
DBUtil.close(connection,statement,resultSet);
}
return null;
}
public void delete(int imageId) {
//1.获取数据库连接
Connection connection = DBUtil.getConnection();
// 2.创建并片接sql语句
// ?作为占位符
String sql = "delete from image_table where imageId = ?";
// 3.执行sql语句
PreparedStatement statement = null;
try {
statement = connection.prepareStatement(sql);
statement.setInt(1,imageId);
int ret = statement.executeUpdate();
if(ret != 1){
throw new ImageServerException("删除数据库操作失败");
}
} catch (SQLException | ImageServerException e) {
e.printStackTrace();
}finally {
// 4.关闭连接
DBUtil.close(connection,statement,null);
}
}
ImageDao 有一个 selectAll 方法,查出所有数据库中的数据。如果数据库中内容只有几千条,这样查找可以;但若是数据库有几亿条数据,这样查就会非常低效,很可能直接把数据库或者应用程序运行崩溃。
更科学的方法是指定一些其他筛选的条件(分页)
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 考虑到查看所有图片和查看指定图片属性
// 通过是否 URL 中带有 imageId 参数进行区分
// 存在 imageId 查看指定图片属性,否咋查看所有图片属性
// 例如:URL / image?imageId=100
// imageId 的值就是 100
// 如果 URL 中不存在 imageId 那么返回 null
String imageId = req.getParameter("imageId");
if (imageId == null || imageId.equals("")) {
// 查看所有图片属性
selectAll(req, resp);
} else {
// 查看指定图片
selectOne(imageId,resp);
}
}
private void selectAll(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setContentType("application/json;charset=utf-8");
// 1.创建一个 ImageDao 对象,并查找数据
ImageDao imageDao = new ImageDao();
List<Image> images = imageDao.selectAll();
// 2.将查找到的结果转换成Json格式的字符串,然后写回给resp对象
Gson gson = new GsonBuilder().create();
// jsonData 就是一个 json 格式的字符串,和之前约定的格式是一样的
// 重点体会下列代码,是方法的核心,gson 帮我们完成了大量的格式转换工作
// 只要把之前的相关字段约定成统一的命名,下面的操作就可一步到位
String jsonData = gson.toJson(images);
resp.getWriter().write(jsonData);
}
private void selectOne(String imageId, HttpServletResponse resp) throws IOException {
resp.setContentType("application/json;charset=utf-8");
// 1.创建一个 ImageDao 对象,并查找数据
ImageDao imageDao = new ImageDao();
Image image = imageDao.selectOne(Integer.parseInt(imageId));
// 2.将查找到的结果转换成Json格式的字符串,然后写回给resp对象
Gson gson = new GsonBuilder().create();
String jsonData = gson.toJson(image);
resp.getWriter().write(jsonData);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1.获取图片的属性信息,并且写入数据库
// a.创建factory对象和upload对象
FileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
// b.通过upload对象进一步解析请求,解析HTTP请求中body的内容
// FileItem代表一个文件对象,HTTP支持一个请求同时上传多个文件
List<FileItem> items = null;
try {
items = upload.parseRequest(req);
} catch (FileUploadException e) {
// 出现异常说明解析出错
e.printStackTrace();
// 告诉客户端出现的具体的错误
resp.setContentType("application/json; charset=utf-8");
resp.getWriter().write("{\"ok\":false,\"reason\":\"请求解析失败\"}");
return;
}
// c.将FileItem中的属性提取出来,转换成Image对象,存入数据库中
// 当前只考虑一张图片的情况
FileItem fileItem = items.get(0);
Image image = new Image();
image.setImageName(fileItem.getName());
image.setSize((int) fileItem.getSize());
// 手动获取时间,并转成格式化日期
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd");
image.setUploadTime(simpleDateFormat.format(new Date()));
image.setContentType(fileItem.getContentType());
// MD5 暂时不用要求
image.setMd5(DigestUtils.md5Hex(fileItem.get()));
// 构造一个路径来进行保存
image.setPath("./image/" + image.getMd5());
// 存到数据库
ImageDao imageDao = new ImageDao();
// 看看数据库中是否存在相同的MD5的图片,不存在,返回 null
Image existImage = imageDao.selectByMd5(image.getMd5());
imageDao.insert(image);
// 2.获取图片的内容信息,并且写入磁盘
if (existImage == null) {
File file = new File(image.getPath());
try {
fileItem.write(file);
} catch (Exception e) {
e.printStackTrace();
resp.setContentType("application/json; charset=utf-8");
resp.getWriter().write("{\"ok\":false,\"reason\":\"写磁盘失败\"}");
return;
}
}
// 3.给客户端返回一个结果数据
/*resp.setContentType("application/json; charset=utf-8");
resp.getWriter().write("{\"ok\":true}");*/
//上传之后可以直接看到新上传的图片
resp.sendRedirect("images.html");
}
@Override
protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("application/json; charset=utf-8");
// 1.先获取到请求中的 imageId
String imageId = req.getParameter("imageId");
if (imageId == null || imageId.equals("")) {
resp.setStatus(200);
resp.getWriter().write("{ \"ok\": false, \"reason\": \"请求解析失败\"}");
return;
}
// 2.创建 ImageDao 对象,查看到该图片对象对应的相关属性(为了知道这个图片对应文件路径)
ImageDao imageDao = new ImageDao();
Image image = imageDao.selectOne(Integer.parseInt(imageId));
if (image == null) {
// 此时请求中传入的 id 在数据库中不存在
resp.setStatus(200);
resp.getWriter().write("{ \"ok\": false, \"reason\": \"imageId 在数据库中不存在\"}");
return;
}
// 3.删除数据库中的记录
imageDao.delete(Integer.parseInt(imageId));
// 4.删除本地磁盘文件
File file = new File(image.getPath());
file.delete();
resp.setStatus(200);
resp.getWriter().write("{ \"ok\": true }");
}
标签
告诉 Tomcat 当前这个 Servlet 对应到代码中的哪个类
标签
告诉 Tomcat 当前这个 Servlet 对应到的 URL 的 path 是什么
serverStart
Socket socket
socket.bind(服务器IP + 端口号)
while(true) {
Socket newSocket = socket.accept();
// 读取数据(客户端发来数据,读到的数据相当于一份完整的HTTP请求)
Byte[] inputBuffer = newSocket.read();
// 解析读到的 HTTP 请求格式的数据
HttpServletRequest req = parse(inputBuffer);
// 调用自己编写的代码(ImageServlet)
// 根据 url 中的 path 决定创建哪个对象
// h 对象本质上是一个 ImageServlet
// 根据 web.xml
HttpServlet h = build(req.getUrl)
if(req.getMethod().equals("GET")) {
h.doGet(req,resp);
} else if(req.getMethod().equals("GET")) {
h.doGet(req,resp);
} else if() {}
// 执行完上述方法之后,再把 resp 对象转为字符串,写回到 socket 中
newSocket.write(resp.toString());
}
1.关于前端代码的知识