我的云相册

我的云相册目录

一.引言

二.项目功能

三.技术选型

四.服务器设计

数据库设计

前后端交互接口设计(服务器API设计)

五.基于Servlet搭建服务器(后端)

新增图片

查看图片信息

删除图片

查看图片内容

六.前端页面设计

七.细节优化

八.效果展示


一.引言

云相册其实就是一个可以存储图片的HTTP服务器,可以完成对图片的增删改查功能,同时搭配简单的前端页面辅助完成图片上传和展示。

二.项目功能

实现图片的上传,查询,删除,以及展示功能

三.技术选型

1.数据库存储图片元信息——MySQl

2.使用JDBC操作数据库

3.前后端交互——Ajax异步提交

4.文件上传------使用了第三方库commons-fileupload

5.磁盘的存储优化——Md5

6使用Gson这个库完成json的解析与构造

7.使用Postman工具进行简单的测试

8.使用html,css,JavaScript技术构建简单的网页

9.使用tomcat部署项目

四.服务器设计

数据库设计

1.创建数据库表,存储图片的属性信息

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)

  ); 

我的云相册_第1张图片

2.封装数据库操作

1)创建DBUtil类,辅助创建连接

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;
    public static DataSource getDataSource(){
       
    }
    public static Connection getConnection()  {
       
    }
    public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet){

    }
}

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; 
}

3)创建ImageDao类,实现对Image对象的增删改查

public class ImageDao {
    /*
     * 功能描述:把image对象插入到数据库中
     * @return void
     */
    public void insert(Image image){
    }
    /*
     * 功能描述:查找数据库中所有图片的信息
     * @return java.util.List
     */
    public List selectAll(){
    }
    /*
     * 功能描述:根据imageId查找指定的图片信息
     * @return dao.Image
     */
    public Image selectOne(int imageId){
    }
    /*
     * 功能描述:根据imageId删除指定的图片
     * @return void
     */
    public void delete(int imageId){   
    }
    //按照md5查找
    public Image selectByMd5(String md5){
    }
}

前后端交互接口设计(服务器API设计)

前置知识:在服务器api设计部分,我们将会用到一种轻量级的数据交换格式——JSON。json只是一种数据格式,和编程语言无关(出身于JavaScript), json可以将对象中表示的一组数据转换为字符串,然后可以在网络或者程序之间轻松的传递这个字符串,并在需要的时候将它还原为各编程语言所支持的数据格式。我们在这里使用json完成数据的序列化,方便进行网络传输。

Gson:是google搞的开源的JSON解析库,我们在使用json时需要将gson库加入我们的依赖中,可在maven仓库中进行下载:

 
      com.google.code.gson
      gson
      2.8.6
 

下面以一个简单示例来学习Json的使用:

public class TestGson {
    public static void main(String[] args) {
        HashMaphashmap=new HashMap<>();
        hashmap.put("name","曹操");
        hashmap.put("skill1","剑气");
        hashmap.put("skill2","三段跳");
        hashmap.put("skill3","加攻击并吸血");
        hashmap.put("skill4","加攻速");
        //通过map转成一个JSON结构的字符串
        //1.创建一个gson对象
        Gson gson=new GsonBuilder().create();
        //2.使用toGson方法把键值对结构转换成JSON字符串
        String str=gson.toJson(hashmap);
        System.out.println(str);
    }
}

打印结果为一组键值对的集合:

我的云相册_第2张图片

接下来开始正式设计前后端交互api,即我们的HTTP协议要构建成什么样子。在此部分我们使用body字段中ok为true表示成功,ok为false表示失败,或者[ ]有内容表示成功,[ ]没有内容表示失败。

1.新增图片

首先写一个简单的的upload.html来上传图片:






设定接口如下:

我的云相册_第3张图片

我的云相册_第4张图片

2.查看所有图片属性

我的云相册_第5张图片

3.查看指定图片属性

我的云相册_第6张图片

     }

4.删除指定图片

