文件上传
文件上传的必要前提
- form 表单的 enctype 取值必须是:multipart/form-data (默认值是:application/x-www-form-urlencoded) enctype:是表单请求正文的类型
- method 属性取值必须是 Post
- 提供一个文件选择 <input type="file" name=""/>
单个文件上传
controller
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.util.UUID;
/**
* @author 贾宇光
* @version 1.0
* @date 2020/7/8 10:11
*/
@Controller
public class FileController {
@GetMapping("file")
public String file(HttpServletRequest request) {
System.out.println("本项目路径:" + System.getProperty("user.dir"));
System.out.println("tomcat路径:" + request.getSession().getServletContext().getRealPath("/uploads"));
return "MultipartFile";
}
@RequestMapping(value = "/fileupload")
@ResponseBody
public String fileupload2(@RequestParam("upload") MultipartFile upload,
HttpServletRequest request) throws Exception {
System.out.println("文件名称:" + upload.getOriginalFilename());
System.out.println("文件是否是js为后缀:" + upload.getOriginalFilename().endsWith(".js"));
// 先获取到要上传的文件目录
// String path = request.getSession().getServletContext().getRealPath("/uploads");
String path = System.getProperty("user.dir") + "/uploads";
System.out.println(path);
// 创建File对象,一会向该路径下上传文件
File file = new File(path);
// 判断路径是否存在,如果不存在,创建该路径
if (!file.exists()) {
file.mkdirs();
}
// 获取到上传文件的名称
String filename = upload.getOriginalFilename();
String uuid = UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();
// 把文件的名称唯一化
filename = uuid + "_" + filename;
// 上传文件
upload.transferTo(new File(file, filename));
System.out.println(filename);
return "success";
}
}
前端页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>MultipartFile</title>
</head>
<body>
<h3>文件上传</h3>
<form th:action="@{/fileupload}" method="post" enctype="multipart/form-data">
选择文件:<input type="file" name="upload"/><br/>
<input type="submit" value="上传文件"/>
</form>
</body>
</html>
多个文件上传
controller
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.util.ArrayList;
import java.util.UUID;
/**
* @author 贾宇光
* @version 1.0
* @date 2020/7/8 10:11
*/
@Controller
public class FileController {
@GetMapping("file")
public String file(HttpServletRequest request) {
System.out.println("本项目路径:" + System.getProperty("user.dir"));
System.out.println("tomcat路径:" + request.getSession().getServletContext().getRealPath("/uploads"));
return "MultipartFile";
}
@RequestMapping(value = "/fileupload")
@ResponseBody
public String fileupload2(@RequestParam("upload") ArrayList<MultipartFile> uploads,
HttpServletRequest request) throws Exception {
for (MultipartFile upload : uploads) {
System.out.println("文件名称:" + upload.getOriginalFilename());
System.out.println("文件是否是js为后缀:" + upload.getOriginalFilename().endsWith(".js"));
// 先获取到要上传的文件目录
// String path = request.getSession().getServletContext().getRealPath("/uploads");
String path = System.getProperty("user.dir") + "/uploads";
System.out.println(path);
// 创建File对象,一会向该路径下上传文件
File file = new File(path);
// 判断路径是否存在,如果不存在,创建该路径
if (!file.exists()) {
file.mkdirs();
}
// 获取到上传文件的名称
String filename = upload.getOriginalFilename();
String uuid = UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();
// 把文件的名称唯一化
filename = uuid + "_" + filename;
// 上传文件
upload.transferTo(new File(file, filename));
System.out.println(filename);
}
return "success";
}
}
前端页面
<input type="file" name="upload" multiple/>
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>MultipartFile</title>
</head>
<body>
<h3>文件上传</h3>
<form th:action="@{/fileupload}" method="post" enctype="multipart/form-data">
选择文件:<input type="file" name="upload" multiple/><br/>
<input type="submit" value="上传文件"/>
</form>
</body>
</html>
验证表单数据并实现数据的自定义认证
自定义注解类
先自定义注解,以便实体 Bean 中使用它
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//用于指定使用范围,该处限定只能在字段上使用
@Target({ElementType.FIELD})
//表示注解在运行时可以通过反射获取到
@Retention(RetentionPolicy.RUNTIME)
//@Constraint注解,里面传入了一个validatedBy的字段,指定该注解校验逻辑
@Constraint(validatedBy = MyConstraintValidator.class)
public @interface MyConstraint {
/**
* @Description: 错误提示
*/
String message() default "请输入中国政治或者经济中心的城市名";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
自定义验证业务逻辑类
initialize:初始化验证消息的方法
isValid:执行验证的方法
验证方法是用来验证业务逻辑的,他需要继承 ConstraintValidator 接口
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class MyConstraintValidator implements ConstraintValidator<MyConstraint, String> {
//String为校验的类型
@Override
public void initialize(MyConstraint myConstraint) {
//启动时执行
}
/**
* @Description: 自定义校验逻辑
*/
@Override
public boolean isValid(String s, ConstraintValidatorContext validatorContext) {
if (!(s.equals("北京") || s.equals("上海"))) {
return false;
}
return true;
}
}
创建实体
创建实体 Bean,用于表单验证。注意:定义的所有字段都需要被验证,否则会出错
import com.jia.config.MyConstraint;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.*;
import java.io.Serializable;
@Data
public class User implements Serializable {
/**
* 主键ID
*/
private Long id;
@NotBlank(message = "用户名不能为空")
@Length(min = 5, max = 20, message = "用户名长度为5-20个字符")
private String name;
@NotNull(message = "年龄不能为空")
@Min(value = 18 ,message = "最小18岁")
@Max(value = 60,message = "最大60岁")
private Integer age;
/* @NotBlank(message = "电话不可以为空")
@Length(min = 1, max = 13, message = "电话长度需要在13个字符以内")
private String phone;*/
@Email(message = "请输入邮箱")
@NotBlank(message = "邮箱不能为空")
private String email;
/* @NotNull(message = "必须指定用户状态")
@Min(value = 0, message = "用户状态不合法")
@Max(value = 1, message = "用户状态不合法")
private Integer status;*/
@MyConstraint
private String answer;
}
编写验证控制器
import com.jia.pojo.User;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.validation.Valid;
@Controller
public class TestValidator {
@GetMapping("/test")
public String showForm(User user) {
return "form";
}
@GetMapping("/results")
public String results() {
return "results";
}
@PostMapping("/test")
public String checkUser(@Valid User user, BindingResult bindingResult, RedirectAttributes attr) {
//特别注意实体中的属性必须都验证过了,不然不会成功
if (bindingResult.hasErrors()) {
return "form";
}
/**
* @Description:
* 1.使用RedirectAttributes的addAttribute方法传递参数会跟随在URL后面 ,如上代码即为?name=long&age=45
* 2.使用addFlashAttribute不会跟随在URL后面,会把该参数值暂时保存于session,待重定向url获取该参数后从session中移除,
* 这里的redirect必须是方法映射路径。你会发现redirect后的值只会出现一次,刷新后不会出现了,对于重复提交可以使用此来完成。
*/
attr.addFlashAttribute("user", user);
return "redirect:/results";
}
}
addAttribute 方法:用 RedirectAttribute 的 attribute 方法传递参数,参数应跟在 URL 后面
addFlashAttribute 方法:参数不会跟在 URL 后面,会暂存在 session 中
redirect 方法:必须是方法的映射路径。redirect 后的值只会出现一次,刷新后不会出现。可以使用 redirect 方法来防止重复提交
编写视图
提交表单和返回验证
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<body>
<style type="text/css">
.warn{color:red}
</style>
<form th:action="@{/test}" th:object="${user}" method="post">
<div>
<div>
<span>姓名:</span>
<span><input type="text" th:field="*{name}"/></span>
<span class="warn" th:if="${#fields.hasErrors('name')}" th:errors="*{name}">名字错误</span>
</div>
<div>
<span>年龄:</span>
<span><input type="number" th:field="*{age}"/></span>
<span class="warn" th:if="${#fields.hasErrors('age')}" th:errors="*{age}">年龄错误</span>
</div>
<div>
<span>邮箱:</span>
<span><input TYPE="text" th:field="*{email}"/></span>
<span class="warn" th:if="${#fields.hasErrors('email')}" th:errors="*{email}">邮箱错误</span>
</div>
<div>
<span>验证答案:</span>
<span><input TYPE="text" th:field="*{answer}"/></span>
<span class="warn" th:if="${#fields.hasErrors('answer')}" th:errors="*{answer}">答案错误</span>
</div>
<div>
<span>
<button type="submit">提交</button>
</span>
</div>
</div>
</form>
</body>
</html>
编写验证通过后的处理视图
如果验证成功,则在本视图中渲染数据
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<body>
<div th:each="user: ${user}">
恭喜您,<span th:text="${user.name}">名字</span>(先生/女士),数据提交成功!
<div>您的年龄是:
<span th:text="${user.age}"></span></div>
<div> 您的邮箱是:
<span th:text="${user.email}"></span>
</div>
</div>
</body>
</html>
测试结果
处理公共代码块
用 fragment 标记重复代码块
可以通过 th:fragment="xxx" 标签来标记重复代码块,如:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>header</title>
</head>
<body>
<div th:fragment="header" class="header" id="header">
公共 header
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>footer</title>
</head>
<body>
<div th:fragment="footer" class="footer" id="footer">
公共 footer
</div>
</body>
</html>
调用重复代码块
<div th:replace="~{目录/公共资源(html)::xxx}"></div>
<div th:include="~{目录/公共资源(html)::xxx}"></div>
replace
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>使用 header 和 footer</title>
<style>
.header {
color: red;
}
.footer {
color: red;
}
</style>
</head>
<body>
<h2>replace 的调用方式</h2>
<div th:replace="~{header::header}"></div>
<div>replace 正文</div>
<div th:replace="~{footer::footer}"></div>
</body>
</html>
include
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>使用 header 和 footer</title>
<style>
.header {
color: red;
}
.footer {
color: red;
}
</style>
</head>
<body>
<h2>include 的调用方式</h2>
<div th:include="~{header::header}"></div>
<div>include 正文</div>
<div th:include="~{footer::footer}"></div>
</body>
</html>
测试结果
- th:replace :替换当前标签为模板中的标签
<div class="header" id="header"> 公共 header </div>
- th:include : 只加载模板的内容
<div> 公共 header </div>