零基础编写图片服务器(3)

1)现在我们还剩下最后一个API设计,叫做查看文件的内容:

package API;

import Dao.Image;
import Dao.OperateImage;
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.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.sql.SQLException;
import java.util.Scanner;

@WebServlet("/ShowImage")
public class ShowImageServlet extends HttpServlet {
    static class Response{
        public int OK;
        public String reason;
    }
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1解析出ImageID
        ObjectMapper objectMapper=new ObjectMapper();

        String ImageID=req.getParameter("ImageID");
        if(ImageID==null||ImageID.equals("")) {
            Response response=new Response();
            response.OK=3;
            response.reason="当前我们前端传递的参数有问题,我们无法进行查询一张图片看到具体信息的操作";
            resp.setContentType("application/json;charset=utf-8");
            String html = objectMapper.writeValueAsString(response);
            resp.getWriter().write(html);
            return;
        }
        //2根据ImageID来进行查询数据库,从而得到图片的属性信息(主要是我们想知道图片存储的路径);
        OperateImage operateImage=new OperateImage();
        Image image= null;
        try {
            image = operateImage.SelectOne(Integer.parseInt(ImageID));

        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        if(image==null||image.equals(""))
        {
            DeleteImageServlet.Response response=new DeleteImageServlet.Response();
            response.OK=5;
            response.response="数据库中没有ImageID对应的图片信息,我们无法进行查看图片操作"+ImageID;
            resp.setContentType("application/json;charset=utf-8");
            String html= objectMapper.writeValueAsString(response);
            resp.getWriter().write(html);
            return;
        }
//3根据路径打开文件,读取到其中的响应数据,因为咱们的图片属于二进制,所以我们要按照字节流的方式来进行读取
        InputStream inputStream=new FileInputStream(image.getContentPath());
        StringBuilder stringBuilder=new StringBuilder();
        while(true)
        {
            int len=inputStream.read();
            if(len==-1)
            {
                break;
            }
            stringBuilder.append((char)len);
        }
        inputStream.close();
        resp.setContentType(image.getContentType());
        resp.getWriter().write(stringBuilder.toString());
    }
}

2)如果我们这时候把图片上传到图片服务器上面,我们就可以在其它网站上面通过url来进行引用这个图片了

3)resp.setContentType(image.getContentType());设置服务器返回的HTTP数据响应报文是一个图片类型,要不然你给浏览器返回大量的图片二进制数据,浏览器就无法进行识别了

2.实现前端页面,让我们的网页变得更好看

rm java_server* -rf

一:我们为了实现页面的上传:

1)把搜索框改成了文件上传按钮

2)新增了一个提交按钮

3)修改了form属性,新增action(后端服务器路径),method,enctype

二:发现一个小问题,代码中访问List<FileItem>,发现了数组下标越界,也就是出现了空指针异常,说明list数组是空,那么说明并没有包含与文件相关的内容,form表单写的不太对,一定要加上name属性

三:比如说现在有一个div标签:

<div>

<input type="submit" value="提交">

</div>

现在我们想要调整这一块input按钮的高度,那么就可以改一下

<input type="submit" style="height:60px">

四)循环展示图片:原来代码是这样的,目标把网络上显示的这些预览图片替换成服务器展示的图片,循环进行渲染;

只需要把img标签中的src属性改成服务器存放的文件路径的地址就可以了

从network里面去看加载的属性,比如说去查看src的属性加载是否正确

a标签会自动根据src中的属性向后端发送一个HTTP请求

 我们把代码改成这样:

1)这样就可以显示一张图片了,但是我们当前代码里面只有一个img标签,但是实际上我们有多张图片,实际上也就应该有多个img标签,每一个img标签都有一个src属性,里面存放的就是对一个图片的路径

2)所以我们在这里面的做法就是需要获取到服务器上面所有图片的url,就是之前我们在后端写的获取到所有图片的属性信息,ImageServlet,需要先通过JS来进行获取到所有图片属性,再来分别加载每一个图片

