项目:图片服务器

tech2023-09-15  97

文章目录

1 项目背景2 数据库设计3 服务器API设计3.1 Json3.2 新增图片3.3 查看所有图片属性3.4 查看指定图片属性3.5 删除指定图片3.6 查看指定图片内容 4 封装数据库操作4.1 创建 DBUtil 类4.2 创建 Image 类4.3 创建 ImageDao 类4.3.1 实现 ImageDao.insert 方法4.3.2 实现 ImageDao.selectAll 方法4.3.3 实现 ImageDao.selectOne 方法4.3.4 实现 ImageDao.delete 方法 5 实现Servlet5.1 创建 ImageServlet5.1.1 实现 ImageServlet.doPost5.1.2 实现 ImageServlet.doGet5.1.3 实现 doSelectAll5.1.4 实现 doSelectOne5.1.5 实现 ImageServlet.doDelete 5.2 创建 ImageShowServlet 6 写前端页面6.1 使用 HTML 模板6.2 使用 Vue.js6.3 实现展示图片6.4 完善上传功能6.5 实现删除图片 7 后续扩展点7.1 实现基于白名单方式的防盗链7.2 基于 MD5 实现相同内容图片只存一张 8 最终效果及总结

1 项目背景

有时候写博客时需要插入图片,提示可选择图片链接,于是就想设计一个存储图片的服务器,用来保存我们需要的图片,并且每一张图片对应唯一的URL地址,用户可直接使用URL将图片在网页上上传,方便我们使用。

2 数据库设计

数据存储模块,一方面在数据库中存储图片的属性信息,另一方面,将图片的正文信息以文件形式直接存储到磁盘上,所以数据库中记录一个 path 对应到磁盘上的文件。

建表语句:

CREATE TABLE `image_table` ( `imageId` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT, `imageName` varchar(50) DEFAULT NULL, `size` int(11) DEFAULT NULL, `uploadTime` varchar(50) DEFAULT NULL, `contentType` varchar(50) DEFAULT NULL, `path` varchar(1024) DEFAULT NULL, `md5` varchar(1024) DEFAULT NULL )

md5是什么?(MD5_百度百科) 这是一种常见的字符串 hash 算法,具有三个特性:

不管源字符串多长,得到的 md5 都是固定的长度;源字符串稍微变化一点点内容,md5 值会发生很大的改变;计算 md5 值的过程很简单,但是通过 md5 值几乎无法推测出源字符串。

查看表结构(desc image_table;)如下:

3 服务器API设计

3.1 Json

Json 是一种常见的数据格式组织方式,源于 JavaScript ,是一种键值对风格的数据格式。Java 中可以使用 Gson 库来完成 Json 的解析和构造。

在 Maven 中新增 Gson 的依赖:

<dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.8.2</version> </dependency>

3.2 新增图片

请求: POST /image Content-Type: application/x-www-form-urlencoded ...[正文内容,包含图片自身的一些信息]... ...[图片正文的二进制内容]... 响应: 上传成功: HTTP/1.1 200 OK { "ok": true, } 上传失败: HTTP/1.1 200 OK { "ok": false, "reason":"具体失败原因" }

3.3 查看所有图片属性

请求: GET /image 响应: 获取成功: HTTP/1.1 200 OK [ { "imageId": 1, "imageName": "1.png", "contentType": "image/png", "md5": "[md5值]" ... }, { ... }, ... ] 获取失败: HTTP/1.1 200 OK { "ok": false, "reason":"具体失败原因" }

3.4 查看指定图片属性

请求: GET /image?imageId=1 响应: 获取成功: HTTP/1.1 200 OK { "imageId": 1, "imageName": "1.png", "contentType": "image/png", "md5": "[md5值]", ... } 获取失败: HTTP/1.1 200 OK { "ok": false, "reason":"具体失败原因" }

3.5 删除指定图片

请求: DELETE /image?imageId=1 响应: 删除成功: HTTP/1.1 200 OK { "ok": true } 删除失败: HTTP/1.1 200 OK { "ok": false, "reason":"具体失败原因" }

