其他分享
首页 > 其他分享> > 65-探花07-小视频功能实现(第7章)

65-探花07-小视频功能实现(第7章)

作者:互联网

第7章 小视频功能实现

今日内容介绍

63572753355

学习目标

1. 小视频功能实现

【目标】

掌握发布小视频功能

【路径】

1:小视频功能介绍

2:小视频技术方案分析

3:FastDFS入门案例

4:小视频功能实现

【讲解】

小视频功能类似于抖音、快手小视频的应用,用户可以上传小视频进行分享,也可以浏览查看别人分享的视频,并且可以对视频评论和点赞操作。

效果:

1568878011614

查看详情:

1568878327076

评论:

1568878353854

点赞:

1568878397710

1.1. 技术方案

对于小视频的功能的开发,核心点就是:存储 + 推荐 + 加载速度

1.2. FastDFS介绍

1.2.1. FastDFS是什么?

FastDFS是分布式文件系统。使用 FastDFS很容易搭建一套高性能的文件服务器集群提供文件上传(发布小视频)、下载(查看小视频)等服务。

1.2.2. 工作原理-面试

FastDFS 架构包括 Tracker server (调度服务器)和 Storage server(存储服务器)。客户端(发起请求)请求 Tracker server 进行文件上传、下载,通过 Tracker server 调度最终由 Storage server 完成文件上传和下载。

Tracker server 作用是负载均衡和调度,通过 Tracker server 在文件上传时可以根据一些策略找到 Storage server 提供文件上传服务。可以将 tracker 称为追踪服务器或调度服务器。

Storage server 作用是文件存储,客户端上传的文件最终存储在 Storage 服务器上,Storage server 没有实现自己的文件系统而是利用操作系统的文件系统来管理文件。可以将storage称为存储服务器。

6bd41a6af1fef5cc89dda7c94208c6a64f3.png

每个 tracker 节点地位平等。收集 Storage 集群的状态。

Storage 分为多个组,每个组之间保存的文件是不同的。每个组内部可以有多个成员,组成员内部保存的内容是一样的,组成员的地位是一致的,没有主从的概念。

1.2.3. 文件的上传

63572963470

1746338-20190925190927228-1815093216.png

客户端上传文件后存储服务器将文件 ID 返回给客户端,此文件 ID 用于以后访问该文件的索引信息。文件索引信息包括:组名,虚拟磁盘路径,数据两级目录,文件名。

1.2.4. 文件的下载

63572990152

img

客户端下载请求到Tracker服务,Tracker返回给客户端storage的信息,客户端根据这些信息进行请求storage获取到文件。

1.3. FastDFS环境搭建

1.3.1. 搭建服务

我们使用docker进行搭建。目前所有的组件全部以docker的形式配置

#进入目录
cd /root/docker-file/fastdfs/
#修改docker-compose.yml(TRACKER_SERVER=实际虚拟机ip:22122)
vi docker-compose.yml
#删除之前的数据目录
cd /data/fastdfs
rm -rf storage/ tracker/
#进入目录
cd /root/docker-file/fastdfs/
#启动
docker-compose up -d
#查看容器
docker ps -a
#查看fastdfs中数据
cd /data/fastdfs/storage/data

1568942782857

FastDFS调度服务器地址:10.10.20.160:22122
FastDFS存储服务器地址:http://10.10.20.160:8888/

1.3.2. FastDFS入门分析

在tanhua-server模块pom文件,引入fastdfs的依赖如下

<dependency>
    <groupId>com.github.tobato</groupId>
    <artifactId>fastdfs-client</artifactId>
    <version>1.26.7</version>
    <exclusions>
        <exclusion>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
        </exclusion>
    </exclusions>
</dependency>

1.3.3. application.yml

在tanhua-server模块application.yml加入以下配置

# ===================================================================
# 分布式文件系统FDFS配置
# ===================================================================
fdfs:
  so-timeout: 1500
  connect-timeout: 600
  #缩略图生成参数
  thumb-image:
    width: 150
    height: 150
  #TrackerList参数,支持多个
  tracker-list: 192.168.137.151:22122
  web-server-url: http://192.168.137.151:8888/

