SpringBoot聚合项目:达内知道(四)-Spring Validation、学生首页标签列表显示

1.Spring 验证框架

1.1 什么是Spring验证框架?

  Spring验证框架能约束控制器接收到用户填写数据的格式,一般有非空 / 正则表达式等常见要求。Spring 验证框架的英文是Spring Validation,它常用于Spring项目接收到数据的验证。

1.2 为什么需要Spring Validation?

  我们在前端(html)页面中已经编写了对数据格式的验证?为什么java还要写呢?

  因为黑客是可以直接绕过浏览器向服务器发请求的(有专门的软件可以向服务器发出请求),如果服务器端没有验证,任何数据都直接增加到数据库,使服务器崩溃。

  如果添加了服务器验证,相当于加锁,这样才能保证服务器安全。由于我们自己写服务器验证比较麻烦,所以使用Spring Validation框架进行快速实现。

1.3 怎么使用Spring Validation?

先在portal项目的pom.xml文件中添加依赖:

 <!-- 验证框架 -->
 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-validation</artifactId>
 </dependency>

我们到需要进行验证的封装数据的类中(vo/RegisterVo.java)进行格式设置,代码如下:

 package cn.tedu.knows.portal.vo;
 
 import lombok.Data;
 
 import javax.validation.constraints.NotBlank;
 import javax.validation.constraints.Pattern;
 
 @Data
 public class RegisterVo {//Vo:Value Object, 值对象,封装数据的类
     //Spring Validation框架通过注解约束属性格式
     @NotBlank(message = "邀请码不能为空!")
     private String inviteCode;
     @NotBlank(message = "电话号不能为空!")
     @Pattern(regexp = "^1\\d{10}$",message = "手机号格式不正确!")/*正则表达式开始^,结束$*/
     private String phone;
     @NotBlank(message = "昵称不能为空")
     @Pattern(regexp = "^.{2,20}$",message = "昵称是2~20个字符!")
     private String nickname;
     @NotBlank(message = "密码不能为空")
     @Pattern(regexp = "^\\w{6,20}$",message = "密码必须是6~20个字符(字母、数字、下划线_)!")
     private String password;
     @NotBlank(message = "确认密码不能为空!")/*是否与上面密码相同,已在前端进行设置*/
     private String confirm;
 }
 

Spring Validation常用注解含义:

  • @NotBlank:只能作用在字符串上,判断当前属性不能为null,而且对当前属性调用trim方法后length()必须大于0 ;

  • @Pattern:只能作用在字符串上,可以指定一个正则表达式,判断属性值是否满足正则表达式要求;

  • @NotNull:可以作用在任何引用类型上,判断当前属性不能为null;

  • @NotEmpty:一般作用在集合(或数组)类型上,判断当前集合(或数组)不能为空且长度不能为0。

最终使用时,在控制器SystemController中编写代码,代码如下:

 package cn.tedu.knows.portal.controller;
 
 import cn.tedu.knows.portal.exception.ServiceException;
 import cn.tedu.knows.portal.service.IUserService;
 import cn.tedu.knows.portal.vo.RegisterVo;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.validation.BindingResult;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RestController;
 
 @RestController
 @Slf4j
 public class SystemController {
     @Autowired
     private IUserService userService;
     //注册方法,是post请求
     @PostMapping("/register")
     public String registerStudent(
             //@Validated表示激活后面类型的Spring Validation验证,
             //在需要验证的方法参数后添加指定类型(BindingResult)的参数,
             //验证结果会自动赋值到这个参数中
             @Validated RegisterVo registerVo,
             BindingResult result){
         log.debug("收到注册信息:{}",registerVo);
         // 判断验证有没有错误
         if(result.hasErrors()){
             //获得错误信息
             String msg=result.getFieldError().getDefaultMessage();
             //当有验证不通过时,直接返回错误信息到axios
             return msg;
        }
         //Ctrl+Alt+T可以快速添加try-catch结构
         try {
             //调用业务逻辑层方法实现注册功能
             userService.registerStudent(registerVo);
             return "ok";
        } catch (ServiceException e) {
             //e.printStackTrace()是将错误信息输出到控制台
             //可以保留,但是也要知道,控制台的错误信息是我们主动输出的
             e.printStackTrace();
             return e.getMessage();
        }
    }
 }
 

  编写完毕后,重启服务进行测试,可以先将前端代码index_student.html中表单属性的required和pattern(正则表达式)去掉,然后测试后端代码是否可以正常提示错误信息,两者同时存在时,只会显示前端验证信息。

测试结果:

(1)邀请码错误

 

 

(2)手机号格式不正确

(3)手机号重复注册

(4)昵称格式不正确

(5)密码格式不正确

(6)确认密码格式不正确

 

(7)注册成功

(8)登录成功

 

2.开发学生首页标签列表

学生首页如下:

标签列表开发流程:

  1. 用户在显示首页之后,页面加载完毕时,立即调用查询所有tag标签的方法

  2. 控制器运行调用查询返回所有标签List的业务逻辑层方法

  3. 业务逻辑层利用Mybatis Plus提供的全查Tag的方法,进行返回所有标签的操作

  4. 返回到页面上进行v-for的绑定,最终显示

2.1 开发全查所有标签的业务逻辑层

先编写接口:在ITagService添加查询所有标签的方法

 package cn.tedu.knows.portal.service;
 
 import cn.tedu.knows.portal.model.Tag;
 import com.baomidou.mybatisplus.extension.service.IService;
 
 import java.util.List;
 
 /**
  * <p>
  * 服务类
  * </p>
  *
  * @author tedu.cn
  * @since 2021-08-23
  */
 public interface ITagService extends IService<Tag> {
     //查询返回所有标签的方法
     List<Tag> getTags();
 }
 

