1. 我的动态(查询思路)
2. 圈子的互动(点赞,喜欢,评论)a. 数据表:quanzi_comment(commentType记录操作类型)b. 点赞,喜欢,评论(保存记录)c. 取消点赞,取消喜欢(删除记录)d. 为了保证效率(结合redis缓存,互动数量记录到动态表)
查询别人来访了我的主页的信息,其他用户在浏览我的主页时,需要记录访客数据。访客在一天内每个用户只记录一次。
查询数据时,如果用户查询过列表,就需要记录这次查询数据的时间,下次查询时查询大于等于该时间的数据。
如果,用户没有记录查询时间,就查询最近的5个来访用户。
@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(collection = "visitors")
public class Visitors implements java.io.Serializable{private static final long serialVersionUID = 2811682148052386573L;private ObjectId id;private Long userId; //我的idprivate Long visitorUserId; //来访用户idprivate String from; //来源,如首页、圈子等private Long date; //来访时间private String visitDate;//来访日期private Double score; //得分}
package com.tanhua.dubbo.server.api;import com.tanhua.dubbo.server.pojo.Visitors;import java.util.List;public interface VisitorsApi {/*** 保存访客数据** @param userId 我的id* @param visitorUserId 访客id* @param from 来源* @return*/String save(Long userId, Long visitorUserId, String from);/*** 查询我的访客数据,存在2种情况:* 1. 我没有看过我的访客数据,返回前5个访客信息* 2. 之前看过我的访客,从上一次查看的时间点往后查询5个访客数据*/List queryMyVisitor(Long userId);}
package com.tanhua.dubbo.server.api;import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.dubbo.config.annotation.Service;
import com.tanhua.dubbo.server.pojo.RecommendUser;
import com.tanhua.dubbo.server.pojo.Visitors;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.redis.core.RedisTemplate;import java.util.List;@Service(version = "1.0.0")
public class VisitorsApiImpl implements VisitorsApi {@Autowiredprivate MongoTemplate mongoTemplate;private static final String VISITOR_REDIS_KEY = "VISITOR_USER";@Autowiredprivate RedisTemplate redisTemplate;@Overridepublic String saveVisitor(Long userId, Long visitorUserId, String from) {//校验if (!ObjectUtil.isAllNotEmpty(userId, visitorUserId, from)) {return null;}//查询访客用户在今天是否已经记录过,如果已经记录过,不再记录String today = DateUtil.today();Long minDate = DateUtil.parseDateTime(today + " 00:00:00").getTime();Long maxDate = DateUtil.parseDateTime(today + " 23:59:59").getTime();Query query = Query.query(Criteria.where("userId").is(userId).and("visitorUserId").is(visitorUserId).andOperator(Criteria.where("date").gte(minDate),Criteria.where("date").lte(maxDate)));long count = this.mongoTemplate.count(query, Visitors.class);if (count > 0) {//今天已经记录过的return null;}Visitors visitors = new Visitors();visitors.setFrom(from);visitors.setVisitorUserId(visitorUserId);visitors.setUserId(userId);visitors.setDate(System.currentTimeMillis());visitors.setId(ObjectId.get());//存储数据this.mongoTemplate.save(visitors);return visitors.getId().toHexString();}@Overridepublic List queryMyVisitor(Long userId) {// 查询前5个访客数据,按照访问时间倒序排序// 如果用户已经查询过列表,记录查询时间,后续查询需要按照这个时间往后查询// 上一次查询列表的时间Long date = Convert.toLong(this.redisTemplate.opsForHash().get(VISITOR_REDIS_KEY, String.valueOf(userId)));PageRequest pageRequest = PageRequest.of(0, 5, Sort.by(Sort.Order.desc("date")));Query query = Query.query(Criteria.where("userId").is(userId)).with(pageRequest);if (ObjectUtil.isNotEmpty(date)) {query.addCriteria(Criteria.where("date").gte(date));}List visitorsList = this.mongoTemplate.find(query, Visitors.class);//查询每个来访用户的得分for (Visitors visitors : visitorsList) {Query queryScore = Query.query(Criteria.where("toUserId").is(userId).and("userId").is(visitors.getVisitorUserId()));RecommendUser recommendUser = this.mongoTemplate.findOne(queryScore, RecommendUser.class);if(ObjectUtil.isNotEmpty(recommendUser)){visitors.setScore(recommendUser.getScore());}else {//默认得分visitors.setScore(90d);}}return visitorsList;}
}
//com.tanhua.server.service.TanHuaServicepublic TodayBest queryUserInfo(Long userId) {UserInfo userInfo = this.userInfoService.queryUserInfoByUserId(userId);if(ObjectUtil.isEmpty(userInfo)){return null;}TodayBest todayBest = new TodayBest();todayBest.setId(userId);todayBest.setAge(userInfo.getAge());todayBest.setGender(userInfo.getSex().name().toLowerCase());todayBest.setNickname(userInfo.getNickName());todayBest.setTags(Convert.toStrArray(StrUtil.split(userInfo.getTags(),',')));todayBest.setAvatar(userInfo.getLogo());//缘分值User user = UserThreadLocal.get();todayBest.setFateValue(this.recommendUserService.queryScore(userId, user.getId()).longValue());//记录来访用户this.visitorsApi.saveVisitor(userId, user.getId(), "个人主页");return todayBest;}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class VisitorsVo {private Long id; //用户idprivate String avatar;private String nickname;private String gender; //性别 man womanprivate Integer age;private String[] tags;private Long fateValue; //缘分值/*** 在vo对象中,补充一个工具方法,封装转化过程*/public static VisitorsVo init(UserInfo userInfo, Visitors visitors) {VisitorsVo vo = new VisitorsVo();BeanUtils.copyProperties(userInfo,vo);if(userInfo.getTags() != null) {vo.setTags(userInfo.getTags().split(","));}vo.setFateValue(visitors.getScore().longValue());return vo;}
}
/*** 谁看过我*/@GetMapping("visitors")public ResponseEntity queryVisitorsList(){List list = movementService.queryVisitorsList();return ResponseEntity.ok(list);}
public List queryVisitorsList() {User user = UserThreadLocal.get();List visitorsList = this.visitorsApi.queryMyVisitor(user.getId());if (CollUtil.isEmpty(visitorsList)) {return Collections.emptyList();}List
小视频功能类似于抖音、快手小视频的应用,用户可以上传小视频进行分享,也可以浏览查看别人分享的视频,并且可以对视频评论和点赞操作。
1、视频发布(视频:容量大,视频存储到什么位置?)
2、查询视频列表(问题:数据库表)
3、关注视频作者
4、视频播放(客户端获取视频的URL地址,自动的播放)
效果:
查看详情:
评论:
点赞:
视频存储
对于小视频的功能的开发,核心点就是:存储 + 推荐 + 加载速度 。
FastDFS是分布式文件系统。使用 FastDFS很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。
FastDFS 架构包括 Tracker server 和 Storage server。客户端请求 Tracker server 进行文件上传、下载,通过 Tracker server 调度最终由 Storage server 完成文件上传和下载。
Tracker server 作用是负载均衡和调度,通过 Tracker server 在文件上传时可以根据一些策略找到 Storage server 提供文件上传服务。可以将 tracker 称为追踪服务器或调度服务器。
Storage server 作用是文件存储,客户端上传的文件最终存储在 Storage 服务器上,Storage server 没有实现自己的文件系统而是利用操作系统的文件系统来管理文件。可以将storage称为存储服务器。
每个 tracker 节点地位平等。收集 Storage 集群的状态。
Storage 分为多个组,每个组之间保存的文件是不同的。每个组内部可以有多个成员,组成员内部保存的内容是一样的,组成员的地位是一致的,没有主从的概念。
客户端上传文件后存储服务器将文件 ID 返回给客户端,此文件 ID 用于以后访问该文件的索引信息。文件索引信息包括:组名,虚拟磁盘路径,数据两级目录,文件名。
客户端下载请求到Tracker服务,Tracker返回给客户端storage的信息,客户端根据这些信息进行请求storage获取到文件。
企业中搭建FastDFS是一个比较繁琐和复杂的过程(多个服务器之间的配合和配置等,专业的人员搭建),但是在学习阶段。由于所有的组件全部配置到linux虚拟机,已docker运行。所以linux的内存有要求(运行的过程中,可能会出现fastdfs的容器,启动之后自动关闭,表示虚拟机内存不足,适当的扩大内存),学习环境中使用一台调度服务器,一台存储服务器
我们使用docker进行搭建。目前所有的组件全部以docker的形式配置
#进入目录
cd /root/docker-file/fastdfs/
#启动
docker-compose up -d
#查看容器
docker ps -a
FastDFS调度服务器地址:192.168.136.160:22122
FastDFS存储服务器地址:http://192.168.136.160:8888/
导入依赖:
找到tanhua-server
的pom文件,打开fastdfs的依赖如下
com.github.tobato fastdfs-client 1.26.7 ch.qos.logback logback-classic
# ===================================================================
# 分布式文件系统FDFS配置
# ===================================================================
fdfs:so-timeout: 1500connect-timeout: 600#缩略图生成参数thumb-image:width: 150height: 150#TrackerList参数,支持多个tracker-list: 192.168.136.160:22122web-server-url: http://192.168.136.160:8888/
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.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.FileInputStream;
import java.io.FileNotFoundException;@RunWith(SpringRunner.class)
@SpringBootTest(classes = TanhuaServerApplication.class)
public class TestFastDFS {//测试将文件上传到FastDFS文件系统中//从调度服务器获取,一个目标存储服务器,上传@Autowiredprivate FastFileStorageClient client;@Autowiredprivate FdfsWebServer webServer;// 获取存储服务器的请求URL@Testpublic void testFileUpdate() throws FileNotFoundException {//1、指定文件File file = new File("D:\\1.jpg");//2、文件上传StorePath path = client.uploadFile(new FileInputStream(file),file.length(), "jpg", null);//3、拼接访问路径String url = webServer.getWebServerUrl() + path.getFullPath();}
}
存储服务器:
{"_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"
}
tanhua-domain中配置实体类Video
package com.tanhua.domain.mongo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.bson.types.ObjectId;
import org.springframework.data.mongodb.core.mapping.Document;@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(collection = "video")
public class Video implements java.io.Serializable {private static final long serialVersionUID = -3136732836884933873L;private ObjectId id; //主键idprivate Long vid; //自动增长private Long created; //创建时间private Long userId;private String text; //文字private String picUrl; //视频封面文件,URLprivate String videoUrl; //视频文件,URLprivate Integer likeCount=0; //点赞数private Integer commentCount=0; //评论数private Integer loveCount=0; //喜欢数
}
tanhua-dubbo-interface工程定义API接口VideoApi
package com.tanhua.dubbo.api.mongo;import com.tanhua.domain.mongo.FollowUser;
import com.tanhua.domain.mongo.Video;
import com.tanhua.domain.vo.PageResult;public interface VideoApi {}
tanhua-dubbo-service工程定义API接口实现类VideoApiImpl
package com.tanhua.dubbo.api.mongo;import com.tanhua.domain.mongo.FollowUser;
import com.tanhua.domain.mongo.Video;
import com.tanhua.domain.vo.PageResult;
import com.tanhua.dubbo.utils.IdService;
import org.apache.dubbo.config.annotation.Service;
import org.bson.types.ObjectId;
import org.checkerframework.checker.units.qual.C;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;import java.util.List;@Service
public class VideoApiImpl implements VideoApi {}
tanhua-server定义SmallVideoController
package com.tanhua.server.controller;import com.tanhua.server.service.VideoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;import java.io.IOException;@RestController
@RequestMapping("/smallVideos")
public class SmallVideoController {@Autowiredprivate SmallVideosService videosService;}
tanhua-server定义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.autoconfig.templates.OssTemplate;
import com.tanhua.domain.db.UserInfo;
import com.tanhua.domain.mongo.FollowUser;
import com.tanhua.domain.mongo.Video;
import com.tanhua.domain.vo.PageResult;
import com.tanhua.domain.vo.VideoVo;
import com.tanhua.dubbo.api.UserInfoApi;
import com.tanhua.dubbo.api.mongo.VideoApi;
import com.tanhua.server.interceptor.UserHolder;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;import java.io.IOException;
import java.util.ArrayList;
import java.util.List;@Service
public class SmallVideosService {@Autowiredprivate OssTemplate ossTemplate;@Autowiredprivate FastFileStorageClient client;@Autowiredprivate FdfsWebServer webServer;@Referenceprivate VideoApi videoApi;@Referenceprivate UserInfoApi userInfoApi;@Autowiredprivate RedisTemplate redisTemplate;
}
http://192.168.136.160:3000/project/19/interface/api/214
@RestController
@RequestMapping("/smallVideos")
public class SmallVideosController {@Autowiredprivate SmallVideosService videosService;/*** 发布视频* 接口路径:POST* 请求参数:* videoThumbnail:封面图* videoFile:视频文件*/@PostMappingpublic ResponseEntity saveVideos(MultipartFile videoThumbnail,MultipartFile videoFile) throws IOException {videosService.saveVideos(videoThumbnail,videoFile);return ResponseEntity.ok(null);}
}
@Service
public class SmallVideosService {@Referenceprivate VideoApi videoApi;@Referenceprivate UserInfoApi userInfoApi;@Autowiredprivate OssTemplate ossTemplate;@Autowiredprivate FastFileStorageClient storageClient;@Autowiredprivate FdfsWebServer webServer;@Autowiredprivate RedisTemplate redisTemplate;/*** 发布视频*///@CacheEvict(value = "videoList",allEntries = true)public ResponseEntity saveVideos(MultipartFile videoThumbnail, MultipartFile videoFile) throws IOException {//1、封面图上传到阿里云OSS,获取地址String picUrl = ossTemplate.upload(videoThumbnail.getOriginalFilename(), videoThumbnail.getInputStream());//2、视频上传到fdfs上,获取请求地址//获取文件的后缀名String filename = videoFile.getOriginalFilename(); //ssss.aviString sufix = filename.substring(filename.lastIndexOf(".")+1);StorePath storePath = storageClient.uploadFile(videoFile.getInputStream(),videoFile.getSize(), sufix, null);//文件输入流,文件长度,文件后缀,元数据String videoUrl = webServer.getWebServerUrl() + storePath.getFullPath();//3、创建Video对象,并设置属性Video video = new Video();video.setUserId(UserHolder.getUserId());video.setPicUrl(picUrl);video.setVideoUrl(videoUrl);video.setText("我就是我,不一样的烟火");//4、调用API完成保存videoApi.save(video);//5、构造返回值return ResponseEntity.ok(null);}
}
VideoApi与VideoApiImpl中编写保存video的方法
//视频服务实现类
@Service
public class VideoApiImpl implements VideoApi {@Autowiredprivate MongoTemplate mongoTemplate;@Autowiredprivate IdService idService;//保存public void save(Video video) {video.setId(ObjectId.get());video.setCreated(System.currentTimeMillis());video.setVid(idService.getNextId("video"));mongoTemplate.save(video);}
}
对于SpringBoot工程进行文件上传,默认支持最大的文件是1M。为了解决这个问题,需要在application.yml中配置文件限制
如果上传视频,会导致异常,是因为请求太大的缘故:
在tanhua-server工程的application.yml中添加解析器,配置请求文件和请求体
Spring:servlet:multipart:max-file-size: 30MBmax-request-size: 30MB
数据库表:video
创建VO对象
创建controller对象,并配置分页查询接口方法
创建service对象,其中调用API,构造vo对象返回值
在API服务层,创建方法,分页查询小视频列表,返回PageResult
/*** 视频列表*/@GetMappingpublic ResponseEntity queryVideoList(@RequestParam(defaultValue = "1") Integer page,@RequestParam(defaultValue = "10") Integer pagesize) {PageResult result = videosService.queryVideoList(page, pagesize);return ResponseEntity.ok(result);}
public PageResult queryVideoList(Integer page, Integer pagesize) {//1、调用API查询分页数据 PageResult
VideoApi与VideoApiImpl中编写分页查询方法
//分页查询视频列表public PageResult findAll(Integer page, Integer pagesize) {//1、查询总数long count = mongoTemplate.count(new Query(), Video.class);//2、分页查询数据列表Query query = new Query().limit(pagesize).skip((page-1) * pagesize).with(Sort.by(Sort.Order.desc("created")));List list = mongoTemplate.find(query, Video.class);//3、构建返回return new PageResult(page,pagesize,(int) count,list);}
tanhua-domain定义返回VO对象VideoVo
package com.tanhua.domain.vo;import com.tanhua.domain.db.UserInfo;
import com.tanhua.domain.mongo.Video;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.beans.BeanUtils;import java.io.Serializable;@Data
@NoArgsConstructor
@AllArgsConstructor
public class VideoVo implements Serializable {private Long userId;private String avatar; //头像private String nickname; //昵称private String id;private String cover; //封面private String videoUrl; //视频URLprivate String signature; //发布视频时,传入的文字内容private Integer likeCount; //点赞数量private Integer hasLiked; //是否已赞(1是,0否)private Integer hasFocus; //是否关注 (1是,0否)private Integer commentCount; //评论数量public static VideoVo init(UserInfo userInfo, Video item) {VideoVo vo = new VideoVo();//copy用户属性BeanUtils.copyProperties(userInfo,vo); //source,target//copy视频属性BeanUtils.copyProperties(item,vo);vo.setCover(item.getPicUrl());vo.setId(item.getId().toHexString());vo.setSignature(item.getText());vo.setHasFocus(0);vo.setHasLiked(0);return vo;}
}
在运行测试时,及其容易出现空指针等异常。
之所以出现这类问题“”或者空指针异常,是由于MongoDB中非关系数据库,不能自动约束检测表关系。我们检查Video数据库表得知。其中有几条数据的发布人是虚拟构造,在用户表中并不存在
解决思路很简单,删除错误数据即可
关注用户是关注小视频发布的作者,这样我们后面计算推荐时,关注的用户将权重更重一些。
关注用户
package com.tanhua.domain.mongo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.bson.types.ObjectId;
import org.springframework.data.mongodb.core.mapping.Document;//用户关注表(关注小视频的发布作者)
@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(collection = "focus_user")
public class FocusUser implements java.io.Serializable{private static final long serialVersionUID = 3148619072405056052L;private ObjectId id; //主键idprivate Long userId; //用户id 106private Long followUserId; //关注的用户id 1private Long created; //关注时间
}
/*** 关注视频作者*/
@PostMapping("/{id}/userFocus")
public ResponseEntity userFocus(@PathVariable("id") Long followUserId) {videosService.userFocus(followUserId);return ResponseEntity.ok(null);
}/*** 取消关注视频作者*/
@PostMapping("/{id}/userUnFocus")
public ResponseEntity userUnFocus(@PathVariable("id") Long followUserId) {videosService.userUnFocus(followUserId);return ResponseEntity.ok(null);
}
//关注视频作者public void userFocus(Long followUserId) {//1、创建FollowUser对象,并设置属性FollowUser followUser = new FollowUser();followUser.setUserId(UserHolder.getUserId());followUser.setFollowUserId(followUserId);//2、调用API保存videoApi.saveFollowUser(followUser);//3、将关注记录存入redis中String key = Constants.FOCUS_USER_KEY + UserHolder.getUserId();String hashKey = String.valueOf(followUserId);redisTemplate.opsForHash().put(key,hashKey,"1");}//取消关注视频作者public void userUnFocus(Long followUserId) {//1、调用API删除关注数据videoApi.deleteFollowUser(UserHolder.getUserId(),followUserId);//2、删除redis中关注记录String key = Constants.FOCUS_USER_KEY + UserHolder.getUserId();String hashKey = String.valueOf(followUserId);redisTemplate.opsForHash().delete(key,hashKey);}
VideoApi与VideoApiImpl中编写关注方法
解决重复关注的问题:
在保存关注数据时,可以根据userId和followUserId查询数据库,如果存在则不再保存数据
查询视频列表是,从redis中获取关注数据
实现缓存逻辑有2种方式:
Spring从3.1开始定义了org.springframework.cache.Cache和org.springframework.cache.CacheManager接口来统一不同的缓存技术;并支持使用JCache(JSR-107)注解简化我们开发;
Cache接口为缓存的组件规范定义,包含缓存的各种操作集合;
Cache接口下Spring提供了各种xxxCache的实现;如RedisCache,EhCacheCache ,ConcurrentMapCache等;
每次调用需要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。
使用Spring缓存抽象时我们需要关注以下两点;
1、确定方法需要被缓存以及他们的缓存策略
2、从缓存中读取之前缓存存储的数据
内部使用AOP的形式,对redis操作进行简化
名称 | 解释 |
---|---|
@Cacheable | 主要针对方法配置,能够根据方法的请求参数对其进行缓存 |
@CacheEvict | 清空缓存 |
导入SpringDataRedis的依赖,并在application.yml中配置 (略)
然后在启动类注解@EnableCaching开启缓存
@SpringBootApplication
@EnableCaching //开启缓存
public class DemoApplication{public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);}
}
package com.tanhua.server.test;import com.tanhua.domain.db.UserInfo;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;@Service
public class UserInfoService {//根据id查询public UserInfo queryById(Long userId) {//从数据库查询UserInfo user = new UserInfo();user.setId(userId);user.setNickname("ceshi");return user;}//根据id修改public void update(Long userId) {UserInfo user = new UserInfo();user.setId(userId);user.setNickname("itcast");}
}
@Cacheable
注解会先查询是否已经有缓存,有会使用缓存,没有则会执行方法并缓存。
@Cacheable(value = "user",key = "#userId")
public UserInfo queryById(Long userId) {//从数据库查询UserInfo user = new UserInfo();user.setId(userId);user.setNickname("ceshi");return user;
}
此处的value
是必需的,它指定了你的缓存存放在哪块命名空间。
此处的key
是使用的spEL表达式,参考上章。这里有一个小坑,如果你把methodName
换成method
运行会报错,观察它们的返回类型,原因在于methodName
是String
而methoh
是Method
。
此处的User
实体类一定要实现序列化public class User implements Serializable
,否则会报java.io.NotSerializableException
异常。
到这里,你已经可以运行程序检验缓存功能是否实现。
@CachEvict
的作用 主要针对方法配置,能够根据一定的条件对缓存进行清空 。
//根据id修改
@CacheEvict(value = "user",key = "#userId")
public void update(Long userId) {//修改用户UserInfo user = new UserInfo();user.setId(userId);user.setNickname("itcast");
}
修改VideoService,分页列表存入缓存,发布视频删除缓存
由于使用Reids缓存处理数据时,不能缓存ResponseEntity对象,所以需要修改方法返回值为PageResult
@Cacheable(value="videoList",key="#page + '_' + #pagesize")
public PageResult queryVideoList(Integer page, Integer pagesize) {//1、调用API查询 : PageReulstPageResult result = videoApi.findAll(page,pagesize);//2、获取分页中的list集合 ListList items = (List)result.getItems();//3、循环视频列表,一个Video构造一个VoList list = new ArrayList<>();for (Video item : items) {UserInfo userInfo = userInfoApi.findById(item.getUserId());VideoVo vo = VideoVo.init(userInfo, item);//从redis中获取,当前用户是否已经关注了视频发布作者String key = "followUser_"+UserHolder.getUserId()+"_"+item.getUserId();if (redisTemplate.hasKey(key)) {vo.setHasFocus(1);}list.add(vo);}//4、替换result中的item数据result.setItems(list);//5、构造返回值result;
}
//发布视频
@CacheEvict(value="videoList",allEntries = true)
public ResponseEntity saveVideo(MultipartFile videoThumbnail, MultipartFile videoFile) throws IOException {//1、图片上传到阿里云oss,获取请求地址String picUrl = ossTemplate.upload(videoThumbnail.getOriginalFilename(), videoThumbnail.getInputStream());//2、视频上传到fdfs上,获取请求地址String filename = videoFile.getOriginalFilename(); //xxxx.avi//获取文件后缀String sufix = filename.substring(filename.lastIndexOf(".")+1);StorePath storePath = client.uploadFile(videoFile.getInputStream(),videoFile.getSize(), sufix, null); //文件输入流,文件长度(大小),文件的后缀名,元数据(null)String videoUrl = webServer.getWebServerUrl() + storePath.getFullPath();//3、构建Video对象,并设置属性Video video = new Video();video.setPicUrl(picUrl);video.setVideoUrl(videoUrl);video.setText("传智播客是一个负责任的教育机构~"); //客户端未传递,手动模拟video.setUserId(UserHolder.getUserId());//4、调用api保存videoApi.save(video);//5、构建返回值return ResponseEntity.ok(null);
}