1.3.4. 测试

在tanhua-server模块test包下

package com.tanhua.server.test;

import com.github.tobato.fastdfs.domain.conn.FdfsWebServer;
import com.github.tobato.fastdfs.domain.fdfs.StorePath;
import com.github.tobato.fastdfs.service.FastFileStorageClient;
import com.tanhua.server.TanhuaServerApplication;
import org.apache.commons.io.FileUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.io.File;
import java.io.IOException;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = TanhuaServerApplication.class)
public class FastDFSTest {

    @Autowired
    private FastFileStorageClient client;

    @Autowired
    private FdfsWebServer fdfsWebServer;

    @Test
    public void testUploadFile() throws IOException {
        File file = new File("C:\\1.jpg");
        StorePath storePath = client.uploadFile(FileUtils.openInputStream(file), file.length(), "jpg", null);
        System.out.println(storePath.getFullPath());
        System.out.println(storePath.getPath());

        //获取文件请求地址
        String url = fdfsWebServer.getWebServerUrl()+storePath.getFullPath();
        System.out.println(url);
    }
}

存储服务器:

1.4. 服务消费者-发布小视频

61409284482

1569074668308

1.4.1. SmallVideosController

package com.tanhua.server.controller;

import com.tanhua.server.service.SmallVideosService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;

/**
 * 小视频控制层
 */
@RestController
@RequestMapping("/smallVideos")
public class SmallVideosController {

    @Autowired
    private SmallVideosService smallVideosService;

    /**
     * 发布小视频
     * videoThumbnail:视频封面文件
     * videoFile:视频文件
     */
    @RequestMapping(method = RequestMethod.POST)
    public ResponseEntity saveSmallVideo(MultipartFile videoThumbnail,MultipartFile  videoFile) throws IOException {
        smallVideosService.saveSmallVideo(videoThumbnail,videoFile);
        return ResponseEntity.ok(null);
    }
}

1.4.2. SmallVideosService

package com.tanhua.server.service;

import com.github.tobato.fastdfs.domain.conn.FdfsWebServer;
import com.github.tobato.fastdfs.domain.fdfs.StorePath;
import com.github.tobato.fastdfs.service.FastFileStorageClient;
import com.tanhua.commons.templates.OssTemplate;
import com.tanhua.domain.mongo.Video;
import com.tanhua.dubbo.api.mongo.VideoApi;
import com.tanhua.server.interceptor.UserHolder;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;

/**
 * 小视频业务逻辑层
 */
@Service
@Transactional
public class SmallVideosService {

    @Autowired
    private OssTemplate ossTemplate;

    //客户端文件上传
    @Autowired
    private FastFileStorageClient client;

    //文件访问对象
    @Autowired
    private FdfsWebServer fdfsWebServer;

    @Reference
    private VideoApi videoApi;

    /**
     * 发布小视频
     * videoThumbnail:视频封面文件
     * videoFile:视频文件
     */
    public void saveSmallVideo(MultipartFile videoThumbnail, MultipartFile videoFile) throws IOException {
        //1.将封面文件保存阿里云oss
        String picUrl = ossTemplate.upload(videoThumbnail.getOriginalFilename(), videoThumbnail.getInputStream());
        //2将视频文件保存fastdfs
        //2.1 获取文件后缀
        String suffix = videoFile.getOriginalFilename().substring(videoFile.getOriginalFilename().lastIndexOf(".")+1);
        StorePath storePath = client.uploadFile(videoFile.getInputStream(), videoFile.getSize(), suffix, null);
        String videoUrl = fdfsWebServer.getWebServerUrl()+storePath.getFullPath();
        //3调用服务保存小视频记录 video
        Video video = new Video();
        video.setUserId(UserHolder.getUserId());//当前发布小视频的用户id
        video.setVid(1l);//没有用到
        video.setText("视频文字主题描述");
        video.setPicUrl(picUrl);//视频封面文件
        video.setVideoUrl(videoUrl);//视频路径
        videoApi.saveSmallVideo(video);
    } 
}

1.5. 服务提供者-发布小视频

1.5.1. Video

tanhua-domain模块mongo中创建pojo

