【SpringBoot】restful风格的增删改查

准备

  1. 将静态资源(css,img,js)添加到项目中,放到springboot默认的静态资源文件夹下
  2. 将模板文件(html)放到template文件夹下。
  3. 添加两个实体类和对应的DAO类
    在这里插入图片描述
    如果你的静态资源明明放到了静态资源文件夹下却无法访问,请检查一下是不是在自定义的配置类上加了@EnableWebMvc注解

1.默认访问首页

template文件夹不是静态资源文件夹,默认是无法直接访问的,所以要添加视图映射。

@Configuration
public class MyMVCConfig implements WebMvcConfigurer {
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("login");
        registry.addViewController("/index").setViewName("login");
        registry.addViewController("/index.html").setViewName("login");
    }
}

然后index页面就可以通过以下几个地址访问:http://localhost:8080/ http://localhost:8080/index http://localhost:8080/index.html

i18n国际化

  1. 编写国际化配置文件,抽取页面需要显示的国际化消息
    SpringBoot自动配置好了管理国际化资源文件的组件:
@ConfigurationProperties(prefix = "spring.messages")
public class MessageSourceAutoConfiguration {
    
    /**
	 * Comma-separated list of basenames (essentially a fully-qualified classpath
	 * location), each following the ResourceBundle convention with relaxed support for
	 * slash based locations. If it doesn't contain a package qualifier (such as
	 * "org.mypackage"), it will be resolved from the classpath root.
	 */
	private String basename = "messages";  
    //我们的配置文件可以直接放在类路径下叫messages.properties;
    
    @Bean
	public MessageSource messageSource() {
		ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
		if (StringUtils.hasText(this.basename)) {
            //设置国际化资源文件的基础名(去掉语言国家代码的)
			messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(
					StringUtils.trimAllWhitespace(this.basename)));
		}
		if (this.encoding != null) {
			messageSource.setDefaultEncoding(this.encoding.name());
		}
		messageSource.setFallbackToSystemLocale(this.fallbackToSystemLocale);
		messageSource.setCacheSeconds(this.cacheSeconds);
		messageSource.setAlwaysUseMessageFormat(this.alwaysUseMessageFormat);
		return messageSource;
	}

创建i18n文件夹存放配置文件,文件名格式为基础名(login)+语言代码(zh)+国家代码(CN)
在这里插入图片描述

  1. 在配置文件中添加国际化文件的位置和基础名
spring.messages.basename=i18n.login

如果配置文件中没有配置基础名,就在类路径下找基础名为message的配置文件

  1. 将页面文字改为获取国际化配置,格式#{key}
    <body class="text-center">
        <form class="form-signin" action="dashboard.html">
            <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" 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" 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">中文</a>
            <a class="btn btn-sm">English</a>
        </form>

    </body>

  1. 然后就可以更改浏览器语言,页面就会使用对应的国际化配置文件.
    在这里插入图片描述
  2. 原理

国际化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;
            }
        }
    }

实现点击链接切换语言

修改页面,点击连接携带语言参数

    <a class="btn btn-sm" href="?l=zh_CN">中文</a>
    <a class="btn btn-sm" href="?l=en_US">English</a>

自己实现区域信息解析器:

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();
    }

}

如果没有生效,请检查@Bean的那个方法的名称是否为localeResolver

实现登录功能

  1. 提供登录的controller
@Controller
public class LoginController {

    //@RequestMapping(value = "/user/login",method = RequestMethod.POST)
    @PostMapping("/user/login")
    //@RequestParam把请求中的指定名称的参数传递给控制器中的形参赋值
    public String login(@RequestParam("username") String username,
                        @RequestParam("password") String password,
                        Map<String,Object> map){
        if(!StringUtils.isEmpty(username) && password.equals("12345")){
            //登陆后防止表单重复提交,重定向
            return "redirect:/dashboard.html";
        }else {
            map.put("msg","用户名密码错误");
            return "index";
        }
    }
}


  1. 修改表单提交地址,输入框添加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>

  1. 由于登录失败是转发,所以页面的静态资源请求路径会不正确,使用模板引擎语法替换
<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">
  1. 防止表单重复提交,在视图解析器中增加
registry.addViewController("/dashboard.html").setViewName("dashboard");
  1. 添加登录失败页面显示
<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>

修改页面立即生效

# 配置文件中添加禁用缓存
spring.thymeleaf.cache=false

在页面修改完成以后按快捷键ctrl+f9,重新编译;

拦截器进行登陆检查

  1. 实现拦截器
/**
 * 拦截器
 * @author liwenlong
 * @data 2020/6/3
 */
//登陆检查
public class LoginHandlerInterceptor implements HandlerInterceptor {
    //目标方法执行之前执行
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Object user = request.getSession().getAttribute("loginUser");
        if(user==null){
            //没有登陆.拦截
            request.setAttribute("msg","请登录后查看内容");
            request.getRequestDispatcher("/index.html").forward(request,response);
            return false;
        }else {
            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 {

    }
}

