【SpringBoot学习】35、SpringBoot 简易文件服务器

SpringBoot 简易文件服务器

确定需求

首先,需求分析是核心一点,明白我们需要做成什么效果,再去想使用什么手段实现,当然,这里实现的核心工鞥就是对文件的操作:上传、下载、压缩、打包等

项目源码

核心技术

  • 框架基础:采用 spring boot 2.2.6.RELEASE,因为几乎零配置文件,使用起来更加方便
  • 项目基础:采用 apache-maven-3.6.0,使用坐标极大的简化了 jar 的配置与安装
  • 实体插件:采用 lombok,这个插件能使用注解的形式构建实体类相关的各种操作
  • 图像压缩:采用 Thumbnailator,目前网上算是比较用的普遍的图片压缩工具
  • json 序列化:采用 fastjson,序列化工具使用 alibaba 提供的工具类
  • 二维码工具:采用 zxing,谷歌提供的生成二维码的工具类
  • 文件操作必备的依赖:commons-io、commons-codec

html 上传:文件上传

html 的表单上传文件,应该是开发中所有人接触的第一种上传方法,首先我们准备一个 form 表单,并设置对应的上传路径,表单中有一个 input 属性,type 类型为 file 的输入框,然后设置一个提交按钮即可

<form method="POST" action="/upload" enctype="multipart/form-data">
    <input type="file" name="file"/><br/><br/>
    文件夹名称:<input type="text" name="folder" value="upload"/><br/><br/>
    <input type="submit" value="Submit"/>
</form>

相对应的,后台接收这个请求中的 file 对象,和 folder 参数,参数说明:

参数 类型 默认值 说明
file file
input 输入框所选取的文件对象
folder text upload 存放服务器的文件夹

接收并处理请求,我这里将所有的代码放在 serviceImpl 中编写,因为 controller 控制层理论上只做控制处理,没有逻辑业务

    @ApiOperation("文件上传")
    @PostMapping("/upload")
    public ResponseResult upload(@RequestParam("file") MultipartFile file,
                                 @RequestParam(value = "folder", defaultValue = "default") String folder) 
{
        return ResponseResult.success(fileService.upload(file, folder));
    }

service 接口层返回了一个 string 字符串类型,实际上就是上传成功后返回的访问路径

String upload(MultipartFile file, String folder);

serviceImpl 实现类中处理请求分为三个阶段:验证参数、处理文件、返回访问路径

    @Override
    public String upload(MultipartFile file, String folder) {
        if (file.isEmpty()) {
            throw new FileVerifyException(FileEnums.NOT_FOUND.getInfo());
        }
        return FileUtils.upload(file, folder).getRelativePath();
    }

这里我们看到最后使用工具类 FileUtils 处理文件,工具类的内容我就不一步步分析了,其中涉及了异常处理、常量配置等,不做过多的介绍

html 上传:多文件上传

对于前端来书,多文件上传与单文件上传的区别在于,数量上的不同,选取多个文件,我们可以通过 input 的 multiple=”multiple”属性控制是否可以多选

<form method="POST" action="/uploadMore" enctype="multipart/form-data">
    <input type="file" name="file" multiple="multiple"/><br/><br/>
    文件夹名称:<input type="text" name="folder" value="uploadFile"/><br/><br/>
    <input type="submit" value="Submit"/>
</form>

参数说明:

参数 类型 默认值 说明
file file
增加了 multiple=”multiple”之后,选择多个文件实际上为一个数组对象
folder text upload 存放服务器的文件夹

接收并处理请求,我这里将所有的代码放在 serviceImpl 中编写,因为 controller 控制层理论上只做控制处理,没有逻辑业务

    @ApiOperation("多文件上传")
    @PostMapping("/uploadMore")
    public ResponseResult uploadMore(@RequestParam("file") MultipartFile[] files,
                                     @RequestParam(value = "folder", defaultValue = "default") String folder) 
{
        return ResponseResult.success(fileService.uploadMore(files, folder));
    }

