restful风格的增删改查

382 阅读5分钟

注意

  • 如果静态资源放到了静态资源文件夹下却无法访问,请检查一下是不是在自定义的配置类上加了 @EnableWebMvc注解
  • templete文件夹不是静态资源的文件夹,默认是无法访问的,所以要添加视图映射
package cn.xxxxxx.hellospringbootweb.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@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");
    }
}

 i18n国际化

  1. 编写国际化配置文件,抽取页面需要显示的国际化消息

    创建i18n文件夹存放配置文件,文件名格式为基础名(login)+语言代码(zh)+国家代码(CN)

  2. 在配置文件中添加国际化文件的位置和基础名,如果配置文件中没有配置基础名,就在类路径下找基础名为message的配置文件

    spring.messages.basename=i18n.login
    
    1. 点击切换语言    修改页面,点击连接携带语言参数
    <a class="btn btn-sm" href="?l=zh_CN">中文</a>
    <a class="btn btn-sm" href="?l=en_US">English</a>
    

实现登陆功能

1,提供登陆的Controller

@Controller
public class UserController {

    @PostMapping("/user/login")
    public String login(@RequestParam String username, @RequestParam String password, HttpSession session, Model model) {
        if (!StringUtils.isEmpty(username) && "123456".equals(password)) {

            //登录成功,把用户信息方法哦session中,防止表单重复提交,重定向到后台页面
            session.setAttribute("loginUser", username);
            return "redirect:/main.html";
        }
        //登录失败,返回到登录页面
        model.addAttribute("msg", "用户名或密码错误!");
        return "login";
    }
}

2,修改表单的提交地址,输入框添加name值与参数名称相对应

        <form class="form-signin" action="dashboard.html" th:action="@{/user/login}" method="post">
            <img class="mb-4" 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" name="username" class="form-control" th:placeholder="#{login.username}" placeholder="Username" 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}]]
        </label>
            </div>
            <button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}">Sign in</button>
            <p class="mt-5 mb-3 text-muted">© 2017-2018</p>
            <a class="btn btn-sm" href="?l=zh_CN">中文</a>
            <a class="btn btn-sm" href="?l=en_US">English</a>
        </form>

3,由于登陆失败是转发,所以得修改静态资源的请求路径,在其中添加模版引擎

<link  href="asserts/css/bootstrap.min.css" th:href="@{/asserts/css/bootstrap.min.css}" rel="stylesheet">
<!-- Custom styles for this template -->
<link href="asserts/css/signin.css" th:href="@{/asserts/css/signin.css}" rel="stylesheet">

4,添加登陆页面的显示,将msg传回主页面

<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
<!--msg存在才显示该p标签-->
<p th:text="${msg}" th:if="${not #strings.isEmpty(msg)}" style="color: red"></p>

修改页面使其立即生效

在配置文件里面添加如下的命令,在页面修改完成之后,按快捷键ctrl+f9,重新编译

# 禁用缓存
spring.thymeleaf.cache=false

拦截器进行登陆检查

1,实现拦截器

package cn.xxxxxx.hellospringbootweb.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class LoginHandlerInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        Object loginUser = request.getSession().getAttribute("loginUser");
        if (loginUser == null) {
            //未登录,拦截,并转发到登录页面
            request.setAttribute("msg", "您还没有登录,请先登录!");
            request.getRequestDispatcher("/index").forward(request, response);
            return false;
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

2,注册拦截器

package cn.clboy.hellospringbootweb.config;

import cn.clboy.hellospringbootweb.interceptor.LoginHandlerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
    //定义不拦截路径
    private static final String[] excludePaths = {"/", "/index", "/index.html", "/user/login", "/asserts/**"};

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        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();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //添加不拦截的路径,SpringBoot已经做好了静态资源映射,所以我们不用管
        registry.addInterceptor(new LoginHandlerInterceptor())
                .excludePathPatterns(excludePaths);
    }
}

注意:在spring2.0+的版本中,只要用户自定义了拦截器,则静态资源会被拦截。但是在spring1.0+的版本中,是不会拦截静态资源的。因此我们需要将静态资源排除到拦截器的拦截路径之外

案例,实现员工的增删改查