package com.tanhua.domain.mongo;

import lombok.Data;
import org.bson.types.ObjectId;
import org.springframework.data.mongodb.core.mapping.Document;

import java.io.Serializable;

@Data
@Document(collection = "video")
public class Video implements Serializable {
    private ObjectId id; //主键id
    private Long userId;
    private Long vid; // 处境id

    private String text; //文字
    private String picUrl; //视频封面文件,URL
    private String videoUrl; //视频文件,URL
    private Long created; //创建时间

    private Integer likeCount=0; //点赞数
    private Integer commentCount=0; //评论数
    private Integer loveCount=0; //喜欢数
}

1.5.2. VideoApi

tanhua-dubbo-interface模块mongo中创建VideoApi

package com.tanhua.dubbo.api.mongo;

import com.tanhua.domain.mongo.Video;

/**
 * 小视频服务接口
 */
public interface VideoApi {
    /**
     * 发布小视频
     * @param video
     */
    void saveSmallVideo(Video video);
}

1.5.3. VideoApiImpl

tanhua-dubbo-service模块mongo中创建VideoApiImpl

package com.tanhua.dubbo.api.mongo;

import com.tanhua.domain.mongo.Video;
import org.apache.dubbo.config.annotation.Service;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;

/**
 * 小视频服务接口实现类
 */
@Service
public class VideoApiImpl implements VideoApi{

    @Autowired
    private MongoTemplate mongoTemplate;

    /**
     * 发布小视频
     * @param video
     */
    @Override
    public void saveSmallVideo(Video video) {
        video.setId(ObjectId.get());//主键id
        video.setCreated(System.currentTimeMillis());//发布小视频时间
        mongoTemplate.insert(video);
    }
}

1.5.4. 测试

app测试

61339694571

postman测试:

61339700995

如果上传视频,会导致异常,是因为请求太大的缘故:

1569075180126

解决:application.yml

Spring:
  servlet:
    multipart:
      max-file-size: 30MB
      max-request-size: 30MB

【小结】

掌握发布小视频功能

2. 小视频列表

【目标】

掌握小视频列表功能

【路径】

1:小视频列表分析

2:小视频列表实现

【讲解】

63573445536

61409714488

2.1. 小视频列表分析

数据库表:video

{
    "_id" : ObjectId("5fa60707ed0ad13fa89925cc"),
    "vid" : NumberLong(1),
    "userId" : NumberLong(1),
    "text" : "我就是我不一样的烟火~",
    "picUrl" : "https://tanhua-dev.oss-cn-zhangjiakou.aliyuncs.com/images/video/video_1.png",
    "videoUrl" : "https://tanhua-dev.oss-cn-zhangjiakou.aliyuncs.com/images/video/1576134125940400.mp4",
    "created" : NumberLong(1604716296066),
    "likeCount" : 0,
    "commentCount" : 0,
    "loveCount" : 0,
    "_class" : "com.tanhua.domain.mongo.Video"
}

2.2. 服务提供者-小视频列表

2.2.1. VideoApi

tanhua-dubbo-interface模块mongo中VideoApi新增方法

    /**
     * 分页查询小视频列表
     * @param page
     * @param pagesize
     * @return
     */
    PageResult<Video> findPageSmallVideo(int page, int pagesize);

2.2.2. VideoApiImpl

tanhua-dubbo-service模块mongo中创建VideoApiImpl新增方法

@Service
public class VideoApiImpl implements VideoApi {

    @Autowired
    private MongoTemplate mongoTemplate;

    @Autowired
    private IdService idService;

    /**
     * 分页查询
     * @param page
     * @param pagesize
     * @return
     */
    @Override
    public PageResult findPage(int page, int pagesize) {
        PageResult pageResult = new PageResult();
        pageResult.setPagesize((long)pagesize);
        pageResult.setPage((long)page);
        Query query = new Query();
        long count = mongoTemplate.count(query, Video.class);
        pageResult.setCounts(count);

        query.with(PageRequest.of(page-1,pagesize)).with(Sort.by(Sort.Order.desc("created")));
        List<Video> videos = mongoTemplate.find(query, Video.class);

        pageResult.setItems(videos);

        return pageResult;
    }
}