接口上Ctrl+Alt+B,跳到TagServiceImpl实现类重写方法代码如下:

 package cn.tedu.knows.portal.service.impl;
 
 import cn.tedu.knows.portal.model.Tag;
 import cn.tedu.knows.portal.mapper.TagMapper;
 import cn.tedu.knows.portal.service.ITagService;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
 import java.util.List;
 
 /**
  * <p>
  * 服务实现类
  * </p>
  *
  * @author tedu.cn
  * @since 2021-08-23
  */
 @Service
 public class TagServiceImpl extends ServiceImpl<TagMapper, Tag> implements ITagService {
     //从Spring容器中取TagMapper
     @Autowired
     private TagMapper tagMapper;
     //全查所有Tags
     @Override //重写方法
     public List<Tag> getTags() {
         List<Tag> list = tagMapper.selectList(null);//查询全部
         return list;
    }
 }
 

2.2 编写控制层代码

TagController添加查询并返回所有标签的方法,代码如下:

 package cn.tedu.knows.portal.controller;
 
 
 import cn.tedu.knows.portal.model.Tag;
 import cn.tedu.knows.portal.service.ITagService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 
 import org.springframework.web.bind.annotation.RestController;
 
 import java.util.List;
 
 /**
  * <p>
  * 前端控制器
  * </p>
  *
  * @author tedu.cn
  * @since 2021-08-23
  */
 @RestController
 //@RequestMapping("/portal/tag")
 @RequestMapping("/v1/tags") //满足微服务的要求/标准
 public class TagController {
     @Autowired
     private ITagService tagService;
 
     //查询所有标签的控制方法
     //@GetMapping("")意思是不添加任何额外路径
     //访问这个控制层方法的路径就是localhost:8080/v1/tags
     @GetMapping("")
     public List<Tag> tags(){
         List<Tag> list = tagService.getTags();
         return list;
    }
 }
 

2.3 完成页面绑定

最后还要将页面绑定和引用写好,就能显示了。

在index_student.html 的head标签结束前引入axios框架:ax+Tab

   <!--引入axios框架-->
   <script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.min.js"></script>
 </head>

164行 附近id为tagsApp的div:

 <!--引入标签的导航栏-->
 <div class="container-fluid"  th:fragment="tags_nav" >
   <div class="nav font-weight-light" id="tagsApp">
     <a href="tag/tag_question.html" class="nav-item nav-link text-info"><small>全部</small></a>
     <a href="tag/tag_question.html"
        class="nav-item nav-link text-info"
         v-for="tag in tags">
       <small v-text="tag.name">Java基础</small><!--添加内容-->
     </a>
   </div>
 </div>

页面末尾添加引用:

 </body>
 <script src="js/utils.js"></script>
 <script src="js/tags_nav.js"></script>
 </html>

编写完成后,重启服务,测试,注意要登录后才能正常加载首页标签列表,下次课解决这个登录后才能加载的问题。

2.4 完善放行设置

  经过业务分析,我们得出,达内知道首页需要根据当前登录用户来显示具体信息,所以首页是登录之后才能访问的。因为上述原因,我们将/index_student.html的放行设置从Spring-Security的配置中取消。

SecurityConfig类中放行设置修改如下:

 .antMatchers(  //匹配路径
 //           "/index_student.html", //学生首页,需要登录后才能查看
             "/js/*",    //当前目录下的所有文件
             "/css/*",   //当前目录下的所有文件
             "/img/**",  //当前目录及子目录下的所有文件
             "/bower_components/**",    //当前目录及子目录下的所有文件
             "/login.html", //放行自定义登录页面路径
             "/register.html", //放行注册页面
             "/register" //放行注册路径
        ).permitAll()  //上述路径全部允许直接访问

2.5 设置标签的缓存

  每个登录用户首页上标签列表中的所有标签信息都是一样的,每个登录的用户都需要查询,如果都从数据库中查询的话,效率会低。我们可以将所有标签都保存在内存中,需要时直接从内存中取,提高效率,这样的做法就算是缓存标签信息。

适合做缓存的数据需要同时满足如下3种特征:

  1. 数据量不大

  2. 经常被访问

  3. 不频繁修改,或对修改不敏感的

  那么我们上次课编写的查询所有标签就应该使用缓存来处理,处理思路是:项目启动后,第一次查询数据库,然后将查询出的所有标签保存在内存中,之后的所有请求都从内存中获得,不再查询数据库,提高效率。

修改TagServiceImpl类中获得所有标签的方法,代码如下:

 //声明一个List保存所有标签,用作缓存
 private List<Tag> tags=new CopyOnWriteArrayList<>();//CopyOnWriteArrayList为线程安全的集合
 
 //从Spring容器中取TagMapper
 @Autowired
 private TagMapper tagMapper;
 
 //全查所有Tags
 @Override //重写方法
 public List<Tag> getTags() {
     //判断tags属性是否是空,如果是空,表示当前查询是第一次查询
     // 1 2 3
     if(tags.isEmpty()){//保证效率
             synchronized (tags){ //考虑线程安全,加锁
                 if(tags.isEmpty()){//保证只有一次
                     List<Tag> list = tagMapper.selectList(null);//查询全部
                     tags.addAll(list);//把所有标签都放到线程安全的集合中
                }
            }
        }
    }
     return tags;
 }

重启服务,感受缓存的效率提高(人眼几乎察觉不到其中的差别,相对之前,刷新后加载的更快了)。

 

posted @ 2021-08-25 22:02  Coder_Cui  阅读(729)  评论(1编辑  收藏  举报