文章目录
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 的方式上传
先引入 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,
processData: false, // 使数据不做处理
contentType: false, // 不要设置Content-Type请求头
success: function (res) {
console.log(res);
if (res.code == 200) {
alert("上传成功");
} else {
alert("上传失败");
}
},
error: function (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, 0, 1024 * 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, 0, 8192)) != -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, 0, 0, null);
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, 6, 6);
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);
}
}