模板文件云存储管理 Sisyphus通过easyExcel实现模板文件的上传和下载,OSS云存储平台存储模板文件,对应的文件名存储在DUCC配置中心,技术架构如下图所示。
核心逻辑
技术组件
EasyExcel
简介
EasyExcel是一个基于Java的简单、省内存的读写Excel的阿里巴巴开源项目。在尽可能节约内存的情况下支持读写百M的Excel,Github:https://github.com/alibaba/easyexcelhttps://github.com/liurenjin/easypoi
更少的内存占用
easyExcel能大大减少占用内存的主要原因是在解析Excel时没有将文件数据一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析。通过一行一行的解析模式,并将一行的解析结果以观察者的模式通知处理(AnalysisEventListener)。
底层源码
简单使用
实体类
@Data
public class Hero {
@ExcelProperty(value={"第一列"})
private int id;
@ExcelProperty(value={"第二列"})
private String name;
}
监听类 HeroListener
public class HeroListener extends AnalysisEventListener<Hero> {
Logger logger = LoggerFactory.getLogger(HeroListener.class);
//每次读取100条数据就进行保存操作
private static final int BATCH_COUNT = 100;
//由于每次读都是新new UserInfoDataListener的,所以这个list不会存在线程安全问题
List<Hero> list = new ArrayList<>();
//这个组件是Spring中的组件,这边推荐两种方法注入这个组件
//第一种就是提供一个UserInfoDataListener的构造方法,这个方法提供一个参数是UserInfoDataListener类型
//另外一种方法就是将 UserInfoDataListener 这个类定义成 UserService 实现类的内部类(推荐这种方式)
//private UserService userService;
@Override
public void invoke(Hero data, AnalysisContext analysisContext) {
logger.info("解析到一条数据:{}", JSON.toJSONString(data));
list.add(data);
if (list.size() >= BATCH_COUNT) {
saveData();
// 存储完成清理 list
list.clear();
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
// 这里也要保存数据,确保最后遗留的数据也存储到数据库
saveData();
logger.info("所有数据解析完成!");
}
private void saveData() {
logger.info("{}条数据,开始存储数据库!", list.size());
//保存数据
//userService.save(list);
logger.info("存储数据库成功!");
}
}
Service层
下载一个excel
@Override
public void testExcelDownload(HttpServletResponse response) {
try {
List<Hero> stuInfo = this.heroDao.getAllHeroInfo();
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode("测试", "UTF-8");
// String fileName = "测试";
response.setHeader("Content-disposition", "attachment;filename="+fileName+".xlsx");
EasyExcel.write(response.getOutputStream(),Hero.class)
.sheet("第一个sheet")
.doWrite(stuInfo);;
} catch (Exception e) {
logger.error("testExcelDownload error", e);
e.printStackTrace();
}
}
对象存储OSS
简介
OSS是一个分布式的对象存储服务,提供的是一个Key-Value对形式的对象存储服务。用户可以根据Object的名称(Key)唯一的获取该Object的内容。相关的名词介绍如下:
Bucket
Bucket是一个用户用来管理所存储Object的存储空间。 每个用户可以拥有多个Bucket。Bucket的名称在OSS的范围内必须是全局唯一的,一旦创建之后无法修改名称。Bucket内部的Object数目是没有限制的。
Bucket对于用户来说是一个管理Object的单元,所有的Object都必须隶属于某个Bucket。Bucket有一些属性用来控制Region、Object的访问控制、Object的生命周期等,这些属性是作用在该Bucket下所有的Object上的,因此用户可以灵活创建不同的Bucket来完成不同的管理功能。
同一个Bucket内部的空间是扁平的,即没有文件系统的目录等概念,所有的Object都是直接隶属于其对应的Bucket。
Object
Object是OSS存储数据的基本单元,称为OSS的对象,也被称为OSS的文件。在本文中,Object,对象,文件指的都是同一个意思。 Object由元信息(Object Meta),用户数据(Data)和文件名(Key)组成。 Object由一个在Bucket内部唯一的Key来标示。Object Meta信息是一个键值对,表示了Object的一些属性,比如最后修改时间、大小等信息,同时用户也可以存储一些自定义的信息在Object Meta信息中。
Object在整个存储的生命周期内都是不可变的。一个Object的生命周期是从上传成功到被删除为止。重复上传同名的Object会导致老的Object被删除然后新的Object取而代之。因此在OSS中,不支持类似文件系统的修改部分内容等操作。
AccessKey
AccessKey,简称AK,指的是访问身份验证中用到的AccessKeyId和AccessKeySecret。 OSS通过使用AccessKeyId和AccessKeySecret对称加密的方法来验证某个请求的发送者身份。AccessKeyId用于标示用户,AccessKeySecret是用户用于加密签名字符串和OSS用来验证签名字符串的密钥,其中AccessKeySecret必须保密。
- Bucket的拥有者申请的AccessKey。
- 被Bucket的拥有者通过RAM授权第三方请求者的AccessKey。
- 被Bucket的拥有者通过STS授权第三方请求者的AccessKey。
Region表示OSS的数据中心所在的区域,物理位置。Endpoint表示OSS对外服务的访问域名。
从设计层面来说,将OSS映射为文件系统是非常低效的,也是不建议的做法。如果一定要挂载成文件系统的话,也尽量只做写新文件、删除文件、读取文件这几种操作。使用OSS应该充分发挥其优点,即海量数据处理能力,优先用来存储海量的非结构化数据,比如图片、视频、文档等。
OSS常见操作
创建Bucket
在上传文件(Object)到OSS之前,您需要创建一个用于存储文件的Bucket。Bucket具有各种配置属性,包括地域、访问权限以及其他元数据。
上传文件
Bucket创建完成后,可以通过多种方式上传不同大小的文件。
下载文件
文件上传完成后,可以将文件下载至浏览器默认路径或本地指定路径。
列举文件
当Bucket内存储了大量的文件后,可以选择列举Bucket内的全部或部分文件。
删除文件
当不再需要保留上传的文件时,可以手动删除单个或多个文件,也可以通过配置生命周期规则自动删除单个或多个文件。
DUCC配置中心
简介
DUCC为京东的一款配置中心产品,是在原来UCC的基础上升级的新一代配置中心,类似与apollo。采用长轮询拉取&定时拉取,相对的开源配置中心有spring-cloud-config、diamond、disconf和apollo
https://github.com/spring-cloud/spring-cloud-config
https://github.com/takeseem/diamond
https://github.com/knightliao/disconf
https://github.com/ctripcorp/apollo/
DUCC的操作方式:
- 平台上,首先创建自己的应用,然后在对应命名空间-配置环境-工作区新建对应的键值型配置参数,最后发布即可。
- 工程中,配置对应的DUCC资源管理器,添加ducc平台连接信息,具体使用时,通过key获取配置。
特点
- 支持多环境(或称分组),分组可以合并
- 内置强大的基于插件的数据绑定框架,支持多种类型等转换;
- 支持Log4j、Log4j2、Logback的动态修改日记级别功能。
- 支持Spring原生注解、支持自定义注解,客户端代码入侵性低
- 支持客户端多配置源,支持自定义配置,如ZK、Consol扩展
- 支持配置预案切换
部分代码分析
持续更新完善中
Pojo和PojoExcel 实体类
public class Pojo {
}
public class PojoExcel {
}
PojoListener 监听类
public class PojoListener extends AnalysisEventListener<PojoExcel> {
private static final Logger LOGGER = LoggerFactory.getLogger(PojoListener.class);
private List<PojoExcel> dataList;
public PojoListener() {
}
public PojoListener(List<PojoExcel> dataList) {
this.dataList = dataList;
}
@Override
public void invoke(PojoExcel data, AnalysisContext analysisContext) {
LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data));
dataList.add(data);
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
LOGGER.info("所有数据解析完成!");
}
}
pojoExcelUtil 文件工具类
public static void writePojoExcel(List<PojoExcel> excelList, HttpServletResponse response, InputStream inputStream) throws Exception{
EasyExcel.write(response.getOutputStream()).withTemplate(inputStream).autoCloseStream(Boolean.TRUE).sheet().doFill(excelList);
}
public static List<PojoExcel> readPojoExcel(InputStream in){
List<PojoExcel> dataList = new ArrayList<>();
PojoListener listener = new PojoListener(dataList);
EasyExcel.read(in,PojoExcel.class, listener).sheet("上传落地页").doRead();
return dataList;
}
Service层逻辑
//读取文件
List<KolTaskAccountHrefExcel> dataList = pojoExcelUtil.readPojoExcel(file.getInputStream());
//写出文件
String filename = duccResourceManager.getConfigValue(DuccConstant.POJO_FILENAME);//通过DUCC获取文件名
pojoExcelUtil.writePojoExcel(dataList, response, pojoOssService.getExcelFileFromOss("模板文件.xlsx"));
// 获取OSS文件
@Override
public InputStream getExcelFileFromOss(String fileName) {
StorageObject Object = jingdongStorageService.bucket(excelBucketName).object(fileName).get();
if (storageObject == null) {
return null;
}
return storageObject.getInputStream();
}
//<property name="excelBucketName" value="${jss.bucketName.excel}"/>
//jss.bucketName.excel=OSS系统中文件存放位置
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。