ProjectDay04

续 Spring验证框架

上次课我们已经将实体类验证规则编写在属性上了

下面要启动SpringValidation框架的验证了

控制器中启动验证功能

我们的注册功能的控制器是SystemController

打开这个类,找到注册方法

修改代码如下

@PostMapping("/register")
public String register(
        // 我们可以通过添加@Validated注解启动SpringValidation的验证
        // 一旦在控制器方法参数前添加@Validated,表示控制器方法运行前
        // 先由SpringValidation框架按照RegisterVO类中编写的验证规则进行验证
        @Validated RegisterVO registerVO,
        // 下面的参数就是SpringValidation框架验证的结果对象
        // 对registerVO对象属性的验证信息会自动保存到result对象中
        BindingResult result
) {
    // 使用@Slf4j提供的log对象,将registerVO信息输出到日志
    log.debug("接收到表单信息:{}", registerVO);
    // 判断result对象中有没有验证失败的信息
    if(result.hasErrors()){
        // 进入if表示registerVO对象中有属性没有通过验证
        // 下面来获取这个信息(信息就是验证未通过的message的值)
        String msg=result.getFieldError().getDefaultMessage();
        return msg;
    }
    try {
        userService.registerStudent(registerVO);
        return "ok";
    } catch (ServiceException e) {
        // 将错误信息输出到日志的error级别
        log.error("注册失败", e);
        // 控制器返回的字符串就是业务逻辑层中的错误信息文本
        return e.getMessage();
    }
}

下面可以进行测试

但是测试之前,要先删除html页面中的html5的表单验证代码

建议在测试成功之后,回复删除掉的html5验证代码

开发显示首页标签

首页标签效果

在这里插入图片描述

显示首页标签的开发流程

在这里插入图片描述

显示首页标签列表的开发流程

1.学生访问首页,在页面加载完毕时利用Vue调用axios请求

2.axios请求到TagController控制器中的方法

3.控制器方法调用TagService业务逻辑层中获得所有标签的方法

4.TagService会使用Mapper连接数据库查询所有标签并返回给控制器

5.控制器得到所有标签后显示在页面上

之前完成的注册时有表单提交的

下面要完成的查询所有标签的功能是没有表单的,普通的get请求

实际开发中,程序员一般会从底层编写代码

数据访问层->业务逻辑层->控制层

本次功能查询的所有标签的数据访问层方法,已经有MybatisPlus框架提供了,无需我们编写,所以直接从业务逻辑层开始

开发业务逻辑层

开发业务逻辑层先编写接口

查询所有表单明显是ITagService接口,添加方法如下

public interface ITagService extends IService<Tag> {

    // 定义全查所有标签的业务逻辑层方法
    List<Tag> getTags();

}

TagServiceImpl实现类编写代码如下

@Service
public class TagServiceImpl extends ServiceImpl<TagMapper, Tag> implements ITagService {

    @Autowired
    private TagMapper tagMapper;
    @Override
    public List<Tag> getTags() {
        // 调用查询所有标签的方法
        List<Tag> tags=tagMapper.selectList(null);
        // 千万别忘了返回tags
        return tags;
    }
}

编写控制层代码

TagController类编写调用业务逻辑层方法,获得所有标签

并将所有标签返回给前端页面

@RestController
@RequestMapping("/v1/tags")
public class TagController {

    // 添加业务逻辑层的依赖注入
    @Autowired
    private ITagService tagService;

    // @GetMapping("")写法的含义就是只使用类上定义的路径作为当前控制方法的路径
    // localhost:8080/v1/tags
    @GetMapping("")
    public List<Tag> tags(){
        List<Tag> tags=tagService.getTags();
        return tags;
    }
}

重启portal项目

我们想测试一下这个控制器方法是否能够正确运行,返回所有标签

我们可以打开浏览器,在浏览器地址栏直接输入localhost:8080/v1/tags

如果看到所有标签的json格式返回值表示一切正确

我们将这样的操作称之为"浏览器同步测试"

特别适合测试支持Get请求的控制器方法,今后我们可以多使用这种方法测试我们的java代码,尤其是报错的时候,可以定位错误出现的位置

显示结果如下

在这里插入图片描述

编写Vue绑定和Js代码

这次我们的页面换为了index_student.html

要想让这个页面显示出所有标签

我们要按下面步骤依次完成

1.添加axios的引用

  <!--引入CDN服务器的框架文件-->
  <script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.min.js"></script>   
</head>

2.在页面尾部添加js引用

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

3.index_student.html的163行附近进行Vue的绑定操作

<!--引入标签的导航栏-->
<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>

