文件上传 spring-mvc.xml 配置
应用是用的比较多的消息转换器
- 一是 负责解析资源
<bean class="org.springframework.http.converter.ResourceHttpMessageConverter"/>
- 二是 负责解析Json
<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
此外若要进行文件的上传还要配置一个multipartResolver
- 多媒体文件解析器
<bean id="multipartResolver"class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation=
"http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="zju.edu.als.controller"/>
<!--扫描注解驱动,必须有-->
<mvc:annotation-driven>
<mvc:message-converters register-defaults="false"> <!-将默认的取消掉->
<!-- 配置Spring的转换器 -->
<bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/>
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/plain;charset=UTF-8</value>
</list>
</property>
</bean>
<bean class="org.springframework.http.converter.ResourceHttpMessageConverter"/>
<bean class="org.springframework.http.converter.xml.SourceHttpMessageConverter"/>
<bean class="org.springframework.http.converter.xml.Jaxb2CollectionHttpMessageConverter"/>
<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>application/json;charset=UTF-8</value>
<value>application/*+json;charset=UTF-8</value>
<value>text/json;charset=UTF-8</value>
<value>text/html;charset=UTF-8</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<!--内部资源视图解析器,当Controller下返回具体jsp文件名时,使其定位到具体的jsp文件-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
<aop:aspectj-autoproxy proxy-target-class="true"/>
<!--添加 Shiro Spring AOP 权限注解的支持-->
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- set the max upload size100MB -->
<property name="maxUploadSize">
<value>104857600</value>
</property>
<property name="maxInMemorySize">
<value>4096</value>
</property>
</bean>
<mvc:resources location="/WEB-INF/js/" mapping="/js/**"/>
<mvc:resources location="/WEB-INF/css/" mapping="/css/**"/>
<!- messageConverter 不能设置在外面,要在mvc命令空间标签内进行设置即可->
<!-- <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">-->
<!-- <property name="messageConverters">-->
<!-- <list>-->
<!-- <bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/>-->
<!-- <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">-->
<!-- <property name="supportedMediaTypes">-->
<!-- <list>-->
<!-- <value>application/json;charset=UTF-8</value>-->
<!-- <value>text/json;charset=UTF-8</value>-->
<!-- <value>text/html;charset=UTF-8</value>-->
<!-- </list>-->
<!-- </property>-->
<!-- </bean>-->
<!-- <bean class="org.springframework.http.converter.StringHttpMessageConverter">-->
<!-- <property name="supportedMediaTypes">-->
<!-- <list>-->
<!-- <value>text/plain;charset=UTF-8</value>-->
<!-- </list>-->
<!-- </property>-->
<!-- </bean>-->
<!-- </list>-->
<!-- </property>-->
<!-- </bean>-->
</beans>
文件上传Controller
@Controller
@PropertySource("classpath:script.properties")
@Slf4j
public class CtRunController {
/**
* default ${homeDir}/upload
*/
private String uploadDir;
/**
* default ${homeDir}/ct_run.sh
*/
private String ctRunFile;
@Autowired
Environment env;
@PostConstruct
public void init() {
uploadDir = Paths.get(env.getProperty("homeDir"), env.getProperty("uploadDir")).toString();
ctRunFile = Paths.get(env.getProperty("homeDir"), env.getProperty("ctRunFile")).toString();
File file = new File(uploadDir);
if (!file.exists()) {
try {
if (file.createNewFile()) {
log.info("文件创建成功:{}", file.getName());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private static final String JPG = "jpg";
private static final String JPEG = "jpeg";
private static final String PNG = "png";
private static final String SUFFIX = "-result.";
@ResponseBody
@RequestMapping(value = "/ct", method = RequestMethod.POST)
public Result imageUpload(@RequestParam("file") MultipartFile file) {
String sourceName = StringUtils.cleanPath(file.getOriginalFilename());
if (!sourceName.endsWith(JPG) && !sourceName.endsWith(JPEG) && !sourceName.endsWith(PNG)) {
return Result.fail("请上传正确的图片文件格式");
}
Path filePath = Paths.get(uploadDir, sourceName);
store(file, filePath);
String copyName = sourceName.replaceAll("\\.", SUFFIX);
new Thread(() -> realCtRun(sourceName, copyName)).start();
return Result.succeed("/alsprj/ct/" + copyName);
}
/**
* 这种方式就是经过一个 ResourceHttpMessageConverter 来帮助我们实现了
* 进一步设置body 和 请求头的工作 。HttpEntityMethodProcessor
*
* @param filename 文件名
* @return 资源
*/
@RequestMapping(value = "ct/{filename:.+}", method = RequestMethod.GET)
@ResponseBody
public ResponseEntity<?> serveFile(@PathVariable String filename, HttpServletResponse response) throws MalformedURLException {
Path path = Paths.get(uploadDir, filename);
if (!path.toFile().exists()) {
response.setStatus(HttpStatus.MOVED_PERMANENTLY.value());
return null;
}
UrlResource urlResource = new UrlResource(path.toUri());
return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + path.getFileName() + "\"").body(urlResource);
}
/**
* 这种方法就是不经过框架,直接自己设置 outputStream, 和请求头
*
* @param filename 文件名
* @param response 全局的一个响应
*/
public void imageDownload(@PathVariable String filename, HttpServletResponse response) {
File file = Paths.get(uploadDir, filename).toFile();
if (!file.exists()) {
response.setStatus(HttpStatus.MOVED_PERMANENTLY.value());
return;
}
try (InputStream inputStream = new FileInputStream(file)) {
OutputStream outputStream = response.getOutputStream();
String fileName = URLEncoder.encode(file.getName(), "UTF-8");
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileName + "\"");
response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.IMAGE_JPEG_VALUE);
response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(file.length()));
Streams.copy(inputStream, outputStream, true);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
Files.delete(file.toPath());
Files.delete(Paths.get(uploadDir, filename.replaceAll(SUFFIX, ".")));
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void realCtRun(String sourceName, String copyName) {
Path path = Paths.get(uploadDir, sourceName);
try {
GeneralExecutor executor = DefaultExecutorFactory.getExecutor(GeneralExecutor.class);
String result = executor.execute(ctRunFile, path.toString());
log.info("cmd[{} {}] result[{}]", ctRunFile, path.toString(), result);
mockCtRun(sourceName, copyName);
} catch (ExecuteException | ExecutorNotFoundExecption e) {
e.printStackTrace();
}
}
private void mockCtRun(String sourceName, String copyName) {
try {
Thread.sleep(5000);
Files.copy(Paths.get(uploadDir, sourceName), Paths.get(uploadDir, copyName), StandardCopyOption.REPLACE_EXISTING);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
private Resource loadAsResource(Path file) {
try {
Resource resource = new UrlResource(file.toUri());
if (resource.exists() || resource.isReadable()) {
return resource;
} else {
throw new RuntimeException(
"Could not read file: " + file.getFileName());
}
} catch (MalformedURLException e) {
throw new RuntimeException("Could not read file: " + file.getFileName(), e);
}
}
private void store(MultipartFile file, Path save) {
String filename = StringUtils.cleanPath(file.getOriginalFilename());
try {
if (file.isEmpty()) {
throw new RuntimeException("Failed to store empty file " + filename);
}
if (filename.contains("..")) {
throw new RuntimeException(
"Cannot store file with relative path outside current directory "
+ filename);
}
try (InputStream inputStream = file.getInputStream()) {
Files.copy(inputStream, save,
StandardCopyOption.REPLACE_EXISTING);
}
} catch (IOException e) {
throw new RuntimeException("Failed to store file " + filename, e);
}
}
}
从messageConverter到大佬
org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite
在处理controller中方法的返回结果时,会从HandlerMethodReturnValueHandlerComposite的
returnValueHandlers中选出一个handler进行处理,如果是传输文件用的是ResponseEntitiy 则对应选出HttpEntitiyMethodProcessor进行处理
HttpEntityMethodProcessor 和 RequestResponseBodyMethodProcessor都是AbstractMessageConverterMethodProcessor具体实现类,最终会在这儿选择一个合适的。
MessageConverter进行内容进一步转换。具体得转换内容即是设置serveletResponse的响应体和响应头,然后正常走完 dipathcerServelet的函数栈,接着统一处理产生的异常,再从Filter栈返回,最终由Tomcat的将response由TCP协议发往客户端。