我正在参加「构思开发 投稿大赛」概况请看:构思开发大赛来了!
- 个人网站: IT常识小屋
- 版权: 本文由【IT学习日记】原创、需求转载请联系博主
- 假如文章对你有协助、欢迎关注、点赞、保藏(一键三连)和订阅专栏哦
前语
一个优异的开发者,一定是会运用各种东西来提升自己的开发功率。 前段时刻,博主在Gitee/Github开源了一个提升开发功率的东西,东西内集成了各种常用东西如csv、excel、ftp、文件体系等等,只需求简单调用API,就可以得到想要的成果,可以极大协助开发者提升功率,下面来一同看看这款东西怎么运用吧。
东西介绍
报表的导出、导入功用、文件上传、下载等在平常业务中是最常见不过的功用了,许多小伙伴在开发的时分才会去网上找之前别人编写过的事例参考,可是许多博客记载的都是时刻比较长远或许不完整的代码,这导致在引进的时分还要处理引进的许多不知道问题。
现在博主开源的“轮子之王”包括了这些常见的功用,源码全开发,每种功用都有相应的比如阐明,项目会持续更新迭代,有问题还可以及时给项目提issue,信任比大大都网上的博客代码可靠性更高。
项目地址如下:
Github:github.com/it-learning…
Gitee:gitee.com/it-learning…
项目结构如下:
一、excel东西
该东西完结采用的是开源的easyexcel结构。easyexcel是阿里的开发人员对poi结构进行了优化,处理了poi在大数据量时或许呈现OOM反常,并且兼容xls和xlsx两种文件类型的一个开源结构。
excel东西集成功用如下:
- excel的导入(可以自定义转化后的excel数据处理的业务逻辑,支撑抛出反常、业务回滚、记载解析时的反常数据)
- 导出(支撑固定表头,兼容多sheet页和动态表头,兼容多sheet页)功用。
excel东西的特色如下:
运用过easyexcel结构的一些读者知道,每个导入功用都要写一个对应的Listener进行数据转化,在许多时刻其实转化的逻辑都是类似的,不同的只不过是转化后数据处理的业务逻辑不一样。
本开源项目的excel东西则运用Java中的泛型和Java8中的Consumer接口将相同的部分(转化逻辑)抽取出来,不同的部分则独自传入(数据处理的业务逻辑),这样就防止了每个导入都需求创立一个相类似的Listerner,减少了类的创立和提高了开发功率。
部分源码如下:
/**
* 通用导入excel文件办法
*
* @param fileStream 导入的文件流
* @param rowDto 接收excel每行数据的实体
* @param rowAction 将接收到的实体进行自定义的业务处理逻辑办法
* @param <T> 实体类型
*/
public static <T> void importFile(InputStream fileStream, T rowDto, ThrowingConsumer<List<T>> rowAction) {
// 获取excel通用监听器
ExcelImportCommonListener<T> commonListener = new ExcelImportCommonListener<>(rowAction);
// 读取excel文件并导入
EasyExcel.read(fileStream, rowDto.getClass(), commonListener).sheet().doRead();
}
/**
* excel文件导出(可以包括多个sheet页),固定表头(经过实体指定特点的方法)
* @param response
* @param fileName 导出文件名
* @param head 导出表头(多个sheet页便是多个调集元素)
* @param exportData 需求导出数据
* @param sheetNames sheet页的称号,为空则默认以:sheet + 数字规则命名
*/
public static <T> void exportFile(String fileName, List<T> head, List<List<T>> exportData, List<String> sheetNames, HttpServletResponse response) {
if (Objects.isNull(response) || StrUtil.isBlank(fileName) || CollUtil.isEmpty(head)) {
log.info("ExcelExportUtil exportFile required param can't be empty");
return;
}
ExcelWriter writer = null;
try {
response.setContentType(ExportConstant.EXCEL_CONTENT_TYPE);
response.setCharacterEncoding(ExportConstant.UTF_8);
response.setHeader(ExportConstant.CONTENT_DISPOSITION, ExportConstant.ATTACHMENT_FILENAME + fileName + ExportConstant.XLSX_SUFFIX);
// 设置导出的表格样式
HorizontalCellStyleStrategy horizontalCellStyleStrategy = getExportDefaultStyle();
writer = EasyExcel.write(response.getOutputStream()).registerWriteHandler(horizontalCellStyleStrategy).build();
for (int itemIndex = 0; itemIndex < exportData.size(); itemIndex++) {
// 表头数据
Object headData = head.get(itemIndex);
// sheet页的数据
List<T> list = exportData.get(itemIndex);
WriteSheet sheet = EasyExcel.writerSheet(itemIndex, CollUtil.isEmpty(sheetNames) ? ExportConstant.SHEET_NAME + itemIndex + 1 : sheetNames.get(itemIndex)).head(headData.getClass()).build();
writer.write(list, sheet);
}
} catch (Exception e) {
log.error("ExcelExportUtil exportFile in error:{}", e);
} finally {
if (null != writer) {
writer.finish();
}
}
}
运用事例如下(在东西中每个项目都有详细的事例,不懂的还可以留言跟博主沟通):
/**
* 导入用户数据事例
*
* @param file
*/
@Transactional(rollbackFor = Exception.class)
public void uploadUserListDemoWithExcel(MultipartFile file, String username) throws Exception {
// 此处先校验导入的文件类型是否为excel
String type = FileTypeUtil.getType(file.getInputStream());
if (StrUtil.isBlank(type) || type.contains(ImportConstant.XLS_TYPE) || type.contains(ImportConstant.XLSX_TYPE)) {
// 回来校验失利信息
return;
}
User user = new User();
user.setId(100);
user.setName("外层");
user.setPassword("外层");
userService.save(user);
// 调用统一导入办法
ExcelImportUtil.importFile(file.getInputStream(), new UserDto(), UserServiceImpl::saveUserList);
}
/**
* 导出事例
*
* @param response
*/
public void exportUserListDemoWithExcel(HttpServletResponse response) {
// 表头(运用excel中的注解定义,假如表头不固定,请运用ExcelExportUtil.exportWithDynamicData进行导出)
List<UserExportVo> head = Stream.of(new UserExportVo()).collect(Collectors.toList());
// 数据(运用两层list为了兼容多个sheet页,假如是不同的sheet页则放在不同的List调集中)
List<List<UserExportVo>> exportDataList = new ArrayList<>();
List<UserExportVo> exportItem = new ArrayList<>();
// 查询数据
List<User> dbData = userService.list();
// 将数据转化成导出需求的实践数据格式,此处仅仅演示
for (User user : dbData) {
UserExportVo vo = new UserExportVo();
BeanUtil.copyProperties(user, vo);
exportItem.add(vo);
}
exportDataList.add(exportItem);
// sheet页称号-自定义,假如没有则传空
List<String> sheetNameList = Stream.of("sheet1").collect(Collectors.toList());
ExcelExportUtil.exportFile("user", head, exportDataList, sheetNameList, response);
}
二、csv东西
Csv即逗号分隔值,也可以称为字符分隔符,与excel等文件比较,excel文件中会包括许多格式信息,占用的空间会更大,所以Csv在许多大数据场景导出、导入场景是十分常见的。该东西完结采用的是开源的univocity-parsers结构完结。
之前有一篇专门讲解轮子之王项目为何运用univocity-parsers结构集成csv的详细过程,有兴趣的读者可以点击链接查看:集成csv东西的来龙去脉
部分源码如下:
/**
* 运用实体bean接收csv数据文件并进行数据落盘
*
* @param inputStream
* @param errorList
* @param rowDtoClass
* @param rowAction
* @param <T>
*/
public static <T> void importCsvWithBean(InputStream inputStream, List<String> errorList, Class rowDtoClass, ThrowingConsumer<List<T>> rowAction) {
// 定义bean解析者:用于将csv中数据绑定到实体特点中,然后存储带list调集上
BeanListProcessor<T> rowProcessor = new BeanListProcessor<>(rowDtoClass);
CsvParserSettings setting = getDefaultSetting(errorList);
setting.setProcessor(rowProcessor);
// 创立csv文件解析
CsvParser csvParser = new CsvParser(setting);
csvParser.parse(inputStream);
// 获取数据映射后的调集
List<T> dataList = rowProcessor.getBeans();
// 校验必填字段
for (T row : dataList) {
// 校验导入字段
ImportValid.validRequireField(row, errorList);
}
// 执行数据耐久化
persistentBeanDataToDb(dataList, rowAction);
}
/**
* 导出csv文件(表头和行都以实体的方法)
*
* @param response
* @param head
* @param rowDataList
*/
public static <T> void exportCsvWithBean(HttpServletResponse response, String fileName, T head, List<T> rowDataList) {
CsvWriter writer = null;
try {
// 设置呼应头格式
response.setContentType(ExportConstant.EXCEL_CONTENT_TYPE);
response.setCharacterEncoding(ExportConstant.UTF_8);
response.setHeader(ExportConstant.CONTENT_DISPOSITION, ExportConstant.ATTACHMENT_FILENAME + fileName + ExportConstant.CSV_SUFFIX);
// 设置导出格式
CsvWriterSettings setting = getDefaultWriteSetting();
// 创见bean处理器,用于处理写入数据
BeanWriterProcessor<?> beanWriter = new BeanWriterProcessor<>(head.getClass());
setting.setRowWriterProcessor(beanWriter);
// 导出数据
writer = new CsvWriter(response.getOutputStream(), setting);
writer.processRecords(rowDataList);
writer.flush();
} catch (Exception e) {
log.error("CsvExportUtil exportCsvWithBean in error:{}", e);
} finally {
if (Objects.nonNull(writer)) {
writer.close();
}
}
}
运用事例如下:
/**
* 导出事例
*
* @param response
*/
public void exportUserListWithCsv(HttpServletResponse response) {
List<UserExportCsvVo> exportItem = new ArrayList<>();
// 查询数据
List<User> dbData = userService.list();
// 运用字符串数组方法作为表头导出csv数据
List<Object> head = Stream.of("id", "name", "password").collect(Collectors.toList());
List<List<Object>> dataList = new ArrayList<>();
for (User user : dbData) {
List<Object> row = new ArrayList<>();
row.add(user.getId());
row.add(user.getName());
row.add(user.getPassword());
dataList.add(row);
}
CsvExportUtil.exportCsvWithString(response, "demo", head, dataList);
}
/**
* 导入用户数据事例(csv模式)
*
* @param file
*/
@Transactional(rollbackFor = Exception.class)
public void uploadUserListWithCsv(MultipartFile file) throws Exception {
// 此处先校验导入的文件类型是否为csv
String type = FileTypeUtil.getType(file.getInputStream());
if (StrUtil.isBlank(type) || type.contains(ImportConstant.CSV_TYPE)) {
// 回来校验失利信息
return;
}
User user = new User();
user.setId(100);
user.setName("外层");
user.setPassword("外层");
userService.save(user);
List<String> errorLogList = new ArrayList<>();
// 调用统一导入办法
// 方法一:运用csv数据映射到dto实体的方法进行数据导入
//CsvImportUtil.importCsvWithBean(file.getInputStream(), errorLogList, UserCsvDto.class, UserServiceImpl::saveUserListWithCsv);
// 方法二、运用csv数据映射到字符串数组的方法进行数据导入
CsvImportUtil.importCsvWithString(file.getInputStream(), errorLogList, UserCsvDto.class, UserServiceImpl::saveUserListWithCsvStringArrDemo);
// 假如存在解析反常,输出解析反常并进行业务回滚
if (CollUtil.isNotEmpty(errorLogList)) {
throw new RuntimeException(StrUtil.toString(errorLogList));
}
}
三、ftp东西
Ftp文件上传下载比较excel、csv等呈现的场景较少,可是,假如你参加的项目是政府或许涉及到第三方旧体系对接的时分,许多时分就需求运用到它。因为许多旧体系或许政府项目运用的技能比较旧或许有制度约束,一般都是以文件的形式与你进行交互,此时ftp东西就很有用了。
Ftp东西运用的commons-net开源结构进行完结,详细的集成流程之前独自运用一篇文章进行了十分详细的介绍,有需求的读者可以点击后面链接查看:手把手教你建立ftp服务器,并用程序完结ftp上传下载功用
部分源码如下:
/**
* 上传
*
* @return
*/
public boolean upload(FtpUploadParam param) {
boolean flag = false;
FTPClient ftpClient = new FTPClient();
//1 测验衔接
if (connect(ftpClient, param.getHostname(), param.getPort(), param.getUsername(), param.getPassword())) {
try {
//2 检查工作目录是否存在,不存在则创立
if (!ftpClient.changeWorkingDirectory(param.getWorkingPath())) {
ftpClient.makeDirectory(param.getWorkingPath());
}
// 将文件编码成Ftp服务器支撑的编码类型(FTP协议里边,规则文件名编码为iso-8859-1,所以目录名或文件名需求转码。)
String fileName = new String(param.getSaveName().getBytes(ftpClientCharset), ftpServerCharset);
// 3 上传文件
if (ftpClient.storeFile(fileName, param.getInputStream())) {
flag = true;
} else {
log.warn("FtpUtils uploadFile unsuccessfully!!");
}
} catch (IOException e) {
log.error("FtpUtils upload in error:{}", e);
} finally {
disconnect(ftpClient);
}
}
return flag;
}
/**
* @description: 下载ftp文件
* @param:
* @param: param
* @param: downloadFileName
* @return:
* @date: 2022/7/14 10:56
*/
public boolean download(FtpDownloadParam param, String downloadFileName) {
FTPClient ftpClient = new FTPClient();
FileOutputStream out = null;
boolean downloadResult = false;
//1 测验衔接
if (connect(ftpClient, param.getHostname(), param.getPort(), param.getUsername(), param.getPassword())) {
try {
String localPath = param.getDownloadPath() + param.getFileName();
out = new FileOutputStream(new File(localPath));
//2 检查工作目录是否存在,不存在回来
// if (!ftpClient.changeWorkingDirectory(param.getWorkingPath())) {
// return false;
// }
/*
* 翻开FTP服务器的PASS模式(不记得FTP协议支撑的模式请翻到文章第一阶段)
* 这个办法的意思便是每次数据衔接之前,ftp client告诉ftp server开通一个端口来传输数据. 因为ftp
* server或许每次开启不同的端口来传输数据,可是在linux上,因为安全约束,或许某些端口没有开启,或许呈现呈现阻塞
*/
ftpClient.enterLocalPassiveMode();
// 设置文件的传输方法-二进制
ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
// 将文件编码成Ftp服务器支撑的编码类型(FTP协议里边,规则文件名编码为iso-8859-1,所以目录名或文件名需求转码。)
// 短少编码转化会导致:从FTP服务器下载下来的文件是破损的,无法被翻开
downloadResult = ftpClient.retrieveFile(new String(downloadFileName
.getBytes(ftpClientCharset), ftpServerCharset), out);
out.flush();
} catch (IOException e) {
log.error("FtpUtils upload in error:{}", e);
return false;
} finally {
try {
if (Objects.nonNull(out)) {
out.close();
}
} catch (Exception e) {
log.error("FtpUtils upload in error:{}", e);
}
disconnect(ftpClient);
}
}
return downloadResult;
}
详细运用事例如下:
@PostMapping("/ftp/upload")
public void upload() {
try {
FtpUploadParam param = new FtpUploadParam();
param.setHostname(ftpConfig.getServerHostname());
param.setPort(ftpConfig.getServerPort());
param.setUsername(ftpConfig.getServerUsername());
param.setPassword(ftpConfig.getServerPassword());
param.setWorkingPath(ftpConfig.getServerWorkingPath());
param.setSaveName("xxx.mp3");
InputStream in = new FileInputStream(new File("D:/uploadfile/like.mp3"));
param.setInputStream(in);
ftpUtils.upload(param);
} catch (Exception e) {
log.error("TestFtpServerController upload 过错:{}", e);
}
}
@PostMapping("/ftp/download")
public void download() {
try {
FtpDownloadParam param = new FtpDownloadParam();
param.setHostname(ftpConfig.getServerHostname());
param.setPort(ftpConfig.getServerPort());
param.setUsername(ftpConfig.getServerUsername());
param.setPassword(ftpConfig.getServerPassword());
param.setWorkingPath(ftpConfig.getServerWorkingPath());
param.setDownloadPath("D:/downloadFile/");
param.setFileName("xxx.mp3");
ftpUtils.download(param, "xxxx.mp3");
} catch (Exception e) {
log.error("TestFtpServerController download 过错:{}", e);
}
}
四、分布式文件体系东西
非结构化数据通常是运用文件的方法进行存储,这时分不可防止地要运用到文件体系进行办理。 分布式文件体系东西运用了第三方开源结构seaweedfs进行建立,可以完结程序上传,删去、下载、查询,并有文件分布式存储,防止单点故障,节省成本等特色。
前面也专门经过一篇文章讲解了:为何要运用seaweedfs结构建立分布式文件体系的,感兴趣的读者可以经过下方链接进行查看:Gitee图床溃散后,我运用Seaweedfs建立了文件体系并封装成轮子开源
部分源码如下:
/**
* @description: 上传单个文件到文件服务器
* @param: file
* @return: 文件的fid + 文件的请全拜访地址
* @author: it
*/
public String uploadFile(MultipartFile file) throws Exception {
FileSource fileSource = getFileSource();
FileTemplate fileTemplate = new FileTemplate(fileSource.getConnection());
// 上传文件
FileHandleStatus handleStatus = fileTemplate.saveFileByStream(file.getOriginalFilename(), file.getInputStream(), contentType);
// 获取上传文件的拜访地址
String fileUrl = fileTemplate.getFileUrl(handleStatus.getFileId());
// 封闭当前衔接
fileSource.shutdown();
return handleStatus.getFileId() + StrUtil.DASHED + fileUrl;
}
/**
* @description: 依据文件下载文件
* @param: fid
* @param: response
* @param: fileName
* @author: it
*/
public void downloadFileByFid(HttpServletResponse response, HttpServletRequest request, String fid, String fileName) throws Exception {
FileSource fileSource = getFileSource();
FileTemplate fileTemplate = new FileTemplate(fileSource.getConnection());
StreamResponse fileStream = fileTemplate.getFileStream(fid);
// 设置呼应头
response.setContentType(CommonConstant.CONTENT_TYPE);
response.setCharacterEncoding(CommonConstant.UTF_8);
String encodeFileName = buildingFileNameAdapterBrowser(request, fileName);
response.setHeader(CommonConstant.CONTENT_DISPOSITION, CommonConstant.ATTACHMENT_FILENAME + encodeFileName);
// 读取并写入到呼应输出
InputStream inputStream = fileStream.getInputStream();
byte[] fileByte = new byte[inputStream.available()];
inputStream.read(fileByte);
response.getOutputStream().write(fileByte);
response.getOutputStream().flush();
fileSource.shutdown();
}
详细运用事例如下:
/**
* @description: 上传文件
* @param:
* @param: file
* @return:
* @author: it
* @date: 2022/7/14 17:01
*/
@ResponseBody
@RequestMapping("upload")
public void uploadFile(MultipartFile file) {
try {
String fileUrl = seaweedFsUtil.uploadFile(file);
System.out.println(fileUrl);
} catch (Exception e) {
log.error("TestSeaweedFsController uploadFile in error:{}", e);
}
}
/**
* @description: 下载文件
* @param:
* @param: fileId
* @return:
* @author: it
* @date: 2022/7/14 17:01
*/
@RequestMapping("download")
public void downloadFile(HttpServletResponse response, HttpServletRequest request, String fileId, String fileName) {
try {
seaweedFsUtil.downloadFileByFid(response, request, fileId, fileName);
} catch (Exception e) {
log.error("TestSeaweedFsController downloadFile in error:{}", e);
}
}
东西更新
到此为止,轮子之王已集成的东西就介绍完毕了,后续还会不断更新、集成新的轮子,下面给我们介绍一下下一段时刻项目的一些工作(假如读者有想要集成的轮子,欢迎提issue或许这文章下面留言):
- 集成一个可视化界面,更好地介绍开源东西中各个轮子的引进事例,便利我们运用。
- 集成word文件导出东西
- 集成pdf文件导出东西
- 集成复杂报表的报表导出东西(运用freemaker结构)
- 待更新…
集成方案介绍相关文章
- 手把手教你建立ftp服务器,并用程序完结ftp上传下载功用
- 集成csv东西的来龙去脉
- Gitee图床溃散后,我运用Seaweedfs建立了文件体系并封装成轮子开源
写在最后
开源之路不容易,开源之心不忘掉!假如博主开源的项目对您有所协助,请给项目star,给博主更多动力,假如阅读文章给您有所协助,请给博主点赞、关注。
该开源项目会持续更新和保护,希望有更多读者可以提出建议和主意,假如有需求集成的东西,欢迎给项目提issue或许这文章下面留言,博主看到后会及时回复。