在项目微服务的升级过程中,我们通常会设置一个网关,作为一个洪流的出入口,在Spring Cloud 中提供了对应的功能,也就是Spring Cloud Gateway。对于旧的项目springMVC,实际也就是将spring-webmvc升级为spring-webflux,但你会发现fromdata 形式的数据,在webmvc可以被封装成参数,而在webflux中却不能,是不支持吗?
在spring官方文档中,有提及对fromdata表单数据的获取,如下图
在这里插入图片描述
fromdata表单数据需要通过ServerWebExchange 的getFormData()获取【也可以通过getMultipartData()方法获取数据】
主要对数据处理的代码如下:

//exchange 为 ServerWebExchange 
exchange.getMultipartData().map(data ->{
            Map<String, Part> stringPartMap = data.toSingleValueMap();
            //表单参数名
            Part name= stringPartMap.get("name");
            //将name进行处理转换
            处理转换代码
            //返回处理后的值
            return name;
        });

具体的例子:比如需要从表单中取出file(MultipartFile)

@PostMapping("/api/test2")
public Object test2(ServerWebExchange exchange){
     return exchange.getMultipartData().map(data ->{
         Map<String, Part> stringPartMap = data.toSingleValueMap();
         Part file = stringPartMap.get("file");
         if(file instanceof FilePart){
             File file1 = null;
             try {
                 FilePart filePart = (FilePart)file;
                 file1 = new File(filePart.filename());
                 filePart.transferTo(file1);
                 //获取到MultipartFile 
                 MultipartFile multipartFile = FileUtil.fileToMultipartFile(file1);
                 
                 //业务代码块
                 
             } catch (Exception e) {
                 throw new RuntimeException("参数分析错误!");
             }finally {
                 if(file1.exists()){
                     file1.delete();
                 }
             }
         }
         return "success";
     });
 }

文件处理工具类

public class FileUtil {
    public static MultipartFile fileToMultipartFile(File file) {
        FileItem fileItem = createFileItem(file);
        return new CommonsMultipartFile(fileItem);
    }

    public static FileInputStream fileToFileInputStream(File file) {
        FileInputStream fileInputStream = null;
        try {
            fileInputStream = new FileInputStream(file);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        return fileInputStream;
    }

    public static File filePartToFile(FilePart filePart,File file) {
        file = new File(filePart.filename());
        filePart.transferTo(file);
        return file;
    }

    private static FileItem createFileItem(File file) {
        FileItemFactory factory = new DiskFileItemFactory(16, null);
        FileItem item = factory.createItem("textField", "text/plain", true, file.getName());
        int bytesRead;
        byte[] buffer = new byte[8192];
        try {
            FileInputStream fis = new FileInputStream(file);
            OutputStream os = item.getOutputStream();
            while ((bytesRead = fis.read(buffer, 0, 8192)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
            os.close();
            fis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return item;
    }
}

直此,我们就能够获取到fromdata的数据

但这样子对于想获取fromdata数据,每次都需要引用ServerWebExchange ,然后通过getMultipartData()方法获取数据,比较臃肿,能否对数据重新进行封装呢,答案是可以的,下面我通过注解和配置封装参数解析类 进行fromdata数据的二次封装,代码案例如下:
controller层代码:

 @PostMapping("/api/test2")
    public String test2(@ParameterConversion(name = "file",type = MultipartFile.class) MultipartFile multipartFile,
                        @ParameterConversion(name = "name",type = String.class) String name,
                        @ParameterConversion(name = "id",type = Integer.class) Integer id){
        
        //业务代码块
                 
        return "success";
    }

注解类

@Inherited
@Documented
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface ParameterConversion {
    //解析的名称
    String name();
    //转化的类型
    Class<?> type();
}

WebConfigurer 配置类

@Configuration
public class WebConfigurer implements WebFluxConfigurer {

    /**
     * 加入自定义方法参数解析器
     * @param configurer
     */
    @Override
    public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) {
        List<HttpMessageReader<?>> readers=new ArrayList<>();
        //添加Http消息编解码器
        readers.add(new DecoderHttpMessageReader<>(new Jackson2JsonDecoder()));
        //消息编解码器与Resolver绑定
        configurer.addCustomResolver(new ParamsResolver(readers));
    }

}

请求参数二次解析封装

public class ParamsResolver extends AbstractMessageReaderArgumentResolver {
    public ParamsResolver(List<HttpMessageReader<?>> readers) {
        super(readers);
    }

    protected ParamsResolver(List<HttpMessageReader<?>> messageReaders, ReactiveAdapterRegistry adapterRegistry) {
        super(messageReaders, adapterRegistry);
    }

    /**
     * 判断是否需要解析参数
     * @param methodParameter
     * @return
     */
    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
        System.out.println("into supportsParameter");
        if (!methodParameter.hasParameterAnnotation(ParameterConversion.class)) {
            return false;
        } else {
            ParameterConversion parameterAnnotation= methodParameter.getParameterAnnotation(ParameterConversion.class);
            return parameterAnnotation != null && methodParameter.getParameterType().isAssignableFrom(parameterAnnotation.type())
                    && Objects.nonNull(parameterAnnotation.name());
        }
    }

    @Override
    public Mono<Object> resolveArgument(MethodParameter methodParameter, BindingContext bindingContext, ServerWebExchange serverWebExchange) {
        System.out.println("into resolveArgument");
        ParameterConversion parameterAnnotation= methodParameter.getParameterAnnotation(ParameterConversion.class);
        String name = parameterAnnotation.name();
        Class<?> type = parameterAnnotation.type();
        return serverWebExchange.getMultipartData().map(data -> {
            Part part = data.toSingleValueMap().get(name);
            if (part == null)
                throw new RuntimeException("ParameterConversion fail");
            Object partObj = partToClass(part, type);
            Assert.notNull(partObj,"ParameterConversion fail, 不支持该类型转换");
            return partObj;
        });
    }

    private Object partToClass(Part part,Class<?> type){
        if (type == MultipartFile.class && part instanceof FilePart) {
            File file = null;
            try {
                FilePart filePart = (FilePart) part;
                file = new File(filePart.filename());
                filePart.transferTo(file);
                return FileUtil.fileToMultipartFile(file);
            } finally {
                if (file != null && file.exists()) {
                    file.delete();
                }
            }
        } else if (type == FilePart.class) {
            return part;
        }else if (type == String.class){
            return bufferToStr(part.content());
        } else if (type == Integer.class) {
            return Integer.parseInt(bufferToStr(part.content()));
        } else if (type == BigDecimal.class) {
            return new BigDecimal(bufferToStr(part.content()));
        } else {
            return null;
        }
    }

    private String bufferToStr(Flux<DataBuffer> content){
        AtomicReference<String> res = new AtomicReference<>();
        content.subscribe(buffer -> {
            byte[] bytes = new byte[buffer.readableByteCount()];
            buffer.read(bytes);
            DataBufferUtils.release(buffer);
            res.set(new String(bytes, StandardCharsets.UTF_8));
        });
        return res.get();
    }
}

以上代码,为作者个人观点,如有不足处,请指教;

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