service 接口层返回了一个 string 字符串类型,实际上就是上传成功后返回的访问路径,多个路径之间使用“,”分隔开

String uploadMore(MultipartFile[] files, String folder);

serviceImpl 实现类中处理请求分为三个阶段:验证参数、处理文件、返回访问路径

    @Override
    public String uploadMore(MultipartFile[] files, String folder) {
        if (files.length == 0) {
            throw new FileVerifyException(FileEnums.NOT_FOUND.getInfo());
        }
        List<String> list = new ArrayList<>();
        for (MultipartFile file : files) {
            list.add(FileUtils.upload(file, folder).getRelativePath());
        }
        if (CollectionUtils.isEmpty(list)) {
            throw new FileSaveException(FileEnums.SAVE_ERROR.getInfo());
        }
        return StringUtils.join(list, FilePathConst.SEPARATOR);
    }

我这里多文件上传实际上是遍历文件单个上传,然后拼接返回值,如果有更多的办法,还请连一下作者哟

html 上传:图片上传 – 压缩

图片上传并且压缩,这个是非常常见的功能需求,如果说客户上传一 10 张普通的图片大小为 10M,1000 个客户上传的总大小为 10G 不到一点,对于服务器而言,存在一个的空间限制,而且访问图片的同时,由于图片过大,导致加载速度过慢,这些问题都会导致客户的体验度下降,这里我们就可以考虑在不影响图片清晰度的情况下,降低图片占用的空间

<form method="POST" action="/uploadByThumbnail" enctype="multipart/form-data">
    <input type="file" name="file" multiple="multiple"/><br/><br/>
    文件夹名称:<input type="text" name="folder" value="uploadByThumbnail"/><br/><br/>
    是否保存原图:<input type="radio" name="saveOld" value="true" checked></label>
    <input type="radio" name="saveOld" value="false"></label>
    <br/><br/>
    <input type="submit" value="Submit"/>
</form>

参数说明:

参数 类型 默认值 说明
file file
input 输入框所选取的文件对象
folder text uploadByThumbnail 存放服务器的文件夹
saveOld boolean true 是否保存原图

相对应的控制层处理和普通文件上传是类似的,只是多了一个参数

    @ApiOperation("图片上传并压缩")
    @PostMapping("/uploadByThumbnail")
    public ResponseResult uploadByThumbnail(@RequestParam("file") MultipartFile[] files,
                                            @RequestParam(value = "folder", defaultValue = "default") String folder,
                                            @RequestParam(value = "saveOld", defaultValue = "true") boolean saveOld) 
{
        return ResponseResult.success(fileService.uploadByThumbnail(files, folder, saveOld));
    }

service 接口同样返回访问路径,多个路径使用“,”隔开

String uploadByThumbnail(MultipartFile[] files, String folder, boolean saveOld);

serviceImpl 接口实现类处理请求

    @Override
    public String uploadByThumbnail(MultipartFile[] files, String folder, boolean saveOld) {
        if (files.length == 0) {
            throw new FileVerifyException(FileEnums.NOT_FOUND.getInfo());
        }
        List<String> list = new ArrayList<>();
        for (MultipartFile file : files) {
            list.add(FileUtils.uploadByThumbnail(file, folder, saveOld).getRelativePath());
        }
        if (CollectionUtils.isEmpty(list)) {
            throw new FileSaveException(FileEnums.SAVE_ERROR.getInfo());
        }
        return StringUtils.join(list, FilePathConst.SEPARATOR);
    }

多图片上传并压缩也是同样的道理,这里需要注意一点,比如我上传两个图片,并设置为保存原图,实际上服务器保存了 4 张图片,服务器的返回值的格式为:

