gpt4 book ai didi

java - 在 Spring MVC 中转换和验证 CSV 文件上传

转载 作者:行者123 更新时间:2023-12-02 05:31:50 25 4
gpt4 key购买 nike

我有一个包含站点列表的客户实体,如下所示:

public class Customer {

@Id
@GeneratedValue
private int id;

@NotNull
private String name;

@NotNull
@AccountNumber
private String accountNumber;

@Valid
@OneToMany(mappedBy="customer")
private List<Site> sites
}

public class Site {

@Id
@GeneratedValue
private int id;

@NotNull
private String addressLine1;

private String addressLine2;

@NotNull
private String town;

@PostCode
private String postCode;

@ManyToOne
@JoinColumn(name="customer_id")
private Customer customer;
}

我正在创建一个表单,允许用户通过输入姓名和帐号并提供网站的 CSV 文件(格式为“addressLine1”、“addressLine2”、“town”)来创建新客户, “邮政编码”)。需要验证用户的输入并向他们返回错误(例如“文件不是 CSV 文件”、“第 7 行出现问题”)。

我首先创建一个转换器来接收 MultipartFile 并将其转换为站点列表:

public class CSVToSiteConverter implements Converter<MultipartFile, List<Site>> {

public List<Site> convert(MultipartFile csvFile) {

List<Site> results = new List<Site>();

/* open MultipartFile and loop through line-by-line, adding into List<Site> */

return results;
}
}

这有效,但没有验证(即,如果用户上传二进制文件或其中一个 CSV 行不包含城镇),似乎没有办法将错误传回(并且转换器似乎不是执行验证的正确位置)。

然后,我创建了一个表单支持对象来接收 MultipartFile 和 Customer,并对 MultipartFile 进行验证:

public class CustomerForm {

@Valid
private Customer customer;

@SiteCSVFile
private MultipartFile csvFile;
}

@Documented
@Constraint(validatedBy = SiteCSVFileValidator.class)
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SiteCSVFile {

String message() default "{SiteCSVFile}";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};
}

public class SiteCSVFileValidator implements ConstraintValidator<SiteCSVFile, MultipartFile> {

@Override
public void initialize(SiteCSVFile siteCSVFile) { }

@Override
public boolean isValid(MultipartFile csvFile, ConstraintValidatorContext cxt) {

boolean wasValid = true;

/* test csvFile for mimetype, open and loop through line-by-line, validating number of columns etc. */

return wasValid;
}
}

这也有效,但我必须重新打开 CSV 文件并循环遍历它才能实际填充 Customer 中的列表,这看起来不太优雅:

@RequestMapping(value="/new", method = RequestMethod.POST)
public String newCustomer(@Valid @ModelAttribute("customerForm") CustomerForm customerForm, BindingResult bindingResult) {

if (bindingResult.hasErrors()) {
return "NewCustomer";
} else {

/*
validation has passed, so now we must:
1) open customerForm.csvFile
2) loop through it to populate customerForm.customer.sites
*/

customerService.insert(customerForm.customer);

return "CustomerList";
}
}

我的 MVC 配置将文件上传限制为 1MB:

@Bean
public MultipartResolver multipartResolver() {
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
multipartResolver.setMaxUploadSize(1000000);
return multipartResolver;
}

是否有一种同时进行转换和验证的 Spring 方式,而无需打开 CSV 文件并循环遍历两次,一次用于验证,另一次用于实际读取/填充数据?

最佳答案

恕我直言,将整个 CSV 加载到内存中不是一个好主意,除非:

  • 您确定它总是很小(如果用户点击错误的文件怎么办?)
  • 验证是全局的(仅真实用例,但似乎不在这里)
  • 您的应用程序永远不会在负载严重的生产环境中使用

如果您不想绑定(bind)您的文件,您应该坚持使用 MultipartFile 对象,或者使用公开 InputStream 的包装器(以及最终您可能需要的其他信息)商务舱到 Spring 。

然后,您仔细设计、编码和测试一个以 InputStream 作为输入的方法,逐行读取它并调用逐行方法来验证和插入数据。类似的东西

class CsvLoader {
@Autowired Verifier verifier;
@Autowired Loader loader;

void verifAndLoad(InputStream csv) {
// loop through csv
if (verifier.verify(myObj)) {
loader.load(myObj);
}
else {
// log the problem eventually store the line for further analysis
}
csv.close();
}
}

这样,您的应用程序仅使用它真正需要的内存,仅循环一次其他文件。

编辑:精确表达我的意思包装Spring MultipartFile

首先,我将验证分为 2 部分。正式验证位于 Controller 层,仅控制:

  • 有一个“客户”字段
  • 文件大小和 mimetype 看起来不错(例如:size > 12 && mimetype = text/csv)

恕我直言,内容的验证是业务层验证,可以稍后进行。在此模式中,SiteCSVFileValidator 将仅测试 csv 的 mimetype 和大小。

通常,您避免直接使用业务类中的 Spring 类。如果不担心, Controller 会直接将 MultipartFile 发送到服务对象,同时传递 BindingResult 以直接填充最终的错误消息。 Controller 变为:

@RequestMapping(value="/new", method = RequestMethod.POST)
public String newCustomer(@Valid @ModelAttribute("customerForm") CustomerForm customerForm, BindingResult bindingResult) {

if (bindingResult.hasErrors()) {
return "NewCustomer"; // only external validation
} else {

/*
validation has passed, so now we must:
1) open customerForm.csvFile
2) loop through it to validate each line and populate customerForm.customer.sites
*/

customerService.insert(customerForm.customer, customerForm.csvFile, bindingResult);
if (bindingResult.hasErrors()) {
return "NewCustomer"; // only external validation
} else {
return "CustomerList";
}
}
}

在服务类别中,我们有

insert(Customer customer, MultipartFile csvFile, Errors errors) {
// loop through csvFile.getInputStream populating customer.sites and eventually adding Errors to errors
if (! errors.hasErrors) {
// actually insert through DAO
}
}

但是我们在服务层的方法中得到了2个Spring类。如果有问题,只需将 customerService.insert(customerForm.customer, customerForm.csvFile, BindingResult); 行替换为:

List<Integer> linesInError = new ArrayList<Integer>();
customerService.insert(customerForm.customer, customerForm.csvFile.getInputStream(), linesInError);
if (! linesInError.isEmpty()) {
// populates bindingResult with convenient error messages
}

然后服务类仅将检测到错误的行号添加到 linesInError 但它只获取InputStream,其中可能需要说出原始文件名。您可以将名称作为另一个参数传递,或使用包装类:

class CsvFile {

private String name;
private InputStream inputStream;

CsvFile(MultipartFile file) {
name = file.getOriginalFilename();
inputStream = file.getInputStream();
}
// public getters ...
}

并调用

customerService.insert(customerForm.customer, new CsvFile(customerForm.csvFile), linesInError);

没有直接的 Spring 依赖

关于java - 在 Spring MVC 中转换和验证 CSV 文件上传,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25460779/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com