  1. 注册拦截器
@Configuration
public class MyMVCConfig implements WebMvcConfigurer {
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("login");
        registry.addViewController("/index").setViewName("login");
        registry.addViewController("/index.html").setViewName("login");
        registry.addViewController("/login").setViewName("login");
        registry.addViewController("/login.html").setViewName("login");
        registry.addViewController("/dashboard.html").setViewName("dashboard");

    }
    @Bean //假如到容器中,当Springboot检测到容器中有用户定义的区域信息解析器,就不会再用默认的
    public LocaleResolver localeResolver(){
        return new MyLoacaleResolver();
    }

    //定义不拦截路径
    private static String[] excludePaths = {"/", "/index", "/index.html", "/user/login", "/asserts/**","/webjars/**"};

    //注册拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //添加拦截与不拦截的路径
        registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**").excludePathPatterns(excludePaths);
    }
}

在spring2.0+的版本中,只要用户自定义了拦截器,则静态资源会被拦截。但是在spring1.0+的版本中,是不会拦截静态资源的。因此,在使用spring2.0+时,配置拦截器之后,我们要把静态资源的路径加入到不拦截的路径之中。

员工列表

使用rest风格:

普通CRUD(uri来区分操作)RestfulCRUD
查询getEmpemp—GET
添加addEmp?xxxemp—POST
修改updateEmp?id=xxx&xxx=xxemp/{id}—PUT
删除deleteEmp?id=1emp/{id}—DELETE

实验的请求架构;

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

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

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

@Controller
public class EmployeeController {
    @Autowired
    EmployeeDao employeeDao;
    //查询所有员工,返回列表页面
    @GetMapping("/emps")
    public String list(Model model){
        Collection<Employee> employees = employeeDao.getAll();
        //放在请求域当中
        model.addAttribute("emps","employees");
        //thymeleaf默认会拼串
        //classpath:/templates/xx.html
        return "emp/list";
    }
}
  1. 修改后台页面,更改左侧侧边栏,将customer改为员工列表,并修改请求路径
<li class="nav-item">
    <a class="nav-link" th:href="@{/emps}">
        <svg .....>
            ......
        </svg>
        员工列表
    </a>
</li>
  1. 同样emp/list页面的左边侧边栏是和后台页面一模一样的,每个都要修改很麻烦,接下来,抽取公共片段.

thymeleaf公共页面元素抽取

语法
~{templatename::selector}:模板名::选择器

~{templatename::fragmentname}:模板名::片段名

官方模板:

1、抽取公共片段
<div th:fragment="copy">
&copy; 2011 The Good Thymes Virtual Grocery
</div>

2、引入公共片段
<div th:insert="~{footer :: copy}"></div>
~{templatename::selector}:模板名::选择器
~{templatename::fragmentname}:模板名::片段名

3、默认效果:
insert的公共片段在div标签中
如果使用th:insert等属性进行引入,可以不用写~{}:
行内写法可以加上:[[~{}]];[(~{})];

三种引入公共片段的th属性:
  • th:insert:将公共片段整个插入到声明引入的元素中
  • th:replace:将声明引入的元素替换为公共片段
  • th:include:将被引入的片段的内容包含进这个标签中
/*公共片段*/
<footer th:fragment="copy">
&copy; 2011 The Good Thymes Virtual Grocery
</footer>

/*引入方式*/
<div th:insert="footer :: copy"></div>
<div th:replace="footer :: copy"></div>
<div th:include="footer :: copy"></div>


/*效果*/
<div>
    <footer>
    &copy; 2011 The Good Thymes Virtual Grocery
    </footer>
</div>

<footer>
&copy; 2011 The Good Thymes Virtual Grocery
</footer>

<div>
&copy; 2011 The Good Thymes Virtual Grocery
</div>

后台页面抽取

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

dashboard.html:

        <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>

list.html:

<body>

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

......

使用选择器的方式 抽取左侧边栏代码

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>
        ......

引入片段传递参数

实现点击当前项高亮

将dashboard.html中的公共代码块抽出为单独的html文件,放到commos文件夹下

在引入代码片段的时候可以传入参数,然后在sidebar代码片段模板中判断当前点击的链接。

语法:

~{templatename::selector(变量名=)}

/*或者在定义代码片段时,定义参数*/
<nav th:fragment="topbar(A,B)"
/*引入时直接传递参数*/
~{templatename::fragmentname(A值,B值)}

topbar.html

<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<body>
<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>
</body>
</html>

sidebar.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<nav id="sidebar" class="col-md-2 d-none d-md-block bg-light sidebar">
    <div class="sidebar-sticky">
        <ul class="nav flex-column">
            <li class="nav-item">
                <a class="nav-link active" th:class="${currentURI}=='main.html'?'nav-link active':'nav-link'" th:href="@{/main.html}">
      .....
</body>
</html>

然后在dashboard.html和list.html中引入

<body>
<div th:replace="commons/topbar::topbar"></div>
<div class="container-fluid">
    <div class="row">
        <div th:replace="commons/sidebar::#sidebar(currentURI='main.html')"></div>
        ......

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

<div class="container-fluid">
    <div class="row">
        <div th:replace="commons/sidebar::#sidebar(currentURI='emps')"></div>
        ......

  1. 显示员工数据,添加增删改按钮
        <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>

版权声明:本文为zzu_seu原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。