图片服务器

项目核心知识点

  1. 简单的Web服务器设计开发能力(Servlet)
  2. 使用数据库(MySQL)JDBC操作MySQL
  3. 数据库设计(根据实际场景设计数据库表结构)
  4. 前后端交互的API的设计(基于HTTP协议)
  5. 认识JSON数据格式,学习使用Java中的Gson这个库操作JSON数据
  6. 学习测试HTTP服务器,Postman
  7. 使用HTML CSS JavaScript技术构建一个简单的网页

项目功能:
实现一个服务器完成对图片的上传,查看(属性/内容),删除操作。(重点)
同时也要实现一个简单的页面来展示当前的图片(了解)

一、服务器设计

1.数据库设计

  • 数据库中存储的图片的属性(元信息)

  • 图片正文,以文件的形式直接存在磁盘上的

  • 数据库中就记录一个path对应到磁盘上的文件

  • 校验和: 通过一个更短的字符串,来验证整体数据是否正确,短的字符串是根据原串内容通过一定的规则来计算出来的

  • md5(字符串哈希算法):图片的md5校验和

create database java_image_server;
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));

2.服务器API设计(前后端交互接口设计)
a)JSON 一种数据组织的格式。格式键值对结构。
使用JSON完成数据的序列化,方便进行网络传输
Gson:google搞得一个开源的JSON的JSON解析库
b)文件上传操作在HTML中是如何完成的
文件上传在HTTP协议中是如何进行的
c)设计前后端交互API

  • 新增图片
    请求:
POST /image
Content-Type:multipart/form-data;
//正文内容:包含图片自身的一些信息
//图片正文的二进制内容

响应:

//上传成功
HTTP/1.1 200 OK
{
	"ok":true
}
//上传失败
HTTP/1.1 200 OK
{
	"ok":false,
	"reason":"具体的失败原因"
}
  • 查看所有图片属性
    请求:
GET /image

响应:

HTTP/1.1 200 OK
[
	{
		imageId:1,
		imageName:"1.png",
		contentType:"image/png",
		size:10000,
		uploadTime:"20200404",
		path:"./data/1.png"
		md5:"11223344",
	},
	{
	.......
	}
]
  • 查看指定图片属性
    请求:
GET /image?imageId=[具体的数值]

响应:

HTTP/1.1 200 OK
{
	imageId:1,
	imageName:"1.png",
	contentType:"image/png",
	size:10000,
	uploadTime:"20200404",
	path:"./data/1.png"
	md5:"11223344",
}
  • 删除指定图片
    请求:
DELETE /image?imageId=[具体的图片id]

服务器实现代码的时候就可以判定方法,如果是DLETE方法,就执行删除操作
删除也不一定非得用DELETE方法。例如:
GET /image?imageId=xxx&delete=1

  • 查看指定图片内容
    请求:
GET /imageShow?imageId=[具体的图片id]

二、源码开发
1.数据库操作

  • 先创建DBUtil封装一下获取数据库连接的过程

  • dao数据访问层。这里面的类围绕着数据操作展开

  • Image对应到一个图片对象(包含图片的相关属性)

  • ImageDao Image对象的管理器。借助这个类完成Image对象的增删改查操作
    ImageDao有个selectAll方法,查找出所有数据库中的数据。如果数据库中内容有个几千条,这样查ok;如果数据库有几亿条,这样查就会非常低效,很可能直接把数据库或者应用程序搞挂
    更科学的做法可以指定一些其他筛选条件

    插入图片

    public void insert(Image image){
        //1.获取数据库连接
        Connection connection=DBUtil.getConnection();
        //2.创建并拼接SQL语句
        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.getMad5());

            //3.执行SQL语句
            int ret=statement.executeUpdate();
            if(ret!=1){
                //程序出现问题,抛出一个异常
                throw new JavaImageServerException("插入数据库出错!");
            }

        } catch (SQLException e) {
            e.printStackTrace();
        }catch (JavaImageServerException e) {
            e.printStackTrace();
        }finally {
            //4.关闭连接和statement对象
            DBUtil.close(connection,statement,null);
        }
    }

查看所有图片信息

public List<Image> selectAll(){
        List<Image> images=new ArrayList<Image>();
        Connection connection=DBUtil.getConnection();
        String sql="select * from image_table";
        PreparedStatement statement=null;
        ResultSet resultSet=null;
        try {
            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.setPath(resultSet.getString("path"));
                image.setMad5(resultSet.getString("md5"));
                image.setContentType(resultSet.getString("contentType"));
                images.add(image);
            }
            return images;
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            DBUtil.close(connection,statement,resultSet);
        }
        return null;
    }

根据imageId查找指定的图片信息

    public Image selectOne(int imageId){
        Connection connection=DBUtil.getConnection();
        String sql="select * from image_table where imageId=?";
        PreparedStatement statement=null;
        ResultSet resultSet=null;
        try {
            statement=connection.prepareStatement(sql);
            statement.setInt(1,imageId);
            resultSet=statement.executeQuery();
            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.setPath(resultSet.getString("path"));
                image.setMad5(resultSet.getString("md5"));
                image.setContentType(resultSet.getString("contentType"));
                return image;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            DBUtil.close(connection,statement,resultSet);
        }
        return null;
    }
    public void delete(int imageId){
        Connection connection=DBUtil.getConnection();
        String sql="delete from image_table where imageId=?";
        PreparedStatement statement=null;
        try {
            statement=connection.prepareStatement(sql);
            statement.setInt(1,imageId);
            int ret=statement.executeUpdate();
            if(ret!=1){
                throw new JavaImageServerException("删除数据库操作失败");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }catch (JavaImageServerException e) {
            e.printStackTrace();
        }finally {
            //4.关闭连接和statement对象
            DBUtil.close(connection,statement,null);
        }
    }

根据Md5查找图片

public Image selectMd5(String md5){
        Connection connection=DBUtil.getConnection();
        String sql="select * from image_table where md5=?";
        PreparedStatement statement=null;
        ResultSet resultSet=null;
        try {
            statement=connection.prepareStatement(sql);
            statement.setString(1,md5);
            resultSet=statement.executeQuery();
            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.setPath(resultSet.getString("path"));
                image.setMad5(resultSet.getString("md5"));
                image.setContentType(resultSet.getString("contentType"));
                return image;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            DBUtil.close(connection,statement,resultSet);
        }
        return null;
    }
}

