0 源代码
1 默认访问首页功能
当不提交任何请求,即localhost:8080/时默认访问login.html
方法:
(1)重写webMvcConfigurerAdapter
,在里面覆写addViewControllers
函数,并用@Bean
把重写的注入容器
(2)html里面静态资源引用
注意:所有的webMvcConfigurerAdapter
组件会一起起作用——覆盖
config/MyConfig.java
@Bean
public WebMvcConfigurerAdapter webMvcConfigurerAdapter(){
WebMvcConfigurerAdapter adapter = new WebMvcConfigurerAdapter() {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("login");
registry.addViewController("/login.html").setViewName("login");
}
};
return adapter;
}
<link href="#" th:href="@{/css/signin.css}" rel="stylesheet" />
解读:
@Bean
等价于用xml配置
registry.addViewController("/").setViewName("login");
,addViewController
的参数为发送了什么请求,setViewName
的参数为基础名字(会被自动补充路径和.html)设置要跳转到哪,适用于页面跳转不需要或者没有任何业务逻辑处理的过程,只是单纯的路由跳转过程或者是点击一个按钮跳转到另一个页面。上面就是/和/login.html都跳到login.html页面
th:href
用于表达要跳到哪里:
@{}定义url链接 如果是需要从model中取值的话,写法为 th:href="@{${model中的name值}}"
有的时候我们不止需要从model中进行取值,还需写字符串与model中的值进行拼接,写法为th:href="@{‘字符串’+${model中的nam值}}"
2 国际化
2.1 根据浏览器语言自动切换语言
方法:
(1) 抽取(找出)页面需要的国际化消息,编辑国际化配置文件properties
(2) 把我们写的配置文件直接放在类路径下
(3) 从html中读取
(4) 设置编码
具体实现:
第一步,编写三个properties文件,分别存储默认/中文/英文的显示内容,然后在里面设置
第二步,spring自动配置好了国际化,只需要把我们写的配置文件直接放在类路径下,并命名为叫 网页基础名_语言缩写_国家缩写.properties 即可,例如: login_en_US.properties
第三步,从html中读取
对于需要修改的地方,加入th:text标签,替换<>外的本内容,#就是用来读国际化文件,{}放刚刚设置的变量名,如:
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
第四步,idea- file- setting - file encoding- 选utf8和打勾,防止中文乱码
2.2 根据点击链接切换国际化,点中文按钮显示中文
方法:自己编写localResolver
,加到容器中
具体实现:
(1) html修改“中文”“english”按钮的代码,修改他们点击后发送的请求:
<a href="#" class="btn btn-sm" th:href="@{/index.html?lg=zh_CN}">中文</a>
<a href="#" class="btn btn-sm" th:href="@{/index.html?lg=en_US}">English</a>
(2) 在component目录新建一个MyLocaleResolver.class
,继承原来的LocaleResolver
在里面,根据_切分,locale对象根据切分结果存入对应的国家和语言
public class MyLocaleResolver implements LocaleResolver {
//解析区域信息
@Override
public Locale resolveLocale(HttpServletRequest request) {
String l = request.getParameter("lg");
Locale locale = Locale.getDefault();
if(!StringUtils.isEmpty(l)){
String[] split = l.split("_");
locale = new Locale(split[0], split[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
}
}
(3)在config-MyMvcConfig.java
内加入以下内容,以将刚刚写的注入容器
@Bean
public LocaleResolver localeResolver(){
return new MyLocalResolver();
}
3 登录功能
3.1 用户名密码验证
功能:输入用户名和密码+验证密码,若密码为空,显示提示
方法:建立一个LoginController
,前端发送post请求,后端处理该请求,判断用户名和密码是否正确,正确则去main.html,错误则返回login.html
LoginController.java
@Controller
public class LoginController {
@PostMapping(value ="/user/login")
public String login(@RequestParam("username")String username,
@RequestParam("password")String password,
Map<String,Object> map){
if(!StringUtils.isEmpty(username) && "123456".equals(password)){
//登录成功
return "redirect:/main.html";
}else{
map.put("msg", "用户名密码错误");
return "login";
}
}
}
Login.html
<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
<form class="form-signin" th:action="@{/user/login}" method="post">
<input type="text" name="username" class="form-control" placeholder="Username" th:placeholder="#{login.username}" required="" autofocus=""/>
<input type="password" name="password" id="inputPassword" class="form-control" placeholder="Password" th:placeholder="#{login.password}" required="" />
解读:
@Controller
下,return的字符串,会回到该字符串对应的html
html中加一行,判断从后台传来的msg是否为空,是空就显示msg键对应值的信息(“用户名密码错误”),这里只是用户名不为空&&密码为123456就行
前端的form表单里,发出名为/user/login的请求,后台的@PostMapping(value ="/user/login")
处理名为/user/login的post请求。
前端下划线是上一部国际化设置的,读取properties的内容,加粗是定义了名字,后台由@RequestParam
按名字接收发来请求里面对应的值,再判断
语法:@RequestParam(value=”参数名”,required=”true/false”,defaultValue=””)
- value:参数名
- required:是否包含该参数,默认为true,表示该请求路径中必须包含该参数,如果不包含就报错。
- defaultValue:默认参数值,如果设置了该值,required=true将失效,自动为false,如果没有传该参数,就使用默认值
Redirect 防止重复提交
3.2 拦截器
功能:拦截未登录的用户访问内部页面,不然直接访问登录成功后的页面,密码就无意义了
方法:
在component文件夹下新建一个LoginHandlerInterceptor.java
拦截器,继承原来的拦截器
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){
//已经登录
return true;
}
//未经过验证
request.setAttribute("msg", "没权限请先登录");
request.getRequestDispatcher("/index.html").forward(request, response);
return false;
}
@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 {
}
}
在MyMvcConfig
配置中重写拦截器方法,加入到容器中
//所有的webMvcConfigurerAdapter组件会一起起作用
@Bean //註冊到容器去
public WebMvcConfigurerAdapter webMvcConfigurerAdapter(){
WebMvcConfigurerAdapter adapter = new WebMvcConfigurerAdapter() {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("login");
registry.addViewController("/index.html").setViewName("login");
registry.addViewController("/main.html").setViewName("Dashboard");
}
//注册拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
//静态资源 css js img 已经做好了静态资源映射
registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**").
excludePathPatterns("/index.html","/","/user/login");
}
};
return adapter;
}
在LoginHandler中添加登录成功写入session
@Controller
public class LoginController {
@PostMapping(value ="/user/login")
public String login(@RequestParam("username")String username,
@RequestParam("password")String password,
Map<String,Object> map,
HttpSession session){
if(!StringUtils.isEmpty(username) && "123456".equals(password)){
//登录成功,防止重复提交
session.setAttribute("loginUser", username);
return "redirect:/main.html";
}else{
map.put("msg", "用户名密码错误");
return "login";
}
}
}
解读:
为login函数的参数增加一个HttpSession
类型变量session
,将用户名密码正确的用户的username
保存到叫loginUser
的session
里面,后台调用getAttribute
函数获取loginUser的值,如果没有的话说明刚刚没有存进来,也就说明没有登录成功,则又设置一个msg给前端展示,前端根据${msg}
读到msg对应的值,打印出”没权限请先登录”的提示消息,而且还要调用分发器,当作他们发出了/index.html这个请求,而根据MyMvcConfig
的设置registry.addViewController("/index.html").setViewName("login");
,这个请求会去到login.html
,也就是密码错误将会重新回到登录页面而且登录页面会多一句“没权限”的提示。
注释:
(1)
session.setAttribute("sessionName",Object);
用来设置session值
- sessionName是名称
- object是你要保存的对象。
session.getAttribute("sessionName");
用来得到对应名称的session值,即得到object对象,注意需要进行类型转换!
这两个共同把自己要的数据放在session里面传来传去
(2)拦截器:
在我的config里覆写addInterceptors
函数,new LoginHandlerInterceptor()
把拦截器引入,覆写的目的是实现后面的addPathPatterns("/**")
对所有请求都拦截,但是(excludePathPatterns
)排除了一些连接请求的拦截。
登录成功后,刷新的话是重新提交user/login请求,会重新提交表单,因此最好是使用重定向
4 抽取公共片段
功能:侧边栏当点击进入员工列表时,员工列表变蓝,原来的变灰
方法:将侧边栏提取成公共片段存入新的bar.html
,并定义参数activeUri
,再在dashboar/list等有侧边栏的地方引用,引用的时候读取参数,根据参数的内容确定显示方式
具体方法:
把侧边栏放到一个公共的bar.html
里
在commos.bar.html
里,员工管理那一行的代码:
<li class="nav-item">
<a class="nav-link"
th:class="${activeUri}=='emps'?'nav-link active':'nav-link'"
href="https://getbootstrap.com/docs/4.1/examples/dashboard/#" th:href="@{/emps}">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-users"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M23 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg>
员工管理
</a>
</li>
dashboard那行:
<li class="nav-item">
<a class="nav-linkactive"
th:class="${activeUri}=='main.html'?'nav-linkactive':'nav-link'"
href="https://getbootstrap.com/docs/4.1/examples/dashboard/#"th:href="@{/main.html}">
<svgxmlns="http://www.w3.org/2000/svg"width="24"height="24"viewBox="002424"fill="none"stroke="currentColor"stroke-width="2"stroke-linecap="round"stroke-linejoin="round"class="featherfeather-home"><path d="M39l9-797v11a22001-22H5a22001-2-2z"></path><polylinepoints="92291215121522"></polyline></svg>
Dashboard<spanclass="sr-only">(current)</span>
</a>
</li>
在dashboard.html(主页)引入侧边栏时:
<div th:replace="commons/bar :: sidebar(activeUri='main.html')"></div>
而在list.html(员工列表)引入侧边栏时:
<div th:replace="commons/bar::sidebar(activeUri='emps')"></div>
解读:
抽取公共片段:
~{templatename::fragmentname}
模板名::片段名称 footer::copy
比如在原始地方:
Footer.html
<div id="footid" th:fragment="copy">xxx</div>
引用的地方:
<div th:insert=~{footer::copy}></div>
而引用也有三种方法。
如:
<div th:insert="footer :: copy"></div>
这段原始代码,将会根据引用的不同分别变成:
th:insert :加个外层标签 +1
<div>
<footer>
© 2011 The Good Thymes Virtual Grocery
</footer>
</div>
th:replace :完全替换 1
<footer>
© 2011 The Good Thymes Virtual Grocery
</footer>
th:include:就替换里面的内容 -1
<div>
© 2011 The Good Thymes Virtual Grocery
</div>
5 增删查改
1、GET请求会向数据库发索取数据的请求,从而来获取信息,该请求就像数据库的select操作一样,只是用来查询一下数据,不会修改、增加数据,不会影响资源的内容,即该请求不会产生副作用。无论进行多少次操作,结果都是一样的。
2、PUT请求是向服务器端发送数据的,从而改变信息,该请求就像数据库的update操作一样,用来修改数据的内容,但是不会增加数据的种类等,也就是说无论进行多少次PUT操作,其结果并没有不同。
3、POST请求,同PUT请求类似,都是向服务器端发送数据的,但是该请求会改变数据的种类等资源,就像数据库的insert操作一样,会创建新的内容。几乎目前所有的提交操作都是用POST请求的。
4、DELETE请求顾名思义,就是用来删除某一个资源的,该请求就像数据库的delete操作。
1、POST /url 创建
2、DELETE /url/xxx 删除
3、PUT /url/xxx 更新
4、GET /url/xxx 查看
5.1 查看员工:
新建一个EmployeeController
类,获取员工信息,发送到前端
@Controller
public class EmployeeController {
@Autowired
EmployeeDao employeeDao;
/**
* 查询所有员工返回列表页面
*/
@GetMapping(value = "/emps")
public String list(Model model){
Collection<Employee> employees = employeeDao.getAll();
model.addAttribute("emps",employees);
return "emp/list";
}
}
前端接收员工集合,并用each循环,展示
<table class="table table-striped table-sm">
<thead>
<tr>
<th>#</th>
<th>lastName</th>
<th>email</th>
<th>gender</th>
<th>department</th>
<th>birth</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr th:each="emp:${emps}">
<td th:text="${emp.id}">1</td>
<td th:text="${emp.lastName}">1</td>
<td th:text="${emp.email}">1</td>
<td th:text="${emp.gender}">1</td>
<td th:text="${emp.department.departmentName}">1</td>
<td th:text="${#dates.format(emp.birth,'yyyy-MM-dd HH:mm:ss')}">1</td>
<td>
<button class="btn btn-sm btn-primary">编辑</button>
<button class="btn btn-sm btn-danger">删除</button>
</td>
</tr>
</tbody>
</table>
model.addattribute()
可以往前台传数据(可以传对象,List),前端通过 ${}可以获取到,类似于request.setAttribute("sts",sts)
引入dao层,利用dao层的getAll
函数将所有员工的信息返回到一个employee类型的collection里,用emps把获得到的集合传到前端
@GetMapping(value = "/emps")
负责处理名为emps的GET请求
return让页面跳转到emp文件夹下的list.html
前端的th:each
,对每个emps里的emp进行遍历,下面将文本内容替换成emp的id,email等信息(employee的dao层定义了employee有哪些元素)
5.2 新增员工
功能1:
在员工列表页面点击添加按钮,跳转到新增员工信息页面
页面跳转:
在EmployeeController
中添加addEmpPage
方法
@GetMapping(value = "/emp")
public String toAddPage(Model model){
//来到添加页面,查出所有部门显示
Collection<Department> depts = departmentDao.getDepartments();
model.addAttribute("depts",depts);
return "emp/add";
}
当前端发送emp请求时,该函数处理叫emp的get请求,方法为先获取到所有的部门信息并放到depts里,跳转到emp文件夹下的add.html
新增员工信息页面add.html:
<form>
<!-- LastName -->
<div class="form-group">
<label for="LastName">LastName</label>
<input type="text" class="form-control" id="LastName" placeholder="LastName">
</div>
<!-- Email -->
<div class="form-group">
<label for="Email">Email</label>
<input type="email" class="form-control" id="Email" placeholder="zhangsan@163.com">
</div>
<!--gender-->
<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>
<!-- department -->
<div class="form-group">
<label for="exampleFormControlSelect1">department</label>
<select class="form-control" id="exampleFormControlSelect1">
<option th:each="dept:${depts}" th:value="${dept.id}" th:text="${dept.departmentName}"></option>
</select>
</div>
<!--Birth-->
<div class="form-group">
<label for="birthDate">Birth</label>
<input type="text" class="form-control" id="birthDate" placeholder="2012-12-12">
</div>
<button type="submit" class="btn btn-primary">添 加</button>
</form>
注意,这里的department的选择内容,是后台通过employee的dao层,获取所有部门的名称再用depts变量传到前端,供用户选择。用了option标签,value为部门id,显示的文本为部门名称
功能2:
在新增员工信息页面填写好新员工的信息,点击按钮回到员工列表页面
方法:新建一个postMapping
的方法用来接受页面的添加POST请求,修改添加页面,添加name属性,点击按钮向后台发送一个emp名字的post请求,用dao层的save函数将新employee的信息存储起来,之后重定向到emps请求(也就是list.html),此时遍历员工信息的时候就会展示出来。
页面:
<form th:action="@{/emp}" method="post">
<!-- LastName -->
<div class="form-group">
<label for="LastName">LastName</label>
<input type="text" class="form-control" id="LastName" name="lastName" placeholder="LastName">
</div>
<!-- Email -->
<div class="form-group">
<label for="Email">Email</label>
<input type="email" class="form-control" id="Email" name="email" placeholder="zhangsan@163.com">
</div>
<!--gender-->
<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>
<!-- department -->
<div class="form-group">
<label >department</label>
<select class="form-control" name="department.id">
<option th:each="dept:${depts}" th:value="${dept.id}" th:text="${dept.departmentName}"></option>
</select>
</div>
<div class="form-group">
<label for="birthDate">Birth</label>
<input type="text" class="form-control" id="birthDate" placeholder="2012-12-12" name="birth">
</div>
<button type="submit" class="btn btn-primary">添 加</button>
</form>
后台controller新增函数
@PostMapping(value = "/emp")
public String addEmp(Employee employee){
employeeDao.save(employee);
//来到员工列表页面、redirect:重定向到一个地址,forward转发到一个地址
return "redirect:/emps";
}
修改页面第一行表示,该表单会像后台发送emp的post请求。
name="email"这种,是用name来给employee对应的属性赋值。
5.3 修改员工信息:到修改页面,修改按钮
给编辑按钮写上跳转链接,跳转到emp/emp.id请求
<td>
<a href="#" th:href="@{/emp/}+${emp.id}" class="btn btn-sm btn-primary">编辑</a>
<button class="btn btn-sm btn-danger">删除</button>
</td>
增加跳转到员工页面的controller,接收emp/emp.id的拼接请求,通过发送来的id,找到对应的emp对象,通过addAttribute发送给前端,然后跳转到add页面(这里add和编辑用的都是add.html)
@GetMapping(value = "/emp/{id}")
public String toEditPage(@PathVariable("id") Integer id ,Model model){
Employee emp = employeeDao.getEmpById(id);
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("emp",emp);
model.addAttribute("depts",departments);
return "emp/add";
}
修改add页面,让他能兼容增加和修改
<form th:action="@{/emp}" method="post">
<!--发送put请求-->
<!--1.SpringMVC配置HiddenHttpMethodFilter
2.页面创建一个post表单
3.创建一个 input name_method 值就是我们请求的方式-->
<input type="hidden" name="_method" value="put" th:if="${emp!=null}">
<input type="hidden" name="id" th:value="${emp.id}" th:if="${emp!=null}">
<!-- LastName -->
<div class="form-group">
<label for="LastName">LastName</label>
<input type="text" class="form-control" id="LastName" name="lastName" placeholder="LastName" th:value="${emp!=null}?${emp.lastName}">
</div>
<!-- Email -->
<div class="form-group">
<label for="Email">Email</label>
<input type="email" class="form-control" id="Email" name="email" placeholder="zhangsan@163.com" th:value="${emp!=null}?${emp.email}">
</div>
<!--gender-->
<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!=null}?${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!=null}?${emp.gender}==0">
<label class="form-check-label" >女</label>
</div>
</div>
<!-- department -->
<div class="form-group">
<label >department</label>
<select class="form-control" name="department.id" >
<option th:selected="${emp!=null}?${dept.id == emp.department.id}" th:each="dept:${depts}" th:value="${dept.id}" th:text="${dept.departmentName}"></option>
</select>
</div>
<div class="form-group">
<label for="birthDate">Birth</label>
<input type="text" class="form-control" id="birthDate" placeholder="2012-12-12" name="birth" th:value="${emp!=null}?${#dates.format(emp.birth,'yyyy-MM-dd HH:mm:ss')}">
</div>
<button type="submit" class="btn btn-primary" th:text="${emp!=null}?'修改':'添加'">添 加</button>
</form>
</main>
若emp不为空,说明是修改,则提交put请求
对于名字的显示,如果emp不为空,则显示他原来的,否则显示空(但是有一个placeholder所以看起来有内容),其余邮件等同理
最下面的按钮也是,根据emp为不为空,显示编辑or添加
5.4 删除:
员工列表页的删除按钮:
<button th:attr="del_uri=@{/emp/}+${emp.id}" class="btn btn-sm btn-danger deleteBtn">
删除
</button>
form表单, name-value这一对,对应着_method和delete,_method的值delete就是请求方式
<form id="deleteEmpForm" method="post">
<input type="hidden" name="_method" value="delete">
</form>
写js,提交请求
return false;禁用btn提交效果
<script>
$(".deleteBtn").click(function () {
$("#deleteEmpForm").attr("action",$(this).attr("del_uri")).submit();
return false;
})
</script>
controller中新建函数,处理delete请求
@PathVariable("id")
接收请求路径中占位符的值
调用dao层的函数,根据id删除对应的员工后,重定向仍回到员工列表页面
@DeleteMapping(value = "/emp/{id}")
public String deleteEmp(@PathVariable("id") Integer id){
employeeDao.deleteEmpById(id);
return "redirect:/emps";
}