3)此时我们应该是用Vue JS的框架来进行更方便的编写代码,引入vue,直接引入这一段文件即可,直接粘贴到head标签里面就可以了,此时我们的vue就下载安装成功了,自动会向服务器下载一些第三方的JS,在后面的代码中就可以直接使用

 <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

4)我们的JS的变量类型都是在初始化的时候进行自动推导的,var声明这是一个变量

vue框架的用法:

Vue.js

Vue vue=new Vue({}),里面的参数只有一个,那就是一个JSON格式的数据

1)el:表示我要把这个Vue对象关联到哪一个html标签上面,假设说el:"#app"表示把vue这个变量关联到id属性是app的这个HTML标签上;

2)data:里面是一个键值对,关联的变量名以及具体的值

在浏览器的控制台上面通过vue对象.data属性中间的键就可以得到里面的值,并且还可以进行修改里面的值,当我们进行修改vue对象.data属性的值的时候,对应的HTML页面中关联的属性也会发生变化;

3)methods代表着vue对象中的方法

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body >
    <div id="User">{{username}}</div>
//最终这里面的username会被替换成vue中的username的值
    <script>
        var app=new Vue({
            el:"#User",//获取到里面的ID属性,指定选中的html标签
            data:{
                username:"李佳伟",
            }

        });
    </script>
</body>
</html>

1)我们在浏览器的控制台上面输入app.username,就可以显示信息了,会自动替换div中的uaername信息

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body >
    <div id="User">
        <div>{{username}}</div>
        <div>{{password}}</div>
        <div>{{number}}</div>
    </div>
<!-- //最终这里面的username会被替换成vue中的username的值 -->
    <script>
        var app=new Vue({
            el:"#User",//获取到里面的ID属性,指定选中的html标签
            data:{
                username:"李佳伟",
                password:"12503487",
                number:"生命在于运动"
            }
        });
    </script>
</body>
</html>

2)上面还可以进行相应替换,所以说咱们vue所做的核心工作就是把页面HTML显示的内容和咱们JS中显示的内容紧紧的关联到一起,咱们修改对应的JS的变量就可以很方便的影响到页面的显示情况;

咱们还可以在控制随意修改,一敲回车就可以了,咱们的{{username}}这种写法就叫做差值表达式,里面的变量会自动替换成vue变量的内容,不需要手动干预,而是有框架自动完成的

1)我们首先先从vue对象中构造一组写死的images数据,我们先进行借助这些写死的数据来进行循环渲染页面

1)这里面div里面的v-for可以循环访问一组数据

2)这里面的v-bind是把某一个数据绑定到html标签上面的某一个属性

3)此时如果我们在标签内部使用Vue对象中的数据,就要使用差值表达式

4)如果说在标签属性里面使用Vue对象的数据,比如说src属性,不需要使用使用差值表达式,但是需要搭配Vue中的命令,v:bind;

5)v-on:绑定某一个事件的处理函数,点击鼠标,双击,右键,按下某个键,调整传功窗口大小,html在收到事件之后会进行一定的响应

<script>
 var app=new Vue({
        el:"#container",
        data:{
            images:[
                {
                    imageID:11,
                    contentType:"image.png",
                    contentPath:"images/1.png",
                    imageName:"1.png",
                    md5:"aabbcc"
                },
                {
                    imageID: 12,
                    contentType:"image.png",
                    contentPath:"images/2.png",
                    imageName:"2.png",
                    md5:"aabbcc"
                }
            ]
        }
    })
<div class="am-g am-g-fixed blog-fixed blog-content">
  <figure data-am-widget="figure" class="am am-figure am-figure-default "   data-am-figure="{  pureview: 'true' }">
      <div id="container">   
               <!--这里面的image就是循环访问vue中的images里面的元素,类似于JAVA中的for each循环,images就相当于是一个数组,里面的images元素就相当于是一个image对象-->
          <div v-for="image in images">
            <img v-bind:src="'ShowImage?ImageID='+image.imageID">
            <h3>{{image.imageName}}</h3>
          </div>
    </div> 
  </figure>

最终代码:

1)接下来我们可以通过浏览器中的JS代码请求服务器,获取到服务器上面有那些图片,把这个数据作为Vue渲染的依据