我的云相册_第7张图片

5.查看指定图片内容

我的云相册_第8张图片

五.基于Servlet搭建服务器(后端)

此部分需要创建两个Servlet类,一个用来完成图片的增删改查(ImageServlet),另一个用来展示图片的详细内容(ImageShowServlet),这两个类都需要继承HttpServlet。

HttpServlet中提供的doXXX系列方法和HTTP协议是一一对应的, 例如:

 如果给服务器发送的是get请求,tomcat就会自动调用HttpServlet中的doGet()方法;

 如果给服务器发送的是post请求,tomcat就会自动调用HttpServlet中的doPost()方法;

 如果给服务器发送的是get请求,tomcat就会自动调用HttpServlet中的doDelete()方法;

新增图片

新增图片对应ImageServlet类的doPost方法,这里需要用到Commons FileUpload,需要在maven仓库中下载jar包。


  commons-fileupload
  commons-fileupload
  1.4

 

具体操作:

1.获取到图片相关的属性信息(Image对象),并写入数据库。

  •    创建factory对象和upload
  •   使用upload对象解析请求
  •   对请求信息进行解析,转换成Image对象
  •  将Image对象写入数据库

2.获取到图片内容,写入到磁盘中

3.设置返回的响应结果

代码片段:

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1.获取图片的属性信息,并且存入数据库
        //1.1  需要创建一个factory对象,和upload对象,这是为了获取图片属性所做的准备工作
        //        固定的逻辑
        FileItemFactory factory=new DiskFileItemFactory();
        ServletFileUpload upload=new ServletFileUpload(factory);
        //1.2 通过upload对象进一步解析请求(解析HTTP请求中奇怪的body中的内容)
        //     FileItem就代表一个上传的文件对象
        //     理论上来说,HTTP支持一个请求中同时上传多个文件
        Listitems=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;
        }
        //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("yyyyMMdd");
        image.setUploadTime(simpleDateFormat.format(new Date()));
        image.setContentType(fileItem.getContentType());
        //自己构造一个路径来保存,引入一个时间戳是为了能够让文件路径能够唯一
        //image.setPath("./image/"+System.currentTimeMillis()+"_"+image.getImageName());
        //image.setMd5("11223344");
        //存到数据库中
        ImageDao imageDao=new ImageDao();
        imageDao.insert(image);
        //2.获取图片内容信息,并且写入磁盘文件
        File file=new File(image.getPath());
        if (existImage==null) {
            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}");
    }

测试:使用upload.html上传一个图片,检验服务器响应是否为{ok:true},并且查看数据库是否写入成功。

我的云相册_第9张图片

选择一张图片上传后,服务器的响应如下,代表上传成功。

我的云相册_第10张图片

查看图片信息

这里分成两种情况,一是查看所有图片信息,二是查看指定图片信息。根据请求中是否含有imageId这个参数来决定。含有imageId参数的请求是查看指定图片信息,未含的是默认查看所有图片信息。

获取图片信息对应于ImageServlet类中的doGet()方法:

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      //考虑到查看所有图片属性和查看指定图片属性
        //通过是否URL中带有参数来进行区分
        //存在imageId则查看指定图片属性,否则就查看所有图片属性
        //例如:URL/image?imageId=100,imageId的值就是“100”
        //如果imageId中不存在imageId,那么返回null
        String imageId=req.getParameter("imageId");
        if(imageId==null||imageId.equals("")){
            //查看所有图片属性
            selectAll(imageId,resp);
        }else{
            //查看指定图片
            selectOne(imageId,resp);
        }
    }