2.2.3. VideoVo

package com.tanhua.domain.vo;

import lombok.Data;

import java.io.Serializable;

@Data
public class VideoVo implements Serializable {

    private String id;
    private Long userId;
    private String avatar; //头像
    private String nickname; //昵称
    private String cover; //封面
    private String videoUrl; //视频URL
    private String signature; //签名
    private Integer likeCount; //点赞数量
    private Integer hasLiked; //是否已赞(1是,0否)
    private Integer hasFocus; //是是否关注 (1是,0否)
    private Integer commentCount; //评论数量
}

2.2. 服务消费者-小视频列表

2.2.1. SmallVideosController

    /**
     * 小视频列表- 分页查询
     * page:当前页码 pagesize:每页显示记录数
     */
    @RequestMapping(method = RequestMethod.GET)
    public ResponseEntity findPageSmallVideo(@RequestParam(defaultValue = "1") int page,
                                             @RequestParam(defaultValue = "10")  int pagesize){
        PageResult<VideoVo> pageResult = smallVideosService.findPageSmallVideo(page,pagesize);
        return ResponseEntity.ok(pageResult);
    }

2.2.2. SmallVideosService

    /**
     * 小视频列表- 分页查询
     * page:当前页码 pagesize:每页显示记录数
     */
    public PageResult<VideoVo> findPageSmallVideo(int page, int pagesize) {
        Long userId = UserHolder.getUserId();//当前用户id
        //1 分页查询小视频列表
        PageResult<Video> videoPageResult =  videoApi.findPageSmallVideo(page,pagesize);
        //2.如果分页列表数据为空 则设置默认分页数据(10条)
        if(videoPageResult == null || CollectionUtils.isEmpty(videoPageResult.getItems())){
            return new PageResult<>(10l,10l,1l,1l,null);
        }
        //3 循环遍历 小视频列表 查询tb_userInfo
        List<VideoVo> videoVoList = new ArrayList<>();
        for (Video video : videoPageResult.getItems()) {
            VideoVo videoVo = new VideoVo();
            UserInfo userInfo = userInfoApi.findUserInfoByUserId(video.getUserId());
            //将userInfo数据copy到videoVo
            BeanUtils.copyProperties(userInfo,videoVo);//头像 昵称
            //将video数据copy到videoVo
            BeanUtils.copyProperties(video,videoVo);//userId videoUrl likeCount commentCount
            videoVo.setId(video.getId().toHexString());//视频主键id
            videoVo.setCover(video.getPicUrl());//视频封面
            videoVo.setSignature(video.getText());//签名
            videoVo.setHasLiked(0);//是否点赞 1是 0否
            videoVo.setHasFocus(0);//是否关注 1是0否
            videoVoList.add(videoVo);
        }

        //4.将数据封装PageResult<VideoVo>
        PageResult<VideoVo> voPageResult = new PageResult<>();
        BeanUtils.copyProperties(videoPageResult,voPageResult); //copy counts pagesize pages page
        voPageResult.setItems(videoVoList);//设置返回集合Vo
        return voPageResult;
    }

2.2.3. 测试

package com.tanhua.dubbo.test;

import com.tanhua.domain.mongo.Video;
import com.tanhua.dubbo.utils.IdService;
import org.bson.types.ObjectId;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class VideoTest {

    @Autowired
    private MongoTemplate mongoTemplate;

    @Autowired
    private IdService idService;

    @Test
    public void testSaveVideos() {

        for (int i = 1; i <=20; i++) {
            Video video = new Video();
            video.setPicUrl("https://tanhua-dev.oss-cn-zhangjiakou.aliyuncs.com/images/video/video_"+i+".png");
            video.setVideoUrl("https://tanhua-dev.oss-cn-zhangjiakou.aliyuncs.com/images/video/1576134125940400.mp4");
            video.setId(ObjectId.get());
            video.setVid(idService.getNextId("video"));
            video.setUserId((long)i);
            video.setCreated(System.currentTimeMillis());
            video.setText("我就是我不一样的烟火~");
            mongoTemplate.save(video);
        }
    }
}

