背景:导入功能需要做成根据编码code
或者名称
实现不同的导入逻辑,编码和名称都是可配置的,未知的变化,这里要写通用的导入、校验和具体的导入、校验。至此我想到采用设计模式之策略模式+工厂模式
实现此需求。若有不妥还望指正。
- 自定义注解
- 创建工厂
spring
启动扫描带注解的策略类放入工厂中- 定义接口+方法
- 定义策略类
api
调用根据code
获取对应策略的实现类
至此大致的思路确定下面就是具体的代码实现细节
自定义注解
此处定义了三个属性,目前只用到了一个
go
import org.springframework.stereotype.Component;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义注解
*/
@Retention(RetentionPolicy.RUNTIME) // 作用于运行时
@Target(ElementType.TYPE) // 作用于类上
@Component
public @interface ImportService {
String templateCode();
int sheetIndex() default 0;
String sheetName() default "";
}
创建工厂
定义importRegisterRespository
存储策略实体类信息
go
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Component
public class ImportRegisterRespository {
private Map<String, Object> importRegisterRespository = new HashMap<>();
public Map<String, Object> getImportRegisterRespository(){
return importRegisterRespository;
}
}
扫描策略类放入map中
扫描带ImportService
注解的类进行扫描,并放入map
中
go
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
import javax.annotation.PostConstruct;
import java.util.Map;
@Component
public class ImportInitiator {
@Autowired
private ImportRegisterRespository importRegisterRespository;
@Autowired
private ApplicationContext applicationContext;
@PostConstruct
public void init(){
Map<String, Object> extensionBeans = applicationContext.getBeansWithAnnotation(ImportService.class);
for(Object bean : extensionBeans.values()){
Class<?> extensionClz = ClassUtils.getUserClass(bean);
ImportService extension = AnnotationUtils.findAnnotation(extensionClz, ImportService.class);
String templateCode = extension.templateCode();
importRegisterRespository.getImportRegisterRespository().put(templateCode, bean);
}
}
}
定义接口
定义2个方法,导入和验证
go
import com.alibaba.fastjson2.JSONObject;
import com.example.demo.dto.importVO.ImportLine;
import java.util.List;
public interface IBatchImportItf{
List<String> doBatchImport(List<JSONObject> data);
Boolean validate(List<ImportLine> importLineList);
}
定义策略类
此类是个半成品,具体的实现还没写完,不过大致思路都成型
可以继续写其他策略类,以此类推
go
import com.alibaba.fastjson2.JSONObject;
import com.example.demo.dto.importVO.ImportLine;
import com.example.demo.importHandler.init.ImportService;
import org.springframework.stereotype.Component;
import java.util.*;
@Component
@ImportService(templateCode = "BASE_IMPORT")
public class BaseImport implements IBatchImportItf {
@Override
public List<String> doBatchImport(List<JSONObject> data) {
List<String> lists = new ArrayList<>();
for (JSONObject datum : data) {
//插入表中 返回当前的批次号给前端
String uuid = UUID.randomUUID().toString();
lists.add(uuid);
}
//前端拿着批号导入正式表中
return lists;
}
@Override
public Boolean validate(List<ImportLine> importLineList) {
for (ImportLine importLine : importLineList) {
//根据uuid去数据库查数据 返回一个list
importLine = new ImportLine(1L,"xxxxx",1,"","具体的json数据");
JSONObject jsonObject = new JSONObject();
//模拟数据
jsonObject.put("order",1);
jsonObject.put("age",20);
jsonObject.put("gender","M-男");
jsonObject.put("province","河南");
jsonObject.put("city","郑州");
jsonObject.put("country","中原区");
//模拟规则 判空规则 根据 sheetCode查出配置的信息
Map<String,String> nullableFlagMap = new HashMap<String,String>(){{
put("age","Y");
put("province","Y");
put("city","Y");
put("country","Y");
}};
for (String key : nullableFlagMap.keySet()) {
if(Objects.isNull(jsonObject.get(key))){
importLine.setMsg(key+"不允许为空");
}
}
//更新 ImportLine 根据 batchId 和 rowIndex 去更新数据库
}
return null;
}
}
api调用
此api
也是个半成品,不过思路已确定
api
分三部分:
1、导入临时表,临时表返回批次id
2、通用验证,根据配置验证,主要是验证必输性,根据第一步的批次id查询数据验证,返回boolean
值,当返回true
再根据具体的策略实现类业务验证
3、导入正式表
go
import com.alibaba.fastjson2.JSONObject;
import com.example.demo.dto.importVO.ImportLine;
import com.example.demo.dto.importVO.TemplateDO;
import com.example.demo.dto.importVO.TemplateSheet;
import com.example.demo.dto.importVO.TemplateSheetCol;
import com.example.demo.importHandler.IBatchImportItf;
import com.example.demo.importHandler.init.ImportRegisterRespository;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.apache.poi.xssf.usermodel.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
@Autowired
ImportRegisterRespository importRegisterRespository;
@PostMapping("/import/temp")
public List<String> importTemp(MultipartFile file) {
Optional.ofNullable(file).orElseThrow(() -> new RuntimeException("文件不能为空"));
//获取初始化的导入对象
Map<String, Object> importServiceMap = importRegisterRespository.getImportRegisterRespository();
//先执行basr类 BASE_IMPORT
IBatchImportItf baseImport = (IBatchImportItf)importServiceMap.get("BASE_IMPORT");
/*
1 读取excel数据
2 先查出模板配置信息,转换成kv对象,k是排序跟excel的col匹配,v是字段驼峰编码跟excel的值匹配
比如:kv - {"1":"city"} excel从左往右读取数据,读到1从kv中获取city再拿excel中的具体值,组装数据{"city":"郑州市"}
3 这个就是最终要的结果 {"city":"郑州市","provice":"河南省","gender":"男"}
*/
InputStream inputStream = null;
Workbook workbook = null;
try{
inputStream = file.getInputStream();
workbook = new XSSFWorkbook(inputStream);
// 获取工作簿中的所有工作表数量
int numberOfSheets = workbook.getNumberOfSheets();
List<String> sheetUUID = new ArrayList<>();
// 遍历每一个工作表
for (int i = 0; i < numberOfSheets; i++) {
Sheet sheet = workbook.getSheetAt(i);
System.out.println("Sheet name: " + sheet.getSheetName());
if (workbook.isSheetHidden(i)) {
System.out.println("Sheet is hidden netx one.");
continue;
}
// 检查工作表是否被隐藏
// 遍历行
// 遍历每一行,从第3行开始(索引为1),第一行是说明,第二行是标题
List<JSONObject> list = new ArrayList<>();
for (int rowIndex = 2; rowIndex <= sheet.getLastRowNum(); rowIndex++) {
Row row = sheet.getRow(rowIndex);
if (row == null) {
// 如果行为空,则跳过
continue;
}
//模拟查询到数据sheet页列的数据信息
HashMap<Integer, String> sheetColMap = new HashMap<Integer, String>() {{
put(0, "order");
put(1, "age");
put(2, "gender");
put(3, "province");
put(4, "city");
put(5, "country");
}};
JSONObject jsonObject = new JSONObject();
// 遍历单元格
for (int j = 0; j < Integer.parseInt(String.valueOf(row.getLastCellNum())); j++) {
Cell cell = row.getCell(j);
String keyName = sheetColMap.get(j);
switch (cell.getCellType()) {
case STRING:
System.out.print(cell.getStringCellValue() + "\t");
jsonObject.put(keyName,cell.getStringCellValue());
break;
case NUMERIC:
if (DateUtil.isCellDateFormatted(cell)) {
System.out.print(cell.getDateCellValue() + "\t");
jsonObject.put(keyName,cell.getDateCellValue());
} else {
System.out.print(cell.getNumericCellValue() + "\t");
jsonObject.put(keyName,cell.getNumericCellValue());
}
break;
case BOOLEAN:
System.out.print(cell.getBooleanCellValue() + "\t");
jsonObject.put(keyName,cell.getBooleanCellValue());
break;
case FORMULA:
System.out.print(cell.getCellFormula() + "\t");
jsonObject.put(keyName,cell.getCellFormula());
break;
default:
System.out.print("UNKNOWN\t");
jsonObject.put(keyName,"UNKNOWN");
break;
}
System.out.println(jsonObject);
}
//至此一行读取完毕
list.add(jsonObject);
}
sheetUUID.addAll(baseImport.doBatchImport(list));
}
return sheetUUID;
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
try {
inputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
workbook.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
@PostMapping("/import/validate")
public void importValidate(@RequestBody List<String> uuidList) {
//获取初始化的导入对象
Map<String, Object> importServiceMap = importRegisterRespository.getImportRegisterRespository();
//先执行base类 BASE_IMPORT
IBatchImportItf baseImport = (IBatchImportItf)importServiceMap.get("BASE_IMPORT");
//根据uuid查到sheetCode,根据sheetCode在map中找到对应的实现类
Boolean baseValidate = baseImport.validate(Arrays.asList(new ImportLine()));
//todo 更新ImportLine表的错误信息,只要更新msg字段就代表错误
//基础校验通过 继续走业务校验
if(baseValidate){
for (String uuid : uuidList) {
//根据uuid查到sheetCode,根据sheetCode在map中找到对应的实现类
String sheetItemCode = "one";
IBatchImportItf itemImport = (IBatchImportItf)importServiceMap.get(sheetItemCode);
itemImport.validate(Arrays.asList(new ImportLine()));
//todo 更新ImportLine表的错误信息,只要更新msg字段就代表错误
}
}
}
@PostMapping("/import/business-table")
public void importBusinessTable(@RequestBody List<String> uuidList) {
for (String uuid : uuidList) {
String sheetCode = "one";
//根据uuid查到sheetCode 再拿出jsonDate
//获取初始化的导入对象
Map<String, Object> importServiceMap = importRegisterRespository.getImportRegisterRespository();
//先执行base类 BASE_IMPORT
IBatchImportItf itemImport = (IBatchImportItf)importServiceMap.get(sheetCode);
itemImport.doBatchImport(null);
}
}