restful风格的增删改查
1. 导入静态资源文件
- 将静态资源(css,img,js)添加到项目中,放到springboot默认的静态资源文件夹下
- 将模板文件(html)放到template文件夹下
1.1 默认访问首页
template文件夹不是静态资源文件夹,默认是无法直接访问的,所以要添加视图映射,可以在MVC配置中写,也可以再controller中写。
//使用WebMvcConfigurerAdapter可以来扩展SpringMVC的功能 ,实现WebMvcConfigurer接口
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
}
}
2. i18n国际化
- 编写国际化配置文件,抽取页面需要显示的国际化消息
创建i18n文件夹存放配置文件,文件名格式为基础名(login)+语言代码(zh)+国家代码(CN)
- SpringBoot 自动配置好了管理国际化资源文件的组件,
MessageSourceAutoConfiguration
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnMissingBean(
name = {"messageSource"},
search = SearchStrategy.CURRENT
)
@AutoConfigureOrder(-2147483648)
@Conditional({MessageSourceAutoConfiguration.ResourceBundleCondition.class})
@EnableConfigurationProperties
public class MessageSourceAutoConfiguration {
private static final Resource[] NO_RESOURCES = new Resource[0];
public MessageSourceAutoConfiguration() {
}
@Bean
@ConfigurationProperties(
prefix = "spring.messages"
)
public MessageSourceProperties messageSourceProperties() {
return new MessageSourceProperties();
}
@Bean
public MessageSource messageSource(MessageSourceProperties properties) {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
//basename为messages,我们的国际化配置文件可以放在类路径下交messages.properties中,这样可以直接使用
//如果不使用messages.properties,需要自己指定国际化配置文件
if (StringUtils.hasText(properties.getBasename())) {
//设置国际化资源文件的基础名(去掉语言国家代码的) messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename())));
}
if (properties.getEncoding() != null) {
messageSource.setDefaultEncoding(properties.getEncoding().name());
}
messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
Duration cacheDuration = properties.getCacheDuration();
if (cacheDuration != null) {
messageSource.setCacheMillis(cacheDuration.toMillis());
}
messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
return messageSource;
}
....
在配置文件中添加国际化文件的位置和基础名
spring.messages.basename=i18n.login
如果配置文件中没有配置基础名,就在类路径下找基础名为message的配置文件
- 将页面文字改为获取国际化配置,格式#{key}
<body class="text-center">
<form class="form-signin" action="dashboard.html">
<img class="mb-4" src="asserts/img/bootstrap-solid.svg" th:src="@{/asserts/img/bootstrap-solid.svg}" alt="" width="72" height="72">
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
<label class="sr-only">Username</label>
<input type="text" class="form-control" th:placeholder="#{login.username}" placeholder="Username" required="" autofocus="">
<label class="sr-only">Password</label>
<input type="password" class="form-control" th:placeholder="#{login.password}" placeholder="Password" required="">
<div class="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me"> [[#{login.remember}]]
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" th:text="#{login.btn}" type="submit">Sign in</button>
<p class="mt-5 mb-3 text-muted">© 2017-2018</p>
<a class="btn btn-sm">中文</a>
<a class="btn btn-sm">English</a>
</form>
</body>
- 更改浏览器语言,页面就会使用对应的国际化配置文件
2.1 原理
国际化Locale(区域信息对象),LocaleResolver(获取区域信息对象的组件);
在springmvc配置类WebMvcAutoConfiguration中注册了该组件
@Bean
/**
*前提是容器中不存在这个组件,
*所以使用自己的对象就要配置@Bean让这个条件不成立(实现LocaleResolver 即可)
*/
@ConditionalOnMissingBean
/**
* 如果在application.properties中有配置国际化就用配置文件的
* 没有配置就用AcceptHeaderLocaleResolver 默认request中获取
*/
@ConditionalOnProperty(
prefix = "spring.mvc",
name = {"locale"}
)
public LocaleResolver localeResolver() {
if (this.mvcProperties.getLocaleResolver() == org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties.LocaleResolver.FIXED) {
return new FixedLocaleResolver(this.mvcProperties.getLocale());
} else {
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
return localeResolver;
}
}
默认的就是根据请求头带来的区域信息获取Locale进行国际化
public Locale resolveLocale(HttpServletRequest request) {
Locale defaultLocale = this.getDefaultLocale();
if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
return defaultLocale;
} else {
Locale requestLocale = request.getLocale();
List<Locale> supportedLocales = this.getSupportedLocales();
if (!supportedLocales.isEmpty() && !supportedLocales.contains(requestLocale)) {
Locale supportedLocale = this.findSupportedLocale(request, supportedLocales);
if (supportedLocale != null) {
return supportedLocale;
} else {
return defaultLocale != null ? defaultLocale : requestLocale;
}
} else {
return requestLocale;
}
}
}
2.2 点击连接切换语言
实现点击连接切换语言,而不是更改浏览器
- 自己实现区域信息解析器
public class MyLocaleResolver implements LocaleResolver {
@Override
public Locale resolveLocale(HttpServletRequest httpServletRequest) {
//获取请求参数中的语言
String language = httpServletRequest.getParameter("l");
//没带区域信息参数就用系统默认的
Locale locale = Locale.getDefault();
if (!StringUtils.isEmpty(language)) {
//提交的参数是zh_CN (语言代码_国家代码)
String[] s = language.split("_");
locale = new Locale(s[0], s[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {
}
}
- 在配置类中将其注册到容器中
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("login");
registry.addViewController("/index").setViewName("login");
registry.addViewController("/index.html").setViewName("login");
}
@Bean
public LocaleResolver localeResolver() {
return new MyLocaleResolver();
}
}
- 修改页面,点击连接携带语言参数
<a class="btn btn-sm" th:href="@{/index.html(l='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">English</a>
3. 登录
- 编写Controller页面
@Controller
public class LoginController {
//@RequestMapping(value = "/login", method = RequestMethod.POST)
@PostMapping("/login")//使用这个注解,请求方式直接是post方式
public String login(String userName, String password, Map<String, Object> map){
if(!StringUtils.isEmpty(userName) && "123456".equals(password)){
//登录成功,重定向到dashboard页面,防止表单重复提交
return "redirect:/main.html";
}else{
//登录失败
map.put("msg", "用户名或密码错误");
return "login";
}
}
}
- 修改表单提交地址,输入框添加name值与参数名称对应
<form class="form-signin" method="post" action="/login">
<img class="mb-4" src="asserts/img/bootstrap-solid.svg" th:src="@{/asserts/img/bootstrap-solid.svg}" alt="" width="72" height="72">
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
<p style="color: red" th:text="${msg}" th:if="${ not #strings.isEmpty('msg')}"></p>
<label class="sr-only">Username</label>
<input type="text" name="userName" class="form-control" th:placeholder="#{login.username}" placeholder="Username" required="" autofocus="">
<label class="sr-only">Password</label>
<input type="password" name="password" class="form-control" th:placeholder="#{login.password}" placeholder="Password" required="">
<div class="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me"> [[#{login.remember}]]
- 添加登录失败信息
<!--先判断msg是否为空,如果为空,就不显示文本-->
<p style="color: red" th:text="${msg}" th:if="${ not #strings.isEmpty('msg')}"></p>
3.1 修改页面立即生效
在配置文件中添加spring.thymeleaf.cache=false
,禁止使用模板引擎的缓存
在页面修改完以后,使用ctrl + F9
,重新编译
3.2 编写拦截器进行登录检查
- 编写拦截器类,实现HandlerInterecptor接口,重写preHandle方法
public class LoginHandlerInterceptor implements HandlerInterceptor {
@Override //目标方法执行之前
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession();
Object user = session.getAttribute("userName");
if(user == null){
//未登录,返回登录页面
request.setAttribute("msg", "没有权限,请登录");
request.getRequestDispatcher("/index.html").forward(request, response);
return false;
}
//已登录,放行请求
return true;
}
}
- 注册拦截器
//使用WebMvcConfigurerAdapter可以来扩展SpringMVC的功能 ,实现WebMvcConfigurer接口
@Configuration
//@EnableWebMvc //使SpringMVC的自动配置失效
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//收到hi请求,返回t页面,也就是将t.html交给模板引擎渲染
// registry.addViewController("/hi").setViewName("t");
//添加映射器
registry.addViewController("/").setViewName("login");
registry.addViewController("/index").setViewName("login");
registry.addViewController("/index.html").setViewName("login");
registry.addViewController("/main.html").setViewName("dashboard");
}
@Bean
public LocaleResolver localeResolver(){
return new MyLocaleResolver();
}
//定义不拦截路径
String[] excludedPath = {"/", "/index", "/index.html", "/asserts/**","/webjars/**"};
@Override //注册拦截器
public void addInterceptors(InterceptorRegistry registry) {
//拦截所有请求,并且排除一些不需要拦截的请求和静态资源请求
registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**").excludePathPatterns(excludedPath);
}
}
在spring2.0+的版本中,只要用户自定义了拦截器,则静态资源会被拦截。但是在spring1.0+的版本中,是不会拦截静态资源的。
因此,在使用spring2.0+时,配置拦截器之后,我们要把静态资源的路径加入到不拦截的路径之中。
4. CRUD-员工列表
CRUD 满足Rest风格
Rest 指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是 RESTful。
Rest 风格特点
- 每一个URI代表1种资源;
- 客户端使用GET、POST、PUT、DELETE4个表示操作方式的动词对服务端资源进行操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源;
普通CRUD(uri来区分操作) | RestfulCRUD | |
---|---|---|
查询 | getEmp | emp—GET |
添加 | addEmp?xxx | emp—POST |
修改 | updateEmp?id=xxx&xxx=xx | emp/{id}—PUT |
删除 | deleteEmp?id=1 | emp/{id}—DELETE |
4.1 实验的请求架构
实验功能 | 请求URI | 请求方式 |
---|---|---|
查询所有员工 | emps | GET |
查询某个员工(来到修改页面) | emp/1 | GET |
来到添加页面 | emp | GET |
添加员工 | emp | POST |
来到修改页面(查出员工进行信息回显) | emp/1 | GET |
修改员工 | emp | PUT |
删除员工 | emp/1 | DELETE |
4.2 查询所有员工
- 编写controller 层
@Controller
public class EmployeeController {
@Autowired
public EmployeeDao employeeDao;
@GetMapping("/emps")
//查询所有员工,返回list页面
public String findAll(Model model){
Collection<Employee> employees = employeeDao.getAll();
model.addAttribute("emps", employees);
//thmeleaf默认会进行拼串,classpath://templates/emp/list.html
return "emp/list";
}
}
- 编写dao层,这里省略Service 层的编写,并且使用静态数据
@Repository
public class EmployeeDao {
private static Map<Integer, Employee> employees = null;
@Autowired
private DepartmentDao departmentDao;
static{
employees = new HashMap<Integer, Employee>();
employees.put(1001, new Employee(1001, "E-AA", "aa@163.com", 1, new Department(101, "D-AA")));
employees.put(1002, new Employee(1002, "E-BB", "bb@163.com", 1, new Department(102, "D-BB")));
employees.put(1003, new Employee(1003, "E-CC", "cc@163.com", 0, new Department(103, "D-CC")));
employees.put(1004, new Employee(1004, "E-DD", "dd@163.com", 0, new Department(104, "D-DD")));
employees.put(1005, new Employee(1005, "E-EE", "ee@163.com", 1, new Department(105, "D-EE")));
}
private static Integer initId = 1006;
public void save(Employee employee){
if(employee.getId() == null){
employee.setId(initId++);
}
employee.setDepartment(departmentDao.getDepartment(employee.getDepartment().getId()));
employees.put(employee.getId(), employee);
}
//查询所有员工
public Collection<Employee> getAll(){
return employees.values();
}
public Employee get(Integer id){
return employees.get(id);
}
public void delete(Integer id){
employees.remove(id);
}
}
- 修改页面
<li class="nav-item">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#" th:href="@{/emps}">
用户管理
</a>
</li>
emp/list页面的左边侧边栏是和后台页面一模一样的,每个都要修改很麻烦,接下来,抽取公共片段
4.3 thymeleaf公共页面元素抽取
抽取公共片段
<div th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
</div>
引入公共片段
<div th:insert="~{footer :: copy}"></div>
~{templatename::selector} 模板名::选择器
~{templatename::fragmentname} 模板名::片段名
默认效果
insert的公共片段在div标签中
三种引入公共片段的th属性
- th:insert :将公共片段整个插入到声明引入的元素中
- th:replace:将声明引入的元素替换为公共片段
- th:include:将被引入的片段内容包含进这个标签
<footer th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
</footer>
使用三种方式引入公共片段
<body>
...
<div th:insert="footer :: copy"></div>
<div th:replace="footer :: copy"></div>
<div th:include="footer :: copy"></div>
</body>
结果如下
<body>
...
<div>
<footer>
© 2011 The Good Thymes Virtual Grocery
</footer>
</div>
<footer>
© 2011 The Good Thymes Virtual Grocery
</footer>
<div>
© 2011 The Good Thymes Virtual Grocery
</div>
</body>
当使用这三个属性来引入公共片段时,可以省略~{}
,不使用这三个属性的话,可以使用行内写法[[~{...}]
或者 [(~{...})]
。
另外,声明公共片段时,可以只给公共片段指定一个id,使用时用~{模板名::#id}
//声明公共片段
<nav class="col-md-2 d-none d-md-block bg-light sidebar" id="leftbar">
使用公共片段
<div class="row" th:insert="dashboard::#leftbar">
具体说明可以参考官方文档
4.4 引入片段传递参数
实现点当前项高亮
将dashboard.html中的公共代码块抽出为单独的html文件,放到commos文件夹下
在引入代码片段的时候可以传递参数,然后在sidebar代码片段模板中判断当前点击的链接
语法:
~{templatename::selector(变量名=值)}
/*或者在定义代码片段时,定义参数*/
<nav th:fragment="topbar(A,B)"
/*引入时直接传递参数*/
~{templatename::fragmentname(A值,B值)}
显示员工数据,添加增删改按钮
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<h2><button class="btn btn-sm btn-success">添加</button></h2>
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th>员工号</th>
<th>姓名</th>
<th>性别</th>
<th>部门</th>
<th>生日</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<!--emp代表epms中的单个对象-->
<tr th:each="emp:${emps}">
<td th:text="${emp.id}"></td>
<td th:text="${emp.lastName}"></td>
<td th:text="${emp.email}"></td>
<td th:text="${emp.gender} == 1 ? '男' : '女'" ></td>
<!--使用thymeleaf中的内置对象修改日期格式-->
<td th:text="${#dates.format(emp.birth, 'yyyy-MM-dd')}"></td>
<td>
<button class="btn btn-sm btn-primary">编辑</button>
<button class="btn btn-sm btn-danger">删除</button>
</td>
</tr>
</tbody>
</table>
</div>
</main>
4.5 员工添加
1 创建添加页面add.html,表单使用的是BootStrap提供的模板,可以去BootStrap官网找。
<body>
<!--模板名会使用thymeleaf的前后缀配置规则-->
<div th:replace="~{common/bar::topbar}"></div>
<div class="container-fluid">
<div class="row" >
<!--引入左边栏-->
<div th:replace="common/bar::#leftbar(activeUri='emps')"></div>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<form>
<div class="form-group">
<label for="exampleFormControlInput1">员工姓名</label>
<input type="text" name="lastName" class="form-control" id="exampleFormControlInput1" placeholder="员工姓名">
</div>
<div class="form-group">
<label for="exampleFormControlInput1">邮箱</label>
<input type="email" name="email" class="form-control" placeholder="邮箱">
</div>
<div class="form-group">
<label for="exampleFormControlInput1">性别</label><br>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" id="inlineRadio1" value="1">
<label class="form-check-label" for="inlineRadio1">男</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" id="inlineRadio2" value="0">
<label class="form-check-label" for="inlineRadio2">女</label>
</div>
</div>
<div class="form-group">
<label for="exampleFormControlSelect1">部门</label>
<select class="form-control" id="exampleFormControlSelect1">
<!--提交的是部门id-->
<option th:value="${d.id}" th:each="d:${deparments}" th:text="${d.departmentName}"></option>
</select>
</div>
<div class="form-group">
<label for="exampleFormControlInput1">出生日期</label>
<input type="text" name="birth" class="form-control" placeholder="出生日期">
</div>
<button type="submit" class="btn btn-success" >添加</button>
</form>
</main>
</div>
</div>
2 点击链接跳转到添加页面
<a href="/emp" th:href="@{/emp}" class="btn btn-sm btn-success">添加员工</a>
3 添加映射方法
//到添加页面
@GetMapping("/toAddPage")
public String toAddPage(Model model){
//获取部门信息
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("deparments", departments);
return "/emp/add";
}
4 修改页面遍历添加下拉选项
<select class="form-control">
<option th:each="dept:${departments}" th:text="${dept.departmentName}"></option>
</select>
5 表单提交,添加员工
<form th:action="@{/emp}" method="post">
//添加员工
@PostMapping("/emp")
public String add(Employee employee) {
System.out.println(employee);
employeeDao.save(employee);
//redirect: 表示重定向 forward:请求转发
//重定向到员工列表
return "redirect:/emps";
}
4.6 日期格式修改
表单提交的日期格式必须是yyyy/MM/dd的格式,可以在配置文件中修改格式
#指定日期格式
spring.mvc.format.date=yyyy-MM-dd
4.7 员工修改
1 点击按钮跳转到编辑页面
<a class="btn btn-sm btn-primary" th:href="@{/emp/}+${emp.id}">编辑</a>
2 到员工修改页面
//到修改员工页面
@GetMapping("/emp/{id}") //使用@PathVariable注解获取url中的id值,赋给参数id
public String toUpdate(@PathVariable("id") Integer id, Model model) {
Employee employee = employeeDao.get(id);
model.addAttribute("emp", employee);
//获取部门信息
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("deparments", departments);
return "/emp/edit";
}
@PathVariable 和 @RequestParam的区别
@PathVariable是获取url上的数据,@RequestParam是获取请求参数的(包括post表单提交)
3 信息回显以及修改提交方式为put
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<form th:action="@{/emp}" method="post">
<!-- 员工id-->
<input type="hidden" name="id" th:value="${emp.id}">
<!--发送put请求,修改员工数据,表单页面只支持get/post方式,不能直接修改method=put
1. SpringMvc配置HiddenHttpMethodFilter (SpringBoot自动配置好的)
2. 创建一个post表单
3. 创建一个input项,name="_method",值就是我们指定的请求方式
-->
<input type="hidden" name="_method" value="put">
<div class="form-group">
<label for="exampleFormControlInput1">员工姓名</label>
<input type="text" name="lastName" class="form-control" id="exampleFormControlInput1" placeholder="员工姓名" th:value="${emp.lastName}">
</div>
<div class="form-group">
<label for="exampleFormControlInput1">邮箱</label>
<input type="email" name="email" class="form-control" placeholder="邮箱" th:value="${emp.email}">
</div>
<div class="form-group">
<label for="exampleFormControlInput1">性别</label><br>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" id="inlineRadio1" th:checked="${emp.gender==1}">
<label class="form-check-label" for="inlineRadio1">男</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" id="inlineRadio2" value="0" th:checked="${emp.gender==0}">
<label class="form-check-label" for="inlineRadio2">女</label>
</div>
</div>
<div class="form-group">
<label for="exampleFormControlSelect1">部门</label>
<select name="department.id" class="form-control" id="exampleFormControlSelect1">
<!--提交的是部门id-->
<option th:value="${d.id}" th:each="d:${deparments}" th:text="${d.departmentName}" th:selected="${d.id} == ${emp.department.id}"></option>
</select>
</div>
<div class="form-group">
<label for="exampleFormControlInput1">出生日期</label>
<input type="text" name="birth" class="form-control" placeholder="出生日期" th:value="${#dates.format(emp.birth, 'yyyy-MM-dd')}">
</div>
<button type="submit" class="btn btn-success" >修改</button>
</form>
</main>
4 手动配置HiddenMethodFilter
spring.mvc.hiddenmethod.filter.enabled=true
5 修改员工信息
//修改员工信息
@PutMapping("/emp")
public String update(Employee employee) {
System.out.println(employee);
employeeDao.save(employee);
return "redirect:/emps";
}
4.8 删除员工
1 修改删除按钮
<!--使用th:attr自定义属性-->
<button th:attr="del_uri=@{/emp/} + ${emp.id}" class="btn btn-sm btn-danger deleteBtn">删除</button>
2 在外部写一个form表单,并将提交方式改为delete
<form id="deleteEmpForm" method="post">
<input type="hidden" name="_method" value="delete">
</form>
3 使用js,每次点击删除按钮,就提交一次表单
<script type="text/javascript">
$(".deleteBtn").click(function(){
//获取删除按钮中自定义的deleteUri标签,$(this).attr(del_uri)
// 把表单的action改为del_uri,并提交
$("#deleteEmpForm").attr("action", $(this).attr("del_uri")).submit();
return false;
})
</script>