3.6 查看指定图片内容

请求: GET /imageShow?imageId=1 响应: 响应成功: HTTP/1.1 200 OK content-type: image/png [响应 body 中为 图片内容 数据] 响应失败: HTTP/1.1 200 OK { "ok": false, "reason":"具体失败原因" }

4 封装数据库操作

4.1 创建 DBUtil 类

创建一个单例类辅助创建连接,其中 URL 为数据库连接字符串,用户名和密码都是固定的。

private static final String URL = "jdbc:mysql://127.0.0.1:3306/java_image_server?characterEncoding=utf8&useSSL=true"; private static String USERNAME ="root"; private static String PASSWORD ="";

这个类主要包含三个方法:

//这是一个获取单例的方法 public static DataSource getDataSource(){ } //获取链接 public static Connection getConnection() { } //关闭链接 public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet) { }

类的实现代码:

public class DBUtil { private static final String URL = "jdbc:mysql://127.0.0.1:3306/java_image_server?characterEncoding=utf8&useSSL=true"; private static String USERNAME ="root"; private static String PASSWORD =""; //③加 volatile ,保持属性是内存可见的,使第一个线程进行操作后,其他线程可以及时看到更新 private static volatile DataSource dataSource=null; //线程安全问题:①加锁 ②双重判断 ③加volatile public static DataSource getDataSource(){ //通过这个方法创建 DataSource 的实例 if(dataSource==null){//②双重判断(加锁操作是一种比较耗时、低效的操作,双重判断就是希望不要频繁的操作) synchronized (DBUtil.class){//①加锁 if(dataSource==null){ dataSource=new MysqlDataSource(); MysqlDataSource tmpDataSource=(MysqlDataSource)dataSource; tmpDataSource.setURL(URL); tmpDataSource.setUser(USERNAME); tmpDataSource.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(); } } }

4.2 创建 Image 类

//对应到一个图片对象(包含图片的相关属性) public class Image { private int imageId; private String imageName; private int size; private String uploadTime; private String contentType; 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; } @Override public String toString() { return "Image{" + "imageId=" + imageId + ", imageName='" + imageName + '\'' + ", size=" + size + ", uploadTime='" + uploadTime + '\'' + ", contentType='" + contentType + '\'' + ", path='" + path + '\'' + ", md5='" + md5 + '\'' + '}'; } }

4.3 创建 ImageDao 类

public class ImageDao { public boolean insert(Image image) { return false; } public Image[] selectAll() { return null; } public Image selectOne(int imageId) { return null; } public boolean delete(int imageId) { return false; } }
4.3.1 实现 ImageDao.insert 方法
/** * 把 image 对象插入到数据库中 * @param image */ public void insert(Image image){ //1、获取数据库连接 Connection connection=DBUtil.getConnection(); //2、创建并拼装 SQL 语句 //imageId 是自增主键,数据库会自动地生成值; ?表示占位符,在后面会被替换成相关的属性 String sql="insert into image_table values(null,?,?,?,?,?,?)"; 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();//更新数据库 if(ret!=1){ //程序出现问题,抛出一个异常 throw new JavaImageServerException("插入数据库出错!"); //throw 之后程序会直接走到 catch 处,所以关闭连接操作必须放到 finally 中,否则会出问题 } } catch (SQLException | JavaImageServerException e) { e.printStackTrace(); }finally { //4、关闭连接和 statement 对象 DBUtil.close(connection,statement,null); } }

测试一下 insert 方法:

public static void main(String[] args) {//用于简单的测试 //1、测试插入数据 Image image=new Image(); image.setImageName("1.png"); image.setSize(100); image.setUploadTime("20200820"); image.setContentType("image/png"); image.setPath("./data/1.png"); image.setMd5("11223344"); ImageDao imageDao=new ImageDao(); imageDao.insert(image); }

在数据库中查看是否插入成功。

4.3.2 实现 ImageDao.selectAll 方法
/** * 查找数据库中的所有图片的信息 * @return */ 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、关闭连接 DBUtil.close(connection,statement,resultSet); } return null; }

测试一下selectAll 方法:

public static void main(String[] args) {//用于简单的测试 //2、测试查找所有图片信息 ImageDao imageDao=new ImageDao(); List<Image> images=imageDao.selectAll(); System.out.println(images); }
4.3.3 实现 ImageDao.selectOne 方法
/** * 根据 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; //3、执行 SQL 语句 try { statement=connection.prepareStatement(sql); statement.setInt(1,imageId); resultSet=statement.executeQuery(); //4、处理结果集 if(resultSet.next()){//查找结果只有一条,所以用 if / while 都可以 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、关闭连接 DBUtil.close(connection,statement,resultSet); } return null; }

测试一下 selectOne 方法:

public static void main(String[] args) {//用于简单的测试 //3、测试查找指定图片信息 ImageDao imageDao=new ImageDao(); Image image=imageDao.selectOne(1); System.out.println(image); }
4.3.4 实现 ImageDao.delete 方法
/** * 根据 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; //3、执行 SQL 语句 try { statement=connection.prepareStatement(sql); statement.setInt(1,imageId); int ret=statement.executeUpdate(); if(ret!=1){ throw new JavaImageServerException("删除数据库操作失败"); } } catch (SQLException | JavaImageServerException e) { e.printStackTrace(); }finally { //4、关闭连接 DBUtil.close(connection,statement,null); } }

测试一下 delete 方法:

public static void main(String[] args) {//用于简单的测试 //4、测试删除指定图片 ImageDao imageDao=new ImageDao(); imageDao.delete(1); }

在数据库中查看是否删除成功。

5 实现Servlet

首先在项目根目录下创建一个 servlet 包,在这个包中创建两个 Servlet 类,一个用来完成图片的增删改查 (ImageServlet) ,一个用来展示图片的详细内容 (ImageShowServlet) 。

5.1 创建 ImageServlet

public class ImageServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.doGet(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.doPost(req, resp); } @Override protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.doDelete(req, resp); } }

同时要记得把这个类加到 web.xml 中,其中类名要写完整的带包的名字。

<servlet> <servlet-name>ImageServlet</servlet-name> <servlet-class>api.ImageServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>ImageServlet</servlet-name> <url-pattern>/image</url-pattern> </servlet-mapping>
5.1.1 实现 ImageServlet.doPost

该方法对应上传图片,这里需要用到 Commons FileUpload, 可以在 Maven 仓库中找到这个包, 并且使用 maven 下载。

<dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.4</version> </dependency>

用 upload.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>

实现 doPost 方法:

/** * 上传图片 * @param req * @param resp * @throws ServletException * @throws IOException */ @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // resp.setContentType("text/html;charset=utf-8");//让浏览器以 UTF-8 的方式解析 // resp.setStatus(200); // resp.getWriter().write("hello"); //1、获取图片的属性信息,并且存入数据库 //(1)需要创建一个 factory 对象和 upload 对象,这是为了获取图片属性做的准备工作(固定的逻辑) FileItemFactory factory=new DiskFileItemFactory(); ServletFileUpload upload=new ServletFileUpload(factory); //(2)通过 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; } //(3)把 FileItem 中的属性提取出来,转换成 Image 对象,才能保存到数据库中 FileItem fileItem=items.get(0);// 当前只考虑一张图片的情况 Image image=new Image(); image.setImageName(fileItem.getName()); image.setSize((int)fileItem.getSize()); // 手动获取一下当前的日期,并转换成格式化日期,yyyyMMdd==>年月日 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/"+System.currentTimeMillis()+"_"+image.getImageName()); 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("index.html"); }

验证该方法,可以使用刚写的 upload.html ,上传一张图片,检查服务器响应是否正确,数据库是否写入成功,图片文件是否上传成功。

5.1.2 实现 ImageServlet.doGet

这里分两种情况,一个是获取所有图片信息,一个是获取单个图片信息,根据请求中是否带有 imageId 参数来决定。

/** * 查看图片属性:既能查看所有,也能查看指定图片 * @param req * @param resp * @throws ServletException * @throws IOException */ @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //HttpServletRequest req —— 请求(方法,URL,各种header,body),包含了请求中的所有信息 //HttpServletResponse resp —— 响应(状态码,各种header,body),要生成的结果就放到里面去 //当前这个 doGet 方法就是要根据请求,生成响应 //在网页上显示一个 hello world ,应该修改 “响应”(响应的body部分) // resp.setStatus(200); // resp.getWriter().write("hello");//这个代码就是把 hello 这个字符串放到 http 响应的 body 中了 //考虑到查看所有图片属性和查看指定图片属性 //通过 URL 中是否带有 imageId 参数来进行区分 //存在 imageId 查看指定图片属性,否则就查看所有图片属性 String imageId=req.getParameter("imageId");//得到的是 String 类型的数据,如果 URL 中不存在 imageId 那么返回 null if(imageId==null||imageId.equals("")){//不存在 imageId 或者 imageId 为空字符串 //查看所有图片属性 selectAll(req,resp); }else{ //查看指定图片属性 selectOne(imageId,resp); } }
5.1.3 实现 doSelectAll
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); }
5.1.4 实现 doSelectOne
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));//当前得到的 imageId 还是 String 类型,还应该转换一下它的类型 //2、使用 gson 把查到的数据转换成 json 格式,并写回给响应对象 Gson gson=new GsonBuilder().create(); String jsonData=gson.toJson(image); resp.getWriter().write(jsonData); }
5.1.5 实现 ImageServlet.doDelete
/** * 删除指定图片 * @param req * @param resp * @throws ServletException * @throws IOException */ @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(imageId==null){ //此时请求中传入的 imageId 在数据库中不存在 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}"); }

5.2 创建 ImageShowServlet

/** * 查看指定图片内容 * @param req * @param resp * @throws ServletException * @throws IOException */ @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //1、解析出 imageId String 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 查找数据库,得到对应图片的属性信息(需要知道图片存储的路径) ImageDao imageDao=new ImageDao(); Image image=imageDao.selectOne(Integer.parseInt(imageId)); //3、根据路径打开文件,读取其中的内容,写入到响应对象中 resp.setContentType(image.getContentType()); File file=new File(image.getPath()); // 由于图片是二进制文件,应该使用字节流的方式读取文件 OutputStream outputStream=resp.getOutputStream(); FileInputStream fileInputStream=new FileInputStream(file); byte[] buffer=new byte[1024]; while(true){ int len=fileInputStream.read(buffer); if(len==-1){ //文件读取结束 break; } //此时已经读到一部分数据,放到 buffer 里,把 buffer 中的内容写到响应对象中 outputStream.write(buffer); } fileInputStream.close(); outputStream.close(); }

修改 web.xml :

<servlet> <servlet-name>ImageShowServlet</servlet-name> <servlet-class>api.ImageShowServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>ImageShowServlet</servlet-name> <url-pattern>/imageShow</url-pattern> </servlet-mapping>

6 写前端页面

6.1 使用 HTML 模板

直接在网上搜索免费的网页模板,将其解压缩,拷贝到项目的 webapp 目录中。删除模板中不需要的部分,保留自己所需要的部分。

6.2 使用 Vue.js

(参考 Vue 文档)

创建 Vue 对象:

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> var app = new Vue({ el: '#app', data: { message: 'Hello Vue!' } })

6.3 实现展示图片

构造数据:

修改 js 代码:

var app = new Vue({ el:'#app', data: { images: [ { imageId: 1, imageName: "1.png", contentType: "image/png", md5: "aabbccdd", }, { imageId: 2, imageName: "2.png", contentType: "image/png", md5: "aabbccdd", } ] }, methods: { }, });

修改 html 代码,和数据关联:

使用 v-bind:src 把图片的 src 通过 imageShow 接口获取到使用 {{image.imageName}} 表示图片标题 <div class="am-g am-g-fixed blog-fixed blog-content" id="app"> <figure data-am-widget="figure" class="am am-figure am-figure-default " data-am-figure="{ pureview: 'true' }"> <div id="container"> <div v-for="image in images"> <img style="width: 200px;height: 200px" v-bind:src="'imageShow?imageId='+image.imageId"> <h3>{{image.imageName}}</h3> </div> </div> </figure> </div>

从服务器获取数据:

在 method 中新增获取所有图片的方法:

getImages(){ $.ajax({ url:"image", type:"get", context:this, success:function(data,status) { //此处的代码在浏览器收到响应后,才会执行到 //参数中的 data 就相当于收到的 HTTP 响应中的 body 部分 this.images=data; $('#app').resize(); } }) } //页面加载时调用 app.getImages();

部署到服务器上,测试效果。

6.4 完善上传功能

当前的上传请求会返回一个 JSON 格式的数据,而我们更需要的是直接能看到上传的效果,所以修改上传接口的响应,直接返回一个 302 响应,重定向回主页。

修改 ImageServlet.doPost ,在上传成功代码最后,加上一个重定向。

resp.sendRedirect("index.html");

6.5 实现删除图片

图片下面新增删除按钮:

<button style="width: 100%" v-on:click="remove(image.imageId)" class="am-btn am-btn-success">删除</button>

实现事件处理函数:

remove(imageId){ $.ajax({ url:"image?imageId=" + imageId, type:"delete", context:this, success:function (data,status) { this.getImages(); //弹出对话框 alert("删除成功!"); } }) }

验证删除效果。

阻止点击事件冒泡:

此时发现个问题, 点击删除按钮之后, 会触发预览图片效果。这是因为 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>

7 后续扩展点

7.1 实现基于白名单方式的防盗链

通过 HTTP 中的 refer 字段判定是否是指定网站请求图片,修改 ImageShowServlet.doGet 方法。

新增属性:

static private HashSet<String> whiteList=new HashSet<>(); static { whiteList.add("http://127.0.0.1:8085/java_image_server/index.html"); }

新增以下逻辑:

String referer=req.getHeader("Referer"); if(!whiteList.contains(referer)){ resp.setContentType("application/json;charset=utf-8"); resp.getWriter().write("{\"ok\":false,\"reason\":\"未授权的访问\"}"); return; }

7.2 基于 MD5 实现相同内容图片只存一张

整体思路:

修改上传图片代码, 使用 md5 作为文件名;修改 DAO 层代码, 在 DAO 层实现一个 selectByMD5 方法, 根据 MD5 来查找数据库中的图片信息;修改上传图片代码, 存储文件时先判定, 该 md5 对应的文件是否存在, 存在就不必写磁盘了;修改删除图片代码, 先删除数据库记录, 删除完毕后, 看数据库中是否存在相同 md5 的记录. 如果不存在, 就删除磁盘文件。

实现计算 MD5:

修改 pom.xml ,引入依赖

<!-- https://mvnrepository.com/artifact/commons-codec/commons-codec --> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.14</version> </dependency>

修改 ImageServlet.doPost 方法,实现计算 MD5。

image.setMd5(DigestUtils.md5Hex(fileItem.get())); image.setPath("./image/"+image.getMd5());

修改 ImageDao :

新增方法 selectByMD5:

//通过 MD5 查找数据库内容 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; //3、执行 SQL 语句 try { statement=connection.prepareStatement(sql); statement.setString(1,md5); resultSet=statement.executeQuery(); //4、处理结果集 if(resultSet.next()){//查找结果只有一条,所以用 if / while 都可以 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、关闭连接 DBUtil.close(connection,statement,resultSet); } return null; }

根据 MD5 决定写入文件:

修改 ImageServlet.doPost 方法,如果该 MD5 值的文件不存在, 才真的写入磁盘;如果该 MD5 值的文件存在,则直接使用原来的文件,不必再写一次磁盘。

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("index.html");

根据 MD5 决定删除文件:

多个图片对应一个文件,删除任何一个图片,都会导致文件被删除,该怎么办呢?

其实通过 selectByMd5 值对应的图片在数据库中是否存在,如果不存在这个 MD5 ,才真正删除磁盘文件。

8 最终效果及总结

整体框架:

最新回复(0)