自定义springboot组件--excel导入(自定义参数解析器)

一 引入

日常开发中,我们经常用到excel的导入,由于excel导入的的步骤相似度较大,本文基于自定义参数解析器实现对excel导入基本操作的抽离,在项目中快速实现excel的基本导入功能.

二 自定义参数解析器(HandlerMethodArgumentResolver )

自定义参数解析器需要实现 HandlerMethodArgumentResolver 接口,我们先来看看该接口:

public interface HandlerMethodArgumentResolver {
	boolean supportsParameter(MethodParameter parameter);
	@Nullable
	Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;

}

这个接口中就两个方法:

  • supportsParameter:该方法表示是否启用这个参数解析器,返回 true 表示启用,返回 false 表示不启用。
  • resolveArgument:这是具体的解析过程,就是从 request 中取出参数的过程,方法的返回值就对应了接口中参数的值。既把这个最终的结果赋值给当前参数

三 自定义spingboot组件

下文中的excle导入组件基于easyexcel实现

申明一个导入注解

@Documented
@Target({ ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestExcel {
    /**
     * 前端上传文件字段名称
     * @return
     */
    String fileName() default "file";

    /**
     * 是否跳过空行
     */
    boolean ignoreEmptyRow() default false;

    /**
     * 数据读取监听器
     * @return
     */
    Class<? extends ListAnalysisEventListener<?>> readListener() default DefaultListAnalysisEventListener.class;

}

申明数据读取监听器

抽象一个list监听器,在里面定义一个获得数据和获取数据校验失败的结果集

public abstract class ListAnalysisEventListener<T> extends AnalysisEventListener<T> {
    /**
     * 获取excel解析结果的对象列表
     * @return
     */
    public abstract List<T> getList();

    /**
     * 校验错误信息
     * @return
     */
    public abstract List<ErrorMessage> getErrors();
}

@Slf4j
public class DefaultListAnalysisEventListener extends ListAnalysisEventListener<Object>{

    private final List<Object> list = new ArrayList<Object>();

    private final List<ErrorMessage> errorMessageList = new ArrayList<ErrorMessage>();

    /**
     * 行号
     */
    private Long lineNum = 1L;

    @Override
    public List<Object> getList() {
        return list;
    }

    @Override
    public List<ErrorMessage> getErrors() {
        return errorMessageList;
    }

    @Override
    public void invoke(Object data, AnalysisContext context) {
        lineNum++;
        //校验当前数据
        Set<ConstraintViolation<Object>> validates = Validators.validate(data);

        if (!validates.isEmpty()){
            Set<String> errors = validates.stream().map(ConstraintViolation::getMessage)
                    .collect(Collectors.toSet());
            errorMessageList.add(new ErrorMessage(lineNum,errors));
        }else {
            list.add(data);
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        log.info("excel read analysed");
    }
}

定义校验工具及错误信息

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ErrorMessage {
    /**
     * 行号
     */
    private Long lineNum;

    private Set<String> errors = new HashSet<>();

    public ErrorMessage(Set<String> errors) {
        this.errors = errors;
    }

    public ErrorMessage(String error) {
        HashSet<String> objects = new HashSet<>();
        objects.add(error);
        this.errors = objects;
    }
}

public class Validators {
    public static final Validator validator;
    static {
        ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
        validator=validatorFactory.getValidator();
    }

    public static <T> Set<ConstraintViolation<T>> validate(T object){
        return validator.validate(object);
    }


}

定义参数转换器

public enum LocalDateStringConverter implements Converter<LocalDate> {

	/**
	 * 实例
	 */
	INSTANCE;

	@Override
	public Class supportJavaTypeKey() {
		return LocalDate.class;
	}

	@Override
	public CellDataTypeEnum supportExcelTypeKey() {
		return CellDataTypeEnum.STRING;
	}

	@Override
	public LocalDate convertToJavaData(CellData cellData, ExcelContentProperty contentProperty,
			GlobalConfiguration globalConfiguration) throws ParseException {
		if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) {
			return LocalDate.parse(cellData.getStringValue());
		}
		else {
			DateTimeFormatter formatter = DateTimeFormatter
					.ofPattern(contentProperty.getDateTimeFormatProperty().getFormat());
			return LocalDate.parse(cellData.getStringValue(), formatter);
		}
	}

	@Override
	public CellData<String> convertToExcelData(LocalDate value, ExcelContentProperty contentProperty,
			GlobalConfiguration globalConfiguration) {
		DateTimeFormatter formatter;
		if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) {
			formatter = DateTimeFormatter.ISO_LOCAL_DATE;
		}
		else {
			formatter = DateTimeFormatter.ofPattern(contentProperty.getDateTimeFormatProperty().getFormat());
		}
		return new CellData<>(value.format(formatter));
	}

}
public enum LocalDateTimeStringConverter implements Converter<LocalDateTime> {

	/**
	 * 实例
	 */
	INSTANCE;

	private static final String MINUS = "-";

	@Override
	public Class supportJavaTypeKey() {
		return LocalDateTime.class;
	}

	@Override
	public CellDataTypeEnum supportExcelTypeKey() {
		return CellDataTypeEnum.STRING;
	}

	@Override
	public LocalDateTime convertToJavaData(CellData cellData, ExcelContentProperty contentProperty,
			GlobalConfiguration globalConfiguration) throws ParseException {
		String stringValue = cellData.getStringValue();
		String pattern;
		if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) {
			pattern = switchDateFormat(stringValue);
		}
		else {
			pattern = contentProperty.getDateTimeFormatProperty().getFormat();
		}
		DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
		return LocalDateTime.parse(cellData.getStringValue(), formatter);
	}

	@Override
	public CellData<String> convertToExcelData(LocalDateTime value, ExcelContentProperty contentProperty,
			GlobalConfiguration globalConfiguration) {
		String pattern;
		if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) {
			pattern = DateUtils.DATE_FORMAT_19;
		}
		else {
			pattern = contentProperty.getDateTimeFormatProperty().getFormat();
		}
		DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
		return new CellData<>(value.format(formatter));
	}

	/**
	 * switch date format
	 * @param dateString dateString
	 * @return pattern
	 */
	private static String switchDateFormat(String dateString) {
		int length = dateString.length();
		switch (length) {
		case 19:
			if (dateString.contains(MINUS)) {
				return DateUtils.DATE_FORMAT_19;
			}
			else {
				return DateUtils.DATE_FORMAT_19_FORWARD_SLASH;
			}
		case 17:
			return DateUtils.DATE_FORMAT_17;
		case 14:
			return DateUtils.DATE_FORMAT_14;
		case 10:
			return DateUtils.DATE_FORMAT_10;
		default:
			throw new IllegalArgumentException("can not find date format for:" + dateString);
		}
	}

}