实验功能请求URI请求方式
查询所有员工empsGET
查询某个员工(来到修改页面)emp/1GET
来到添加页面empGET
添加员工empPOST
来到修改页面(查出员工进行信息回显)emp/1GET
修改员工empPUT
删除员工emp/1DELETE
  1. 为了页面结构清晰,在template文件夹下新建emp文件夹,将list.html移动到emp文件夹下

  2. 将dao层和实体层java代码复制到项目中daoentities

  3. 添加员工controller,实现查询员工列表的方法

    @Controller
    public class EmpController {
    
        @Autowired
        private EmployeeDao employeeDao;
    
        @GetMapping("/emps")
        public String emps(Model model) {
            Collection<Employee> empList = employeeDao.getAll();
            model.addAttribute("emps", empList);
            return "emp/list";
        }
    
    }
    
  4. 修改后台页面,更改左侧的侧边栏,并修改请求路径

    <li class="nav-item">
        <a class="nav-link" th:href="@{/emps}">
            <svg .....>
                ......
            </svg>
            员工列表
        </a>
    </li>
    

thymeleaf公共页面元素抽取(参考官方文档

  • ~{templatename::selector}:模板名::选择器
  • ~{templatename::fragmentname}:模板名::片段名
/*公共代码片段*/
<footer th:fragment="copy">
    &copy; 2011 The Good Thymes Virtual Grocery
</footer>

/*引用代码片段*/
<div th:insert="~{footer :: copy}"></di

/*(〜{...}包围是完全可选的,所以上⾯的代码 将等价于:*/
<div th:insert="footer :: copy"></di

三种引入公共片段的th属性:

  • th:insert:将公共片段整个插入到声明引入的元素中
  • th:replace:将声明引入的元素替换为公共片段
  • th:include:将被引入的片段的内容包含进这个标签中

后台页面的抽取

1,将后台主页中的顶部导航栏作为片段,在list中引入

        <nav th:fragment="topbar" class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0">
            <a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">Company name</a>
            <input class="form-control form-control-dark w-100" type="text" placeholder="Search" aria-label="Search">
            <ul class="navbar-nav px-3">
                <li class="nav-item text-nowrap">
                    <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">Sign out</a>
                </li>
            </ul>
        </nav>

2,list.html

<body>

<div th:replace="dashboard::topbar"></div>

......

3,使用选择器的方式抽取左侧边栏的代码,就是我们将不同html文件的公共部分抽取出来,作为一个模版,其余需要的html文件只需要引入即可使用。比如现在我们将名称为sidebar的模版放在dashboard.html里面,而在list.html里面进行复用。

<!--dashboard.html-->
<div class="container-fluid">
    <div class="row">
        <nav id="sidebar" class="col-md-2 d-none d-md-block bg-light sidebar" ......
<!--list.html-->
<div class="container-fluid">
    <div class="row">
        <div th:replace="dashboard::#sidebar"></div>
        ......

4,显示员工数据,添加增删改按钮

        <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>
                        <th>操作</th>
                    </tr>
                    </thead>
                    <tbody>
                    <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>
                        <td th:text="${emp.department.departmentName}"></td>
                        <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>

5,员工添加页面 add.html

......
<body>
<div th:replace="commons/topbar::topbar"></div>

<div class="container-fluid">
    <div class="row">
        <div th:replace="commons/sidebar::#sidebar(currentURI='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>LastName</label>
                    <input name="lastName" type="text" class="form-control" placeholder="zhangsan">
                </div>
                <div class="form-group">
                    <label>Email</label>
                    <input  name="email" type="email" class="form-control" placeholder="zhangsan@atguigu.com">
                </div>
                <div class="form-group">
                    <label>Gender</label><br/>
                    <div class="form-check form-check-inline">
                        <input class="form-check-input" type="radio" name="gender" value="1">
                        <label class="form-check-label"></label>
                    </div>
                    <div class="form-check form-check-inline">
                        <input class="form-check-input" type="radio" name="gender" value="0">
                        <label class="form-check-label"></label>
                    </div>
                </div>
                <div class="form-group">
                    <label>department</label>
                    <select name="department.id" class="form-control">
                        <option th:each="dept:${departments}" th:text="${dept.departmentName}" th:value="${dept.id}"></option>
                    </select>
                </div>
                <div class="form-group">
                    <label>Birth</label>
                    <input name="birth" type="text" class="form-control" placeholder="zhangsan">
                </div>
                <button type="submit" class="btn btn-primary">添加</button>
            </form>
        </main>
    </div>
</div>
......

6,点击链接,跳转到添加页面

<a href="/emp" th:href="@{/emp}" class="btn btn-sm btn-success">添加员工</a>

7,EmpController添加映射方法

    @Autowired
    private DepartmentDao departmentDao;

    @GetMapping("/emp")
    public String toAddPage(Model model) {
        //准备部门下拉框数据
        Collection<Department> departments = departmentDao.getDepartments();
        model.addAttribute("departments",departments);
        return "emp/add";
    }

8, 修改页面遍历添加下拉选项

<select class="form-control">
    <option th:each="dept:${departments}" th:text="${dept.departmentName}"></option>
</select>

9,表单提交,添加员工

<form th:action="@{/emp}" method="post">
    @PostMapping("/emp")
    public String add(Employee employee) {
        System.out.println(employee);
        //模拟添加到数据库
        employeeDao.save(employee);
        //添加成功重定向到列表页面
        return "redirect:/emps";
    }

10,日期格式的修改

表单提交的格式必须是yyyy/MM/dd的格式,可以在配置文件中修改格式

spring.mvc.date-format=yyyy-MM-dd

员工修改

  1. 点击按钮跳转到编辑页面

     <a th:href="@{/emp/}+${emp.id}" class="btn btn-sm btn-primary">修改</a>
    
  2. 添加编辑页面,将表单的提交方式设置为post方式,提供_method参数

    <body>
    <div th:replace="commons/topbar::topbar"></div>
    
    <div class="container-fluid">
        <div class="row">
            <div th:replace="commons/sidebar::#sidebar(currentURI='emps')"></div>
            <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}">
                    <!--http请求方式-->
                    <input type="hidden" name="_method" value="put">
                    <div class="form-group">
                        <label>LastName</label>
                        <input name="lastName" th:value="${emp.lastName}" type="text" class="form-control" placeholder="zhangsan">
                    </div>
                    <div class="form-group">
                        <label>Email</label>
                        <input  name="email" th:value="${emp.email}" type="email" class="form-control" placeholder="zhangsan@atguigu.com">
                    </div>
                    <div class="form-group">
                        <label>Gender</label><br/>
                        <div class="form-check form-check-inline">
                            <input class="form-check-input" type="radio" name="gender" value="1" th:checked="${emp.gender==1}">
                            <label class="form-check-label"></label>
                        </div>
                        <div class="form-check form-check-inline">
                            <input class="form-check-input" type="radio" name="gender" value="0" th:checked="${emp.gender==0}">
                            <label class="form-check-label"></label>
                        </div>
                    </div>
                    <div class="form-group">
                        <label>department</label>
                        <select name="department.id" class="form-control">
                            <option th:each="dept:${departments}" th:value="${dept.id}" th:selected="${dept.id}==${emp.department.id}" th:text="${dept.departmentName}"></option>
                        </select>
                    </div>
                    <div class="form-group">
                        <label>Birth</label>
                        <input name="birth" type="text" class="form-control" placeholder="zhangsan" th:value="${#dates.format(emp.birth,'yyyy-MM-dd')}">
                    </div>
                    <button type="submit" class="btn btn-primary">添加</button>
                </form>
            </main>
        </div>
    </div>
    
    ......
    
  3. controller转发到编辑页面,回显员工信息

        @GetMapping("/emp/{id}")
        public String toEditPage(@PathVariable Integer id, Model model) {
            Employee employee = employeeDao.get(id);
            //准备部门下拉框数据
            Collection<Department> departments = departmentDao.getDepartments();
            model.addAttribute("emp", employee).addAttribute("departments", departments);
            return "emp/edit";
        }
    
  4. 提交表单修改员工的信息

        @PutMapping("/emp")
        public String update(Employee employee) {
            employeeDao.save(employee);
            return "redirect:/emps";
        }
    

员工删除

  1. 点击删除提交发出delete请求

        @DeleteMapping("/emp/{id}")
        public String delete(@PathVariable String id){
            employeeDao.delete(id);
            return "redirect:/emps";
        }
    
  2. 如果提示不支持POST请求,在确保代码无误的情况下查看是否配置启动HiddenHttpMethodFilter

  3. 这个好像是2.0版本以后修改的
    spring.mvc.hiddenmethod.filter.enabled=true
    

  4. 如果删除不掉,请修改EmployeeDao,把String转为Integer类型

        public void delete(String id) {
            employees.remove(Integer.parseInt(id));
        }