目录
前言
**针对于系统来说,必不可少的就是导出数据到本地(而基于导出到本地数据的问题)就有了excel。此功能将用户想要导出的数据写入excel中方便用户查看与分析,OK,话不多说直接上流程~
基于JAVA策略模式实现
策略模式作用与原理
- 作用:减少代码冗余,提高系统复用性,使用者只需要关注业务不需要关注调用方等等
- 原理:针对于之前的多重if判断JAVA给出了一个策略模式,定义了一组算法,将每个算法包装起来,从而使算法与算法之间互相转换,例如Map,key-value都是特定的算法,调用只需要传key由算法自动获取value值~
本章实现流程
我们可以将策略模式看为角色的组成具体含义如下
1. 策略角色工厂:策略接口,内置一个特定方法由具体策略实现类去做其他的事情,
2. 具体策略角色:实现角色工厂,子从父,每个实现都有各自的行为,互不影响
3. 策略分发类:也就是通过一些处理,根据前端传递的信息用于执行某个策略实现类返回结果
4. 向外挂出的环境角色,持有一个角色工厂,用于客户端传递特定信息执行具体策略实现
环境准备
导依赖(这里我使用阿里封装的Excel导出API)
EasyExcel******LOMBOK
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>RELEASE</version>
</dependency>
策略角色工厂
/**
* 策略容器
*/
public interface ExcelStatery {
/**
* 具体策略执行-获取结果体通用方法
* @params 前端传递json字符串作为特定接口的条件导出数据时筛选
* @return
*/
R polycyImpleMent(String params);
}
**其中的通用返回体接收类是通用的,具体代码如下
R
@Data
@NoArgsConstructor
@ToString
@SuppressWarnings("all")
public class R<T> {
private int code;
private T data;
private String msg;
private R(int code, T data, String msg) {
this.code = code;
this.data = data;
this.msg = msg;
}
public static <T> R<T> data(T data) {
return data(data, "操作成功");
}
public static <T> R<T> data(T data, String msg) {
return data(200, data, msg);
}
public static <T> R<T> data(int code, T data, String msg) {
return new R(code, data, data == null ? "暂无承载数据" : msg);
}
public static <T> R<T> fail(int code, String msg) {
return new R(code, (Object) null, msg);
}
}
策略实现类1(针对机台报表)
//注入Spring容器(接口实现类)
@Service
//加载容器时命名为machineListImpl(用途在于后面的策略分发功能)
@Qualifier("machineListImpl")
public class MachineListImpl implements ExcelStatery {
//重写策略实现类,自己的实现业务自定义扩展即可
@Override
public R polycyImpleMent(String params) {
MachAndDeptDto machAndDeptDto = new MachAndDeptDto();
//这里列举一下当前策略执行有几个前端传的条件~~选看
if (Func.isNotEmpty(params)) {
JSONObject jsonObject = (JSONObject)JSONObject.parse(params);
Map<String,Object> maps=(Map<String,Object>)jsonObject;
machAndDeptDto.setDeptCode((String) maps.get("deptCode"));
machAndDeptDto.setEndDate((String) maps.get("endDate"));
machAndDeptDto.setStartDate((String) maps.get("startDate"));
machAndDeptDto.setMachineType((String) maps.get("machineType"));
machAndDeptDto.setTenantCode((String) maps.get("tenantCode"));
}
//模拟当前策略实现类最后返回数据结果集
List<MachSumMeterDto> machSumMeterDtos = new LinkedList<>();
for (int i = 0; i < 20; i++) {
MachSumMeterDto machSumMeterDto = new MachSumMeterDto();
machSumMeterDto.setMachineName("哈哈");
machSumMeterDto.setCurrDate("今天");
machSumMeterDto.setMachineCode("001");
machSumMeterDtos.add(machSumMeterDto);
}
//放入通用返回体中-返回
return R.data(machSumMeterDtos );
}
}
策略实现类2(针对员工报表)
//注入Spring容器(接口实现类)
@Service
//加载容器时命名为empNameListImpl(用途在于后面的策略分发功能)
@Qualifier("machineListImpl")
public class EmpNameListImpl implements ExcelStatery {
//重写策略实现类,自己的实现业务自定义扩展即可
@Override
public R polycyImpleMent(String params) {
MachAndDeptDto machAndDeptDto = new MachAndDeptDto();
//这里列举一下当前策略执行有几个前端传的条件~~选看
if (Func.isNotEmpty(params)) {
JSONObject jsonObject = (JSONObject)JSONObject.parse(params);
Map<String,Object> maps=(Map<String,Object>)jsonObject;
machAndDeptDto.setDeptCode((String) maps.get("deptCode"));
machAndDeptDto.setEndDate((String) maps.get("endDate"));
machAndDeptDto.setStartDate((String) maps.get("startDate"));
machAndDeptDto.setMachineType((String) maps.get("machineType"));
machAndDeptDto.setTenantCode((String) maps.get("tenantCode"));
}
//模拟当前策略实现类最后返回数据结果集
List<EmpSumMeterDto> empSumMeterDtos = new LinkedList<>();
for (int i = 0; i < 20; i++) {
EmpSumMeterDto e=new EmpSumMeterDto();
e.setName("张三")
empSumMeterDtos.add(e);
}
//放入通用返回体中-返回
return R.data(empSumMeterDtos);
}
}
实体类(这里我只举例一个)
很简单,阿里封装之后Excel导出只需要在返回的实体中,想哪个字段作为导出列就加上一个注解即可
@Data
@ApiModel("员工个人产量")
public class EmpSumMeterDto {
@ApiModelProperty("员工编号")
@ExcelProperty("员工编号")
private String empCode;
@ApiModelProperty("员工姓名")
@ExcelProperty("员工姓名")
private String name;
@ApiModelProperty("效率")
@ExcelProperty("效率")
private String efficiency;
@ApiModelProperty("总米数")
@ExcelProperty("总米数")
private String meters;
@ApiModelProperty("总运行时间(小时)")
@ExcelProperty("总运行时间(小时)")
private String sumRunSeconds;
@ApiModelProperty("总停机时间(小时)")
@ExcelProperty("总停机时间(小时)")
private String sumStopSeconds;
@ApiModelProperty("当天日期")
@ExcelProperty("当天日期")
private String currDate;
@ApiModelProperty("班别")
@ExcelProperty("班别")
private String shiftClass;
@ApiModelProperty("机台")
@ExcelProperty("机台")
private String machineCode;
@JsonSerialize(nullsUsing = NullSerializer.class)
@ExcelProperty("折合产量")
@ApiModelProperty("折合产量")
private Double sumConMeters;
@JsonSerialize(nullsUsing = NullSerializer.class)
@ExcelProperty("计件工资")
@ApiModelProperty("计件工资")
private Double sumMoneys;
@ApiModelProperty("部门-前端不用显示")
@ExcelIgnore
private String deptCode;
@ApiModelProperty("梳节个数-前端不用显示")
@JsonSerialize(nullsUsing = NullSerializer.class)
//例如这个:这个就说明Excel导出时这个不写入返回
@ExcelIgnore
private Long gbNos;
}
创建枚举类
其作用就是在响应客户端以及前端交互时指定具体的策略执行~
//可以看到下面的implName就是刚才两个具体实现策略类的注入Bean名称
//获取时可以直接调用Spring的bean对象获取方法进行操作
@Getter
@AllArgsConstructor
@SuppressWarnings("all")
/**
* 枚举类
* implName:实际执行策略Bean名称
* apiClassPath:实际执行策略Bean返回体的实体路径
*/
public enum StateryEnum {
MACHINE_LIST_IMPL("machineListImpl", "org.tfcloud.weave.dto.MachSumMeterDto"),
EMPNAME_LIST_IMPL("empNameListImpl", "org.tfcloud.weave.dto.EmpSumMeterDto");
String implName;
String apiClassPath;
}
Bean名称获取对象工具类
@Component
public class SpringApplicationContextUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
/**
* 初始化注入当前Bean
*
* @param applicationContext
* @throws BeansException
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
/**
* 获取当前ApplicationContext对象
*
* @return
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* 根据Bean名称获取指定对象三种形式
* @param beanName
* @return
*/
public static Object getBean(String beanName) {
return getApplicationContext().getBean(beanName);
}
public static <T> T getBean(Class<T> tClass) {
return getApplicationContext().getBean(tClass);
}
public static <T> T getBean(String beanName, Class<T> tClass) {
return getApplicationContext().getBean(beanName, tClass);
}
}
前面策略工厂,实现类,相关枚举,工具类等都已经准备完毕,这时候就可以写与策略工厂直接进行通信获取某个特定的策略实现类以达到对应效果
具体调用执行策略工厂分发类
- 这里我使用了一个缓存作为每次导出时最后返回的是哪个策略执行类的Object对象,用意就是在响应客户端回写Excel数据时使用的是哪个结果类对象的实例
- 这里还有一个注意点,SpringBoot普通类再注入其它对象或接口时,不会随着当前类的加载注入进去,所以这时候需要处理一下,在当前类初始化之前直接赋值,那么问题就迎刃而解了
//具体调用执行策略工厂分发
@Component
@AllArgsConstructor
@SuppressWarnings("all")
public class StateryObtainFactory {
@Autowired
private RedisTemplate redisTemplate;
private static StateryObtainFactory stateryObtainFactory;
/**
* 初始化对象注入当前类
*/
@PostConstruct
public void init() {
stateryObtainFactory = this;
stateryObtainFactory.redisTemplate = this.redisTemplate;
}
/**
* 动态获取执行策略实现接口
* @param paramCodeType
* @return
*/
public static ExcelStatery getStateryBean(String paramCodeType) {
//获取指定枚举对象
StateryEnum stateryEnum = StateryEnum.valueOf(paramCodeType);
String stateryName = stateryEnum.getImplName();
//存储redis当前使用策略指定类路径
stateryObtainFactory.
redisTemplate.
opsForValue().
set(ServerUtils.STATERY_CLASSPATH__KEY, stateryEnum.getApiClassPath());
ExcelStatery excelStatery = SpringApplicationContextUtil.getBean(stateryName, ExcelStatery.class);
return excelStatery;
}
}
对外环境角色类
- paramCode:就是之前定义的枚举实现类的标识名称
- params:为具体报表的查询条件等等(json字符串形式接收)
@Component
@AllArgsConstructor
// 通用获取指定策略执行~
public class ExcelContextEveryRole {
/**
* 返回结果体
* @param paramCode
* @param params
* @return
*/
public R findDataListByParamCode(String paramCode, String params) {
ExcelStatery statery = StateryObtainFactory.getStateryBean(paramCode);
R data = statery.polycyImpleMent(params);
return R.data(data);
}
}
Controller通用接口
@RestController
@AllArgsConstructor
@RequestMapping("excelPort")
@Api(value = "报表导出~",tags = "报表导出接口入口")
@SuppressWarnings("all")
public class ExcelResultController {
private ExcelContextEveryRole excelContextEveryRole;
private RedisTemplate redisTemplate;
@GetMapping("exPortWithForm")
@ApiOperation(value = "通用报表导出",notes = "传入指定报表名称即可",position = 1,produces = "application/octet-stream")
@ApiParam(value = "指定报表名称",name = "paramCode",required = true)
public void exPortWithForm(HttpServletResponse response, @RequestParam("paramCode")String paramCode,@RequestParam(value = "params",required = false)String params){
//调用策略工厂的对外开放环境角色类得到数据集
R data = excelContextEveryRole.findDataListByParamCode(paramCode, params);
//获取当前时间戳
String dateTime = String.valueOf(Calendar.getInstance().getTimeInMillis()) + ".xlsx";
//设置响应体的编码格式
response.setCharacterEncoding("utf-8");
response.setContentType("application/vnd.ms-excel");
try {
response.setHeader("Content-disposition",
"attachment;filename="+ new String(dateTime.getBytes(),"iso-8859-1")+ "" );
//转换获取结果集(List)
List<Object> dataData = (List<Object>)((R)data.getData()).getData();
//核心导出API方法写入Excel
EasyExcel.write( response.getOutputStream(),
//从redis获取当前执行特定策略实现类的返回实体对象的类路径,
//通过反射拿到当前类对象,也就是导出时的Excel各项列名称~
Class.forName((String)redisTemplate.opsForValue().get(ServerUtils.STATERY_CLASSPATH__KEY)))
.sheet()
.doWrite(dataData);
} catch (Exception e) {
}
}
}
访问形式
例:
这个url执行之后返回的就是机台报表Excel,直接响应前台页面下载
localhost:8888/excelPort/exPortWithForm?paramCode=MACHINE_LIST_IMPL
这个url执行之后返回的就是员工报表Excel,直接响应前台页面下载
localhost:8888/excelPort/exPortWithForm?paramCode=EMPNAME_LIST_IMPL
看不懂或者有疑问联系我
Q:2509647976 微:x331191249