图片服务器项目编写思路
引言:在QQ中更换头像时,点击上传本地照片时,看到下面这个窗口有了想法,自己是否也可以做出类似的图片服务器呢?(当然自己的肯定没有这个高大上~~~~~~)
一. 项目背景
- 现在很多网页都可以见到上传图片的功能,假如我们上传一张本地图片后,网页就会显示我们刚刚所上传的图片,比如博客上传一张图、个人信息提交头像页面等等。那么这背后的原理是什么呢?
- 其实当我们浏览网页的时候,本质上是从 对端服务器 获取资源,浏览器在获得这些资源之后进行解析和渲染,呈现在我们用户面前的就是绚丽多彩的页面了。
- 一般来讲这些资源文档主要有三种格式:HTML、CSS、JS。HTML相当于网页的骨架,CSS相当于网页的衣服,用来规定网页的样式,比如字体大小以及排版等等,而JavaScript则主要负责一些动态的逻辑,比如在网页上按下一个按键后会显示什么等等。
二.涉及到的知识点
- 简单的 Web 服务器设计能力
- Java 操作 MySQL 数据库
- 数据库设计
- 响应风格 API
- gson 的使用
- HTTP 协议的理解
- Servlet 的使用
- 基于 md5 进行校验
- 软件测试的基本思想和方法
三.整体实现功能
核心就是实现一个 HTTP 服务器,实现对图片的增删查功能,同时搭配简单的页面辅助完成对图片的上传,显示,阅览功能。(实现一个 HTTP 服务器,然后用这个服务器来存储图片,针对每个图片提供一个唯一的 url,有了这个 url 之后就可以借助它把图片展示到其他网页上。)
- 项目模块划分:
本项目的结构主要分为两个部分,数据存储模块和服务器模块。使用MySQL存储图片的属性信息,将图片内容保存到本地磁盘,服务器向外提供诸如上传图片、获取单个图片的属性信息、获取所有图片的属性信息、和删除图片等API接口。
四.数据库设计
(1)imageId:图片的 id ,设置为自增主键
(2)imageName:图片名称
(3)size:图片大小
(4)uploadTime:图片的上传时间
(5)contentType:图片的类型(image/jpg,image/png等)
(6)path:图片在磁盘上的存储路径
(7)md5:一种计算校验和的算法(主要用来判断两张图片是否内容相同,即是否两张图片一样)
什么是 md5?
这是一种常见的字符串 hash 算法, 具有三个特性:
- 不管源字符串多长, 得到的最终 md5 值都是固定长度
- 源字符串稍微变化一点点内容, md5 值就会变化很大(降低冲突概率)
- 通过原字符串很容易计算得到 md5 值,但是根据 md5 推导出原字符串很难(几乎不可能)
- md5 常用于哈希算法,加密算法,校验和
五.服务器API设计
Json 是一种常见的数据格式组织方式,源于 JavaScript , 是一种键值对风格的数据格式。Java 中可以使用 Gson 库来完成 Json 的解析和构造. 在 Maven 中新增 对 Gson 库的依赖就可以使用 Json 了
<dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>2.8.2</version>
</dependency>
简单实验(创建一个 TestGson 类):
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;import java.util.HashMap;class Hero {public String name;public String skill1;public String skill2;public String skill3;public String skill4;
}public class TestGson {public static void main(String[] args) {// HashMap<String, Object> hashMap = new HashMap<>();
// hashMap.put("name", "曹操");
// hashMap.put("skill1", "剑气");
// hashMap.put("skill2", "三段跳");
// hashMap.put("skill3", "加攻击并吸血");
// hashMap.put("skill4", "加攻速");Hero hero = new Hero();hero.name = "曹操";hero.skill1 = "剑气";hero.skill2 = "三段跳";hero.skill3 = "加攻击并吸血";hero.skill4="加攻速";// 通过 map 转成 JSON 结构的字符串// 1. 创建一个 gson 对象Gson gson = new GsonBuilder().create();// 2. 使用 toJson 方法把键值对结构转成 JSON 字符串String str = gson.toJson(hero);System.out.println(str); //{"name":"曹操","skill1":"剑气","skill2":"三段跳","skill3":"加攻击并吸血","skill4":"加攻速"}}
}
1.新增图片(上传一个图片文件)
写一个简单的 html 文件来上传图片文件:
<html>
<head></head>
<body><form id="upload-form" action="image" method="post" enctype="multipart/form-data" ><input type="file" id="upload" name="upload" /> <br /><input type="submit" value="Upload" />
</form></body>
</html>
样式展示:
直接抓包,可以看到上传图片的 Http 请求的样式:
2. 设置各种响应格式
json 其最大的优点在于可以减少由于格式的错误导致程序异常,引用这个类可以自动严格按照JSON语法规则(syntax rules)创建JSON text。
(1)上传图片
假设上传图片的 HTTP 请求:
POST /image
Content-Type: application/x-www-form-urlencoded------WebKitFormBoundary5muoelvEmAAVUyQB
Content-Disposition: form-data; name="filename"; filename="图标.jpg"
Content-Type: image/jpeg......[图片正文].....
a)上传图片成功后的 HTTP 响应
HTTP/1.1 200 OK
{"ok": true
}
b)上传图片失败后的 HTTP 响应
HTTP/1.1 200 OK
{"ok": false,"reason":"失败原因"
}
(2)查看所有图片的属性信息
假设查看所有图片的属性信息的请求格式为:
GET /image
a)成功后的 HTTP 响应
HTTP/1.1 200 OK
[{"imageId": 1,"imageName": "1.png","contentType": "image/png","md5": "[md5值]"......},{"imageId": 1,"imageName": "1.png","contentType": "image/png","md5": "[md5值]"......},{"imageId": 1,"imageName": "1.png","contentType": "image/png","md5": "[md5值]"......}
]
b)失败后的 HTTP 响应
HTTP/1.1 200 OK
{"ok": false,"reason":"失败原因"
}
(3)查看指定图片的属性信息
假设请求格式为:
GET /image?imageId=10
a)成功后的 HTTP 响应
HTTP/1.1 200 OK
{"imageId": 1,"imageName": "1.png","contentType": "image/png","md5": "[md5值]"......
}
b)失败后的 HTTP 响应
HTTP/1.1 200 OK
{"ok": false,"reason":"失败原因"
}
(4)删除图片
假设请求格式为:
DELETE /image?imageId=10
a)成功后的 HTTP 响应
HTTP/1.1 200 OK
{"ok": true
}
b)失败后的 HTTP 响应
HTTP/1.1 200 OK
{"ok": false,"reason":"失败原因"
}
(5)查看指定图片的内容
假设请求为:
GET /imageShow?imageId=10
a)成功后的 HTTP 响应
HTTP/1.1 200 OK
content-type: image/png [响应 body 中为 图片的内容数据]
b)失败后的 HTTP 响应
HTTP/1.1 200 OK
{"ok": false,"reason":"失败原因"
}
六.创建一个JavaWeb 项目
(1)Maven 是什么?
- Maven是 Apache 下的一个纯 Java 开发的开源项目,是一个项目构建和管理的工具;它提供了帮助管理、 构建、文档、报告、依赖、发布、分发的方法。可以方便的编译代码、进行依赖管理、管理二进制库等等。Maven是专门用于构建和管理Java相关项目的工具
(2)使用 Maven 的好处?
- maven 的目标是完成项目构建解决一切繁琐事宜。我们具体关注它的以下功能:
*提供一个标准的项目工程目录
*提供项目描述
*提供强大的版本管理工具
*可以分阶段的进行构建过程
*提供了丰富的插件库使用
七.封装数据库操作
dao 包下封装对数据库的相关操作
1.创建 DBUtil 类::创建一个单例类来辅助创建连接,关闭连接
private static final String URL = "jdbc:mysql://127.0.0.1:3306/java_image_server?characterEncoding=utf8&useSSL=true"; //数据库连接字符串
private static final String USERNAME = "root"; //用户名
private static final String PASSWORD = "123456"; //密码
类包含的三个方法:
// 这是一个获取单例的方法
public static DataSource getDataSource() { }
// 获取数据库链接
public static Connection getConnection() { }
// 关闭链接
public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet) {}
类的实现代码:
package dao;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/java_image_server?characterEncoding=utf8&useSSL=true";private static final String USERNAME = "root";private static final String PASSWORD = "123456";private static volatile DataSource dataSource = null; //管理数据源/** (单例模式)* 通过这个方法来创建一个 DataSource 的实例* @return*/public static DataSource getDataSource() {if (dataSource == null) { //双重校验锁// 此处的 synchronized 其实就是给当前这一小块加锁// synchronized 要么修饰一个方法,要么得传个对象,此处修饰整个方法的话锁的粒度太大synchronized (DBUtil.class) {if (dataSource == null) {dataSource = new MysqlDataSource();MysqlDataSource tmpDataSource = (MysqlDataSource) dataSource;tmpDataSource.setURL(URL);tmpDataSource.setUser(USERNAME);tmpDataSource.setPassword(PASSWORD);}}}return dataSource;}/*** 这个方法用来获取一个数据库的连接对象* @return*/public static Connection getConnection() {try {return getDataSource().getConnection();} catch (SQLException e) {e.printStackTrace();}return null;}/*** 这个方法用来关闭连接* @param connection* @param statement* @param resultSet*/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.创建 Image 类
package dao;/*** 一个 Image 对象:和数据库表的设计相对应*/
public class Image {private int imageId;private String imageName;private int size;private String uploadTime; //图片上传时间private String contentType; //图片的格式:image.jpg/image.png 等等private String path; //图片应该往磁盘写的位置private String md5; //校验和机制(用于判断两张图片的内容是否相同)public int getImageId() {return imageId;}public void setImageId(int imageId) {this.imageId = imageId;}public String getImageName() {return imageName;}public void setImageName(String imageName) {this.imageName = imageName;}public int getSize() {return size;}public void setSize(int size) {this.size = size;}public String getUploadTime() {return uploadTime;}public void setUploadTime(String uploadTime) {this.uploadTime = uploadTime;}public String getContentType() {return contentType;}public void setContentType(String contentType) {this.contentType = contentType;}public String getPath() {return path;}public void setPath(String path) {this.path = path;}public String getMd5() {return md5;}public void setMd5(String md5) {this.md5 = md5;}@Overridepublic String toString() {return "Image{" +"imageId=" + imageId +", imageName='" + imageName + '\'' +", size=" + size +", uploadTime='" + uploadTime + '\'' +", contentType='" + contentType + '\'' +", path='" + path + '\'' +", md5='" + md5 + '\'' +'}';}
}
3.创建 ImageDao 类
package dao;
public class ImageDao {//把一个 image 对象插入到数据库中 public void insert(Image image){}//查找数据库中所有图片的属性信息,并存储在 List 中public List<Image> selectAll(){return null; }//根据 imageId 查找指定图片的属性信息public Image selectOne(int imageId){return null;}//根据 imageId 删除指定图片public void delete(int imageId){}//按照 md5 的值来查找数据库中的 “图片”(图片的 md5 值是唯一的)public Image selectByMd5(String md5){return null;}
}
类的实现代码:
package dao;import common.JavaImageServerException;import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;/*** 用来和数据库打交道的一个类*/public class ImageDao {/*** 把一个 Image 对象插入到数据库中*/public void insert(Image image){// 1.获取数据库连接Connection connection=DBUtil.getConnection();// 2.创建并拼装 SQL 语句String sql="insert into image_table values(null,?,?,?,?,?,?)"; // imageId 是自增主键,不需要赋值PreparedStatement statement=null;try {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(); //插入一条记录,影响到的行数应该为1if(ret!=1){//说明程序插入失败了,需要抛出一个异常throw new JavaImageServerException("插入数据库出错");}} catch (SQLException | JavaImageServerException e) {e.printStackTrace();}finally {// 4.关闭连接和statement对象(无论插入是否成功,都需要关闭连接)DBUtil.close(connection,statement,null);}}/*** 查找数据库中所有图片的属性信息,并存储在 List 列表中*/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;try {// 3.执行 SQL 语句statement=connection.prepareStatement(sql);resultSet =statement.executeQuery(); //得到查询结果集// 4.处理结果集 resultSet (把结果放到我们的 List 列表当中去)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 对象,resultSet 对象DBUtil.close(connection,statement,resultSet);}return null; //出错的话返回空的结果集}/*** 根据 imageId 查找指定图片的属性信息* @param imageId* @return*/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;try {// 3.执行 SQL 语句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; //如果失败的话返回null}/*** 根据 imageId 删除指定图片* @param imageId*/public void delete(int imageId){// 1. 获取数据库连接Connection connection=DBUtil.getConnection();// 2. 拼装 SQL 语句String sql="delete from image_table where imageId = ?";PreparedStatement statement=null;try {// 3. 执行 SQL 语句statement=connection.prepareStatement(sql);statement.setInt(1,imageId);int ret=statement.executeUpdate(); // 删除一条记录,那么影响的行数应该为1if(ret!=1){throw new JavaImageServerException("删除操作出错!");}} catch (SQLException | JavaImageServerException e) {e.printStackTrace();}finally {// 4. 关闭连接DBUtil.close(connection,statement,null);}}/*** 按照 md5 的值来查找数据库中的 “图片”Image对象(图片的 md5 值是唯一的)* @param md5* @return*/public Image selectByMd5(String md5){// 1.获取数据库连接Connection connection=DBUtil.getConnection();// 2.创建并拼装 SQL 语句String sql="select * from image_table where md5 = ?";PreparedStatement statement=null;ResultSet resultSet=null;try {// 3.执行 SQL 语句statement=connection.prepareStatement(sql);statement.setString(1,md5); //md5 在数据库中是按字符串形式存储的,所以要调用 setString() 方法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;}// 如果你的数据库在云服务器上, 没有在本地服务器上. 这个程序在本地直接运行就会无法访问数据库// 此处需要把这个程序部署到云服务器上执行,才能看到效果// 打一个 jar 包, 把 jar 包拷贝到云服务器上, 就可以执行了public static void main(String[] args) {// 用于进行简单的测试// 1. 测试插入数据是否正确/* Image image = new Image();image.setImageName("1.png");image.setSize(100);image.setUploadTime("20200217");image.setContentType("image/png");image.setPath("./data/1.png");image.setMd5("11223344");ImageDao imageDao = new ImageDao();imageDao.insert(image);*/// 2.测试查找所有图片信息/* ImageDao imageDao=new ImageDao();List<Image> images=imageDao.selectAll();System.out.println(images);*/// 3.测试根据 imageId 查找指定图片的信息/* ImageDao imageDao=new ImageDao();Image image=imageDao.selectOne(1);System.out.println(image);*/// 4.测试删除图片/* ImageDao imageDao=new ImageDao();imageDao.delete(1);*/}
}
七.实现 Servlet
首先创建一个 api 包,在这个包中创建两个 Servlet 类,这两个Servlet 类都继承自 HttpServlet类,然后重写了对应的 doxxx() 方法。
一个用来完成图片的增删查操作(ImageServlet类)
一个用来展示图片的详细内容(ImageShowServlet类)
注意:要把这两个Servlet 类都要配置到 web.xml 文件中,其中类名要写完整的带包名的名字
1.创建 ImageServlet 类
这个类的 doPost() 对应插入图片操作, doGet() 对应查看图片属性操作,doDelete() 对应删除图片操作。
package api;public class ImageServlet extends HttpServlet {//查看图片属性信息: 既能查看所有图片的属性信息, 也能查看指定图片的属性信息protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { }// 查看所有图片的属性信息 private void selectAll(HttpServletRequest req, HttpServletResponse resp) throws IOException { }// 查看指定图片的属性信息private void selectOne(String imageId, HttpServletResponse resp) throws IOException { }//上传图片 ( 包括上传到数据库,包括写到磁盘对应位置 )@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {}// 删除指定图片@Overrideprotected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {}
}
(1)实现 ImageServlet.doPost()方法
这个方法对应上传图片操作,这里需要用到 Commons FileUpload,在Maven 仓库中找到该包,然后添加到依赖中去
<dependency><groupId>commons-fileupload</groupId><artifactId>commons-fileupload</artifactId><version>1.4</version>
</dependency>
(2)实现 ImageServlet.doGet()方法
这里要分成两种情况, 一个是获取所有图片的属性信息, 一个是获取单个图片的属性信息。根据请求中是否带有 imageId 参数来决定。
类的代码实现:
package api;import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import dao.Image;
import dao.ImageDao;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;public class ImageServlet extends HttpServlet {/*** 查看图片属性: 既能查看所有图片的属性信息, 也能查看指定图片的属性信息* @param req* @param resp* @throws ServletException* @throws IOException*/@Overrideprotected 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);}}/*** 查看所有图片属性信息* @param req* @param resp* @throws IOException*/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);// 3.写响应 (在浏览器界面就可以显示 JSON 格式的字符串了)resp.getWriter().write(jsonData);}/*** 查看指定图片属性信息* @param imageId* @param resp* @throws IOException*/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.创建 Gson 对象,使用 gson 对象吧查到的数据转成 json 格式Gson gson=new GsonBuilder().create();String jsonData=gson.toJson(image);// 3.写响应 (在浏览器界面就可以显示了)resp.getWriter().write(jsonData);}/*** 上传图片 ( 包括上传到数据库,包括写到磁盘对应位置 )* @param req* @param resp* @throws ServletException* @throws IOException*/@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 1.获取图片的属性信息,并且存入数据库// a)需要创建一个 factory 对象和 upload 对象,这是为了获取图片的属性信息而做的准备工作,是固定的逻辑用法FileItemFactory factory=new DiskFileItemFactory();ServletFileUpload upload=new ServletFileUpload(factory);// 理论上来说,HTTP 支持一个请求中同时上传多个文件,FileItem 就代表上传的一个文件对象,多个文件用 List 组织List<FileItem> items=null;try {// b)通过 upload 对象进一步解析请求(解析 HTTP 请求中的 body 部分的内容)items=upload.parseRequest(req);} catch (FileUploadException e) {// 出现异常说明上传图片请求解析出错了,需要告诉客户端出现的具体错误是什么e.printStackTrace();resp.setContentType("application/json; charset=utf-8");resp.getWriter().write("{ \"ok\":false,\"reason\":\"上传图片请求解析出错\" }");return;}// c)把 List<FileItem> 中的属性提取出来,转换成 Image 对象,这样才能保存到数据库当中去FileItem fileItem=items.get(0); //我们一次只上传一个,一个图片就是一个 FileItemImage image=new Image();image.setImageName(fileItem.getName());image.setSize((int)fileItem.getSize());//手动获取一下当前日期,并转成格式化日期,yyyyMMdd => 20200218SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyyMMdd");image.setUploadTime(simpleDateFormat.format(new Date()));image.setContentType(fileItem.getContentType()); //设置图片本身的类型image.setMd5(DigestUtils.md5Hex(fileItem.get()));// md5 值是十六进制的字符串image.setPath("./image/" + image.getMd5());// 最后存到数据库中ImageDao imageDao = new ImageDao();// 还没有插入前,先看看数据库中是否存在相同的 Md5 值的图片, 不存在返回 nullImage existImage = imageDao.selectByMd5(image.getMd5());//不管怎样都往数据库中插入,往数据库中可以插入多张重复的图片,但是尽管多张,但只在磁盘写一份就行imageDao.insert(image);// 2.如果没有插入这张图片前,磁盘没有存在这张图片,即 existImage==null,获取图片的内容信息,并且写入磁盘文件if (existImage == null) {File file = new File(image.getPath()); //往磁盘的那个位置存图片try {fileItem.write(file); //把 fileItem 对应的这个图片文件写到相应位置} 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 }");// HTTP 采用302 可以重定向 到 index.html 页面// 本来显示的是 JSON 格式的数据,但是结果不直接,需要上传成功后跳转到主页面 index.htmlresp.sendRedirect("index.html");}/*** 删除指定图片* @param req* @param resp* @throws ServletException* @throws IOException*/@Overrideprotected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.setContentType("application/json; charset=utf-8");// 1.先获取到请求中的 imageIdString imageId=req.getParameter("imageId");if(imageId==null||imageId.equals("")){resp.setStatus(200);resp.getWriter().write("{ \"ok\":false,\"reason\":\"解析请求中的 imageId 出错\" }");return;}// 2.创建 ImageDao 对象,先查看该 imageId 对应的图片是否存在,若存在则要删除// 既要删除数据库中的内容,也要删除磁盘中存储的对应图片,因此要知道该 imageId 对应的图片对象的存储路径ImageDao imageDao=new ImageDao();Image image=imageDao.selectOne(Integer.parseInt(imageId));if(image==null){ //说明输入的 imageId 是错误的,数据库中没有这个 imageId 对应的图片resp.getWriter().write("{ \"ok\":false,\"reason\":\"传入的 imageId 在该数据库中没有对应值\" }");return;}// 3.走到这说明数据库中有该 imageId 对应的图片,那么就要删除数据库中的记录imageDao.delete(Integer.parseInt(imageId));// 4.删除本地磁盘文件(注意:数据库中可能有多张相同的图片,但是磁盘中只写入了一张图片,删除要注意)// 刚刚已经从数据库中删除了那张图片,若此时数据库中还存有相同的图片,那么不应该删除磁盘图片文件// 相同图片的 md5 值是相同的Image existImage=imageDao.selectByMd5(image.getMd5());// 如果删除完毕后,数据库中没有相同的图片了,那么就删除磁盘文件,否则就不删除if(existImage==null) {File file = new File(image.getPath());file.delete();resp.getWriter().write("{ \"ok\":true }");}}
}
2.实现 ImageShowServlet 类
类的实现代码:
package api;import dao.Image;
import dao.ImageDao;import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashSet;public class ImageShowServlet extends HttpServlet {static private HashSet<String> whiteList=new HashSet<>();static{whiteList.add("http://127.0.0.1:8080/java_image_server/index.html");//你想要哪个网站可以访问你的图片,在 whiteList 中添加一下即可// http://127.0.0.1:8080/java_image_server/imageShow?imageId=22 就不可以访问你的图片}@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {String referer=req.getHeader("Referer");// 如果你的白名单中没有存储这个网址,那就无法访问你的图片内容if(!whiteList.contains(referer)){resp.setContentType("application/json; charset=utf-8");resp.getWriter().write("{ \"ok\":false,\"reason\":\"该网站未授权访问\" }");return;}// 1.已授权,从请求中解析出 imageIdString imageId=req.getParameter("imageId");if(imageId==null||imageId.equals("")){resp.setContentType("application/json; charset=utf-8");resp.getWriter().write("{ \"ok\":false,\"reason\":\"传入的 imageId 是错误的,解析失败\" }");return;}// 2.根据 imageId 查找数据库中对应的图片,得到对应图片的属性信息 Path// (得到 path: 需要知道图片在磁盘上的存储路径 )ImageDao imageDao=new ImageDao();Image image=imageDao.selectOne(Integer.parseInt(imageId));if(image==null){ //没有该 imageId 对应的图片resp.getWriter().write("{ \"ok\":false,\"reason\":\"传入的 imageId 是错误的,没有对应的图片\" }");return;}// 3.path 已经得到,根据路径打开图片文件,读取其中的图片内容,写入到响应对象中resp.setContentType(image.getContentType());File file=new File(image.getPath());FileInputStream fileInputStream=new FileInputStream(file); //由于图片内容是二进制文件,应该使用字节流的方式来读取文件OutputStream outputStream=resp.getOutputStream(); //往响应中写byte[] buffer=new byte[1024];while(true){int len=fileInputStream.read(buffer); //把图片内容读到 buffer 字节数组中去if(len==-1){//文件读取结束(图片内容读取结束)break;}//此时已经读到了一部分数据,放到了 buffer 里,把 buffer 中的内容要写到响应对象中outputStream.write(buffer);}fileInputStream.close(); //关闭输入流outputStream.close(); //关闭输出流}
}
两个 Servlet 类已经实现,然后配置 web.xml 文件
八.写前端页面
1.使用 HTML 模板
直接在百度上搜索 “免费网页模板”, 能找到很多免费模板网站. 可以直接基于现成的漂亮的页面进行修改。
tips: 做减法比做加法更容易.
将网页模板解压缩, 拷贝到项目的 webapp 目录中,然后删除模板中不必要的目录和内置的图片
2.基于模板进行删减
修改之后的 html 代码为 (只截取修改部分的关键代码):
(1)head 标签的修改的
(2)页面的选择文件和提交按钮
注意:为了让 “提交按钮” 和前面的选择文件一样高, 这里加了一个style=“height:41px”
此处我们直接使用 style 属性调整样式, 这是一个简单粗暴的做法. 事实上专业的前端开发很少这样做, 因为代码的可维护性不好~~
(3)页面 Logo 的修改
(4)使用 Vue
Vue 是 JS 的框架,他做的核心工作是把页面显示的内容和 JS 中的代码相互关联到一起,修改 JS 中的变量就能很方便的影响到页面的显示情况。
基本用法:
var 声明这是一个变量,构造方法是使用了 JSON 形式的{} ,el 属性表示我要把当前这个对象关联上某一个 html 的标签(把 app 对象关联到 html 中一个 id=“app” 的标签)
例如:
添加的 JS 代码:
<script>var app=new Vue({// e1 属性表示我要把当前这个对象关联到某一个 html 的标签上(把 app 对象关联到 html 中的一个 id="app 的标签)el: '#app',data: {author:"Miao-hu",images: [ //一个集合数组]},methods:{// 请求格式: GET /imagegetImages(){$.ajax({ // ajax 是在JS中构造 http 请求发送给服务器的一种实现方式url:"image",type:"get",context:this,success:function (data,status) {//此处的代码在浏览器收到响应之后,才会执行到//参数中的 data 就相当于收到的 http 响应中的 body 部分this.images=data;$('#app').resize(); //触发浏览器,让它自动调整大小,让图片正确显示}})},// 请求格式: DELETE /image?imageId=remove(imageId){$.ajax({url:"image?imageId="+imageId,type:"delete",context:this,success:function (data,status) {this.getImages(); //只要删除图片,就重新获取一下服务器上剩余的图片,保证页面显示的图片数量正常alert("删除图片成功!"); //弹出一个对话框}})}}})app.getImages(); //项目只要访问 upload.html 就自动调用该方法
</script>
第一步: id=“app” 把 JS 代码和 该标签绑定
第二步:使用 v-bind:src 把图片的内容 通过 ImageShowServlet 接口获取到.
第三步:使用 {{image.imageName}} 表示图片的名称
第四步:v-for 能够循环访问一个数据
从服务器获取所有图片的数据:
在 methods 中新增获取所有图片数据的方法:
注意:
只要访问 index.html 文件就要加载服务器中的所有图片,所以只要访问 index.html 文件,就要调用该方法
(5)完善上传功能
当前的上传请求成功后会返回一个 JSON 格式的数据. 而我们需要的是直接能看到上传的效果,因此应该修改响应,直接重定向到 index.html 页面观察即可。
修改上传接口中的响应, 直接返回一个 302 响应, 重定向回主页即可。
修改 ImageServlet.doPost()方法
(6)实现删除图片
保证删除按钮的宽度和图片的宽度一样:style=“width:100%”
事件处理函数:
出现异常:
此时发现个问题, 点击删除按钮之后, 会触发预览图片效果。这是因为 JavaScript 的事件冒泡机制导致的。 一个标签接收到的事件会依次传给父级标签.
此处需要阻止 click 事件的冒泡机制. 采用的办法是:
Vue 中使用 v-on:click.stop 即可.
<button style="width:100%" v-on:click.stop="remove(image.imageId)" class="am-btn am-btn-success">删除</button>
此程序的事件冒泡机制:div 标签中有 3 个字标签
①点击 div 就会触发一个点击事件,div 就会处理这个点击事件并显示预览图
②点击 button 删除按钮的时候也会触发一个点击事件,触发删除操作
③点击 cilck 就会先被 button 处理,进行删除操作,然后顺着 button 的父标签,传递给 div 标签,然后 div 再处理一次,触发预览图效果
九.项目拓展点
1.实现基于白名单方式的简单防盗链
通过 HTTP 请求中的 Refer 字段判定是否是指定网站请求服务器图片,未授权网站就无法访问图片内容。
新增属性:
修改 ImageShowServlet.doGet ()方法:
2.基于 MD5 实现相同内容的图片在磁盘上只能存一份
整体思路:
●修改 dao 层代码,在 dao 层实现一个selectByMd5()的方法,根据 Md5 值来查找数据库中的图片信息(是否存在多张相同图片).
●修改上传图片代码,在磁盘上存储文件时先判定,该 md5 对应的图片是否已经存在于磁盘上了,若存在就不必再往磁盘写了.
●修改删除图片代码,先删除数据库记录,删除完毕后,看数据库中是否还存在刚刚相同的 md5 的记录.如果不存在,就删除磁盘文件.
在这里计算 md5 值我们引入依赖:
<dependency><groupId>commons-codec</groupId><artifactId>commons-codec</artifactId><version>1.14</version>
</dependency>
/*** 按照 md5 的值来查找数据库中的 “图片”Image对象(图片的 md5 值是唯一的)* @param md5* @return*/public Image selectByMd5(String md5){// 1.获取数据库连接Connection connection=DBUtil.getConnection();// 2.创建并拼装 SQL 语句String sql="select * from image_table where md5 = ?";PreparedStatement statement=null;ResultSet resultSet=null;try {// 3.执行 SQL 语句statement=connection.prepareStatement(sql);statement.setString(1,md5); //md5 在数据库中是按字符串形式存储的,所以要调用 setString() 方法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;}
十.效果展示图
图片服务器项目编写思路相关推荐
- 图片服务器项目编写设计
待到秋来九月八,我花开后百花杀 项目背景 项目所涉及知识点 服务器设计 数据库设计 服务器API(前后端交互接口)设计 涉及知识点 正式设计接口功能(约定HTTP协议格式) 编写源代码开发 数据库操作 ...
- 图片服务器项目测试用例
单元测试 这里只对imageDAO层进行了单元测试 public class ImageDAOTest {@Testpublic void queryCountByMd5() {ImageDAO im ...
- 课程设计题目:图片服务器项目的全面测试
源包地址:(32条消息) 软件测试课程设计或者大作业-图形服务器-软考文档类资源-CSDN文库 目录 注意:很多图都上传不上去,懒得上传了,自己打开源包里面啥都有 一.课程设计内容 1 二.课程设计 ...
- 图片服务器项目+测试总结
图片服务器项目: 网盘百度链接:https://pan.baidu.com/s/1YIGnhy6QxoLBzf4CGjtM2A 提取码:1111 项目分析+代码 1.项目分析 过程分析:请求:浏览器- ...
- 图床/图片服务器项目详细设计
项目目录 1.前言 1.1 项目背景 1.2 项目意义 1.3 涉及的知识点 2.总体设计 2.1 需求规定 2.1.1 软件功能的规定 2.1.1.1 系统功能 2.1.1.2 数据流图 2.1.1 ...
- Nginx 独立图片服务器的搭建
为什么需要独立图片服务器? 如果你留心的话,可以发现,现在主流的网站都是有单独的图片服务器的,例如,人人网的为rrimg,淘宝的为taobaocdn,下面还有很多的二级域名. 独立的图片服务器有诸多好 ...
- Nginx独立图片服务器搭建教程
Nginx独立图片服务器搭建教程 发布时间:2014-06-04编辑:脚本学堂 本文介绍了nginx独立图片服务器的搭建与配置教程,有需要的朋友参考下. 首先,为什么需要独立图片服务器? 现在主流的网 ...
- 零基础编写图片服务器(1)
1)在我们真正进行WEB开发的时候,编写代码之前,我们都要做两件非常重要的设计:数据库设计+前后端交互API接口的设计+JDBC操作+WEB服务器开发能力,我们还要实现一个简单的页面来进行展示当前的图 ...
- javaweb项目创建图片服务器
javaweb项目,创建tomcat图片服务器,用于保存用户上传的头像等图片信息.前期项目的新建和正常访问的调试不再说明,本文章仅讲解如何创建一个图片服务器. 1.WebContent下新建imgFi ...
最新文章
- Web容器启动中执行某个Java类
- 安装最新版git,git升级
- 获取电脑的唯一识别码_教你如何知道自己的电脑能够装黑苹果
- Java基础篇:封装、继承、多态三大特性
- hdfs mv命令_如何HDFS mv命令工作
- php在苹果手机上传不了图片大小,ThinkPHP后台上传图片无默认尺寸解决方法
- [EffectiveC++]item34:区分接口继承和实现继承
- vCenter 升级错误 VCSServiceManager 1603
- Vue 2.x 文件夹目录
- 项目方案-标书-文档等等编写规范
- java学习之流程控制
- 各种类型变量的定义以及赋值
- VC 定位窗体常用方法
- koa2 从入门到进阶之路 (五)
- split函数python_python有split函数吗
- oracle12c性能测试,Oracle12c IMO 测试
- 电脑W ndoWs未能启动怎么办,电脑出现windows未能启动怎么办
- 猪猪视频显示没有服务器,猪猪小视频软件
- 讲座 | lidar目标检测------图森未来CTO王乃岩
- 新生儿的一类(免费)疫苗(截止2019年)
热门文章
- 我们采访了这些游戏大神,这是他们给新人的建议
- 【金猿技术展】一种分布式 HTAP 数据库上基于索引的数据任意分布方法——为 HTAP 数据库实现 Collocation 优化...
- SQLite 的简单介绍
- SqlSugar-C#中数据库存储的框架
- U8g2菜单界面+按键操作在线模拟
- [4G5G专题-95]:MAC层- 调度 - 无线资源调度概述
- 设置font-family时需要注意的问题
- GOPS现场 | 大规模团队如何实现Jenkins的集中管理——对话龙智技术顾问
- centos8安装nexus
- mingw64安装和环境变量配置教程