先让浏览器发送ajax请求从服务器获取到这个images数据,再进行渲染HTML中的元素

2)此处的代码是在浏览器在收到响应之后,才会执行到,参数中的data就是相当于是HTTP请求中的body部分;

 var app=new Vue({
        el:"#container",
        data:{
            images:[]//最终会有ajax将data中的一个个的images数组赋值给images对象
        },
        methods:{
          getImages(){//用来向服务器来进行获取数据,约定的的请求
            $.ajax({
              url:"Image",
              type:"GET",
              context:this,
              success:function(data,status){//此时的代码在浏览器收到响应之后,才会执行到,data就相当于是HTTP响应中的body部分
               this.images=data;
               $("#container").resize();
              }
            });

          },
          remove(ImageID){
            $.ajax({
              url:"Image?ImageID="+ImageID,
              method:"delete",
              context:this,
              success:function(data,status){
                //再次尝试获取数据
                 this.getImages();
                 //弹出对话框
                 alert("删除成功");
              }
            })
          }
        }
    });
    app.getImages();

需要进行渲染的源代码:

  <div id="container">   
<!--这里面的image就是循环访问vue中的images里面的元素
类似于JAVA中的for each循环,images就相当于是一个数组,
里面的images元素就相当于是一个image对象-->
<div v-for="image in images">
<!--通过v-bind把vue某一个数据关联上html标签上面的一个属性 -->
<img v-bind:src="'ShowImage?ImageID='+image.imageID">
 <h3>{{image.imageName}}</h3>
<!--通过v-on来给我们的按钮关联上一个点击事件,下面的vue代码会自动调用vue中的methods中的函数-->
 <button style="width:100% background-color=green" v-on:click="remove(image.imageID)">
删除图片</button>
   </div>
</div> 

咱们此时的div预览图如下所示:

1)咱们点击div就会触发一个点击事件,div就会处理这个点击事件并显示预览图 

2)咱们点击button也是触发一个点击事件,出发删除操作,div就会处理这个点击事件并显示预览图,

3)所以说点击button也是触发一个点击事件,触发删除操作

但是这个触发操作会顺着父亲的html标签结构向父亲的标签传递给div

div也会在进行处理一次;

4)这就是HTML事件冒泡机制,所以说我们要阻止事件冒泡: v-on:click.stop="";

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
    <div id="user">
        <div  v-for="user in users">
            <a v-bind:href="user.userhref">{{user.userhrefname}}</a>
            <div>{{user.username}}</div>
            <div>{{user.password}}</div>
            <input type="button" v-on:click="remove(user.userID)" v-bind:value="user.uservalue">
        </div>
    </div>
   
    <script>
        var vue=new Vue({
            el:"#user",
            data:{
                users:[{
                    userID:1,
                    username:"zhangsan",
                    password:"12503487",
                    userhref:"http://www.baidu.com",
                    userhrefname:"百度网址",
                    uservalue:"点击跳转到百度网页"
                   },
                   {
                    userID:2,
                    username:"zhangsan",
                    password:"12503487",
                    userhref:"http://www.baidu.com",
                    userhrefname:"搜狗网志",
                    uservalue:"点击转到搜狗主页"
                   }
                ]
            },
            methods:{
            remove(userID){
                alert(userID);
            }
        }
        }
        )

    </script>
</body>
</html>

有了上面的基本知识之后,我们就可以通过浏览器中的JS代码请求我们的后端服务器,来进行获取到服务器上面都有那些图片,把这个数据作为Vue渲染的依据(Vue中的images数组)

1)我们上面的vue中的el是根据ID来进行决定绑定哪一个html标签

2)我们上面中的vue的data是用来存放我们将要渲染的数据

3)我们下面的vues中的methods是用来存放我们的方法,这个方法用于给后端的Http服务器发送请求

4)下面咱们的ajax是构造HTTP请求发送给服务器的一种方式

项目扩展: 

简单的图片防盗链机制:

1)因为你的图片链接可能会被其他人使用,我们在图片上面复制链接地址,再用浏览器打开就会能够访问到我们服务器上面的图片了