1.查看所有图片属性信息

 private void selectAll(String imageId, HttpServletResponse resp) throws IOException {
        resp.setContentType("application/json;charset=utf-8");
        //1.创建一个ImageDao对象,并查找数据库
        ImageDao imageDao=new ImageDao();
        Listimages=imageDao.selectAll();
        //2.把查找到的结果转成JSON格式的字符串,并且写回给resp对象
        Gson gson=new GsonBuilder().create();
        //jsonData就是一个json格式的字符串了,就和之前约定的格式是一样的了
        //重点体会下面这行代码,这个方法的核心,gson帮忙自动完成了大量的格式转换工作
        //只要把之前的相关的字段都约定成统一的命名,下面的操作就可以一步到位的完成整个转换
        String jsonData=gson.toJson(images);
        resp.getWriter().write(jsonData);
    }

2.查看指定图片属性信息

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把查到的数据转换成json格式,并写回给响应对象
        Gson gson=new GsonBuilder().create();
        String jsonData=gson.toJson(image);
        resp.getWriter().write(jsonData);
    }

测试查询所有图片信息:

测试查询指定图片信息:image?imageId=12——查询imageId=12的图片元信息

删除图片

删除图片对应ImageServlet类中的doDelet()方法。

具体步骤:1.解析请求中的imageId

                  2.查找数据库,根据imageId删除数据库中的信息

                  3.根据路径从文件中读取图片数据并且删除,还要记得设置content-type。

代码片段:

 @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}");
        return;
    }

测试删除功能:由于删除功能的实现需要用到js绑定事件,而目前还没有实现到前端部分,因此在这里我用到了一个HTTP服务器的测试工具——Postman,它可以很方便的构造各种请求。

首先打开Postman新建一个请求,输入请求的url:http://localhost:9999/imageServer/image?imageId=19,方法选择get,可以查询到imageId=19的图片的属性信息,如下图:我的云相册_第11张图片

然后再将方法改为Delete,我们可以看到body部分显示了一个json格式的字符串,{"ok":true},此响应表明删除成功了,再通过查询数据库发现imageId=19的图片信息已经不存在了,则说明我们的代码是没有问题的,测试成功。

我的云相册_第12张图片

查看图片内容

查看图片内容对应ImageShowServlet类的doGet()方法

具体步骤:1.解析请求中的ImageId

                  2.根据imageId查找数据库,找到对应的path

                  3.根据路径从文件中读取图片数据,并且设置content-type

代码片段:

 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.根据路径打开文件,读取其中的内容,写入到响应对象中
        OutputStream outputStream=resp.getOutputStream();
        resp.setContentType(image.getContentType());
        File file=new File(image.getPath());
        FileInputStream fileInputStream=new FileInputStream(file);//通过字节流对象读取数据
        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();
    }

测试查看图片内容:例如查找imageId=12的图片内容

我的云相册_第13张图片

六.前端页面设计

对于前端部分,现在网上有很多现成的漂亮的网页模板(模板中心),我们可以直接基于现成的页面进行修改。我在网上找了一个HTML模板zmaze ui,将网页模板解压缩,然后拷贝到webapp目录中。

修改之后的效果:我的云相册_第14张图片

1.导航栏实现上传图片功能代码:

修改过程

  • 文件上传和提交按钮:把input标签及其div拷贝一份,原来的input标签的type改为file,增加name="filename",新的input标签type=“submit,将两个input标签的tyle样式的背景色设为黑色,字体设为白色。
  • 修改form标签属性:新增method="post"  enctype="multipart/form-data"   action="/imageServer/image",需注意:enctype属性一定要是multipart/form-data。否则是提交不了二进制数据的,图片属于二进制数据。
  • :因为我比较喜欢粉色。所以我就将主体body中的style背景色设为了粉色

为了让"上传按钮"和前面一样高,在这里加了style="height:41px"。

2.实现展示图片

页面主题展示图片的预览代码:

{{image.imageName}}

①要想让前端页面上显示我们服务器里面的图片,需要用到Vue框架来实现。

Vue所做的核心工作,是把页面显示的内容和JS中的代码相互关联到一起,这样我们修改JS中的变量就可以很方便的影响到页面的显示情况。

