15.springboot的应用示例
15.springboot的应用示例
3种做法:该项目使用了thymeleaf模板,所以会去templates文件夹下去找index.html
1.后台写个controler
@Controller
public class HelloControl {
@RequestMapping({"/","/index.html"})
public String login(){
return "index";
}
}
2.写个配置类:写个配置类继承WebMvcConfigurerAdapter ,重写里面的addViewControllers方法
@Configuration
public class Myconfg extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//localhost:8080直接跳转到index.html
registry.addViewController("/").setViewName("index");
//localhost:8080/index.html也跳转到index.html
registry.addViewController("/index.html").setViewName("index");
}
}
3.写个配置类:自己创建个WebMvcConfigurerAdapter 加载到容器中
//由之前的源码得知:所有的WebMvcConfigurerAdapter是一起起作用的,所有我们可以创建个WebMvcConfigurerAdapter放入容器中
@Configuration
public class Myconfg{
@Bean
public WebMvcConfigurerAdapter webMvcConfigurerAdapter(){
WebMvcConfigurerAdapter adapter=new WebMvcConfigurerAdapter() {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//localhost:8080直接跳转到index.html
registry.addViewController("/").setViewName("index");
//localhost:8080/index.html也跳转到index.html
registry.addViewController("/index.html").setViewName("index");
}
};
return adapter;
}
}
2.thymeleaf标签中的th:href的好处:
html用th:href标签覆盖了原有的href标签,这样的好处
<!-- Bootstrap core CSS -->
<link href="/asserts/css/bootstrap.min.css" th:href="@{/webjars/bootstrap/4.5.2/css/bootstrap.css}" rel="stylesheet">
<!-- Custom styles for this template -->
<link th:href="@{/asserts/css/signin.css}" href="/asserts/css/signin.css" rel="stylesheet">
1.例如重新设置了项目的访问路径:在application.properties配置中配置
server.servlet.context-path=/crud
这时:通过localhost:8080无法定位到登录页面
需要通过:localhost:8080/crud去访问
分析下面浏览器登录页面源码得知:thymeleaf会自动帮我们拼接上项目的访问路径:crud
登录页面的国际化功能
登录页面如下:
步骤:
1.编写国际化配置文件,抽取页面需要显示的国际化消息:如上图所示
1.1默认显示的配置文件login.properties
login.tip=请登录-
login.username=用户名-
login.password=密码-
login.remember=记住我-
login.btn=登录-
1.2英文显示的配置文件:login_en_US.properties
login.tip=Please sign in
login.username=Username
login.password=Password
login.remember=Remember me
login.btn=Sign in
1.3中文显示的配置文件:login_zh_CN.properties
login.tip=请登录
login.username=用户名
login.password=密码
login.remember=记住我
login.btn=登录
2.配置自定义国际化配置文件的路径
国际化自动加载的代码:MessageSourceAutoConfiguration
springboot自动配置好了管理国际化资源文件的组件:
源码如下:
public class MessageSourceAutoConfiguration {
...
@Bean
public MessageSource messageSource(MessageSourceProperties properties) {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
if (StringUtils.hasText(properties.getBasename())) {
//设置国际化资源文件的基础名(去掉语言国家代码的):properties.getBasename()结果是:message
messageSource.setBasenames(StringUtils
//得到的结论是:我们的配置文件可以直接放在类路径下交message.properties
.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;
}
}
我们自己定义了国际化文件,所以需要在springboot的配置文件中:application.properties指定自定义国际化文件的路径,后面只需要写到基础名即可!!
spring.messages.basename=i18n.login
3.去页面上取值
<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">
<!--使用th:text="#{国际化文件中的名称}"去获取国际化配置显示,template模板通过#{}去获取国际化配置文件中的参数值-->
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
<!--使用th:text="#{login.username}替换用户名-->
<label class="sr-only" th:text="#{login.username}">Username</label>
<!--使用th:placeholder="#{login.username}"替换默认的placeholder="Username"-->
<input type="text" class="form-control" th:placeholder="#{login.username}" placeholder="Username" required="" autofocus="">
<label class="sr-only" th:text="#{login.password}">Password</label>
<input type="password" class="form-control" th:placeholder="#{login.password}" placeholder="Password" required="">
<div class="checkbox mb-3">
<label>
<!--注意这里的input是选框,不能使用th:text替换,而应该使用行内显示[[#{}]]的形式去取国际化配置文件的值-->
<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.如果浏览器默认英语在上面则页面显示英文
1.如果浏览器默认中文在上面则页面显示中文
3.如何设置点击页面剩的中文英文实现页面的改变呢
原理:
国际化Locale(区域化信息对象);LocaleResolver (用来获取区域化对象),源码在:WebMvcAutoConfiguration
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "spring.mvc", name = "locale")
public LocaleResolver localeResolver() {
if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
return new FixedLocaleResolver(this.mvcProperties.getLocale());
}
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
return localeResolver;
}
默认是根据请求头带来的区域信息获取Local进行国际化
实现步骤如下:
1.页面上:
原:
<a class="btn btn-sm">中文</a>
<a class="btn btn-sm">English</a>
现改为:加上th:href="@{/index.html(l='zh_CN')}发起请求,并出入参数,传入参数的格式是(key=value),传到当前页面!
<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>
2.编写自己的区域化对象:
public class MyLocaleResolver implements LocaleResolver {
@Override
public Locale resolveLocale(HttpServletRequest request) {
//获取页面传来的请求参数l
String l = request.getParameter("l");
//获取默认的区域化对象
Locale locale=Locale.getDefault();
//如果页面传值为空,就用默认的区域化对象
if(!StringUtils.isEmpty(l)){
//不为空,zh_CN或en_US进行"-"分割,0-语言 1-国家
String[] split = l.split("_");
locale=new Locale(split[0], split[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
}
}
3.这时自己创建的区域化对象还没有加载到容器中
所以在自己的配置类中:
@Configuration
public class Myconfg extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//localhost:8080直接跳转到index.html
registry.addViewController("/").setViewName("index");
//localhost:8080/index.html也跳转到index.html
registry.addViewController("/index.html").setViewName("index");
}
//由之前的源码得知:所有的WebMvcConfigurerAdapter是一起起作用的,所有我们可以创建个WebMvcConfigurerAdapter放入容器中
@Bean
public WebMvcConfigurerAdapter webMvcConfigurerAdapter(){
WebMvcConfigurerAdapter adapter=new WebMvcConfigurerAdapter() {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//localhost:8080直接跳转到index.html
registry.addViewController("/").setViewName("index");
//localhost:8080/index.html也跳转到index.html
registry.addViewController("/index.html").setViewName("index");
}
};
return adapter;
}
//将其加载到容器中
@Bean
public LocaleResolver localeResolver(){
return new MyLocaleResolver();
}
}
这时实现了页面上点击中文显示中文,点击英语显示英语
4.如何实现用户名密码错误提示
设计原理:登录失败后,后台将错误信息放入到map中,前台进行获取
1.html页面表单:以post的形式体提交,路径为:/user/login
<form class="form-signin" action="dashboard.html" th:action="@{/user/login}" method="post">
...
<form/>
2.对应的controler类:
@Controller
public class HelloControl {
//@RequestMapping(value = "/user/login",method = RequestMethod.POST)
@PostMapping("/user/login")-->和上面的注解效果一致
//@GetMapping:springboot对rest风格的支持
//@DeleteMapping
//@PutMapping
public String login(@RequestParam("username") String userName, @RequestParam("password") String passWord, HttpSession session,Map<String, Object> map) {
System.out.println("页面传来的user:" + userName + " passWord:" + passWord);
if(!StringUtils.isEmpty(userName)&&"123456".equals(passWord)){
System.out.println("账户密码正确!");
//将用户名放入session中
session.setAttribute("username",userName);
return "redirect:/main.html";--->通过重定向到该路径:可以解决刷新表单的重复提交问题...
}else{
System.out.println("登录失败!用户名或密码错误");
map.put("msg","用户名密码错误");--->登录失败后,将错误信息放入到map中,供前台获取!
return "index";
}
}
}
3.重定向的路径到目标页面:
@Configuration
public class Myconfg extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//localhost:8080直接跳转到index.html
registry.addViewController("/").setViewName("index");
//localhost:8080/index.html也跳转到index.html
registry.addViewController("/index.html").setViewName("index");
registry.addViewController("/main.html").setViewName("dashboard");
---->发现main.html路径转发到dashboard.html(模板引擎thymeleaf会将dashboard转为dashboard.html,去templates文件夹下找dashboard.html)
}
。。。。
}
问题:直接访问项目资源:http://localhost:8080/main.html可以直接访问,如何让用户在没有登录的情况下访问资源,跳转到登录页面
实现:登录成功后,将用户信息保存到session中,再创建拦截器,拦截用户的访问资源请求,判断用户是否登录
1.登录成功后将用户信息保存到session中
@PostMapping("/user/login")
public String login(@RequestParam("username") String userName, @RequestParam("password") String passWord, HttpSession session,Map<String, Object> map) {
System.out.println("页面传来的user:" + userName + " passWord:" + passWord);
if(!StringUtils.isEmpty(userName)&&"123456".equals(passWord)){
System.out.println("账户密码正确!");
//将用户名放入session中
session.setAttribute("username",userName);----->登录成功后,将用户名放入到sesison中
return "redirect:/main.html";
}else{
System.out.println("登录失败!用户名或密码错误");
map.put("msg","用户名密码错误");
return "index";
}
}
2.创建拦截器拦截用户的访问资源请求
2.1自建的拦截器
public class LoginHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object user = request.getSession().getAttribute("username");---->判断session中是否有用户名
System.out.println("拦截器获取登录名称:"+user+" 拦截路径:"+request.getRequestURL());
if (user==null){
System.out.println("登录对象为空,请先登录!");
request.setAttribute("msg","没有权限访问,请先登录!");--->将错误提示信息放入到request域中,页面获取
request.getRequestDispatcher("/index.html").forward(request, response);--->没有的话进行请求转发到登录页面
return false;
}else {
return true;---->有的话放行,可以继续访问资源
}
}
...
}
2.2将拦截器加载到容器中
@Configuration
public class Myconfg extends WebMvcConfigurerAdapter {
...
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginHandlerInterceptor())
.addPathPatterns("/**")---->拦截所有请求
.excludePathPatterns("/","/index.html","/user/login","/asserts/**","/webjars/**");---->哪些路径不拦截
// 1./和/index.html不拦截,要不无法打开登录页面
// 2./user/login不拦截,要不无法进行登录验证
// 3."/asserts/**","/webjars/**"不拦截,要不页面无法加载样式
}
...
}
4.页面获取错误提示数据:
<p style="color: #ff0000" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
如何在登录成功页面显示登录名
实现原理:
获取登录成功后放入session域中的username,并展示在页面
<a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#" th:text="${session.username}">用户名称</a>
REST风格的员工增删改查(crud)
什么是rest风格:通过不同的请求方式(get/post/delete等)去区分对资源的CRUD操作(增删改查)
普通的CRUD(通过uri区分操作)
|
RestFulCRUD
|
|
查询
|
getEmp
|
emp---GET
|
添加
|
addEmp?xxx
|
emp---POST
|
修改
|
updateEmp?id=xxx&xxx=xx
|
emp/{id}----PUT
|
删除
|
deleteEmp?id=xxx
|
emp/{id}---delete
|
需求的请求架构
请求URI
|
请求方式
|
|
查询所有员工
|
emps
|
GET
|
查询某个员工(来到修改页面)
|
emp/{id}
|
GET
|
来到添加页面
|
emp
|
GET
|
添加员工
|
emp
|
POST
|
来到修改页面(查询员工进行信息回显)
|
emp/{id}
|
GET
|
修改员工
|
emp
|
PUT
|
删除员工
|
emp/{id}
|
delete
|
3.员工列表:thymeleaf公共页面元素抽取
1.抽取公共片段,格式如下:th:fragment="copy"在公用的地方使用这个标签
<div th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
</div>
2.引入公用的片段
<div th:insert="~{footer :: copy}"></div>
~{templatename::selector} :模板名 :: 选择器
~{templatename::fragmentname} :模板名 :: 片段名
3.三种引入方式:这三种分别是什么效果,示例如下
th:insert:将公共片段插入到指定声明引入元素中
th:replace :将声明引入元素替换为公共片段
th:include:将被引入的片段的内容包含到声明元素中
示例:
1.需要引入的代码:
<footer th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
</footer>
2.th:insert
<div th:insert="footer :: copy"></div>
其效果如下:
<div>
<footer>
© 2011 The Good Thymes Virtual Grocery
</footer>
</div>
2.th:replace
<div th:replace="footer :: copy"></div>
其效果如下:
<footer>
© 2011 The Good Thymes Virtual Grocery
</footer>
3.th:include
<div th:include="footer :: copy"></div>
其效果如下:
<div>
© 2011 The Good Thymes Virtual Grocery
</div>
具体的代码实现:
在刚登录进来的页面:dashboard.html,定义公共的片段
使用th:fragment="片段名称"定义公共的片段
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0" th:fragment="topbar">
<a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#" th:text="${session.username}">用户名称</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引入,并且insert标签会替换掉div标签,这样和原页面(dashboard.html)就保持了一致
引入抽取的topbar,格式为: 模板名 :: 公共片段名 模板名:会使用thymeleaf的前后缀配置规则进行解析
<div th:insert="dashboard::topbar"></div>
如果是insert标签:
原格式是:~{templatename::fragmentname}
可以省略~和大括号:templatename::fragmentname
这样实现了头部的公用,同理可以实现左侧导航栏的公用
侧边栏的引入:
原页面:加了一个id,其他并没有变
<nav class="col-md-2 d-none d-md-block bg-light sidebar" id="sidebar">
...
<nav>
目标页面:通过使用#id的形式去导入公共页面
<div th:insert="~{dashboard :: #sidebar}"></div>
问题:如何让点击哪个侧边栏,哪个侧边栏高亮
实现原理:
发现高亮的元素的class为:class="nav-link active"
不高亮的元素的class为:class="nav-link"
没有active
并且:目标页面在引入公共片段时可以传入参数
所以实现原理是:每个元素判断目标页面传来的参数是不是自己的路径,不是就不加active,是加上active
将头部栏和侧边栏抽取出到templates文件夹下的common/commmonBar.html
示例如下:
1.在首页页面(dashboard.html)引入侧边栏,并传入参数:activeUrl='/main.html'即为首页页面的访问地址/main.html
自定义的视图控制器:registry.addViewController("/main.html").setViewName("dashboard");会将main.html转到目标页面:dashboard.html
<div th:replace="~{common/commonBar :: #sidebar(activeUrl='/main.html')}"></div>
传入多个参数值的话,用逗号隔开
2.在员工列表页面(list.html)
<div th:replace="~{common/commonBar :: #sidebar(activeUrl='/emps')}"></div>
传入员工列表的访问路径!作为区别参数
3.在抽取出的公共页面中进行路径判断,以高亮不同显示!
<a class="nav-link active" href="#"
th:href="@{/main.html}"
这里进行三目运算:如果不是自己的首页路径,就不加active属性,也就不会高亮显示
th:class="${activeUrl=='/main.html'?'nav-link active':'nav-link'}">
首页
<a/>
员工列表同理:
<a class="nav-link active" href="#" th:href="@{/emps}" th:class="${activeUrl=='/emps'?'nav-link active':'nav-link'}">
员工列表
</a>
结论:以上设置实现了点击哪个标签,哪个标签高亮的操作!
查询出员工并循环取值在页面上显示
1.控制类查询代码:
@GetMapping("emps")
public String getEmpsList(Map<String, Object> map){
Collection<Employee> empsList = employeeDao.getAll();
map.put("emps",empsList);-->放入map中,key=emps
//Thymeleaf模板引擎会自动拼串:classpath:/templates/xxx.html,所以会跳转到/templates/emp/list.html页面
return "emp/list";
}
2.页面上的代码(list.html)
<table class="table table-striped table-sm">
<thead>
<tr>
<th>id</th>
<th>名称</th>
<th>邮箱</th>
<th>性别</th>
<th>部门</th>
<th>生日</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr th:each="emp:${emps}">--->获取查询出的emps数据,并进行遍历
<td th:text="${emp.id}"></td>
<td>[[${emp.lastName}]]</td>
<td th:text="${emp.email}"></td>
<td th:text="${emp.gender}=='0'?'女':'男'"></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>