app测试

61340423137

postman测试

61340361450

【小结】

掌握小视频列表功能

3. 关注用户

【目标】

掌握关注用户功能

【路径】

1:关注用户分析

2:关注用户实现

【讲解】

关注用户是关注小视频发布的作者,这样我们后面计算推荐时,关注的用户将权重更重一些。

关注用户

3.1. 服务消费者-关注用户

63573635237

关注用户

61410013430

取消关注用户

61410023717

3.1.1. VideoController


    /**
     * 视频关注
     */
    @RequestMapping(value = "/{uid}/userFocus",method = RequestMethod.POST)
    public ResponseEntity userFocus(@PathVariable("uid") Long videoUserId){
        smallVideosService.userFocus(videoUserId);
        return ResponseEntity.ok(null);
    }



    /**
     * 视频取消关注
     */
    @RequestMapping(value = "/{uid}/userUnFocus",method = RequestMethod.POST)
    public ResponseEntity userUnFocus(@PathVariable("uid") Long videoUserId){
        smallVideosService.userUnFocus(videoUserId);
        return ResponseEntity.ok(null);
    }

3.1.2. VideoService

/**
     * 小视频列表- 分页查询
     * page:当前页码 pagesize:每页显示记录数
     */
    public PageResult<VideoVo> findPageSmallVideo(int page, int pagesize) {
        Long userId = UserHolder.getUserId();//当前用户id
        //1 分页查询小视频列表
        PageResult<Video> videoPageResult =  videoApi.findPageSmallVideo(page,pagesize);
        //2.如果分页列表数据为空 则设置默认分页数据(10条)
        if(videoPageResult == null || CollectionUtils.isEmpty(videoPageResult.getItems())){
            return new PageResult<>(10l,10l,1l,1l,null);
        }
        //3 循环遍历 小视频列表 查询tb_userInfo
        List<VideoVo> videoVoList = new ArrayList<>();
        for (Video video : videoPageResult.getItems()) {
            VideoVo videoVo = new VideoVo();
            UserInfo userInfo = userInfoApi.findUserInfoByUserId(video.getUserId());
            //将userInfo数据copy到videoVo
            BeanUtils.copyProperties(userInfo,videoVo);//头像 昵称
            //将video数据copy到videoVo
            BeanUtils.copyProperties(video,videoVo);//userId videoUrl likeCount commentCount
            videoVo.setId(video.getId().toHexString());//视频主键id
            videoVo.setCover(video.getPicUrl());//视频封面
            videoVo.setSignature(video.getText());//签名
            videoVo.setHasLiked(0);//是否点赞 1是 0否
            String key="follow_user_"+userId+"_"+video.getUserId();
            if(StringUtils.isEmpty(redisTemplate.opsForValue().get(key))) {
                videoVo.setHasFocus(0);//是否关注 1是0否
            }else{
                videoVo.setHasFocus(1);//是否关注 1是0否
            }
            videoVoList.add(videoVo);
        }

        //4.将数据封装PageResult<VideoVo>
        PageResult<VideoVo> voPageResult = new PageResult<>();
        BeanUtils.copyProperties(videoPageResult,voPageResult); //copy counts pagesize pages page
        voPageResult.setItems(videoVoList);//设置返回集合Vo
        return voPageResult;
    }
    /**
     * 视频关注
     */
    public void userFocus(Long videoUserId) {
        Long userId = UserHolder.getUserId();
        //1往follow_user关注表插入记录
        followUserApi.userFocus(userId,videoUserId);
        //2往redis写入关注记录
        String key="follow_user_"+userId+"_"+videoUserId;
        redisTemplate.opsForValue().set(key,"1");
    }


    /**
     * 视频取消关注
     */
    public void userUnFocus(Long videoUserId) {
        Long userId = UserHolder.getUserId();
        //1从follow_user关注表删除记录
        followUserApi.userUnFocus(userId,videoUserId);
        //2从redis删除关注记录
        String key="follow_user_"+userId+"_"+videoUserId;
        redisTemplate.delete(key);
    }

3.2. 服务提供者-关注用户

3.2.1. FollowUser