我在这里利用Vue.js的data属性存储一个images集合(这里面存储的是从后台查询到的图片)。通过v-for遍历images,每一个image发起一个异步请求去请求展示图片的servlet。后台的servlet根据前端传过来的imageId查询到指定的图片,去找到磁盘中相应的图片的二进制数据,再以流的方式传回前端展示。

②v-bind:作用是把数据绑定到HTML标签上的某个属性。在这里使用v-bind:src把图片的数据关联到图片的src上,下面的{{image.imageName}}表示展示出图片的标题。

③修改js代码


其中getImages():获取所有图片的方法

3.实现页面上的删除图片

①在图片下方新增删除按钮:

v-on是一个绑定某种事件的处理函数,v-on:click:给删除按钮绑定删除事件

②删除事件处理函数:在methods中新增remove(imageId)方法

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

此时我们会发现一个bug,在点击删除按钮之后,会触发预览图片效果,这是因为javaScript的事件冒泡机制导致的,一个标签接受的事件会依次传给父级标签,此时需要阻止click事件冒泡,Vue中使用v-on:click.stop即可

七.细节优化

1.完善上传功能:我们当前的上传请求会返回一个json格式的数据,而我们需要的是直接能看到上传的效果,因此可以修改上传的响应,直接返回一个302响应,重定向到主页。

即修改ImageServlet.poPost():将 resp.setContentType("application/json;charset=utf-8")resp.getWriter().write("{\"ok\":true}")   修改为   resp.sendRedirect("index.html")。

2.优化磁盘存储空间——Md5

如果两个图片内容完全一样,在磁盘上只存一份文件就可以了,通过md5可以判定两个图片内容是否是一样的。如果两个图片内容相同,得到的md5一定是相同的,反之,近似的认为md5相同,原图片内容一定相同。因此我们在上传图片的时候,先判定新图片的md5在数据库中是否存在,如果已经存在了,就不把图片内容写道磁盘上,如果不存在,才写磁盘。

①使用md5的前提:引入依赖

 
      commons-codec
      commons-codec
      1.14
 

②修改ImageServlet.doPost()方法,实现计算Md5

// 计算md5
 image.setMd5(DigestUtils.md5Hex(fileItem.get()));
//修改文件路径的生成方法,使用MD5作为路径
 image.setPath("./image/"+image.getMd5());

③修改ImageDao,添加一个根据Md5查找图片的方法

    //按照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;
        try {
            //3.执行sql语句
            statement=connection.prepareStatement(sql);
            statement.setString(1,md5);
            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.关闭连接
            DBUtil.close(connection,statement,resultSet);
        }
        return null;
    }

④根据Md5决定写入文件,修改ImageServlet.doPost()方法。如果该md5的文件不存在,才写入磁盘,如果存在,则直接使用原来的文件,不需要再写入依次磁盘。

        //存到数据库中
        ImageDao imageDao=new ImageDao();
        //看看数据库中是否存在相同的MD5值的图片,不存在,返回null
        Image existImage=imageDao.selectByMd5(image.getMd5());
        imageDao.insert(image);
        //2.获取图片内容信息,并且写入磁盘文件
        File file=new File(image.getPath());
        if (existImage==null) {
            try {
                fileItem.write(file);
            } catch (Exception e) {
                e.printStackTrace();
                resp.setContentType("application/json; charset=utf-8");
                resp.getWriter().write("{\"ok\":false,\"reason\":\"写磁盘失败\"}");
                return; 
            }
        }

 

八.效果展示

主页面(已经测试上传过几张图片)

我的云相册_第15张图片

点击选择文件,上传一张名称为wr.jpg的图片,上传成功,如下

我的云相册_第16张图片

点击名称为wrr.jpg的图片下面的删除按钮,删除此照片。出现删除成功的弹框,点击确定,名称为wrr.jpg的图片就被成功删除

我的云相册_第17张图片

我的云相册_第18张图片

点击第三张照片进行放大查看:

我的云相册_第19张图片

 

项目源码:云相册源代码

     

你可能感兴趣的:(我的云相册)