2)我们要通过一定的机制来限制其他人来进行使用图片,否则图片用的人太多,你的服务器就挂了

3)我们可以在后端代码中判断当前请求的referer字段是否来自我们指定的白名单里面,如果是,才可以进行访问,referer字段是请求协议的header头部分,表示当前请求的上个页面的地址,我们在代码中使用HashSet来进行存一下允许的Referer就可以,判断展示图片的时候看看是否在HashSet中存在即可

我们可以在获取图片具体内容的时候加上这些代码:

       String ImageID=req.getParameter("ImageID");
        ObjectMapper objectMapper=new ObjectMapper();
        OperateImage operateImage=new OperateImage();
        Response response=new Response();
        HashSet<String> hashSet=new HashSet<>();
        hashSet.add("http://127.0.0.1:8080/Java100/index1.html");
        String referer=req.getHeader("Referer");
        if(!hashSet.contains(referer)){
            response.state=3;
            resp.setContentType("application/json;charset=utf-8");
            System.out.println(referer);
            response.reason="您当前没有权限进行访问";
            System.out.println("生命在于运动");
            String html=objectMapper.writeValueAsString(response);
            resp.getWriter().write(html);
            return;
        }

 优化磁盘存储空间:保证上传图片到磁盘里面的这个操作在插入图片到数据库之前

1)在这里面我们要应用到MD5,如果两个图片的内容完全一样,那么我们在磁盘上面只存一份图片就可以了,让两个图片在数据库中的保存路径是一样的

2)图片文件虽然是二进制数据,但是本质上也是字符串,我们需要针对图片内容计算MD5,如果说两个图片内容相同,得到的MD5一定相同的,反之,近似地认为MD5相同,那么原图片内容一定相同

图片内容相同,那么MD5值一定相同,如果图片内容相同

那么MD5的值一定不同,是一个16进制数字

3)理论上是有可能两个图片内容不同,MD5相同,但是实际上出现概率极低,这是因为MD5自身算法上设计所引起的特性

MD5的特点:

1)不管原串多长,得到的MD5是一个固定长度

2)原串哪怕变动一点点,哪怕变一个字节,那么MD5的就会变动很大

3)计算MD5值的过程很简单,但是通过MD5值是无法推测出原来的字符串的,应用到了MD5

实现思路:

在我们上传图片的时候,我们先进行判断一下新图片的MD5的值是否在数据库中已经存在,如果已经存在了,那么就不把图片内容写到磁盘上面,如果不存在MD5值,就写到磁盘里面

具体操作:

1)修改上传代码的逻辑,磁盘文件名用MD5来进行表示,就是说现根据文件内容来进行获取MD5,文件路径的后缀修改成MD5;

2)引入第三方库来进行计算一下MD5,直接在中央仓库里面进行搜索codec;

但是向数据库插入记录是必要的,无论根据MD5的值在数据库中是否查到记录,都应该向数据库中插入这个图片记录

<!-- https://mvnrepository.com/artifact/commons-codec/commons-codec -->
<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.14</version>
</dependency>
 list=upload.parseRequest(req);
            //3.创建Image对象
            FileItem fileItem= list.get(0);
            Image image=new Image();
            image.setImageName(fileItem.getName());
            image.setImageSize((int)fileItem.getSize());
            image.setContentType(fileItem.getContentType());
            image.setMd5(DigestUtils.md5Hex(fileItem.get()));
            image.setContentPath("./image/"+image.getMd5());
            //设置时间
            SimpleDateFormat format=new SimpleDateFormat("yyyyMMdd");
            image.setImageTime(new Timestamp(System.currentTimeMillis()));