package com.tanhua.domain.mongo;

import lombok.Data;
import org.bson.types.ObjectId;
import org.springframework.data.mongodb.core.mapping.Document;

import java.io.Serializable;
 
@Data
@Document(collection = "follow_user")
public class FollowUser implements Serializable {

    private ObjectId id; //主键id
    private Long userId; //用户id
    private Long followUserId; //关注的用户id
    private Long created; //关注时间
}

3.2.2. FollowUserApi

package com.tanhua.dubbo.api.mongo;

/**
 * 关注用户服务接口
 */
public interface FollowUserApi {
    /**
     * 视频关注
     */
    void userFocus(Long userId, Long videoUserId);

    /**
     * 视频取消关注
     * @param userId
     * @param videoUserId
     */
    void userUnFocus(Long userId, Long videoUserId);
}

3.2.3. FollowUserApiImpl

package com.tanhua.dubbo.api.mongo;

import com.tanhua.domain.mongo.FollowUser;
import org.apache.dubbo.config.annotation.Service;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;

/**
 * 关注用户服务接口实现类
 */
@Service
public class FollowUserApiImpl implements FollowUserApi{

    @Autowired
    private MongoTemplate mongoTemplate;

    /**
     * 视频关注
     */
    @Override
    public void userFocus(Long userId, Long videoUserId) {
        FollowUser followUser = new FollowUser();
        followUser.setId(ObjectId.get());//主键id
        followUser.setCreated(System.currentTimeMillis());//关注时间
        followUser.setUserId(userId);//当前用户id
        followUser.setFollowUserId(videoUserId);;//被关注的用户id
        mongoTemplate.insert(followUser);
    }

    /**
     * 视频取消关注
     * @param userId
     * @param videoUserId
     */
    @Override
    public void userUnFocus(Long userId, Long videoUserId) {
        Query query = new Query();
        query.addCriteria(Criteria.where("userId").is(userId).and("followUserId").is(videoUserId));
        mongoTemplate.remove(query,FollowUser.class);
    }
}

3.2.4. 测试

61340698754

【小结】

掌握关注用户功能

4. 缓存

【目标】

掌握SpringCache使用

【路径】

1:SpringCache介绍

2:SpringCache入门案例

3:视频列表缓存处理

【讲解】

实现缓存逻辑有2种方式:

  1. 每个接口单独控制缓存逻辑
  2. 统一控制缓存逻辑

Spring从3.1开始定义了org.springframework.cache.Cache和org.springframework.cache.CacheManager接口来统一不同的缓存技术;并支持使用JCache(JSR-107)注解简化我们开发;

4.1. 重要概念

名称 解释
Cache 缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、ConcurrentMapCache等
CacheManager 缓存管理器,管理各种缓存(cache)组件
@Cacheable 主要针对方法配置,能够根据方法的请求参数对其进行缓存
@CacheEvict 清空缓存
@CachePut 保证方法被调用,又希望结果被缓存。 与@Cacheable区别在于是否每次都调用方法,常用于更新
@EnableCaching 开启基于注解的缓存
keyGenerator 缓存数据时key生成策略
serialize 缓存数据时value序列化策略
@CacheConfig 统一配置本类的缓存注解的属性

4.2. SPEL表达式

Spring Cache提供了一些供我们使用的SpEL上下文数据,下表直接摘自Spring官方文档:

名称 位置 描述 示例
methodName root对象 当前被调用的方法名 #root.methodname
method root对象 当前被调用的方法 #root.method.name
target root对象 当前被调用的目标对象实例 #root.target
targetClass root对象 当前被调用的目标对象的类 #root.targetClass
args root对象 当前被调用的方法的参数列表 #root.args[0]
caches root对象 当前方法调用使用的缓存列表 #root.caches[0].name
Argument Name 执行上下文 当前被调用的方法的参数,如findArtisan(Artisan artisan),可以通过#artsian.id获得参数 #artsian.id
result 执行上下文 方法执行后的返回值(仅当方法执行后的判断有效,如 unless cacheEvict的beforeInvocation=false) #result

注意:

1.当我们要使用root对象的属性作为key时我们也可以将“#root”省略,因为Spring默认使用的就是root对象的属性。 如

