项目:图片服务器

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 算法,具有三个特性:

  1. 不管源字符串多长,得到的 md5 都是固定的长度;
  2. 源字符串稍微变化一点点内容,md5 值会发生很大的改变;
  3. 计算 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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值