目录

  • 一. 项目背景
  • 二.项目使用技术栈与环境
  • 三.项目功能
  • 四.项目具体实现
    • 4.1:数据库、表的设计
    • 4.2:pom.xml文件中引入依赖
    • 4.3:服务器端封装数据库的的设计
    • 4.4:服务器端servlet接口的设计
    • 4.5:前端代码实现:
  • 五:项目效果展示

一. 项目背景

  • 现在很多网页都可以见到上传图片的功能,假如我们上传一张本地图片后,网页就会显示我们刚刚所上传的图片,比如博客上传一张图、个人信息提交头像页面等等。那么这背后的原理是什么呢?
  • 其实当我们浏览网页的时候,本质上是从 对端服务器 获取资源,浏览器在获得这些资源之后进行解析和渲染,呈现在我们用户面前的就是绚丽多彩的页面了。
  • 一般来讲这些资源文档主要有三种格式:HTML、CSS、JS。HTML相当于网页的骨架,CSS相当于网页的衣服,用来规定网页的样式,比如字体大小以及排版等等,而JavaScript则主要负责一些动态的逻辑,比如在网页上按下一个按键后会显示什么等等。

二.项目使用技术栈与环境

所用技术

  • JDBC(Java操作MySQL)
  • 数据库设计(MySQL建库建表,增删查)
  • Gson(反序列化成json字符串)
  • Servlet(服务器端API设计,前后端交互)
  • 基于md5进行校验(决定文件是否要写入磁盘)

平台与环境

  • Windows
  • IDEA
  • Maven

三.项目功能

  • 核心就是实现一个 HTTP 服务器,实现对图片的增删查功能,同时搭配简单的页面辅助完成对图片的上传,显示,阅览功能。(实现一个 HTTP 服务器,然后用这个服务器来存储图片,针对每个图片提供一个唯一的 url,有了这个 url 之后就可以借助它把图片展示到其他网页上。)
  • 本项目的结构主要分为两个部分,数据存储模块和服务器模块。使用MySQL存储图片的属性信息,将图片内容保存到本地磁盘,服务器向外提供诸如上传图片、获取单个图片的属性信息、获取所有图片的属性信息、和删除图片等API接口。

四.项目具体实现

4.1:数据库、表的设计