@Cacheable(key = "targetClass + methodName +#p0")

2.使用方法参数时我们可以直接使用“#参数名”或者“#p参数index”。 如:

@Cacheable(value="users", key="#id")
@Cacheable(value="users", key="#p0")

SpEL提供了多种运算符

类型 运算符
关系 <,>,<=,>=,==,!=,lt,gt,le,ge,eq,ne
算术 +,- ,* ,/,%,^
逻辑 &&,||,!,and,or,not,between,instanceof
条件 ?: (ternary),?: (elvis)
正则表达式 matches
其他类型 ?.,?[…],![…],[1],$[…]

以上的知识点适合你遗忘的时候来查阅,下面正式进入学习!

4.3. 入门案例

4.3.1. 导入依赖

导入SpringDataRedis的依赖,并在application.yml中配置 (略)

4.3.2. 开启缓存支持

然后在启动类注解@EnableCaching开启缓存

@SpringBootApplication
@EnableCaching  //开启缓存
public class DemoApplication{
 
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

4.3.3. 缓存@Cacheable

@Cacheable注解会先查询是否已经有缓存,有会使用缓存,没有则会执行方法并缓存。

package com.tanhua.server.test.cache;

import com.tanhua.domain.db.User;
import com.tanhua.domain.db.UserInfo;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
public class TestUserInfoService {

    /**
     * 查询全部用户
     */
    @Cacheable(value="users")
    public List<UserInfo> findAll() {
        System.out.println("从数据库获取数据");
        //模拟从数据库查询
        List<UserInfo> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            UserInfo info = new UserInfo();
            info.setId((long)i);
            info.setNickname("itcast"+i);
            list.add(info);
        }
        return list;
    }

    /**
     * key : user:2
     *  @Cacheable:
     *      value : 缓存的名称
     *      key:支持spel,可以获取方法上的参数
     */
    @Cacheable(value="user",key = "#userId")
    public UserInfo findById(Long userId) {
        System.out.println("从数据库获取数据");
        UserInfo info = new UserInfo();
        info.setNickname("abc");
        info.setId(userId);
        return info;
    }

}

此处的value是必需的,它指定了你的缓存存放在哪块命名空间。

此处的key是使用的spEL表达式,此处的UserInfo实体类一定要实现序列化public class UserInfo implements Serializable,否则会报java.io.NotSerializableException异常。

到这里,你已经可以运行程序检验缓存功能是否实现。

4.3.4. 清除@CacheEvict

@CachEvict 的作用 主要针对方法配置,能够根据一定的条件对缓存进行清空 。

package com.tanhua.server.test.cache;

import com.tanhua.domain.db.User;
import com.tanhua.domain.db.UserInfo;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
public class TestUserInfoService {
    
    .............
        
     /**
     * 保存用户
     */
    @CacheEvict(value = "users",allEntries=true)
    public void save(UserInfo userInfo) {
        System.out.println("保存用户到数据库中");
    }
    
    @CacheEvict(value="user",key = "#user.id")
    public void update(UserInfo user) {
        System.out.println("更新");
    }
    
}

4.3.5. 测试

package com.tanhua.server.test.cache;

import com.tanhua.domain.db.UserInfo;
import com.tanhua.server.TanhuaServerApplication;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = TanhuaServerApplication.class)
public class CacheTest {

    @Autowired
    private TestUserInfoService userInfoService;

    @Test
    public void testFindAll() {
        List<UserInfo> list = userInfoService.findAll();
        for (UserInfo info : list) {
            System.out.println(info);
        }
    }

    @Test
    public  void testSave() {
        UserInfo userInfo = new UserInfo();
        userInfoService.save(userInfo);
    }

    @Test
    public  void testFind() {
        UserInfo info = userInfoService.findById(1l);
        System.out.println(info);
    }

    @Test
    public  void testUpdate() {
        UserInfo info = new UserInfo();
        info.setId(1l);
        info.setNickname("def");
        userInfoService.update(info);
    }
}

4.4. 视频列表缓存处理

修改VideoService,分页列表存入缓存,发布视频删除缓存

