有人相爱,有人跳海
1. 项目介绍及环境配置
2. 短信验证码登录
3. 用户信息
4. MongoDB
5. 推荐好友列表/MongoDB集群/动态发布与查看
6. 圈子动态/圈子互动
7. 即时通讯(基于第三方API)
8. 附近的人(百度地图APi)
9. 小视频
10.网关配置
11.后台管理
新建 tanhua-model/src/main/java/com/tanhua/model/mongo/Visitors.java
文件:
@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; //我的id
private Long visitorUserId; //来访用户id
private String from; //来源,如首页、圈子等
private Long date; //来访时间
private String visitDate;//来访日期
private Double score; //得分
}
编辑 tanhua-app-server/src/main/java/com/tanhua/server/service/TanhuaService.java
文件:
...
// 查看佳人详情
public TodayBest personalInfo(Long userId) {
// 1. 根据用户id,查询用户详情
UserInfo userInfo = userInfoApi.findById(userId);
// 2. 根据操作人id, 和查看的用户id,查询两者的推荐数据(缘分值)
RecommendUser user = recommendUserApi.queryByUserId(userId, UserHolder.getUserId());
// 构造访客数据,调用API保存
Visitors visitors = new Visitors();
visitors.setUserId(userId);
visitors.setVisitorUserId(UserHolder.getUserId());
visitors.setFrom("首页");
visitors.setDate(System.currentTimeMillis());
visitors.setVisitDate(new SimpleDateFormat("yyyyMMdd").format(new Date()));
visitors.setScore(user.getScore());
visitorsApi.save(visitors);
// 3. 构造返回值
return TodayBest.init(userInfo, user);
}
...
新建 tanhua-dubbo/tanhua-dubbo-interface/src/main/java/com/tanhua/dubbo/api/VisitorsApi.java
文件:
public interface VisitorsApi {
// 保存访客数据
void save(Visitors visitors);
}
新建 tanhua-dubbo/tanhua-dubbo-mongo/src/main/java/com/tanhua/dubbo/api/VisitorsApiImpl.java
文件:
@DubboService
public class VisitorsApiImpl implements VisitorsApi{
@Autowired
private MongoTemplate mongoTemplate;
/**
* 保存访客数据
* 对于同一个访客,一天只能保存一次数据
*/
@Override
public void save(Visitors visitors) {
// 1. 查询访客数据
Query query = Query.query(Criteria.where("userId").is(visitors.getUserId())
.and("visitorUserId").is(visitors.getVisitorUserId())
.and("visitDate").is(visitors.getVisitDate()));
// 2. 不存在,保存
if(!mongoTemplate.exists(query, Visitors.class)) {
mongoTemplate.save(visitors);
}
}
}
首页查询最新访客列表,查询数据时,如果用户查询过列表,就需要记录这次查询数据的时间,下次查询时查询大于等于该时间的数据。如果,用户没有记录查询时间,就查询最近的5个来访用户。
新建 tanhua-model/src/main/java/com/tanhua/model/vo/VisitorsVo.java
文件:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class VisitorsVo {
private Long id; //用户id
private String avatar;
private String nickname;
private String gender; //性别 man woman
private 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;
}
}
编辑 tanhua-app-server/src/main/java/com/tanhua/server/controller/MovementController.java
文件:
@RestController
@RequestMapping("/movements")
public class MovementController {
@Autowired
private MovementService movementService;
@Autowired
private CommentsService commentsService;
/**
* 发布动态
* @return
*/
@PostMapping
public ResponseEntity movements(Movement movement, MultipartFile imageContent[]) throws IOException {
movementService.publishMovement(movement, imageContent);
return ResponseEntity.ok(null);
}
/**
* 查询我的动态
*/
@GetMapping("/all")
public ResponseEntity findByUserId(Long userId,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer pagesize) {
PageResult pr = movementService.findByUserId(userId, page, pagesize);
return ResponseEntity.ok(pr);
}
/**
* 查询好友动态
*/
@GetMapping()
public ResponseEntity movements( @RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer pagesize) {
PageResult pr = movementService.findFriendMovements(page, pagesize);
return ResponseEntity.ok(pr);
}
/**
* 查询推荐动态
*/
@GetMapping("/recommend")
public ResponseEntity recommend( @RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer pagesize) {
PageResult pr = movementService.findRecommendMovements(page, pagesize);
return ResponseEntity.ok(pr);
}
/**
* 查询单条动态
*/
@GetMapping("/{id}")
public ResponseEntity findById(@PathVariable("id") String movementId) {
MovementsVo vo = movementService.findById(movementId);
return ResponseEntity.ok(vo);
}
/**
* 点赞
*/
@GetMapping("/{id}/like")
public ResponseEntity like(@PathVariable("id") String movementId) {
Integer likeCount = commentsService.likeComment(movementId);
return ResponseEntity.ok(likeCount);
}
/**
* 取消点赞
*/
@GetMapping("/{id}/dislike")
public ResponseEntity dislike(@PathVariable("id") String movementId) {
Integer likeCount = commentsService.dislikeComment(movementId);
return ResponseEntity.ok(likeCount);
}
/**
* 喜欢
*/
@GetMapping("/{id}/love")
public ResponseEntity love(@PathVariable("id") String movementId) {
Integer likeCount = commentsService.loveComment(movementId);
return ResponseEntity.ok(likeCount);
}
/**
* 取消喜欢
*/
@GetMapping("/{id}/unlove")
public ResponseEntity unlove(@PathVariable("id") String movementId) {
Integer likeCount = commentsService.disloveComment(movementId);
return ResponseEntity.ok(likeCount);
}
/**
* 谁看过我
*/
@GetMapping("visitors")
public ResponseEntity queryVisitorsList(){
List<VisitorsVo> list = movementService.queryVisitorsList();
return ResponseEntity.ok(list);
}
}
编辑 tanhua-app-server/src/main/java/com/tanhua/server/service/MovementService.java
文件:
@Service
public class MovementService {
@Autowired
private OssTemplate ossTemplate;
@DubboReference
private MovementApi movementApi;
@DubboReference
private UserInfoApi userInfoApi;
@Autowired
private RedisTemplate<String, String> redisTemplate;
@DubboReference
private VisitorsApi visitorsApi;
/**
* 发布动态
*/
public void publishMovement(Movement movement, MultipartFile[] imageContent) throws IOException {
// 1. 判断发布动态的内容是否存在
if(StringUtils.isEmpty(movement.getTextContent())) {
throw new BusinessException(ErrorResult.contentError());
}
// 2. 获取当前登录的用户id
Long userId = UserHolder.getUserId();
// 3. 将文件内容上传到阿里云OSS, 获取请求地址
List<String> medias = new ArrayList<>();
for (MultipartFile multipartFile : imageContent) {
// String upload = ossTemplate.upload(multipartFile.getOriginalFilename(), multipartFile.getInputStream());
// !!! 阿里云OSS收费, 这里暂时跳过
String upload = "https://img0.baidu.com/it/u=1501084209,93021381&fm=253&fmt=auto&app=138&f=JPEG";
medias.add(upload);
}
// 4. 将数据封装到movement对象
movement.setUserId(userId);
movement.setMedias(medias);
//5. 调用API完成动态发布
movementApi.publish(movement);
}
// 查询我的动态
public PageResult findByUserId(Long userId, Integer page, Integer pagesize) {
// 1. 根据用户id, 调用API查询个人动态内容(PageResult -- Movement)
PageResult pr = movementApi.findByUserId(userId, page, pagesize);
// 2. 获取PageResult中item列表对象
List<Movement> items = (List<Movement>) pr.getItems();
// 3. 非空判断
if(items == null) {
return pr;
}
// 4. 循环数据列表
UserInfo userInfo = userInfoApi.findById(userId);
List<MovementsVo> vos = new ArrayList<>();
for (Movement item : items) {
// 5. 一个Movement构建一个VO对象
MovementsVo vo = MovementsVo.init(userInfo, item);
vos.add(vo);
}
// 6. 构建返回值
pr.setItems(vos);
return pr;
}
// 查询好友动态
public PageResult findFriendMovements(Integer page, Integer pagesize) {
// 1. 获取当前用户id
Long userId = UserHolder.getUserId();
// 2. 调用API查询当前用户好友发布的动态列表
List<Movement> list = movementApi.findFriendMovements(page, pagesize, userId);
return getPageResult(page, pagesize, list);
}
// 公共方法
private PageResult getPageResult(Integer page, Integer pagesize, List<Movement> list) {
// 3. 判断列表是否为空
// if(list == null || list.isEmpty()) {
if(CollUtil.isEmpty(list)) {
return new PageResult();
}
// 4. 提取动态发布人的id列表
List<Long> userIds = CollUtil.getFieldValues(list, "userId", Long.class);
// 5. 根据用户id列表获取用户详情
Map<Long, UserInfo> map = userInfoApi.findByIds(userIds, null);
// 6. 一个Movement构造一个vo对象
List<MovementsVo> vos = new ArrayList<>();
for (Movement movement : list) {
UserInfo userInfo = map.get(movement.getUserId());
if(userInfo != null) {
MovementsVo vo = MovementsVo.init(userInfo, movement);
// 添加点赞状态,判断hashKey是否存在
String key = Constants.MOVEMENTS_INTERACT_KEY + movement.getId().toHexString();
String hashKey = Constants.MOVEMENT_LIKE_HASHKEY + UserHolder.getUserId();
if(redisTemplate.opsForHash().hasKey(key, hashKey)) {
vo.setHasLiked(1);
}
// 添加喜欢状态,判断hashKey是否存在
String loveHashKey = Constants.MOVEMENT_LOVE_HASHKEY + UserHolder.getUserId();
if(redisTemplate.opsForHash().hasKey(key, loveHashKey)) {
vo.setHasLoved(1);
}
vos.add(vo);
}
}
// 7. 构造PageResult并返回
return new PageResult(page, pagesize, 0l, vos);
}
// 查询推荐动态
public PageResult findRecommendMovements(Integer page, Integer pagesize) {
// 1. 从redis从获取推荐数据
String redisKey = Constants.MOVEMENTS_RECOMMEND + UserHolder.getUserId();
String redisValue = redisTemplate.opsForValue().get(redisKey);
// 2. 判断推荐数据是否存在
List<Movement> list = Collections.EMPTY_LIST;
if(StringUtils.isEmpty(redisValue)) {
// 3. 如果不存在, 调用API随机构造10条动态数据
list = movementApi.randomMovements(pagesize);
} else {
// 4. 如果存在, 处理pid数据 "16,17,18,19,20,21,10015,10020,10040,10064,10092,10093,10099,10067"
String[] values = redisValue.split(",");
// 4.1 判断当前页的起始条数是否小于数组的总数
if((page - 1) * pagesize < values.length) {
List<Long> pids = Arrays.stream(values).skip((page - 1) * pagesize).limit(pagesize)
.map(e -> Long.valueOf(e))
.collect(Collectors.toList());
// 5. 调用API根据PID数组查询动态数据
list = movementApi.findMovementByPids(pids);
}
}
// 6. 调用公共方法构造返回值
return getPageResult(page, pagesize, list);
}
// 查询单条动态
public MovementsVo findById(String movementId) {
// 1. 调用API查询动态详情
Movement movement = movementApi.findById(movementId);
// 2. 转换vo对象
if(movement != null) {
UserInfo userInfo = userInfoApi.findById(movement.getUserId());
return MovementsVo.init(userInfo, movement);
} else {
return null;
}
}
// 首页 - 访客列表
public List<VisitorsVo> queryVisitorsList() {
// 1. 查询访问时间
String key = Constants.VISITORS_USER;
String hashKey = String.valueOf(UserHolder.getUserId());
String value = (String) redisTemplate.opsForHash().get(key, hashKey);
Long date = StringUtils.isEmpty(value) ? null : Long.valueOf(value);
// 2. 调用API查询数据列表 List
List<Visitors> list = visitorsApi.queryVisitorsList(date, UserHolder.getUserId());
if(CollUtil.isEmpty(list)) {
return new ArrayList<>();
}
// 3. 提取用户id
List<Long> visitorUserIds = CollUtil.getFieldValues(list, "visitorUserId", Long.class);
// 4. 查看用户详情
Map<Long, UserInfo> map = userInfoApi.findByIds(visitorUserIds, null);
// 5. 构造返回
List<VisitorsVo> vos = new ArrayList<>();
for (Visitors visitors : list) {
UserInfo userInfo = map.get(visitors.getVisitorUserId());
if(userInfo != null) {
VisitorsVo vo = VisitorsVo.init(userInfo, visitors);
vos.add(vo);
}
}
return vos;
}
}
编辑 tanhua-dubbo/tanhua-dubbo-interface/src/main/java/com/tanhua/dubbo/api/VisitorsApi.java
文件:
public interface VisitorsApi {
// 保存访客数据
void save(Visitors visitors);
// 首页 - 查询访客列表
List<Visitors> queryVisitorsList(Long date, Long userId);
}
编辑 tanhua-dubbo/tanhua-dubbo-mongo/src/main/java/com/tanhua/dubbo/api/VisitorsApiImpl.java
文件:
@DubboService
public class VisitorsApiImpl implements VisitorsApi{
@Autowired
private MongoTemplate mongoTemplate;
/**
* 保存访客数据
* 对于同一个访客,一天只能保存一次数据
*/
@Override
public void save(Visitors visitors) {
// 1. 查询访客数据
Query query = Query.query(Criteria.where("userId").is(visitors.getUserId())
.and("visitorUserId").is(visitors.getVisitorUserId())
.and("visitDate").is(visitors.getVisitDate()));
// 2. 不存在,保存
if(!mongoTemplate.exists(query, Visitors.class)) {
mongoTemplate.save(visitors);
}
}
// 首页 - 查询访客列表
public List<Visitors> queryVisitorsList(Long date, Long userId) {
Criteria criteria = Criteria.where("userId").is(userId);
if(date != null) {
criteria.and("date").gt(date);
}
Query query = Query.query(criteria).limit(5).with(Sort.by(Sort.Order.desc("date")));
return mongoTemplate.find(query, Visitors.class);
}
}
FastDFS是分布式文件系统。使用 FastDFS很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务
上传:
下载:
探花交友所需的第三方服务组件,已经以Docker-Compose准备好了。仅仅需要进入相关目录,以命令形式启动运行即可
#进入目录
cd /root/docker-file/fastdfs/
#创建容器并启动
docker-compose up –d
#查看容器
docker ps -a
编辑 tanhua-app-server/src/main/resources/application.yml
文件:
...
# ===================================================================
# 分布式文件系统FDFS配置
# ===================================================================
fdfs:
so-timeout: 1500
connect-timeout: 600
#缩略图生成参数
thumb-image:
width: 150
height: 150
#TrackerList参数,支持多个
tracker-list: 192.168.136.160:22122
web-server-url: http://192.168.136.160:8888/
新建 tanhua-app-server/src/test/java/com/tanhua/test/FastDFSTest.java
文件:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = AppServerApplication.class)
public class FastDFSTest {
/**
* 测试FastDFS的文件上传
*/
// 用于文件上传或下载
@Autowired
private FastFileStorageClient client;
@Autowired
private FdfsWebServer webServer;// 获取存储服务器的请求URL
@Test
public void testUpload() throws FileNotFoundException {
//1、指定文件
File file = new File("D:\\Course\\HM\\img\\1.jpg");
//2、文件上传
StorePath path = client.uploadFile(new FileInputStream(file), file.length(), "jpg", null);
//3、拼接访问路径
String fullPath = path.getFullPath();
System.out.println(fullPath);
String url = webServer.getWebServerUrl() + fullPath;
System.out.println(url);
}
}
小视频功能类似于抖音、快手小视频的应用,用户可以上传小视频进行分享,也可以浏览查看别人分享的视频,并且可以对视频评论和点赞操作。
编辑 tanhua-app-server/src/main/resources/application.yml
文件:
...
Spring:
servlet:
multipart:
max-file-size: 30MB
max-request-size: 30MB
新建 tanhua-model/src/main/java/com/tanhua/model/mongo/Video.java
文件:
@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(collection = "video")
public class Video implements java.io.Serializable {
private static final long serialVersionUID = -3136732836884933873L;
private ObjectId id; //主键id
private Long vid; //自动增长
private Long created; //创建时间
private Long userId;
private String text; //文字
private String picUrl; //视频封面文件,URL
private String videoUrl; //视频文件,URL
private Integer likeCount=0; //点赞数
private Integer commentCount=0; //评论数
private Integer loveCount=0; //喜欢数
}
新建 tanhua-app-server/src/main/java/com/tanhua/server/controller/SmallVideoController.java
文件:
@RestController
@RequestMapping("/smallVideos")
public class SmallVideoController {
@Autowired
private SmallVideosService videosService;
/**
* 发布视频
* 接口路径:POST
* 请求参数:
* videoThumbnail:封面图
* videoFile:视频文件
*/
@PostMapping
public ResponseEntity saveVideos(MultipartFile videoThumbnail, MultipartFile videoFile) throws IOException {
videosService.saveVideos(videoThumbnail,videoFile);
return ResponseEntity.ok(null);
}
}
编辑 tanhua-app-server/src/main/java/com/tanhua/server/service/SmallVideosService.java
文件:
@Service
public class SmallVideosService {
@Autowired
private FastFileStorageClient client;
@Autowired
private FdfsWebServer webServer;
@Autowired
private OssTemplate ossTemplate;
@DubboReference
private VideoApi videoApi;
/**
* 上传视频
* @param videoThumbnail 视频封面图片
* @param videoFile 视频文件
*/
public void saveVideos(MultipartFile videoThumbnail, MultipartFile videoFile) throws IOException {
if(videoThumbnail.isEmpty() || videoFile.isEmpty()) {
throw new BusinessException(ErrorResult.error());
}
// 1. 将视频上传到FastDFS,获取访问url
String filename = videoFile.getOriginalFilename();
filename = filename.substring(filename.lastIndexOf(".") + 1);
StorePath storePath = client.uploadFile(videoFile.getInputStream(), videoFile.getSize(), filename, null);
String videoUrl = webServer.getWebServerUrl() + storePath.getFullPath();
// 2. 将封面图片上传到阿里云OSS,获取访问的url
// String upload = ossTemplate.upload(videoThumbnail.getOriginalFilename(), videoThumbnail.getInputStream());
// // !!! 阿里云OSS收费, 这里暂时跳过
String upload = "https://img0.baidu.com/it/u=8672387,2873147723&fm=253&fmt=auto&app=138&f=JPEG";
// 3. 构建Videos对象
Video video = new Video();
video.setUserId(UserHolder.getUserId());
video.setVideoUrl(videoUrl);
video.setPicUrl(upload);
video.setText("我就是我, 是颜色不一样的烟火");
// 4. 调用API保存数据
String videoId = videoApi.save(video);
if(StringUtils.isEmpty(videoId)) {
throw new BusinessException(ErrorResult.error());
}
}
}
新建 tanhua-dubbo/tanhua-dubbo-interface/src/main/java/com/tanhua/dubbo/api/VideoApi.java
文件:
public interface VideoApi {
// 保存视频
String save(Video video);
}
新建 tanhua-dubbo/tanhua-dubbo-mongo/src/main/java/com/tanhua/dubbo/api/VideoApiImpl.java
文件:
@DubboService
public class VideoApiImpl implements VideoApi{
@Autowired
private MongoTemplate mongoTemplate;
@Autowired
private IdWorker idWorker;
// 保存视频
@Override
public String save(Video video) {
// 1. 设置属性
video.setVid(idWorker.getNextId("video"));
video.setCreated(System.currentTimeMillis());
// 2. 调用方法保存对象
mongoTemplate.save(video);
// 3. 返回对象id
return video.getId().toHexString();
}
}
新建 tanhua-model/src/main/java/com/tanhua/model/vo/VideoVo.java
文件:
@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; //视频URL
private 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;
}
}
编辑 tanhua-app-server/src/main/java/com/tanhua/server/controller/SmallVideoController.java
文件:
@RestController
@RequestMapping("/smallVideos")
public class SmallVideoController {
@Autowired
private SmallVideosService videosService;
/**
* 发布视频
* 接口路径:POST
* 请求参数:
* videoThumbnail:封面图
* videoFile:视频文件
*/
@PostMapping
public ResponseEntity saveVideos(MultipartFile videoThumbnail, MultipartFile videoFile) throws IOException {
videosService.saveVideos(videoThumbnail,videoFile);
return ResponseEntity.ok(null);
}
/**
* 视频列表
*/
@GetMapping
public ResponseEntity queryVideoList(@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer pagesize) {
PageResult result = videosService.queryVideoList(page, pagesize);
return ResponseEntity.ok(result);
}
}
编辑 tanhua-app-server/src/main/java/com/tanhua/server/service/SmallVideosService.java
文件:
@Service
public class SmallVideosService {
@Autowired
private FastFileStorageClient client;
@Autowired
private FdfsWebServer webServer;
@Autowired
private OssTemplate ossTemplate;
@DubboReference
private VideoApi videoApi;
@Autowired
private RedisTemplate<String, String> redisTemplate;
@DubboReference
private UserInfoApi userInfoApi;
/**
* 上传视频
* @param videoThumbnail 视频封面图片
* @param videoFile 视频文件
*/
public void saveVideos(MultipartFile videoThumbnail, MultipartFile videoFile) throws IOException {
if(videoThumbnail.isEmpty() || videoFile.isEmpty()) {
throw new BusinessException(ErrorResult.error());
}
// 1. 将视频上传到FastDFS,获取访问url
String filename = videoFile.getOriginalFilename();
filename = filename.substring(filename.lastIndexOf(".") + 1);
StorePath storePath = client.uploadFile(videoFile.getInputStream(), videoFile.getSize(), filename, null);
String videoUrl = webServer.getWebServerUrl() + storePath.getFullPath();
// 2. 将封面图片上传到阿里云OSS,获取访问的url
// String upload = ossTemplate.upload(videoThumbnail.getOriginalFilename(), videoThumbnail.getInputStream());
// // !!! 阿里云OSS收费, 这里暂时跳过
String upload = "https://img0.baidu.com/it/u=8672387,2873147723&fm=253&fmt=auto&app=138&f=JPEG";
// 3. 构建Videos对象
Video video = new Video();
video.setUserId(UserHolder.getUserId());
video.setVideoUrl(videoUrl);
video.setPicUrl(upload);
video.setText("我就是我, 是颜色不一样的烟火");
// 4. 调用API保存数据
String videoId = videoApi.save(video);
if(StringUtils.isEmpty(videoId)) {
throw new BusinessException(ErrorResult.error());
}
}
// 查询视频列表
public PageResult queryVideoList(Integer page, Integer pagesize) {
// 1. 查询redis数据
String redisKey = Constants.VIDEOS_RECOMMEND + UserHolder.getUserId();
String redisValue = redisTemplate.opsForValue().get(redisKey);
// 2. 判断redis数据是否存在
List<Video> list = new ArrayList<>();
int redisPages = 0;
if(!StringUtils.isEmpty(redisValue)) {
// 3. 如果redis数据存在,根据VID查询数据
String[] values = redisValue.split(",");
// 4. 判断redis中数据是否满足本次分页条数
if((page - 1) * pagesize < values.length) {
List<Long> vids = Arrays.stream(values).skip((page - 1) * pagesize).limit(pagesize)
.map(e -> Long.valueOf(e))
.collect(Collectors.toList());
list = videoApi.findMovementsByPids(vids);
}
redisPages = PageUtil.totalPage(values.length, pagesize);
}
// 5. 如果redis数据不存在, 分页查询视频数据
if(list.isEmpty()) {
// page的计算规则, 传入的页码, --redis查询的总页数
list = videoApi.queryVideoList(page - redisPages, pagesize);
}
// 6. 提取视频列表中所有的用户id
List<Long> userIds = CollUtil.getFieldValues(list, "userId", Long.class);
// 7. 查询用户信息
Map<Long, UserInfo> map = userInfoApi.findByIds(userIds, null);
// 8. 构建返回值
List<VideoVo> vos = new ArrayList<>();
for (Video video : list) {
UserInfo info = map.get(video.getUserId());
if(info != null) {
VideoVo vo = VideoVo.init(info, video);
vos.add(vo);
}
}
return new PageResult(page, pagesize, 0l, vos);
}
}
编辑 tanhua-dubbo/tanhua-dubbo-interface/src/main/java/com/tanhua/dubbo/api/VideoApi.java
文件:
public interface VideoApi {
// 保存视频
String save(Video video);
// 根据vid查询数据列表
List<Video> findMovementsByPids(List<Long> vids);
// 分页查询数据列表
List<Video> queryVideoList(int page, Integer pagesize);
}
编辑 tanhua-dubbo/tanhua-dubbo-mongo/src/main/java/com/tanhua/dubbo/api/VideoApiImpl.java
文件:
@DubboService
public class VideoApiImpl implements VideoApi{
@Autowired
private MongoTemplate mongoTemplate;
@Autowired
private IdWorker idWorker;
// 保存视频
@Override
public String save(Video video) {
// 1. 设置属性
video.setVid(idWorker.getNextId("video"));
video.setCreated(System.currentTimeMillis());
// 2. 调用方法保存对象
mongoTemplate.save(video);
// 3. 返回对象id
return video.getId().toHexString();
}
// 根据vid查询数据列表
@Override
public List<Video> findMovementsByPids(List<Long> vids) {
Query query = Query.query(Criteria.where("vid").in(vids));
return mongoTemplate.find(query, Video.class);
}
// 分页查询数据列表
@Override
public List<Video> queryVideoList(int page, Integer pagesize) {
Query query = new Query().skip((page - 1) * pagesize).limit(pagesize)
.with(Sort.by(Sort.Order.desc("created")));
return mongoTemplate.find(query, Video.class);
}
}
在项目中,我们通常会把高频的查询进行缓存。如资讯网站首页的文章列表、电商网站首页的商品列表、微博等社交媒体热搜的文章等等,当大量的用户发起查询时,借助缓存提高查询效率,同时减轻数据库压力。目前的缓存框架有很多:比如Redis、Memcached、Guava、Caffeine等等
Spring Cache是Spring提供的通用缓存框架。它利用了AOP,实现了基于注解的缓存功能,使开发者不用关心底层使用了什么缓存框架,只需要简单地加一个注解,就能实现缓存功能了。用户使用Spring Cache,可以快速开发一个很不错的缓存功能。
Gitee仓库: https://gitee.com/yuan0_0/tanhua_spring_cache.git
编辑 pom.xml
文件:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-cacheartifactId>
dependency>
编辑 src/main/java/com/itheima/cache/CachingApplication.java
文件:
@SpringBootApplication
@EnableCaching // 开启缓存
public class CachingApplication {
public static void main(String[] args) {
SpringApplication.run(CachingApplication.class, args);
}
}
编辑 src/main/java/com/itheima/cache/service/UserService.java
文件:
@CachePut(value="user"")
public User findById(Long id) {
return userDao.findById(id);
}
编辑 src/test/java/com/itheima/cache/test/UserServiceTest.java
文件:
/**
* 根据id查询用户
*/
@Test
public void testFindById() {
for (int i = 0; i < 5; i++) {
User user = userService.findById(1l);
System.out.println(user);
}
}
编辑 pom.xml
文件:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
编辑 src/main/resources/application.yml
文件:
spring:
redis:
port: 6379
host: 192.168.136.160
编辑 src/main/java/com/itheima/cache/service/UserService.java
文件:
@Service
public class UserService {
@Autowired
private UserDao userDao;
// @CachePut(value="user"")
/**
* value:名称空间(分组)
* key: 支持springel
* redis-key的命名规则:
* value + "::" + key
*/
@CachePut(value="user" , key = "'test' + #id")
public User findById(Long id) {
return userDao.findById(id);
}
//@CacheEvict(value="user" , key = "'test' + #id")
@Caching(
evict = {
@CacheEvict(value="user" , key = "'test' + #id"),
@CacheEvict(value="user" , key = "#id")
}
)
public void update(Long id) {
userDao.update(id);
}
}
编辑 src/test/java/com/itheima/cache/test/UserServiceTest.java
文件:
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTest {
@Autowired
private UserService userService;
/**
* 根据id查询用户
*/
@Test
public void testFindById() {
for (int i = 0; i < 5; i++) {
User user = userService.findById(1l);
System.out.println(user);
}
}
@Test
public void testFindById2() {
User user = userService.findById(2l);
System.out.println(user);
}
//更新:更新数据库,删除redis中的缓存数据
@Test
public void testUpdate() {
userService.update(2l);
}
}
编辑 tanhua-app-server/src/main/java/com/tanhua/server/AppServerApplication.java
文件:
//启动类
// @SpringBootApplication
@SpringBootApplication(exclude = {
MongoAutoConfiguration.class,
MongoDataAutoConfiguration.class
}) //排除mongo的自动配置
@EnableCaching //开启缓存
public class AppServerApplication {
public static void main(String[] args) {
SpringApplication.run(AppServerApplication.class,args);
}
}
编辑 tanhua-app-server/src/main/java/com/tanhua/server/service/SmallVideosService.java
文件:
@Service
public class SmallVideosService {
@Autowired
private FastFileStorageClient client;
@Autowired
private FdfsWebServer webServer;
@Autowired
private OssTemplate ossTemplate;
@DubboReference
private VideoApi videoApi;
@Autowired
private RedisTemplate<String, String> redisTemplate;
@DubboReference
private UserInfoApi userInfoApi;
/**
* 上传视频
* @param videoThumbnail 视频封面图片
* @param videoFile 视频文件
*/
@CacheEvict(value="videoList",allEntries = true) // 清空缓存
public void saveVideos(MultipartFile videoThumbnail, MultipartFile videoFile) throws IOException {
if(videoThumbnail.isEmpty() || videoFile.isEmpty()) {
throw new BusinessException(ErrorResult.error());
}
// 1. 将视频上传到FastDFS,获取访问url
String filename = videoFile.getOriginalFilename();
filename = filename.substring(filename.lastIndexOf(".") + 1);
StorePath storePath = client.uploadFile(videoFile.getInputStream(), videoFile.getSize(), filename, null);
String videoUrl = webServer.getWebServerUrl() + storePath.getFullPath();
// 2. 将封面图片上传到阿里云OSS,获取访问的url
// String upload = ossTemplate.upload(videoThumbnail.getOriginalFilename(), videoThumbnail.getInputStream());
// // !!! 阿里云OSS收费, 这里暂时跳过
String upload = "https://img0.baidu.com/it/u=8672387,2873147723&fm=253&fmt=auto&app=138&f=JPEG";
// 3. 构建Videos对象
Video video = new Video();
video.setUserId(UserHolder.getUserId());
video.setVideoUrl(videoUrl);
video.setPicUrl(upload);
video.setText("我就是我, 是颜色不一样的烟火");
// 4. 调用API保存数据
String videoId = videoApi.save(video);
if(StringUtils.isEmpty(videoId)) {
throw new BusinessException(ErrorResult.error());
}
}
// 查询视频列表
@Cacheable(
value="videos",
key = "T(com.tanhua.server.interceptor.UserHolder).getUserId()+'_'+#page+'_'+#pagesize") //userid _ page_pagesize
public PageResult queryVideoList(Integer page, Integer pagesize) {
// 1. 查询redis数据
String redisKey = Constants.VIDEOS_RECOMMEND + UserHolder.getUserId();
String redisValue = redisTemplate.opsForValue().get(redisKey);
// 2. 判断redis数据是否存在
List<Video> list = new ArrayList<>();
int redisPages = 0;
if(!StringUtils.isEmpty(redisValue)) {
// 3. 如果redis数据存在,根据VID查询数据
String[] values = redisValue.split(",");
// 4. 判断redis中数据是否满足本次分页条数
if((page - 1) * pagesize < values.length) {
List<Long> vids = Arrays.stream(values).skip((page - 1) * pagesize).limit(pagesize)
.map(e -> Long.valueOf(e))
.collect(Collectors.toList());
list = videoApi.findMovementsByPids(vids);
}
redisPages = PageUtil.totalPage(values.length, pagesize);
}
// 5. 如果redis数据不存在, 分页查询视频数据
if(list.isEmpty()) {
// page的计算规则, 传入的页码, --redis查询的总页数
list = videoApi.queryVideoList(page - redisPages, pagesize);
}
// 6. 提取视频列表中所有的用户id
List<Long> userIds = CollUtil.getFieldValues(list, "userId", Long.class);
// 7. 查询用户信息
Map<Long, UserInfo> map = userInfoApi.findByIds(userIds, null);
// 8. 构建返回值
List<VideoVo> vos = new ArrayList<>();
for (Video video : list) {
UserInfo info = map.get(video.getUserId());
if(info != null) {
VideoVo vo = VideoVo.init(info, video);
vos.add(vo);
}
}
return new PageResult(page, pagesize, 0l, vos);
}
}
编辑 tanhua-app-server/src/main/java/com/tanhua/server/config/RedisCacheConfig.java
文件:
@Configuration
public class RedisCacheConfig {
//设置失效时间
private static final Map<String, Duration> cacheMap;
static {
cacheMap = ImmutableMap.<String, Duration>builder().put("videos", Duration.ofSeconds(30L)).build();
}
//配置RedisCacheManagerBuilderCustomizer对象
@Bean
public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer() {
return (builder) -> {
//根据不同的cachename设置不同的失效时间
for (Map.Entry<String, Duration> entry : cacheMap.entrySet()) {
builder.withCacheConfiguration(entry.getKey(),
RedisCacheConfiguration.defaultCacheConfig().entryTtl(entry.getValue()));
}
};
}
}