记一次从Sql Server中图片二进制流还原回图片的开发过程

  • Post author:
  • Post category:其他


背景:最近在客户现场做项目上线时,需要数据迁移工作。客户之前用的一个BS桌面应用,其中关于图片的存储全部以二进制流的方式写入到Sql Server数据库表中的某个字段中,如下图所示,由于新开发应用采用文件服务器存储图片、文档等,所以就需要将表中的二进制数据还原成图片保存到文件服务器中。棘手的是原BS应用并没有提供图片下载功能以及存储前图片的MIME类型或者文件后缀,这就要求在读取图片二进制流时需要根据内容判断属于哪种类型的图片文件;

思路:首先观察一下上述图片,发现Sql Server Management工具在展示二进制流字段时会以十六进制的形式展示,并且大致可以归纳出相同图片类型的前四个字节(每两个十六进制算作一个字节)字节是一样的,那么我们在读取二进制流的时候先取出前四个字节转换成对应的十六进制,然后再找到这个十六进制字符串和图片类型的关系,那样就可以判断出这个二进制流是哪个图片类型(JPG,PNG,GIT,BMP等)了。

通过查阅

通过二进制头识别文件类型

这篇文章,找到图片相关的十六进制头大致有这么个对应关系,该篇博客详细描述了其它类型的文件和二进制头的对应关系,如有需要可查阅。由于这次的需求只处理图片,所以只关注图片相关的对应关系!

JPEG (jpg),文件头:FFD8FF 
PNG (png),文件头:89504E47 
GIF (gif),文件头:47494638 
Windows Bitmap (bmp),文件头:424D

好了,思路讲解清楚,直接上代码。以下代码为了达成快速实现,借助Springboot+JDBC的方式,即从jdbcTemplate中获取JDBC对象执行操作,有需要的小伙伴只需要查看while里面的内容即可

@RunWith(SpringRunner.class)
@SpringBootTest
public class ErmsDatahandleApplicationTests {

    @Autowired
    protected JdbcTemplate jdbcTemplate;

    @Test
    public void contextLoads() throws SQLException, IOException {
        String sql = "select [ArticleNo],[图片] from WearPartsInfo where [图片] is not null";
        PreparedStatement statement = jdbcTemplate.getDataSource().getConnection().prepareStatement(sql);
        ResultSet resultSet = statement.executeQuery();
        while (resultSet.next()) {
            // 读取图片二进制流
            InputStream is = resultSet.getBinaryStream("图片");
            /*
                1、从流中读取四个字节数据来判断图片后缀
                2、解决由于从流中读取四个字节后流当前指针位置会移动,导致图片二进制流数据丢失的问题
             */
            is.mark(4);
            byte[] bytes = new byte[4];
            is.read(bytes);
            // 通过分析字节数据内容获取图片后缀
            String imageSuffix = FileUtils.getImageSuffix(bytes);
            if(imageSuffix == null){
                continue;
            }
            is.reset();
            try {
                // 将图片二进制流还原成图片后写入本地硬盘
                ImageIO.write(ImageIO.read(is), imageSuffix, new File("F:\\img\\" + resultSet.getString("ArticleNo") + "." + imageSuffix));
            } catch (Exception ex) {
                // 防止出现未知类型的文件格式,导致还原失败
                System.out.printf("图片写入磁盘异常:%s", resultSet.getString("ArticleNo"));
            }
        }
    }

}

以下代码为工具类,主要提供根据字节数据解析文件后缀的过程,下述代码只罗列了几种,如有其它需要可根据实际情况追加即可;

public class FileUtils {

    /**
     * 根据字节数据推测文件后缀名
     * 1、首先将字节数组内容转换为十六进制字符串
     * 2、拿十六进制字符串和对应头进行简单匹配
     * JPEG (jpg),文件头:FFD8FF
     * PNG (png),文件头:89504E47
     * GIF (gif),文件头:47494638
     * Windows Bitmap (bmp),文件头:424D
     *
     * @param bytes
     * @return
     */
    public static String getImageSuffix(byte[] bytes) {
        // 将二进制转换成16进制字符串
        String hexStr = parseByte2HexStr(bytes);
        if (hexStr == null || hexStr.length() == 0) {
            return null;
        }
        // 进行内容匹配
        String imageSuffix = "jpg";
        if (hexStr.startsWith("89504E47")) {
            imageSuffix = "png";
        } else if (hexStr.startsWith("47494638")) {
            imageSuffix = "gif";
        } else if (hexStr.startsWith("424D")) {
            imageSuffix = "bmp";
        }
        return imageSuffix;
    }

    /**
     * 将二进制转换成16进制字符串
     *
     * @param bytes
     * @return
     */
    public static String parseByte2HexStr(byte[] bytes) {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(bytes[i] & 0xFF);
            if (hex.length() == 1) {
                hex = '0' + hex;
            }
            sb.append(hex.toUpperCase());
        }
        return sb.toString();
    }

}

以上,完了!



版权声明:本文为yu102655原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。