重启服务测试

发现如果不登录直接访问学生首页,有大面积空白,因为学生首页放行,但是/v1/tags没有放行导致的

必须在登录页登录后,再访问学生首页才正常

要解决这个问题我们采用取消学生首页放行的办法

原因有二

1:显示所有标签的控制器方法没有放行

2:更重要的是今后我们实现显示当前登录用户所有问题时,必须知道当前登录用户是谁

打开SecurityConfig类

注释或删除学生首页放行的行

.antMatchers(// 指定路径
        //"/index_student.html",
        "/css/*",
        "/js/*",
        "/img/**",
        "/bower_components/**",
        "/login.html",
        "/register.html",  // 放行注册页
        "/register"        // 放行注册控制器路径
)

再次重启服务,访问学生首页就必须先登录了

实现标签的缓存

什么是缓存

缓存指计算机(服务器)内存保存的数据,一般用于支持快速访问

简单来说,就是将一些经常被使用的数据,保存在内存中,以提高访问效率和速度

为什么需要缓存

因为如果一个数据保存在数据库中,经常被使用时反复连接数据库,效率低

如果将这个数据保存在内存中,在使用时直接从内存获取,提高访问效率,所以使用缓存来保存这些经常被访问的数据

缓存的使用情景

什么时候使用缓存

要结合我们计算机内存的优缺点

内存

优点:快

缺点:

  • 相对硬盘容量小,不能将硬盘所有数据都保存,一旦内存空间不足,运行明显变慢
  • 内存中的数据都是"易失"的,一旦断电,信息就没了

综合内存优缺点

行业中,一般满足下面3中情况时,这样的数据建议保存在缓存中

  • 数据量不能太大
  • 数据经常被访问或使用
  • 数据库缓存的数据不应该被频繁修改,或对修改并不敏感

实现缓存全部标签列表

经过分析,我们的标签列表tags就适合保存在缓存中

以提高访问效率

下面我们就修改TagServiceImpl类,实现标签列表的缓存

// 声明缓存对象
private List<Tag> tags=new CopyOnWriteArrayList<>();
// tags属性用于充当保存所有标签的缓存对象
// 因为TagServiceImpl默认是单例作用域的,所以tags属性也只有一份
// 在今后任何方法的访问中,不会出现新的对象,重复占用内存
// CopyOnWriteArrayList是jdk1.8开始支持的线程安全的集合对象
@Override
public List<Tag> getTags() {
    // 要获得所有标签
    // 先判断缓存对象是不是没有元素
    //     3
    if(tags.isEmpty()){
        synchronized (tags) {
            //     2
            tags.clear();
            List<Tag> list = tagMapper.selectList(null);
            tags.addAll(list);
            System.out.println("tags加载完成");
        }
        //  1
    }
    // 千万别忘了返回tags
    return tags;
}

开发显示学生问题列表

在这里插入图片描述

显示学生问题列表业务流程

在这里插入图片描述

怎么去查询一个用户的所有问题呢?

我们可以先编写一个sql语句

select * from question
where user_id=11 and delete_status=0

sql语句比较简单,而且只有在学生首页时需要查询

所以可以使用QueryWrapper来完成

所以直接开发业务逻辑层

开发业务逻辑层

先写接口方法

IQuestionService添加方法如下

public interface IQuestionService extends IService<Question> {

    // 根据登录用户的用户名查询问题列表
    List<Question> getMyQuestions(String username);
}

转到QuestionServiceImpl类编写实现代码如下

@Service
public class QuestionServiceImpl extends ServiceImpl<QuestionMapper, Question> implements IQuestionService {

    @Autowired
    private QuestionMapper questionMapper;
    @Autowired
    private UserMapper userMapper;

    @Override
    public List<Question> getMyQuestions(String username) {
        // 先根据用户名查询用户信息(用户对象)
        User user=userMapper.findUserByUsername(username);
        // 再使用QueryWrapper完成该用户的问题列表的查询
        QueryWrapper<Question> query=new QueryWrapper<>();
        query.eq("user_id",user.getId());
        query.eq("delete_status",0);
        query.orderByDesc("createtime");
        // 执行查询
        List<Question> list=questionMapper.selectList(query);
        // 别忘了返回list
        return list;
    }
}

编写控制层代码

QuestionController类添加方法

@RestController
@RequestMapping("/v1/questions")
public class QuestionController {

    @Autowired
    private IQuestionService questionService;