//4.将image对象插入到数据库里面
            OperateImage operateImage=new OperateImage();
            operateImage.InsertImage(image);

 3)修改OperateImage,新增一个数据库接口,能够按照MD5的值来进行查找内容

  public Image SelectByMD5(String md5) throws SQLException {
        Connection connection=ConnectionMYSQL.GetConnection();
        String SQL="select * from Image where md5=?";
        PreparedStatement statement=connection.prepareStatement(SQL);
        statement.setString(1,md5);
        ResultSet resultSet= statement.executeQuery();
        while(resultSet.next()){
            Image image=new Image();
            image.setContentType(resultSet.getString("ContentType"));
            image.setImageSize(resultSet.getInt("ImageSize"));
            image.setImageID(resultSet.getInt("ImageID"));
            image.setContentPath(resultSet.getString("ContentPath"));
            image.setMd5(resultSet.getString("md5"));
            image.setImageName(resultSet.getString("ImageName"));
            image.setImageTime(resultSet.getTimestamp("ImageTime"));
            ConnectionMYSQL.Close(connection,statement,resultSet);
            return image;
        }
        return null;

    }

4)继续修改上传图片的逻辑,根据MD5来进行判定当前图片是否要写磁盘,原来的逻辑是无论如何都要进行修改图片,现在先进行查看这个MD5是否存在,再决定是否要写图片,MD5已经存在,就不需要写了

//这个POST方法用于上传图片
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.为了获取图片属性,写几个类
        Response response=new Response();
        ObjectMapper objectMapper=new ObjectMapper();
        FileItemFactory factory=new DiskFileItemFactory();
        ServletFileUpload upload=new ServletFileUpload(factory);
//2.解析body内容
        List<FileItem> list=null;
        try {
             list=upload.parseRequest(req);
            //3.创建Image对象
            FileItem fileItem= list.get(0);
            Image image=new Image();
            image.setImageName(fileItem.getName());
            image.setImageSize((int)fileItem.getSize());
            image.setContentType(fileItem.getContentType());
            image.setMd5(DigestUtils.md5Hex(fileItem.get()));
            image.setContentPath("./image/"+image.getMd5());
            //设置时间
            SimpleDateFormat format=new SimpleDateFormat("yyyyMMdd");
            image.setImageTime(new Timestamp(System.currentTimeMillis()));
//4.将image对象插入到数据库里面
            OperateImage operateImage=new OperateImage();

      //5.将图片写到文件里卖弄
            //看看数据库中是否存在具有相同的MD5值的图片,如果不存在,数据库操作直接就返回null
            Image exists= operateImage.SelectByMD5(image.getMd5());
            System.out.println(exists);
            if(exists==null) {
                File file = new File(image.getContentPath());
                System.out.println(file.getAbsoluteFile());
                fileItem.write(file);
                resp.setContentType("application/json;charset=utf-8");
                response.state = 0;
                response.reason = "将文件上传到指定目录成功";
                //String html=objectMapper.writeValueAsString(response);
            }
            operateImage.InsertImage(image);
       resp.sendRedirect("index1.html");
        } catch (FileUploadException | SQLException e) {
            resp.setContentType("application/json;charset=utf-8");
            response.state=0;
            response.reason="解析body内容失败";
            String html= objectMapper.writeValueAsString(response);
            resp.getWriter().write(html);
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

但是这里面又会出现问题

删除咋办?多个数据库记录对应着一个文件,删除任何一张图片,那么都会导致文件被删除掉了,删除逻辑的调整方式就是通过selectByMd5看看当前的md5的值对应的图片在数据库中是否存在,如果不存在这个md5才可以真正的的删除磁盘文件;

但是对于数据库中的表是自己写的

1)点击settings,搜索maven,全部勾中 

2)确认settings文件是否存在,如果不存在,那么没有配置国内源,settings.xml

3)如果已经存在settings.xml文件,检查有没有配置国内源

<mirror>
      <id>mirrorId</id>
      <mirrorOf>repositoryId</mirrorOf>
      <name>Human Readable Name for this Mirror.</name>
      <url>http://my.repository.com/repo/path</url>
    </mirror>

1)如果配置了国内元,重新下载外部Jar包,还是下载不下来,是因为你本地仓库存储的jar包是不完整的,保存了下载了一半的外部jar包,再去下载maven不知道完整性

2)把本地仓库的jar包全部删除掉(Local reposity)

还不能解决:重复前面的三个流程,多试几遍,一定会成功

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值