受查异常和非受查异常

出现异常之后,处理的具体措施
1.当前接触过的大部分都是打印调用栈
2.让程序直接终止
3.监控报警通知程序员

  1. 基于Servlet来搭建服务器
    a)安装Servlet(maven)
    b)创建一个类,继承HttpServlet父类并且重写这个父类中的一些重要方法

按照现有设定,图片存储的文件路径./image/文件名
如果有两个图片,内容不同,但是名字相同,此时就可能出现上传失败的情况(在路径中加上时间戳)
时间戳:以1970年1月1日0时0分0秒作为基准值计算当时时刻和基准时刻的描述/毫秒数之差

3.上传图片

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1.获取图片的属性信息,并且存入数据库
        // a)需要创建一个factory对象和upload对象
        FileItemFactory factory=new DiskFileItemFactory();
        ServletFileUpload upload=new ServletFileUpload(factory);
            //b)通过upload对象进一步解析
        //FileItem代表一个上传的文件对象
        List<FileItem> items;
        try {
            items=upload.parseRequest(req);
        } catch (FileUploadException e) {
            //出现异常说明解析出错!
            e.printStackTrace();
            //告诉客户端出现的具体的错误
            resp.setContentType("application/json;charset=utf-8");
            resp.getWriter().write("{\"ok\":false,\"reason\":\"请求解析失败\"}");
            return;
        }
        //c)把FileItem中的属性提取出来,转换成Image对象,才能存到数据库中
        FileItem fileItem=items.get(0);
        Image image=new Image();
        image.setImageName(fileItem.getName());
        image.setSize((int)fileItem.getSize());
        SimpleDateFormat format=new SimpleDateFormat("yyyyMMdd");
        image.setUploadTime(format.format(new Date()));
        image.setContentType(fileItem.getContentType());

        image.setMad5(DigestUtils.md5Hex(fileItem.get()));
        image.setPath("./image/"+image.getMad5());
        //存到数据库中
        ImageDao imageDao=new ImageDao();

        Image existImage=imageDao.selectMd5(image.getMad5());
         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("lw-img.html");

    }

4.删除图片:
1).先获取到请求中的imageId
2).创建ImageDao对象,查看到该图片对象对应的相关属性
3).删除数据库中的记录
4).删除本地磁盘文件

protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("application/json;charset=utf-8");
        String imageId=req.getParameter("imageId");
        if(imageId==null||imageId.equals("")){
            resp.setStatus(200);
            resp.getWriter().write("{\"ok\":false,\"reason\":\"请求解析失败\"}");
            return;
        }
        ImageDao imageDao=new ImageDao();
        Image image=imageDao.selectOne(Integer.parseInt(imageId));
        if(image==null){
            resp.setStatus(200);
            resp.getWriter().write("{\"ok\":false,\"reason\":\"在数据库中不存在\"}");
            return;
        }
        imageDao.delete(Integer.parseInt(imageId));
        Image existImage=imageDao.selectMd5(image.getMad5());
        if(existImage==null){
            File file=new File(image.getPath());
            file.delete();
            resp.setStatus(200);
            resp.getWriter().write("{\"ok\":true}");
        }
    }

5.页面主题展示图片预览

<form id="upload-form" action="image" method="post" enctype="multipart/form-data" class="am-topbar-form am-topbar-right am-form-inline">
      <div class="am-form-group">
        <input type="file" id="upload" name="upload" class="am-form-field am-input-sm" >
          <input type="submit" value="Upload" style="height: 41px"/>
      </div>
    </form>

三、项目优化

1.简单的防盗链机制
可以判定当前请求的referer字段(HTTP请求协议中的header的部分),是不是在我们代码中指定的白名单中,如果是,才允许访问
referer表示当前请求的上个页面的地址
在代码中用一个hashSet存一下允许的Referer就可以

防盗链其他方式:https://www.cnblogs.com/wangyongsong/p/8204698.html
2.优化磁盘存储空间
应用到MD5
如果两个图片内容完全一样,就在磁盘上只存一份文件就可以了
通过MD5就能判定两个图片内容是否一样的
图片文件虽然是二进制数据,但本质上也是字符串,针对图片内容计算MD5
如果两个图片内容相同,得到的MD5一定是相同的
反之,近似的认为MD5相同,原图片内容一定相同

MD5的特点:
1.不管原串多长,得到的MD5值是固定长度
2.原串哪怕变动一个字节,MD5值就会变动很大
3.计算MD5值得过程很简单,但是通过MD5值无法推测出原字符串的(应用在密码学)

实现思路:上传图片的时候,先判定下新图片的MD5在数据库中是否存在,如果存在,就不把图片内容写到磁盘上,如果不存在,才存磁盘
1)改上传代码逻辑,磁盘文件名用md5值来表示
2)修改ImageDao,新增一个接口,能够按照MD5查找数据库内容
3)再次修改上传图片逻辑,根据MD5判断,当前图片是否要写磁盘

3. 支持图片处理功能(例如返回指定大小的图片)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值