通过自定义参数解析器实现excel的导入

public class RequestExcelArgumentResolver implements HandlerMethodArgumentResolver {
    /**
     * 支持条件--接受参数中包含RequestExcel注解
     * @param parameter
     * @return
     */
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(RequestExcel.class);
    }

    /**
     * 当前解析结果
     * @param parameter
     * @param mavContainer
     * @param webRequest
     * @param binderFactory
     * @return
     * @throws Exception
     */
    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        // 判断是否采用list接受参数
        Class<?> parameterType = parameter.getParameterType();
        if (!parameterType.isAssignableFrom(List.class)) {
            throw new IllegalArgumentException(
                    "Excel upload request resolver error, @RequestExcel parameter is not List " + parameterType);
        }
        RequestExcel requestExcel = parameter.getParameterAnnotation(RequestExcel.class);
        assert requestExcel!=null;

        //获取自定义读取处理器
        Class<? extends ListAnalysisEventListener<?>> readListenerClass = requestExcel.readListener();
        ListAnalysisEventListener<?> readListener = BeanUtils.instantiateClass(readListenerClass);

        // 获取请求文件流
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        assert request!=null;
        InputStream inputStream;
        if (request instanceof MultipartRequest){
            MultipartRequest multipartRequest = (MultipartRequest) request;
            MultipartFile file = multipartRequest.getFile(requestExcel.fileName());
            assert file!=null;
            inputStream=file.getInputStream();
            //FileUtils.writeToFile(new File("C:\\Users\\likun\\Desktop\\新建文件夹\\lktest.xls"),inputStream);
        }else {
            inputStream=request.getInputStream();
        }
        //将数据读取到指定接受参数中
        // 获取目标类型
        Class<?> excelModelClass = ResolvableType.forMethodParameter(parameter).getGeneric(0).resolve();

        EasyExcel.read(inputStream,excelModelClass,readListener)
                .registerConverter(LocalDateStringConverter.INSTANCE)
                .registerConverter(LocalDateTimeStringConverter.INSTANCE)
                .ignoreEmptyRow(requestExcel.ignoreEmptyRow())
                .sheet().doRead();

        //将校验失败的数据绑定给BindResult
        WebDataBinder webDataBinder = binderFactory.createBinder(webRequest, readListener.getErrors(), "excel");
        ModelMap model = mavContainer.getModel();
        model.put(BindingResult.MODEL_KEY_PREFIX+"excel",webDataBinder.getBindingResult());

        return readListener.getList();
    }
}