    // localhost:8080/v1/questions/my
    @GetMapping("/my")
    public List<Question> my(
            //@AuthenticationPrincipal注解效果
            // 从Spring-Security框架获得当前登录用户的UserDetails对象
            // 赋值给注解之后的参数
            @AuthenticationPrincipal UserDetails user
            ){
        List<Question> questions=questionService
                        .getMyQuestions(user.getUsername());
        // 返回业务逻辑层查询出的所有问题列表
        return questions;

    }
}

重启服务,发送同步请求

localhost:8080/v1/questions/my

一定要求我们登录才能访问,注意,登录的必须是在数据库中有问题的用户

例如st2,xiaom,不要登录自己注册的用户,因为没有问题数据

Vue绑定和js代码

要想将我们查询出的问题列表信息显示在页面上,还需要编写对应的VUE绑定

首先在页面尾部添加引用

<script src="js/utils.js"></script>
<script src="js/tags_nav.js"></script>
<!--  ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓  -->
<script src="js/index.js"></script>
</body>
</html>

然后开始编写html代码的Vue绑定

index_student.html的182行附近

<div id="questionsApp">
<div class="row" style="display: none">
  <div class="alert alert-warning w-100" role="alert">
    抱歉您还没有提问内容, <a href="question/create.html" class="alert-link">您可以点击此处提问</a>,或者点击标签查看其它问答
  </div>
</div>
    		
<div class="media bg-white m-2 p-3"
    v-for="question in questions">
    <!--  ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑  -->
  <div class="media-body w-50">
    <div class="row">
      <div class="col-md-12 col-lg-2">
        <span class="badge badge-pill badge-warning" style="display: none">未回复</span>
        <span class="badge badge-pill badge-info" style="display: none">已回复</span>
        <span class="badge badge-pill badge-success">已解决</span>
      </div>
      <div class="col-md-12 col-lg-10">
        <h5 class="mt-0 mb-1 text-truncate">
          <a class="text-dark"
             href="question/detail.html"
             v-text="question.title">
              <!--  ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑  -->
            eclipse 如何导入项目?
          </a>
        </h5>
      </div>
    </div>

    <div class="font-weight-light text-truncate text-wrap text-justify mb-2" style="height: 70px;">
      <p v-html="question.content">
          <!--  ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑  -->
        eclipse 如何导入项目?
      </p>
    </div>
    <div class="row">
      <div class="col-12 mt-1 text-info">
        <i class="fa fa-tags" aria-hidden="true"></i>
        <a class="text-info badge badge-pill bg-light" href="tag/tag_question.html"><small >Java基础 &nbsp;</small></a>
      </div>
    </div>
    <div class="row">
      <div class="col-12 text-right">
        <div class="list-inline mb-1 ">
          <small class="list-inline-item"
            v-text="question.userNickName">风继续吹</small>
            <!--  ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑  -->
          <small class="list-inline-item">
            <span v-text="question.pageViews">12</span>
              <!--  ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑  -->
            浏览</small>
          <small class="list-inline-item" >13分钟前</small>
        </div>
      </div>
    </div>

  </div>
  <!-- / class="media-body"-->
  <img src="img/tags/example0.jpg"  class="ml-3 border img-fluid rounded" alt="" width="208" height="116">
</div>

因为js代码都是写好的

所以直接重启服务,就能显示所有问题列表内容了!

显示持续时间

所谓持续时间,就是问题发布时间距离现在的时间差

页面上固定值"13分钟前"

我们要将这个值修改为真实的时间

我们设计将持续时间的显示分为4个分段

  • 时间差不足1分钟,显示"刚刚"
  • 时间差不足1小时,显示"XX分钟前"
  • 时间差在1天以内,显示"XX小时前"
  • 时间差在1天以上,显示"XX天前"

实现的逻辑就是用当前时间减去问题发布的时间

在判断这个时间差的范围,代码已经在index.js文件中编写完毕

index_student.html页面的226行附近,添加

<small class="list-inline-item"
  v-text="question.duration">13分钟前</small>

重启服务就能显示持续时间了

显示当前问题关联的标签

在这里插入图片描述

上面的图片就是一个问题关联了多个标签的显示效果

先明确数据库中标签和问题的关系

在这里插入图片描述

因为question和tag是多对多的关系,一旦需要根据问题id查询该问题对应的标签就需要一个3表的关联查询

这个3表关联查询,性能差,程序员不愿意写,带来各种问题

所以我们希望避免关联查询

实际开发中,我们可以将经常需要关联查询的内容直接保存在当前数据表中,在需要时直接查询当前表,避免关联查询

但是我们要知道,这样做会出现数据库表的数据冗余,占用更多数据库空间同时增加数据库维护难度,开发中需要斟酌利弊才能确定的