{
    "code"200,
    "message""OK",
    "count"0,
    "data""/uploadByThumbnail/2020/04/19/95f3da7a-f41b-4767-a229-a64108898f9a.png|/uploadByThumbnail/2020/04/19/95f3da7a-f41b-4767-a229-a64108898f9a_thumbnail.png,/uploadByThumbnail/2020/04/19/4be30b69-3205-462d-9f10-c7e5e851f3bc.png|/uploadByThumbnail/2020/04/19/4be30b69-3205-462d-9f10-c7e5e851f3bc_thumbnail.png"
}

解析返回值:首先我们通过“,”分隔开,得到每张图片的处理结果,然后在使用“|”将返回结果分隔开,得到原图的访问路径和压缩图片的访问路径

ajax 上传:FormData 上传

工作中实际上直接使用 html 上传几乎是没有的,因为通常我们要将上传成功返回的访问路径存入数据库中,接下来就是怎么样拿到上传返回的信息,这里我们举例通过 ajax 的 FormData 的方式上传

FormData 的 API 文档参考

先引入 jquery,最好还是自己下载到本地放入项目当中

<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>

这里我们不需要表单了,直接给 input 绑定点击事件即可

<input type="file" name="file" id="formDataUpload"/><br/><br/>
文件夹名称:<input type="text" name="folder" value="ajax" id="formDataFolder"/><br/><br/>
<input type="button" value="Submit" id="formDataButton"/>