/**
  * 分页查询
  *
  * @param page
  * @param pagesize
  * @return
  */
@Cacheable(value = "videoList", key = "#page + '_' +  #pagesize")
public PageResult<VideoVo> findPage(int page, int pagesize) {
    log.info("****************小视频列表查询*******************");
    //1、调用api分页查询数据列表  PageResult<Video>
    PageResult result = videoApi.findPage(page, pagesize);
    //2、获取分页数据中数据列表   List<Video>
    List<Video> items = (List<Video>) result.getItems();
    //3、循环遍历数据集合,一个video构造一个vo
    List<VideoVo> list = new ArrayList<>();
    if (items != null) {
        for (Video item : items) {
            UserInfo info = userInfoApi.findByUserId(item.getUserId());
            VideoVo vo = new VideoVo();
            BeanUtils.copyProperties(info, vo);
            BeanUtils.copyProperties(item, vo);
            vo.setCover(item.getPicUrl());
            vo.setId(item.getId().toHexString());
            if (StringUtils.isNotEmpty(item.getText())) {
                vo.setSignature(item.getText());//签名
            } else {
                vo.setSignature("默认签名");//签名
            }
            //从redis中获取操作记录,如果存在返回1,不存在返回0
            String key = "followUser_" + UserHolder.getUserId() + "_" + item.getUserId();
            if (redisTemplate.hasKey(key)) {
                vo.setHasFocus(1); //TODO 是否关注
            } else {
                vo.setHasFocus(0); //TODO 是否关注
            }
            vo.setHasLiked(0); //是否点赞
            list.add(vo);
        }
    }
    //4、存入pageResult中
    result.setItems(list);
    //5、构造返回值
    return result;
}

/**
  * 发布小视频
  *
  * @param videoThumbnail
  * @param videoFile
  */
@CacheEvict(value = "videoList", allEntries = true)
public void post(MultipartFile videoThumbnail, MultipartFile videoFile) throws IOException {
    log.info("****************发布小视频*******************");
    //获取当前登录用户的用户id
    Long userId = UserHolder.getUserId();
    // 上传视频图片,获取对就看url
    String picUrl = ossTemplate.upload(videoThumbnail.getOriginalFilename(), videoThumbnail.getInputStream());
    // 获取上传的视频名称
    String videoFileOriginalFilename = videoFile.getOriginalFilename();
    // 截取文件后缀名
    String suffix = videoFileOriginalFilename.substring(videoFileOriginalFilename.lastIndexOf(".") + 1);
    // 上传视频到fdfs
    StorePath storePath = fastFileStorageClient.uploadFile(videoFile.getInputStream(), videoFile.getSize(), suffix, null);
    // 获取视频的完整路径
    String videoUrl = fdfsWebServer.getWebServerUrl() + storePath.getFullPath();
    // 构建Video对象
    Video video = new Video();
    video.setPicUrl(picUrl);
    video.setVideoUrl(videoUrl);
    video.setText("百鸟朝凤");
    video.setUserId(userId);
    // 调用api保存
    log.info("video::::" + video.toString());
    videoApi.save(video);
}

【小结】

掌握Spring Cache使用

1.启动类注解:@EnableCaching //开启缓存

2.spring-data-redis起步依赖引入pom 与 配置

缓存(查询)

@Cacheable(value="users")

@Cacheable(value="user",key = "#userId")

清除缓存(非查询-更新 插入 删除)

@CacheEvict(value = "users",allEntries=true)

@CacheEvict(value="user",key = "#user.id")

总结

1.了解小视频相关技术点

2.FastDFS使用、底层原理(面试前背)

3.重点掌握:发布小视频、小视频列表

4.重点掌握:掌握spring cache 使用

​ a.在启动类开启SpringCache @EnableCaching

​ b.@Cacheable:缓存(value+key) @CacheEvict:清除缓存(allEntries = true将value下所有数据清除)

​ c.在工程中引入spring-boot-starter-data-redis起步依赖


5.关注用户

作业


  1. ↩︎

标签:缓存,07,小视频,video,65,org,import,public
来源: https://www.cnblogs.com/ofanimon/p/16182505.html