为了实现一个问题对象中包含多个标签的表示

我们修改Question实体类,添加一个标签集合的属性

/**
 * 当前问题包含的所有标签的集合
 */
// 声明当前属性不对应数据库表中的任何列
@TableField(exist = false)
private List<Tag> tags;

下面我们就要完成将tagNames属性中例如

"Java基础,Java SE,面试题"的字符串

转换为一个List<Tag>类型的对象,其中包含

  • Java基础标签对象
  • Java SE标签对象
  • 面试题标签对象

具体思路在文档末尾

按照上面思路,我们需要先到TagService业务逻辑层中,声明包含所有标签的Map类型缓存对象

先声明接口中的方法

ITagService添加方法如下

// 定义返回包含所有标签对象Map的方法
Map<String,Tag> getTagMap();

TagServiceImpl类实现代码如下

添加tagMap缓存对象

@Service
public class TagServiceImpl extends ServiceImpl<TagMapper, Tag> implements ITagService {

    @Autowired
    private TagMapper tagMapper;

    // 声明缓存对象
    private List<Tag> tags=new CopyOnWriteArrayList<>();
    // tags属性用于充当保存所有标签的缓存对象
    // 因为TagServiceImpl默认是单例作用域的,所以tags属性也只有一份
    // 在今后任何方法的访问中,不会出现新的对象,重复占用内存
    // CopyOnWriteArrayList是jdk1.8开始支持的线程安全的集合对象
    private Map<String,Tag> tagMap=new ConcurrentHashMap<>();
    // ConcurrentHashMap是一个线程安全的Map集合类型对象,从jdk1.8开始

    @Override
    public List<Tag> getTags() {
        // 要获得所有标签
        // 先判断缓存对象是不是没有元素
        //     3
        if(tags.isEmpty()){
            synchronized (tags) {
                //     2
                tags.clear();
                tagMap.clear();
                List<Tag> list = tagMapper.selectList(null);
                tags.addAll(list);
                // 遍历list对tagMap赋值
                for(Tag t:list){
                    tagMap.put(t.getName(),t);
                }
                System.out.println("tags加载完成");
            }
            //  1
        }
        // 千万别忘了返回tags
        return tags;
    }

    @Override
    public Map<String, Tag> getTagMap() {
        // 判断tagMap是不是empty
        if(tagMap.isEmpty()){
            // 如果tagMap是empty,证明getTags方法也没有运行过
            // 调用getTags方法为tagMap赋值即可
            getTags();
        }
        // 千万别忘了返回
        return tagMap;
    }
}

有了上面提供的缓存的Map对象

我们就可以在Question的业务逻辑层中进行转了

在QuestionServiceImpl类中编写一个转换方法

代码如下

// 编写TagNames转换为List<Tag>的方法
// 需要获得ITagService业务逻辑层中的缓存Map
@Autowired
private ITagService tagService;
private List<Tag> tagNamesToTags(String tagNames){
    //tagNames:"Java基础,Java SE,面试题"
    String[] names=tagNames.split(",");
    // names:{"Java基础","Java SE","面试题"}
    Map<String,Tag> tagMap=tagService.getTagMap();
    // 循环遍历之前声明一个用于接收返回值的List
    List<Tag> tags=new ArrayList<>();
    // 遍历当前的names数组
    for(String name:names){
        // 根据标签名称获得标签对象
        Tag t=tagMap.get(name);
        // 将获得的标签对象赋值到tags中
        tags.add(t);
    }
    // 返回tags
    return tags;

}

英文

Principal:当事人

tagNames转换为List<Tag>的思路

以106号问题为例

tagNames的值为:“Java基础,Java SE,面试题”

我们先利用String类的方法split将这个字符串拆分为String的数组

String[] names=tagNames.split(",");

names:{“Java基础”,“Java SE”,“面试题”}

下面最重要的环节就是怎么将"Java基础"这样的标签名称转换为对应的标签对象

我们知道通过一个名称寻找对应对象最快的数据结构是Map

所以为了进一步提高从内存中通过标签名称获得标签对象的效率

我们先在TagServiceImpl类中再定义一个包含所有标签对象的Map集合

以便我们在这个转换过程中通过标签名称获得标签对象

假设这个缓存的Map对象为tagMap

获得对应标签对象的代码为

Tag t=tagMap.get(names[i]);

最后将这个t对象保存在一个List<Tag>中即可完成转换

list.add(t);

假设缓存对象名称为tags

for(Tag t:tags){
 	if(t.getName.equals("Java基础")){
 		// 进这个if的t对象就是"Java基础"对应的标签对象
 	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值