然后监听点击事件,我们的上传路径直接使用“html 上传:文件上传”模块时使用的即可,主要是拿到返回值

    $('#formDataButton').on('click'function ({
        var formData = new FormData();
        formData.append("file", $('#formDataUpload')[0].files[0]);
        formData.append("folder", $.trim($('#formDataFolder').val()));
        $.ajax({
            url'/upload',
            dataType'json',
            type'POST',
            data: formData,
            processDatafalse// 使数据不做处理
            contentTypefalse// 不要设置Content-Type请求头
            successfunction (res{
                console.log(res);
                if (res.code == 200) {
                    alert("上传成功");
                } else {
                    alert("上传失败");
                }
            },
            errorfunction (res{
                console.log(res);
            }
        });
    });

请求完成之后,在控制台即可知道返回的结果,使用访问前缀拼接上访问路径即可实现访问图片

其他:将指定文件夹打包为 zip

这个案例是实际开发中作者遇到的问题,完整需求是:通过请求参数,生成 1 万张二维码,并下载二维码,这里我们就需要将这 1 万张二维码压缩,并生成 zip,然后返回一个下载路径即可

首先准备一个表单

<form method="POST" action="/folderToZip">
    待压缩的文件路径:<input type="text" name="folderPath" /><br/><br/>
    压缩后存放路径:<input type="text" name="zipPath"/><br/><br/>
    压缩后文件的名称:<input type="text" name="fileName"/><br/><br/>
    <input type="submit" value="Submit"/>
</form>

参数说明:路径都是相对于文件服务器的根目录书写的,例如:/uploadByThumbnail/2020/04/19,不用写绝对路径

参数 类型 默认值 说明
folderPath string
待压缩的文件路径
zipPath string
压缩后存放路径
fileName string
压缩后文件的名称

控制层直接将请求交个 service 处理

    @ApiOperation("将指定文件夹打包为zip")
    @PostMapping("/folderToZip")
    public ResponseResult folderToZip(String folderPath, String zipPath, String fileName) {
        return ResponseResult.success(fileService.folderToZip(folderPath, zipPath, fileName));
    }

service 接口,同样的返回打包完成之后的访问路径,使用访问前缀拼接上访问路径可即可下载 zip 包

String folderToZip(String folderPath, String zipPath, String fileName);

serviceImpl 实现类直接调用工具类处理

    @Override
    public String folderToZip(String folderPath, String zipPath, String fileName) {
        boolean flag = FileUtils.folderToZip(folderPath, zipPath, fileName);
        if (flag) {
            return zipPath + File.separator + fileName + ".zip";
        }
        throw new FileSaveException(FileEnums.FILE_ZIP_ERROR.getInfo());
    }

其他:生成二维码

生成二维码也是很常见的需要,通常情况下,我们将一些信息存放于二维码中,用户直接扫码即可读取相关信息,一般情况下,信息都是经过加密处理的,比如说考试的试卷信息,每张试卷都是独一无二的编号,存放于这个二维码中,考虑安全性,是不能直接被扫码查看的

<form method="POST" action="/createQrCode">
    文件夹名称:<input type="text" name="folder" value="createQrCode"/><br/><br/>
    二维码内容:<input type="text" name="content" value="测试二维码内容"/><br/><br/>
    <input type="submit" value="Submit"/>
</form>

参数说明:

参数 类型 默认值 说明
folder text createQrCode 存放服务器的文件夹
content string 测试二维码内容 二维码内容

控制层

    @ApiOperation("生成二维码")
    @PostMapping("/createQrCode")
    public ResponseResult createQrCode(@RequestParam(value = "content", defaultValue = "默认二维码内容")String content,
                                       @RequestParam(value = "folder", defaultValue = "default") String folder) 
{
        return ResponseResult.success(fileService.createQrCode(content, folder));
    }

service 接口同样是返回二维码的访问地址

String createQrCode(String content, String folder);

serviceImpl 通过谷歌提供的二维码生成工具类生成二维码,这里的工具类被再次封装过

    @Override
    public String createQrCode(String content, String folder) {
        try {
            String datePath = FileUtils.getDatePath(folder, FileUtils.DATE_TYPE_SLASH);
            String fileName = String.valueOf(UUID.randomUUID()).concat(".jpg");
            // 详情查看工具类,功能非常强大
            QrCodeUtils.encode(content, null, FilePathConst.SAVE_POSITION + datePath, fileName, false);
            return datePath + fileName;
        } catch (Exception e) {
            e.printStackTrace();
            throw new FileSaveException(FileEnums.QRCODE_CREATE_ERROR.getInfo());
        }
    }

常用:下载文件

下载文件时一个核心功能,有上传一定有下载,毋庸置疑。当我们上传文件的同时,为了避免文件名重复等为题,我们会给文件名重命名为随机名字,比如我这里使用的 Java UUID 工具:

// 文件名
String fileName = String.valueOf(UUID.randomUUID());

那么问题来了,我下载的时候要指定文件名字,下面就是实现方式

<form method="POST" action="/downloadFile">
    文件路径:<input type="text" name="filePath" value="/default/2020/04/12/1fee42a4-f902-488b-87c6-ca82ea6bc958.jpg"/><br/><br/>
    下载名称:<input type="text" name="fileName" value="我是下载文件的名字.jpg"/><br/><br/>
    <input type="submit" value="Submit"/>
</form>

参数说明:

参数 类型 默认值 说明
filePath string /default/2020/04/12/1fee42a4-f902-488b-87c6-ca82ea6bc958.jpg 访问的相对路径,我这里的默认值是方便测试,之前上传的图片的访问地址
fileName string 我是下载文件的名字.jpg 下载保存的文件名

这里有个细节,很多网上的教程下载中文名字时会发生乱码,我这一步故意设置为中文名字,实际上后台已经解决了这个问题 controller 控制层接收请求

    @ApiOperation("下载文件")
    @PostMapping("downloadFile")
    public void downloadFile(String filePath, String fileName, HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException {
        fileService.downloadFile(filePath, fileName, request, response);
    }

service 接口没有返回值,因为我这里是下载文件

void downloadFile(String filePath, String fileName, HttpServletRequest request, HttpServletResponse response);

serviceImpl 实现调用工具类实现下载

    @Override
    public void downloadFile(String filePath, String fileName, HttpServletRequest request, HttpServletResponse response) {
        FileUtils.download(filePath, fileName, request, response);
    }

工具类:FileUtils

文件操作工具类

/**
 * 文件上传工具类
 *
 * @author Tellsea
 * @date 2019/8/20
 */

@Slf4j
public class FileUtils {

    public static final String DATE_TYPE_SLASH = "yyyy" + File.separator + "MM" + File.separator + "dd";

    public static FileInfo upload(MultipartFile file, String folder) {
        // 文件名
        String fileName = String.valueOf(UUID.randomUUID());
        // 文件类型
        String fileType = file.getOriginalFilename().substring(file.getOriginalFilename().indexOf("."));
        // 时间文件夹
        String datePath = getDatePath(folder, DATE_TYPE_SLASH);
        // 相对路径
        String relativePath = datePath.concat(fileName).concat(fileType);
        // 绝对路径
        String destFile = FilePathConst.SAVE_POSITION.concat(datePath);
        File f = new File(destFile);
        if (!f.exists() && !f.isDirectory()) {
            f.mkdirs();
        }
        // 文件全路径
        destFile = destFile.concat(fileName).concat(fileType);
        try {
            byte[] bytes = file.getBytes();
            Path path = Paths.get(destFile);
            Files.write(path, bytes);
            log.info("上传成功...");
            log.info("相对路径:{}", relativePath);
            log.info("绝对路径:{}", destFile);
            return new FileInfo().setFileName(fileName)
                    .setFileType(fileType)
                    .setDatePath(datePath)
                    .setDestFile(destFile)
                    .setRelativePath(relativePath);
        } catch (Exception e) {
            throw new FileSaveException(FileEnums.SAVE_ERROR.getInfo());
        }
    }

    /**
     * 压缩上传
     *
     * @param file
     * @param folder
     * @param saveOld 是否保存原图
     * @return
     */

    public static FileInfo uploadByThumbnail(MultipartFile file, String folder, boolean saveOld) {
        FileInfo fileInfo = upload(file, folder);
        try {
            File toFile;
            if (saveOld) {
                String path = FilePathConst.SAVE_POSITION
                        .concat(fileInfo.getDatePath())
                        .concat(fileInfo.getFileName())
                        .concat(FilePathConst.THUMBNAIL_SUFFIX)
                        .concat(fileInfo.getFileType());
                toFile = new File(path);
                String newFilePath = fileInfo.getDatePath().concat(fileInfo.getFileName()).concat(FilePathConst.THUMBNAIL_SUFFIX).concat(fileInfo.getFileType());
                fileInfo.setRelativePath(fileInfo.getRelativePath().concat(FilePathConst.THUMBNAIL_SEPARATOR) + newFilePath);
            } else {
                toFile = new File(fileInfo.getDestFile());
            }
            Thumbnails.of(fileInfo.getDestFile())
                    .imageType(BufferedImage.TYPE_INT_ARGB)
                    // 指定图片大小    0-1f  1f是原图
                    .scale(0.5f)
                    // 图片质量  0-1f  1f是原图
                    .outputQuality(0.8f)
                    .toFile(toFile);
        } catch (Exception e) {
            e.printStackTrace();
            throw new FileThumbnailsException(FileEnums.THUMBNAILS_ERROR.getInfo());
        }
        return fileInfo;
    }

    /**
     * 获得保存路径
     *
     * @param folder
     * @param dateFormat
     * @return 例如:/aaa/2020/04/11/
     */

    public static String getDatePath(String folder, String dateFormat) {
        String date = new SimpleDateFormat(dateFormat).format(new Date());
        StringBuffer path = new StringBuffer();
        path.append(File.separator).append(folder).append(File.separator).append(date).append(File.separator);
        return path.toString();
    }

    /**
     * 将指定文件夹打包
     *
     * @param sourceFilePath :待压缩的文件路径
     * @param zipFilePath    :压缩后存放路径
     * @param fileName       :压缩后文件的名称
     * @return
     */

    public static boolean folderToZip(String sourceFilePath, String zipFilePath, String fileName) {
        boolean flag = false;
        File sourceFile = new File(sourceFilePath);
        FileInputStream fis = null;
        BufferedInputStream bis = null;
        FileOutputStream fos = null;
        ZipOutputStream zos = null;

        if (!sourceFile.exists()) {
            throw new FileVerifyException("待压缩的文件目录:" + sourceFilePath + "不存在.");
        } else {
            try {
                File zipFile = new File(zipFilePath + File.separator + fileName + ".zip");
                if (zipFile.exists()) {
                    zipFile.delete();
                }
                File[] sourceFiles = sourceFile.listFiles();
                if (null == sourceFiles || sourceFiles.length < 1) {
                    throw new FileVerifyException("待压缩的文件目录:" + sourceFilePath + "里面不存在文件,无需压缩.");
                } else {
                    fos = new FileOutputStream(zipFile);
                    zos = new ZipOutputStream(new BufferedOutputStream(fos));
                    byte[] bufs = new byte[1024 * 10];
                    for (int i = 0; i < sourceFiles.length; i++) {
                        //创建ZIP实体,并添加进压缩包
                        ZipEntry zipEntry = new ZipEntry(sourceFiles[i].getName());
                        zos.putNextEntry(zipEntry);
                        //读取待压缩的文件并写进压缩包里
                        fis = new FileInputStream(sourceFiles[i]);
                        bis = new BufferedInputStream(fis, 1024 * 10);
                        int read = 0;
                        while ((read = bis.read(bufs, 01024 * 10)) != -1) {
                            zos.write(bufs, 0, read);
                        }
                    }
                    flag = true;
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            } catch (IOException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            } finally {
                //关闭流
                try {
                    if (null != bis) {
                        bis.close();
                    }
                    if (null != zos) {
                        zos.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                    throw new RuntimeException(e);
                }
            }
        }
        return flag;
    }

    /**
     * 得到图片字节流 数组大小
     */

    public static byte[] readStream(InputStream inStream) throws Exception {
        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len = -1;
        while ((len = inStream.read(buffer)) != -1) {
            outStream.write(buffer, 0, len);
        }
        outStream.close();
        inStream.close();
        return outStream.toByteArray();
    }

    /**
     * 将文件转换成Byte数组
     *
     * @param file
     * @return
     */

    public static byte[] getBytesByFile(File file) {
        try {
            FileInputStream fis = new FileInputStream(file);
            ByteArrayOutputStream bos = new ByteArrayOutputStream(1000);
            byte[] b = new byte[1000];
            int n;
            while ((n = fis.read(b)) != -1) {
                bos.write(b, 0, n);
            }
            fis.close();
            byte[] data = bos.toByteArray();
            bos.close();
            return data;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * MultipartFile转File
     *
     * @param param
     * @return
     */

    public static File transfer(MultipartFile param) {
        if (!param.isEmpty()) {
            File file = null;
            try {
                InputStream in = param.getInputStream();
                file = new File(param.getOriginalFilename());
                OutputStream out = new FileOutputStream(file);
                int bytesRead = 0;
                byte[] buffer = new byte[8192];
                while ((bytesRead = in.read(buffer, 08192)) != -1) {
                    out.write(buffer, 0, bytesRead);
                }
                in.close();
                out.close();
                return file;
            } catch (Exception e) {
                e.printStackTrace();
                return file;
            }
        }
        return null;
    }

    /**
     * 获取指定文件的输入流
     *
     * @param logoPath 文件的路径
     * @return
     */

    public static InputStream getResourceAsStream(String logoPath) {
        return FileUtils.class.getResourceAsStream(logoPath);
    }

    /**
     * 下载文件
     *
     * @param filePath 访问路径
     * @param fileName 下载文件名
     * @param request
     * @param response
     */

    public static void download(String filePath, String fileName, HttpServletRequest request, HttpServletResponse response) {
        File file = new File(FilePathConst.SAVE_POSITION + filePath);
        if (file.exists()) {
            response.setHeader("content-type""application/octet-stream");
            response.setContentType("application/octet-stream");
            // 下载文件能正常显示中文
            try {
                response.setHeader("Content-Disposition""attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            // 实现文件下载
            byte[] buffer = new byte[1024];
            FileInputStream fis = null;
            BufferedInputStream bis = null;
            try {
                fis = new FileInputStream(file);
                bis = new BufferedInputStream(fis);
                OutputStream os = response.getOutputStream();
                int i = bis.read(buffer);
                while (i != -1) {
                    os.write(buffer, 0, i);
                    i = bis.read(buffer);
                }
            } catch (Exception e) {
                throw new FileSaveException(FileEnums.DOWNLOAD_ERROR.getInfo());
            } finally {
                if (bis != null) {
                    try {
                        bis.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (fis != null) {
                    try {
                        fis.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        } else {
            throw new FileSaveException(FileEnums.DOWNLOAD_NOT_FOUND_ERROR.getInfo());
        }
    }

    public static void main(String[] args) {
        String path = "D:\Workspace\IDEAWorkspace\file-server\file-static\uploadByThumbnail\2020\04\12";
        folderToZip(path, path, "test");
    }
}

工具类:QrCodeUtils

生成二维码工具类

/**
 * 二维码生成工具类
 *
 * @author user
 * @date 2018/12/5
 */

public class QrCodeUtils {
    private static final String CHARSET = "utf-8";
    public static final String FORMAT = "JPG";
    /**
     * 二维码尺寸
     */

    private static final int QRCODE_SIZE = 300;
    /**
     * LOGO宽度
     */

    private static final int LOGO_WIDTH = 60;
    /**
     * LOGO高度
     */

    private static final int LOGO_HEIGHT = 60;

    /**
     * 生成二维码
     *
     * @param content      二维码内容
     * @param logoPath     logo地址
     * @param needCompress 是否压缩logo
     * @return 图片
     * @throws Exception
     */

    public static BufferedImage createImage(String content, String logoPath, boolean needCompress) throws Exception {
        Hashtable<EncodeHintType, Object> hints = new Hashtable<EncodeHintType, Object>();
        hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
        hints.put(EncodeHintType.CHARACTER_SET, CHARSET);
        hints.put(EncodeHintType.MARGIN, 1);
        BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, QRCODE_SIZE, QRCODE_SIZE,
                hints);
        int width = bitMatrix.getWidth();
        int height = bitMatrix.getHeight();
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                image.setRGB(x, y, bitMatrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF);
            }
        }
        if (logoPath == null || "".equals(logoPath)) {
            return image;
        }
        // 插入图片
        QrCodeUtils.insertImage(image, logoPath, needCompress);
        return image;
    }

    /**
     * 插入LOGO
     *
     * @param source       二维码图片
     * @param logoPath     LOGO图片地址
     * @param needCompress 是否压缩
     * @throws IOException
     */

    private static void insertImage(BufferedImage source, String logoPath, boolean needCompress) throws IOException {
        InputStream inputStream = null;
        try {
            inputStream = FileUtils.getResourceAsStream(logoPath);
            Image src = ImageIO.read(inputStream);
            int width = src.getWidth(null);
            int height = src.getHeight(null);
            if (needCompress) {
                // 压缩LOGO
                if (width > LOGO_WIDTH) {
                    width = LOGO_WIDTH;
                }
                if (height > LOGO_HEIGHT) {
                    height = LOGO_HEIGHT;
                }
                Image image = src.getScaledInstance(width, height, Image.SCALE_SMOOTH);
                BufferedImage tag = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
                Graphics g = tag.getGraphics();
                // 绘制缩小后的图
                g.drawImage(image, 00null);
                g.dispose();
                src = image;
            }
            // 插入LOGO
            Graphics2D graph = source.createGraphics();
            int x = (QRCODE_SIZE - width) / 2;
            int y = (QRCODE_SIZE - height) / 2;
            graph.drawImage(src, x, y, width, height, null);
            Shape shape = new RoundRectangle2D.Float(x, y, width, width, 66);
            graph.setStroke(new BasicStroke(3f));
            graph.draw(shape);
            graph.dispose();
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
        }
    }

    /**
     * 生成二维码(内嵌LOGO)
     * 二维码文件名随机,文件名可能会有重复
     *
     * @param content      内容
     * @param logoPath     LOGO地址
     * @param destPath     存放目录
     * @param needCompress 是否压缩LOGO
     * @throws Exception
     */

    public static String encode(String content, String logoPath, String destPath, boolean needCompress) throws Exception {
        BufferedImage image = QrCodeUtils.createImage(content, logoPath, needCompress);
        mkdirs(destPath);
        String fileName = new Random().nextInt(99999999) + "." + FORMAT.toLowerCase();
        ImageIO.write(image, FORMAT, new File(destPath + "/" + fileName));
        return fileName;
    }

    /**
     * 生成二维码(内嵌LOGO)
     * 调用者指定二维码文件名
     *
     * @param content      内容
     * @param logoPath     LOGO地址
     * @param destPath     存放目录
     * @param fileName     二维码文件名
     * @param needCompress 是否压缩LOGO
     * @throws Exception
     */

    public static String encode(String content, String logoPath, String destPath, String fileName, boolean needCompress) throws Exception {
        BufferedImage image = QrCodeUtils.createImage(content, logoPath, needCompress);
        mkdirs(destPath);
        fileName = fileName.substring(0, fileName.indexOf(".") > 0 ? fileName.indexOf(".") : fileName.length())
                + "." + FORMAT.toLowerCase();
        ImageIO.write(image, FORMAT, new File(destPath + "/" + fileName));
        return fileName;
    }

    /**
     * 当文件夹不存在时,mkdirs会自动创建多层目录,区别于mkdir.
     * (mkdir如果父目录不存在则会抛出异常)
     *
     * @param destPath 存放目录
     */

    public static void mkdirs(String destPath) {
        File file = new File(destPath);
        if (!file.exists() && !file.isDirectory()) {
            file.mkdirs();
        }
    }

    /**
     * 生成二维码(内嵌LOGO)
     *
     * @param content  内容
     * @param logoPath LOGO地址
     * @param destPath 存储地址
     * @throws Exception
     */

    public static String encode(String content, String logoPath, String destPath) throws Exception {
        return QrCodeUtils.encode(content, logoPath, destPath, false);
    }

    /**
     * 生成二维码
     *
     * @param content      内容
     * @param destPath     存储地址
     * @param needCompress 是否压缩LOGO
     * @throws Exception
     */

    public static String encode(String content, String destPath, boolean needCompress) throws Exception {
        return QrCodeUtils.encode(content, null, destPath, needCompress);
    }

    /**
     * 生成二维码
     *
     * @param content  内容
     * @param destPath 存储地址
     * @throws Exception
     */

    public static String encode(String content, String destPath) throws Exception {
        return QrCodeUtils.encode(content, null, destPath, false);
    }

    /**
     * 生成二维码(内嵌LOGO)
     *
     * @param content      内容
     * @param logoPath     LOGO地址
     * @param output       输出流
     * @param needCompress 是否压缩LOGO
     * @throws Exception
     */

    public static void encode(String content, String logoPath, OutputStream output, boolean needCompress)
            throws Exception 
{
        BufferedImage image = QrCodeUtils.createImage(content, logoPath, needCompress);
        ImageIO.write(image, FORMAT, output);
    }

    /**
     * 生成二维码
     *
     * @param content 内容
     * @param output  输出流
     * @throws Exception
     */

    public static void encode(String content, OutputStream output) throws Exception {
        QrCodeUtils.encode(content, null, output, false);
    }
}

微信公众号


原文始发于微信公众号(花海里):【SpringBoot学习】35、SpringBoot 简易文件服务器

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/69624.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!