SpringBoot-Web项目实例

    技术2022-07-20  73

    文章目录

    SpringBoot-Web项目实例1 项目结构2 [国际化]语言切换配置通过按钮切换信息 3 登陆&拦截器登陆重定向&拦截器 4 CRUD-员工列表4.1 RestfulCRUD4.2 访问员工列表thymeleaf公共页面抽取链接高亮修改表格内容,增加按钮 4.3 添加员工页面页面表单 4.4 员工修改4.5 员工删除 5 错误处理5.1 错误处理原理5.2 自定义错误页面

    SpringBoot-Web项目实例

    1 项目结构

    加入Dao层,和实体对象加入静态资源css、img、js进入到静态资源包将页面加入到templates包中,才能通过模板进行调用

    在控制类中增加对登陆页面的访问,可以通过"","/login"进行访问

    @RequestMapping({"","/login"}) public String index(){ return "login"; }

    也可以自己实现SpringMVC配置,进行默认视图转发

    @Configuration //@EnableWebMvc public class MyMvcConfig implements WebMvcConfigurer { @Override public void addViewControllers(ViewControllerRegistry registry) { //浏览器发送/ok 请求来到 /success registry.addViewController("").setViewName("login"); } }

    2 [国际化]语言切换配置

    编写国际化配置文件

    从上到下分别是默认配置、英语和中文 将页面要显示的信息抽取配置

    login.btn=登陆 login.password=密码- login.remember=记住登陆信息- login.tip=请登录! login.username=用户名- 在SpringMVC中需要使用ResourceBundleMessageSource管理国际化资源文件。SpringBoot自动配置好了管理国家化资源的组件。

    只需要在配置文件application.properties加入配置即可

    spring.messages.basename=i18n.login #{…}获取国际化信息

    增加thymeleaf的提示信息

    <html lang="en" xmlns:th="http://www.thymeleaf.org">

    头部的地址进行替换 页面中的信息进行替换,替换成自己配置的信息。 修改完成后运行,修改浏览器的语言,页面刷新会更新语言

    通过按钮切换信息

    SpringBoot中WebMvcAutoConfiguration通过localeResolver()默认的区域信息请求器根据request请求头获取语言信息。

    @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; } 首先修改html连接信息

    再点击标签后会跳转到指定连接并且有指定信息

    <a class="btn btn-sm" th:href="@{/login.html(l='zh_CN')}">中文</a> <a class="btn btn-sm" th:href="@{/login.html(l='en_US')}">English</a> 自己实现LocalResolver()方法

    注意@Component("localeResolver")对Bean对象进行命名,SpringBoot自动对该命名进行扫描,如果不是这个命名扫描不到

    @Component("localeResolver") public class MyLocaleResolver implements LocaleResolver { @Override public Locale resolveLocale(HttpServletRequest request) { String l = request.getParameter("l"); //如果没有地域信息,使用默认 Locale locale = Locale.getDefault();//区域信息 if (!StringUtils.isEmpty("l")){//非空 String[] split = l.split("_"); //获取语言信息,第一个语言第二个国家"zh_CN" locale = new Locale(split[0],split[1]); } return locale; } @Override public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) { } }

    3 登陆&拦截器

    开发期间模版引擎页面修改以后,实时生效

    禁用模版引擎缓存 #禁用缓存 spring.thymeleaf.cache=false 页面修改完cmd+F9,手动重新编译

    登陆

    登陆页面设置登陆form的连接 <form class="form-signin" action="dashboard.html" th:action="@{/user/login}" method="post"> 声明LoginController对登陆进行控制 @Controller public class LoginController { //@RequestMapping(value = "/user/login",method = RequestMethod.POST) //直接表示使用post请求,不需要在@RequestMapping中设置 @PostMapping(value = "/user/login") public String login(@RequestParam("username") String username, @RequestParam("password") String password, Map<String, Object> map){ if (password.equals(username)) //登陆成功 return "dashboard"; else { //登陆失败 map.put("msg","用户名密码错误"); return "login"; } } } 设置login.html页面,如果输入返回消息为空则不显示 <!--判断msg不为空才生成消息内容,密码错误提示--> <p style="color:red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>

    重定向&拦截器

    刷新登陆后的页面会提示下图消息,重新提交表单信息,也就是重复提交用户名和密码。通过发送user/login请求转发到成功页面,刷新还是重复发送。 修改成功语句进行重定向

    if (password.equals(username)) //登陆成功,防止表单重复提交进行重定向 return "redirect:/main.html";

    拦截器进行检查

    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("/login").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 {} }

    SpringMVC自定义配置中加入拦截器

    //注册拦截器 @Override public void addInterceptors(InterceptorRegistry registry) { //添加拦截器并且,设置拦截页面 //需要放行登陆前的页面和静态资源页面 registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**") .excludePathPatterns("/login.html","","/login","/user/login","/webjars/**","/asserts/**"); }

    控制层方法,通过session将信息存储

    @Controller public class LoginController { //@RequestMapping(value = "/user/login",method = RequestMethod.POST) //直接表示使用post请求,不需要在@RequestMapping中设置 @PostMapping(value = "/user/login") public String login(@RequestParam("username") String username, @RequestParam("password") String password, Map<String, Object> map, HttpSession session){ if (password.equals(username)) { //登陆成功,防止表单重复提交进行重定向 session.setAttribute("loginUser", username); return "redirect:/main.html"; } else { //登陆失败 map.put("msg","用户名密码错误"); return "login"; } } }

    4 CRUD-员工列表

    4.1 RestfulCRUD

    restfulCRUD,CRUD满足rest风格,通过HTTP的请求方式区分对资源CRUD的操作。/资源名/资源标示

    -普通CRUDRestfulCRUD查询getEmpemp–Get查询addEmp?xxxemp–POST查询updateEmp?id=xxx&xx=xxxemp/{id}–PUT查询deleteEmp?id=xxxemp/{id}–DELETE

    通过使用RestfulCRUD,URI相对于原先的查询方式变得简单。

    -请求URI请求方式查询所有员工empsGet查询单个员工emp/{id}Get

    4.2 访问员工列表

    增加访问员工控制器,并且将员工信息返回到请求域中

    @Controller public class EmployeeController { @Autowired EmployeeDao employeeDao; //查询所有员工返回列表页面 //将信息放在Model,Map,ModelMap请求域中都行 @GetMapping("/emps") public String list(Model model){ Collection<Employee> employees = employeeDao.getAll(); //放在请求域中 model.addAttribute("emps",employees); //thymeleaf默认拼串 //classpath:/templates/xxx.html return "emp/list"; } }

    thymeleaf公共页面抽取

    1. 抽取公共片段 --使用片段名时使用这个片段 也就是~{templatename::fragmentname} 模版名::片段名 <div th:fragment="copy"> © 2011 The Good Thymes Virtual Grocery </div> 使用选择器时,可以直接命名 id="sidebar" <nav class="col-md-2 d-none d-md-block bg-light sidebar" id="sidebar"> 2. 引入公共片段 <div th:insert="~{footer :: copy}"></div> ~{templatename::selector} 模版名::选择器 ~{templatename::fragmentname} 模版名::片段名 三种引入公共片段方法: 插入标签 <div th:insert="~{footer :: copy}"></div> 替换标签 <div th:replace="~{footer :: copy}"></div> 引入片段内容,包含入标签中 <div th:include="~{footer :: copy}"></div> 效果: <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> 抽取公共片段

    对nav标签进行抽取,在标签中使用th:fragment="topbar"命名为topbar

    <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/#">[[${session.loginUser}]]</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> 引入公共片段

    引入前:

    <div th:insert="~{dashboard::topbar}"></div>

    引入后:

    链接高亮

    <a class="nav-link active">

    修改表格内容,增加按钮

    其中变量emps是从EmployeeController中传入的员工列表,存入了页面model

    <thead> <tr> <th>ID</th> <th>LastName</th> <th>Email</th> <th>Gender</th> <th>Department</th> <th>Birth</th> <th>Option</th> </tr> </thead> <tbody> <tr th:each="emp:${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 HH:mm')}"></td> <td> <button class="btn btn-sm btn-primary">编辑</button> <button class="btn btn-sm btn-danger">删除</button> </td> </tr> </tbody>

    4.3 添加员工页面

    跳转按钮使用<a>而不是<button>

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

    控制器

    //来到员工添加页面 @GetMapping("/emp") public String toInsertPage(Map<String,Object> map){ //查处所有部门在页面显示 Collection<Department> departments = departmentDao.getDepartments(); map.put("depts",departments); //返回到添加页面 return "emp/add"; }

    页面表单

    设置返回页面,已经返回方式给每个返回变量命名,和JavaBean对象命名相同自动封装 <!--设置表单返回页面和方式--> <form th:action="@{/emp}" th:method="post"> <div class="form-group"> <label>LastName</label> <input th:name="lastName" type="text" class="form-control" placeholder="name"> </div> <div class="form-group"> <label>Email</label> <input th:name="email" type="email" class="form-control" placeholder="email@qq.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 class="form-control" th:name="department.id"> <!--显示的是部门名称,提交的是部门id--> <option th:value="${dept.id}" th:each="dept:${depts}" th:text="${dept.departmentName}"></option> </select> </div> <div class="form-group"> <label>Birth</label> <input th:name="birth" type="text" class="form-control" placeholder="xxxx/xx/xx"> </div> <button type="submit" class="btn btn-primary">添加</button> </form>

    4.4 员工修改

    通过修改按钮转到员工信息页面 list.html中修改按钮

    <a class="btn btn-sm btn-primary" th:href="@{/emp/}+${emp.id}">编辑</a>

    控制器

    //员工编辑 //@PathVariable获取路径变量 @GetMapping("/emp/{id}") public String toEditPage(@PathVariable("id") Integer id,Model model){ Employee employee = employeeDao.get(id); model.addAttribute("emp",employee); Collection<Department> departments = departmentDao.getDepartments(); model.addAttribute("depts",departments); return "emp/update"; } //员工修改 @PostMapping("/updateEmp") public String updateEmp(Employee employee){ System.out.println("修改员工数据: "+employee); employeeDao.save(employee); return "redirect:/emps"; }

    update.html页面,可以在标签中判断员工变量是否存在,进行内容显示。不存在显示默认值,存在显示被修改员工信息。<input>标签搜集用户信息

    th:value="${emp!=null}?${emp.lastName}"

    4.5 员工删除

    <form th:action="@{/emp/}+${emp.id}" method="post"> <input type="hidden" name="_method" value="delete"/> <button type="submit" class="btn btn-sm btn-danger">删除</button> </form>

    5 错误处理

    5.1 错误处理原理

    错误处理自动配置:ErrorMvcAutoConfiguration

    给容器中添加以下组件:

    DefaultErrorAttributes:(1)帮助在页面共享信息BasicErrorController:处理默认/error请求ErrorPageCustomizer:系统出现错误以后来到error请求进行处理,(web.xml注册的错误页面规则)DefaultErrorViewResolver :默认响应页面

    步骤:

    一旦系统出现4xx或者5xx类错误, ErrorPageCustomizer就会生效(定制错误响应规则),就会转到/error请求BasicErrorController进行处理 浏览器出错访问得到的是 请求头数据text/html 其他客户端产生的是json数据 请求头数据*/* //产生html类型数据 @RequestMapping(produces = MediaType.TEXT_HTML_VALUE) public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { HttpStatus status = getStatus(request); Map<String, Object> model = Collections .unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML))); response.setStatus(status.value()); //去那个页面作为错误页面,包含页面地址和页面信息 ModelAndView modelAndView = resolveErrorView(request, response, status, model); return (modelAndView != null) ? modelAndView : new ModelAndView("error", model); } //产生json数据 @RequestMapping public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) { HttpStatus status = getStatus(request); if (status == HttpStatus.NO_CONTENT) { return new ResponseEntity<>(status); } Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL)); return new ResponseEntity<>(body, status); } DefaultErrorViewResolver默认SpringBoot可以找到 error/404之类的页面页面能通过 DefaultErrorAttributes获取信息: - timestamp:时间戳 - status:状态码 - error:错误提示 - exception:异常对象 - message:异常消息 - errors:JSR303数据校验的错误

    5.2 自定义错误页面

    如何定制错误页面: 在有模版引擎时,error/状态码;将错误页面命名为错误页面代码.html放在模版引擎文件夹里,error文件里,此时自动转到页面使用4xx/5xx可以匹配本类型所有错误,精确优先如果有404之类 页面能获取的信息 <main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4"> <h1>status:[[${status}]]</h1> <h5>timestamp: [[${timestamp}]]</h5><br> <h5>error:[[${error}]]</h5><br> <h5>exception:[[${exception}]]</h5><br> <h5>message:[[${message}]]</h5><br> <h5>errors:[[${errors}]]</h5><br> </main>

    输入错误格式的生日信息,匹配到了4xx文件

    访问错误页面返回404错误

    Processed: 0.013, SQL: 9