设计模式之策略模式

背景:导入功能需要做成根据编码code或者名称实现不同的导入逻辑,编码和名称都是可配置的,未知的变化,这里要写通用的导入、校验和具体的导入、校验。至此我想到采用设计模式之策略模式+工厂模式实现此需求。若有不妥还望指正。

  1. 自定义注解
  2. 创建工厂
  3. spring启动扫描带注解的策略类放入工厂中
  4. 定义接口+方法
  5. 定义策略类
  6. 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);
        }
    }
相关推荐
2401_8576176218 分钟前
汽车资讯新趋势:Spring Boot技术解读
java·spring boot·后端
小林学习编程1 小时前
从零开始理解Spring Security的认证与授权
java·后端·spring
写bug的羊羊1 小时前
Spring Boot整合Nacos启动时 Failed to rename context [nacos] as [xxx]
java·spring boot·后端
努力的小陈^O^1 小时前
docker学习笔记跟常用命令总结
java·笔记·docker·云原生
童先生1 小时前
如何将java项目打包成docker 镜像并且可运行
java·开发语言·docker
feilieren1 小时前
SpringBoot 2.x 整合 Redis
java·开发语言·spring
2402_857589361 小时前
实验室管理效率提升:Spring Boot技术的力量
java·spring boot·后端
晓看天色*1 小时前
[JAVA]MyBatis框架—获取SqlSession对象
java·开发语言·前端
2401_857636392 小时前
Spring Boot图书馆管理系统:疫情中的技术实现
java·spring boot·后端
要努力学习鸭2 小时前
Java 实现鼠标单击右键弹出菜单项
java