配置自定义参数解析器

@RequiredArgsConstructor
public class RequestExcelConfig implements InitializingBean {
    private final RequestMappingHandlerAdapter requestMappingHandlerAdapter;

    @Override
    public void afterPropertiesSet() throws Exception {
        List<HandlerMethodArgumentResolver> argumentResolvers = requestMappingHandlerAdapter.getArgumentResolvers();
        ArrayList<HandlerMethodArgumentResolver> resloves = new ArrayList<>();
        resloves.add(new RequestExcelArgumentResolver());
        resloves.addAll(argumentResolvers);
        requestMappingHandlerAdapter.setArgumentResolvers(resloves);
    }
}

交给springboot管理

在这里插入图片描述

四 组件调用

@Data
public class PostExcelVO implements Serializable {

	private static final long serialVersionUID = 1L;

	/**
	 * 主键ID
	 */
	@ExcelProperty("岗位编号")
	private Long postId;

	/**
	 * 岗位名称
	 */
	@NotBlank(message = "岗位名称不能为空")
	@ExcelProperty("岗位名称")
	private String postName;

	/**
	 * 岗位标识
	 */
	@NotBlank(message = "岗位标识不能为空")
	@ExcelProperty("岗位标识")
	private String postCode;

	/**
	 * 岗位排序
	 */
	@NotNull(message = "岗位排序不能为空")
	@ExcelProperty("岗位排序")
	private Integer postSort;

	/**
	 * 岗位描述
	 */
	@NotBlank(message = "岗位描述不能为空")
	@ExcelProperty(value = "岗位描述")
	private String remark;


}


@PostMapping("/importExcel")
    @Inner(false)
    public R importExcel(@RequestExcel List<PostExcelVO> list, BindingResult bindingResult){
        // 获取错误信息
        List<ErrorMessage> errorMessages = (List<ErrorMessage>) bindingResult.getTarget();
        errorMessages.forEach(errorMessage -> log.info("校验错误信息:{}",errorMessage));
        log.info("读取结果:{}",list);
        return R.ok();
    }

执行结果:
在这里插入图片描述

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
自定义Spring Boot Starter是一种用于简化Spring Boot应用程序配置和依赖管理的机制。它由几个组件组成,包括starter包、autoconfiguration包和配置文件。 首先,你需要创建一个Maven工程,并创建三个模块。其中,starter包负责引入依赖,autoconfiguration包负责实现装配。在autoconfiguration包中,你需要定义对应的properties类、configuration类和核心业务类。此外,你还需要在autoconfiguration包的/resources/META-INF/目录下添加spring.factories文件,并配置org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.qiejk.demo.springboot.autoconfiguration.DemoAutoConfiguration。这一步是Spring Boot装配的核心,只有添加了这个内容,Spring Boot才会进行自动装配。\[1\] 在父模块的pom文件中,你需要引入Spring Boot的依赖,例如spring-boot-starter-web,以及指定Spring Boot的版本。这样,你的自定义starter模块就可以基于Spring Boot进行开发和使用。\[2\] 最后,你需要创建一个配置文件,用于告诉Spring Boot在启动时扫描该配置文件,并将其中的配置类加载到Spring容器中。这个配置文件的作用是指定自动配置类的位置。\[3\] 通过以上步骤,你就可以创建自定义Spring Boot Starter,并在应用程序中使用它来简化配置和依赖管理。 #### 引用[.reference_title] - *1* [如何自定义springboot-starter](https://blog.csdn.net/sinat_29434159/article/details/123995794)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [SpringBoot自定义Starter篇](https://blog.csdn.net/m0_46571920/article/details/122910726)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值