(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 常用于哈希算法,加密算法,校验和

4.2:pom.xml文件中引入依赖

:JSON 是一种常见的数据格式组织方式,源于 JavaScript , 是一种键值对风格的数据格式。Java 中可以使用 Gson 库来完成 JSON 的解析和构造. 在 Maven 中新增 对 Gson 库的依赖就可以使用 JSON 了

<!-- 打包方式是 war 包,一种用于 web 应用的包,原理类似 jar 包 --><packaging>war</packaging><!-- 指定属性信息 --><properties><encoding>UTF-8</encoding><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target></properties><dependencies><!-- 加入 servlet 依赖 --><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><!-- servlet 版本和 tomcat 版本有对应关系,切记 --><version>3.1.0</version><!-- 这个意思是我们只在开发阶段需要这个依赖,部署到 tomcat 上时就不需要了 --><scope>provided</scope></dependency><!--单元测试矿浆junit依赖 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.11</version><scope>test</scope></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.49</version></dependency><!--JSON库,gson--><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>2.8.2</version></dependency><!--对应上传图片操作 --><dependency><groupId>commons-fileupload</groupId><artifactId>commons-fileupload</artifactId><version>1.4</version></dependency><dependency><groupId>commons-codec</groupId><artifactId>commons-codec</artifactId><version>1.14</version></dependency></dependencies><build><!-- 指定最终 war 包的名称 --><finalName>image_server</finalName>

4.3:服务器端封装数据库的的设计

4.3.1:前期准备工作,首先创建一个maven项目

(1)Maven 是什么?

Maven是 Apache 下的一个纯 Java 开发的开源项目,是一个项目构建和管理的工具;它提供了帮助管理、 构建、文档、报告、依赖、发布、分发的方法。可以方便的编译代码、进行依赖管理、管理二进制库等等。Maven是专门用于构建和管理Java相关项目的工具

(2)使用 Maven 的好处?

maven 的目标是完成项目构建解决一切繁琐事宜。我们具体关注它的以下功能:

  • 提供一个标准的项目工程目录
  • 提供项目描述
  • 提供强大的版本管理工具
  • 可以分阶段的进行构建过程
  • 提供了丰富的插件库使用

4.3.2:dao 包下封装对数据库的相关操作

具体代码实现:
DBUtil工具类:

/*** 创建一个单例类来辅助创建连接,关闭连接*/
public class DBUtil {private static volatile DataSource dataSource = null;//管理数据源// 通过这个方法来创建 DataSource 的实例public static DataSource getDataSource() {//单例模式(双重校验+锁+volatile的饿汉模式)//加锁实际上会牺牲效率,所以不要每次都加锁,只要判断datasource不为空,就直接返回dataSource对象,保证效率if (dataSource == null) {synchronized (DBUtil.class) {if (dataSource == null) {dataSource = new MysqlDataSource();MysqlDataSource tmpDataSource = (MysqlDataSource) dataSource;tmpDataSource.setURL("jdbc:mysql://localhost:3306/image_server_project?user=root&password=1450618603&useSSL=false&characterEncoding=UTF-8");}}}return dataSource;}//建立数据库连接方法public static Connection getConnection() {try {return getDataSource().getConnection();} catch (SQLException e) {throw new RuntimeException("数据库连接失败");}}//关闭数据库连接方法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();}}
}

Image图片类:

/***  一个 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 + '\'' +'}';}
}

数据库操作类:


/***  用来对数据库的增删改查的一个类*  JDBC的操作流程*     1.获取数据库连接*     2.创建并拼装SQL语句*     3.执行SQL语句*     4.关闭结果集resultSet对象、关闭statement对象,关闭连接*/public class ImageDao {/*** 把一个 Image 对象插入到数据库中*/public boolean insert(Image image){Connection connection = null;PreparedStatement statement = null;try {//1.获取数据库连接connection = DBUtil.getConnection();//2.创建并拼接sql语句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 RuntimeException("插入数据库出错");}else {return true;}} catch (SQLException e) {e.printStackTrace();}finally {//4.关闭资源DBUtil.close(connection,statement,null);}return false;}/***  查找数据库中所有图片的属性信息,并存储在 List 列表中* @return 所有图片信息*/public List<Image> selectAll(){List<Image> images = new ArrayList<>();Connection connection = null;PreparedStatement statement = null;ResultSet resultSet= null;try {connection = DBUtil.getConnection();String sql = "select * from image_table";statement = connection.prepareStatement(sql);resultSet = statement.executeQuery();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 {//关闭结果集,statement对象和连接DBUtil.close(connection,statement,resultSet);}return null;//出错的话返回空的结果集}/*** 根据 imageId 查找指定图片的属性信息* @param imageId* @return 返回根据id查找到的图片信息*/public Image selectById(int imageId){Connection  connection = null;PreparedStatement statement = null;ResultSet resultSet = null;try {connection = DBUtil.getConnection();String sql = "select * from image_table where imageId = ?";statement = connection.prepareStatement(sql);statement.setInt(1,imageId);resultSet = statement.executeQuery();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"));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){Connection connection = null;String sql = "delete  from image_table where imageId = ?";PreparedStatement statement = null;try {connection = DBUtil.getConnection();statement = connection.prepareStatement(sql);statement.setInt(1,imageId);int ret = statement.executeUpdate();if (ret != 1) {throw  new RuntimeException("删除数据库异常");}} catch (SQLException e) {e.printStackTrace();}finally {DBUtil.close(connection,statement,null);}}//根据MD5的值在数据库中查找数据public Image selectByMD5(String md5) {Connection  connection = null;PreparedStatement statement = null;ResultSet resultSet = null;try {connection = DBUtil.getConnection();String sql = "select * from image_table where md5 = ?";statement = connection.prepareStatement(sql);statement.setString(1,md5);resultSet = statement.executeQuery();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"));return image;}} catch (SQLException e) {e.printStackTrace();}finally {// 5.关闭连接和statement对象DBUtil.close(connection,statement,resultSet);}return null;  //如果失败的话返回null}
}

4.4:服务器端servlet接口的设计

首先创建一个 api 包,在这个包中创建两个 Servlet 类,这两个Servlet 类都继承自 HttpServlet类,然后重写了对应的 doxxx() 方法。
一个用来完成图片的增删查操作(ImageServlet类)
一个用来展示图片的详细内容(ImageShowServlet类)
注意:要把这两个Servlet 类都要配置到 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><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>

imageServlet类:

public class ImageServlet extends HttpServlet {/*** 查询所有图片/指定图片的内容* @param request* @param response* @throws ServletException* @throws IOException*/protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {//通过URL中是否带有imageId参数来区分是查看所有图片还是指定图片//例如:URL为  /image/imageId=100 name则是查看imageId为100的图片信息//     如果URL后面的键值对都不存在或者只存在键无值,就查询所有图片信息String imageId = request.getParameter("imageId");if (imageId == null || imageId.equals("")) {//查看所有图片信息selectAll(request,response);}else {//查看指定图片信息selectById(imageId,response);}}/***  查看指定图片信息* @param imageId* @param response*/private void selectById(String imageId, HttpServletResponse response) throws IOException {response.setCharacterEncoding("utf-8");response.setContentType("application/json");//1.创建一个ImageDao对象,根据指定id在数据库中查找ImageDao imageDao = new ImageDao();Image image = imageDao.selectById(Integer.parseInt(imageId));//2.创建Gson对象,将查到的数据转换成JSON字符串格式,并且写给响应response对象Gson gson = new GsonBuilder().create();String jsonData = gson.toJson(image);//发送响应给前端PrintWriter writer = response.getWriter();writer.println(jsonData);}private void selectAll(HttpServletRequest request, HttpServletResponse response) throws IOException {response.setCharacterEncoding("utf-8");response.setContentType("application/json");ImageDao imageDao = new ImageDao();List<Image> images = imageDao.selectAll();Gson gson = new GsonBuilder().create();String jsonData = gson.toJson(images);PrintWriter writer = response.getWriter();writer.println(jsonData);}/***  上传图片* @param request* @param response* @throws ServletException* @throws IOException*/protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {//1.获取图片信息,并存入数据库//  1.1需要创建一个factory对象和upload对象,为了获取到图片属性做的准备工作FileItemFactory factory = new DiskFileItemFactory();ServletFileUpload upload = new ServletFileUpload(factory);//  1.2通过upload对象进一步解析请求(解析HTTP请求中的 body 部分)//  理论上来说,HTTP 支持一个请求中同时上传多个文件,FileItem 就代表上传的一个文件对象,多个文件用 List 组织List<FileItem> items = null;try {items = upload.parseRequest(request);} catch (FileUploadException e) {//出现异常说明解析出错e.printStackTrace();//告诉客户端出现的具体错误信息PrintWriter writer = response.getWriter();response.setCharacterEncoding("utf-8");response.setContentType("application/json");writer.write("{\"ok\":false \"reason\":\"上传图片请求解析失败\"}");return;}//   1.3将FileItem中的对象提取出来转换成Image对象,才能存到数据库中FileItem fileItem = items.get(0);Image image = new Image();image.setImageName(fileItem.getName());image.setSize((int)fileItem.getSize());SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");Date date = new Date();String time = simpleDateFormat.format(date);image.setUploadTime(time);image.setContentType(fileItem.getContentType());//计算md5的值image.setMd5(DigestUtils.md5Hex(fileItem.get()));// md5 值是十六进制的字符串 图片内容相同时则md5值相同,图片内容不同时,md5值不同//自定义存储路径,加上数据保证不会出现连续插入两张图片报错,因为每时每刻时间戳是唯一的image.setPath("./image/"+image.getMd5());//存入数据库ImageDao imageDao = new ImageDao();//看看数据库中是否存在相同的md5值的图片,不存在则返回nullImage 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();//出现异常说明写入文件失败PrintWriter writer = response.getWriter();response.setCharacterEncoding("utf-8");response.setContentType("application/json");writer.write("{\"ok\":false \"reason\":\"图片内容写入磁盘失败\"}");return;}}/* PrintWriter writer = response.getWriter();writer.write("{\"ok\":true}");*/response.sendRedirect("index.html");}/***  删除指定图片* @param req* @param resp* @throws ServletException* @throws IOException*/@Overrideprotected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//1.先获取请求中的imageIdString imageId = req.getParameter("imageId");if (imageId==null || imageId.equals("")) {resp.setStatus(200);resp.setCharacterEncoding("utf-8");resp.setContentType("application/json");PrintWriter writer = resp.getWriter();writer.write("{\"ok\":\"false\" \"reason\":\"解析请求失败\"}");return;}//2.创建ImageDao对象,查看到该图片对象对应的相关属性,若存在则要删除//  既要删除数据库中的内容,也要删除磁盘中存储的对应图片,因此要知道该 imageId 对应的图片对象的存储路径ImageDao imageDao = new ImageDao();Image image = imageDao.selectById(Integer.parseInt(imageId));if (image == null) {//说明请求中传入的id在数据库中不存在resp.setStatus(200);resp.setCharacterEncoding("utf-8");resp.setContentType("application/json");PrintWriter writer = resp.getWriter();writer.write("{\"ok\":\"false\" \"reason\":\"数据库中不存在该数据\"}");return;}//3.删除数据库中的记录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.setStatus(200);}}
}

imageShowServlet类:


public class ImageShowServlet extends HttpServlet {//基于白名单的防盗链机制static private HashSet<String> whiteList = new HashSet<>();//白名单static {whiteList.add("http://localhost:8080/index.html");whiteList.add("http://localhost:8080/image_server/index.html");}protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {//防盗链功能,因为服务器同时访问人太多容易崩溃String referer = request.getHeader("Referer");if (!whiteList.contains(referer)) {response.setContentType("application/json; charset=utf-8");response.getWriter().write("{ \"ok\":false,\"reason\":\"该网站未授权访问\" }");return;}request.setCharacterEncoding("utf-8");response.setCharacterEncoding("utf-8");response.setContentType("application/json");//1.解析出imageIdString imageId = request.getParameter("imageId");if (imageId==null || imageId.equals("")) {PrintWriter writer = response.getWriter();writer.write("{\"ok\":\"false\" \"reason\":\"数据解析失败\"}");return;}//2.根据imageId查找数据库,得到对应的图片属性信息(需要知道图片存储的路径)ImageDao imageDao = new ImageDao();Image image = imageDao.selectById(Integer.parseInt(imageId));//3.根据路径打开文件,读取其中的内容,写入到响应体中response.setContentType(image.getContentType());File file = new File(image.getPath());//由于图片是二进制文件,因此使用字节流方式读取FileInputStream fileInputStream = new FileInputStream(file);ServletOutputStream outputStream = response.getOutputStream();byte[] buffer = new byte[1024];while (true) {int len = fileInputStream.read(buffer);if (len == -1) {//文件读取完毕break;}//此时已经读到一部分数据,在buffer中存着,应该写到响应对象中outputStream.write(buffer);}fileInputStream.close();outputStream.close();}}

至此,后端代码告一段落

4.5:前端代码实现:

有句话说的好,下坡易上坡难,我们不会做加法,就学着去做减法,前端不太
了解的情况我们可以在网上下载一些前端模板,对其删减已达到我们想要的效果

基于模板进行删减修改:
(1)title 标签的修改

(2)页面的选择文件和提交按钮

注意:为了让 “提交按钮” 和前面的选择文件一样高, 这里加了一个style=“height:41px”
此处我们直接使用 style 属性调整样式, 这是一个简单粗暴的做法. 事实上专业的前端开发很少这样做, 因为代码的可维护性不好

(3)页面 Logo 的修改


(4)使用 Vue

Vue 是 JS 的框架,他做的核心工作是把页面显示的内容和 JS 中的代码相互关联到一起,修改 JS 中的变量就能很方便的影响到页面的显示情况。

Vue的基本用法:

var 声明这是一个变量,构造方法是使用了 JSON 形式的{} ,el 属性表示我要把当前这个对象关联上某一个 html 的标签(把 app 对象关联到 html 中一个 id=“app” 的标签)

主要JS代码:

第一步: id=“app” 把 JS 代码和 该标签绑定
第二步:使用 v-bind:src 把图片的内容 通过 ImageShowServlet 接口获取到.
第三步:使用 {{image.imageName}} 表示图片的名称
第四步:v-for 能够循环访问一个数据
注:只要访问 index.html 文件就要加载服务器中的所有图片,所以只要访问 index.html 文件,就要调用该方法


<script>var app=new Vue({// e1 属性表示我要把当前这个对象关联到某一个 html 的标签上(把 app 对象关联到 html 中的一个 id="app 的标签)el: '#app',data: {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();         //项目只要访问 index.html 就自动调用该方法
</script>

(5)在返回完后端代码完善上传功能

当前的上传请求成功后会返回一个 JSON 格式的数据. 而我们需要的是直接能看到上传的效果,因此应该修改响应,直接重定向到 index.html 页面观察即可。
修改上传接口中的响应, 直接返回一个 302 响应, 重定向回主页即可。

(6)实现删除图片

此程序的事件冒泡机制:div 标签中有 3 个字标签
①点击 div 就会触发一个点击事件,div 就会处理这个点击事件并显示预览图
②点击 button 删除按钮的时候也会触发一个点击事件,触发删除操作
③点击 cilck 就会先被 button 处理,进行删除操作,然后顺着 button 的父标签,传递给 div 标签,然后 div 再处理一次,触发预览图效果

4.6:项目的两个拓展点:

4.6.1:防盗链机制:增加安全性
通过 HTTP 请求中的 Refer 字段判定是否是指定网站请求服务器图片,未授权网站就无法访问图片内容。

4.6.2:md5校验和机制:实现相同内容的图片在磁盘上只能存一份
整体思路:

  • 修改 dao 层代码,在 dao 层实现一个selectByMd5()的方法,根据 Md5 值来查找数据库中的图片信息(是否存在多张相同图片).
  • 修改上传图片代码,在磁盘上存储文件时先判定,该 md5 对应的图片是否已经存在于磁盘上了,若存在就不必再往磁盘写了.
  • 修改删除图片代码,先删除数据库记录,删除完毕后,看数据库中是否还存在刚刚相同的 md5 的记录.如果不存在,就删除磁盘文件.

五:项目效果展示

效果图:



此处我们发现,数据库和浏览器都存有7张图片,然而本地磁盘只存有六张图片,这正是由于我们用了md5实现了相同内容的图片在磁盘上只能存一份,并加入了时间戳,保证每时每刻的时间是不相同的,所以即使两张图片的内容相同,但是由于时间是不同的,所以图片数据插入数据库不会报错,至此整个项目基本就告一段落了,由于本人能力有限,些许地方还有实现不当,希望各位小伙伴发现了可以不吝批评

简易图片服务器项目说明相关推荐

  1. Python中使用Flask、MongoDB搭建简易图片服务器

    转自:http://www.ctolib.com/topics-43840.html 1.前期准备 通过 pip 或 easy_install 安装了 pymongo 之后, 就能通过 Python ...

  2. 分布式图片服务器FastDFS

    分布式图片服务器FastDFS 什么是FastDFS 一.简介和架构 二.FastDFS安装和测试 三,配置nginx代理及安装fastdfs-nginx-module 什么是FastDFS 一.简介 ...

  3. 图片服务器的url hash架构

    什么是urlhash架构 url hash架构对url进行一次hash算法,然后通过hash结果找到对应的服务器.因为针对单一个url的hash结果是一样的,所以理论上这个url会被永久分配到固定的一 ...

  4. python3编写简易统计服务器

    打点这个功能总是美其名曰"帮助提升用户体验",其实说白了就是记录用户做了哪些操作.目前国内很多通用软件都做了相关功能,像360.QQ等这样的以用户体验出众的软件,其打点的面自然也很 ...

  5. 使用 Nginx 搭建图片服务器

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 作者 | ITDragon龙 链接 | cnblogs.com/i ...

  6. kindeditor扩展粘贴截图功能修改图片上传路径并通过webapi上传图片到图片服务器...

    2019独角兽企业重金招聘Python工程师标准>>> kindeditor是一个非常好用的富文本编辑器,它的简单使用我就不再介绍了. 而kindeditor却对图片的处理不够理想. ...

  7. RHEL5系统 sendmail+qpopper 架设简易邮件服务器

    转自 "小蜗牛技术之家" 博客  原稿: http://snailwarrior.blog.51cto.com/680306/139098 [小蜗牛奋力之作 ] 目标:在RHEL5 ...

  8. Nginx 独立图片服务器的搭建

    为什么需要独立图片服务器? 如果你留心的话,可以发现,现在主流的网站都是有单独的图片服务器的,例如,人人网的为rrimg,淘宝的为taobaocdn,下面还有很多的二级域名. 独立的图片服务器有诸多好 ...

  9. 【推荐】大型网站图片服务器架构的演进

    构建在Windows平台之上的网站,往往会被业内众多架构师认为很"保守".很大部分原因,是由于微软技术体系的封闭和部分技术人员的短视造成的.由于长期缺乏开源支持,所以只能" ...

最新文章

  1. 【基本操作】主席数统计区间不同颜色个数
  2. python学习之第一课时--初始python
  3. cgo引用定义不一致_应急照明、消防应急照明和疏散指示系统的定义与分类
  4. DarkTrack 4 Alien Version Released RAT 下载地址视频教程
  5. Bear and Finding Criminals
  6. hdu 4407 Sum
  7. Elasticsearch聚合深入详解——对比Mysql实现
  8. 子类重写父类变量_为什么在子类中不重写超类的实例变量
  9. C++ opencv的一些基础知识
  10. Kafka 分布式消息系统详解
  11. 全国高校安徽考区计算机,关于做好2021年上半年全国高校(安徽考区)计算机水平考试报名工作的通知...
  12. 基于51单片机步进电机proteus仿真
  13. Vue 记录一次安装插件引起的项目崩溃(This is probably not a problem with npm,there is likely additional logging outp)
  14. Labeled Faces in the Wild
  15. 谷歌浏览器,查找CSS选择器
  16. 量子计算机当游戏服务器,为何需要量子计算机
  17. Rasa课程、Rasa培训、Rasa面试、Rasa实战系列之Lexical Ambiguity、Fallback Detection
  18. cdma2000解析_CDMA2000 1x EV-DO技术解析
  19. NOI.5.37雇佣兵
  20. RuntimeError: cuDNN error: CUDNN_STATUS_EXECUTION_FAILED,以及tensorflow1.14.0+torch1.2.0+CUDA10.0配置

热门文章

  1. 《逆袭必看》的人不懂的顶级学习方法“承重墙学习法”_java管理系统
  2. 命令行界面命令模式及相互切换、交换机命令行操作模式及模式间的切换过程、命令行界面基本功能、命令的快捷键功能、交换机基本配置命令、交换机特权模式下基本命令、交换机全局配置模式下基本命令、
  3. 3dsmax制作迈克杰克逊-3D建模人物模型教程
  4. yolov5训练过程可视化
  5. E题:变流器负载试验中的能量回馈装置(本科)--2018年TI杯大学生电子设计竞赛
  6. csgo跳跃只能改一个键位吗_csgo跳扔投掷物指令设置方法 怎么绑定鼠标跳扔投掷物...
  7. 中央空调的安装维护教学实训QY-JDW03
  8. java类似金山打字的键盘模拟_Java仿金山打字通打字母游戏《Java就该这样学》
  9. java 表驱动_表驱动法编程(数据驱动)
  10. 温度控制c语言流程图,AT89C2051单片机温度控制器设计+电路图+流程图.doc