基于List的点赞列表
基于SortedSet的点赞排行榜
@TableField(exist = false)字段
1. 博客发布
上传博客
@Slf4j
@RestController
@RequestMapping("upload")
public class UploadController {
@PostMapping("blog")
public Result uploadImage(@RequestParam("file") MultipartFile image) {
try {
// 获取原始文件名称
String originalFilename = image.getOriginalFilename();
// 生成新文件名
String fileName = createNewFileName(originalFilename);
// 保存文件
image.transferTo(new File(SystemConstants.IMAGE_UPLOAD_DIR, fileName));
// 返回结果
log.debug("文件上传成功,{}", fileName);
return Result.ok(fileName);
} catch (IOException e) {
throw new RuntimeException("文件上传失败", e);
}
}
@GetMapping("/blog/delete")
public Result deleteBlogImg(@RequestParam("name") String filename) {
File file = new File(SystemConstants.IMAGE_UPLOAD_DIR, filename);
if (file.isDirectory()) {
return Result.fail("错误的文件名称");
}
FileUtil.del(file);
return Result.ok();
}
private String createNewFileName(String originalFilename) {
// 获取后缀
String suffix = StrUtil.subAfter(originalFilename, ".", true);
// 生成目录
String name = UUID.randomUUID().toString();
int hash = name.hashCode();
int d1 = hash & 0xF;
int d2 = (hash >> 4) & 0xF;
// 判断目录是否存在
File dir = new File(SystemConstants.IMAGE_UPLOAD_DIR, StrUtil.format("/blogs/{}/{}", d1, d2));
if (!dir.exists()) {
dir.mkdirs();
}
// 生成文件名
return StrUtil.format("/blogs/{}/{}/{}.{}", d1, d2, name, suffix);
}
}
发布博客
blog对象来接收前端发来的信息,调用mp提供的接口向博客表里save一条数据,但是用户id前端没有传递过来,需要去ThreadLocal中去获取
controller层
@PostMapping
public Result saveBlog(@RequestBody Blog blog) {
// 获取登录用户
UserDTO user = UserHolder.getUser();
blog.setUserId(user.getId());
// 保存探店博文
blogService.save(blog);
// 返回id
return Result.ok(blog.getId());
}
查看博客模块
查看博客不仅需要返回的blog对象,还需要给blog的用户信息(名称、信息);
所以需要两个表的信息,但是避免麻烦可以直接在Blog实体类中,添加用户头像和用户名称两个属性,并加上mp提供的注解@TableField(exist = false) ,表示当前属性不属于表中字段;
这里这个操作非常巧妙,当exist = false 时,表示该字段在数据库表中不存在,仅存在于Java实体类中;
/**
* 根据id获取blog信息
* @param id
* @return
*/
@Override
public Result queryBlogById(Long id) {
//1. 查询blog
Blog blog = getById(id);
if(blog == null) {
return Result.fail("博客不存在");
}
//2. 查询blog有关的用户
queryBlogUser(blog);
//3. 查询blog是否被点赞
isBlogLiked(blog);
return Result.ok(blog);
}
/**
* 查询与该blog有关的用户
* @param blog
*/
private void queryBlogUser(Blog blog) {
Long userId = blog.getUserId();
User user = userService.getById(userId);
blog.setName(user.getNickName());
blog.setIcon(user.getIcon());
}
通过userService中mp方法获取user对象,同时在blog继续设置icon和name两个属性,这样达到不用多设置一个实体类来完成这些查询操作;
2. Redis的SortedSet数据结构
要获取点赞前五的用户,此时的用户ID存储不能使用set集合,因为set集合是无序的;
一目了然要选择SortedSet,其中的score值我们可以采用时间戳,之后就可以按照时间戳进行点赞的排序;
但是SortedSet并没有SISMEMBER命令,所以选择使用ZSCORE命令;
该命令表示元素存在返回score,不存在返回nil;
以下代码均为做了排行榜处理的代码,使用的数据结构是SortedSet而不是Set
点赞功能实现及逻辑代码
/**
* 点赞博客的功能,只有点赞时要做登录校验
* 参数博客id
* @param id
* @return
*/
@Override
public Result likeBlog(Long id) {
//1. 获取登录用户
Long userId = UserHolder.getUser().getId();
//2. 判断用户是否已经点赞
String key = BLOG_LIKED_KEY + id;
Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());
if(score == null) {
//3. 如果未点赞,可以点赞
//3.1 数据库点赞数 + 1
boolean isSuccess = update().setSql("liked = liked + 1").eq("id", id).update();
//3.2 保存用户到Redis的SortedSet集合 ZADD key value score
if(isSuccess) {
stringRedisTemplate.opsForZSet().add(key, userId.toString(), System.currentTimeMillis());
}
}else {
//4. 如果已点赞,取消点赞
//4.1 数据库点赞数 - 1
boolean isSuccess = update().setSql("liked = liked - 1").eq("id", id).update();
//4.2 把用户从Redis的SortedSet集合移除
if(isSuccess) {
stringRedisTemplate.opsForZSet().remove(key, userId.toString());
}
}
return Result.ok();
}
分页查询下的点赞业务
/**
* 主页blog的信息
* @param current
* @return
*/
@Override
public Result queryHotBlog(Integer current) {
// 根据用户查询
Page<Blog> page = query()
.orderByDesc("liked")
.page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE));
// 获取当前页数据
List<Blog> records = page.getRecords();
// 查询用户
records.forEach(blog -> {
this.queryBlogUser(blog);
this.isBlogLiked(blog);
});
return Result.ok(records);
}
根据id查询博客详情页下的点赞业务
/**
* 根据id获取blog信息
* @param id
* @return
*/
@Override
public Result queryBlogById(Long id) {
//1. 查询blog
Blog blog = getById(id);
if(blog == null) {
return Result.fail("博客不存在");
}
//2. 查询blog有关的用户
queryBlogUser(blog);
//3. 查询blog是否被点赞
isBlogLiked(blog);
return Result.ok(blog);
}
提取出来的工具方法
此处获取登录用户要做判空处理,否则不登录会出现空指针异常
/**
* 查询blog是否被点赞,设置实体类blog中的isLike字段值
* 注意此处要判断是否登录的情况,进行判空操作!!!
* @param blog
*/
private void isBlogLiked(Blog blog) {
//1. 获取登录用户
UserDTO user = UserHolder.getUser();
if(user == null)
return;
Long userId = user.getId();
//2. 判断用户是否已经点赞
String key = BLOG_LIKED_KEY + blog.getId();
Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());
blog.setIsLike(score != null);
}
/**
* 查询与该blog有关的用户
* @param blog
*/
private void queryBlogUser(Blog blog) {
Long userId = blog.getUserId();
User user = userService.getById(userId);
blog.setName(user.getNickName());
blog.setIcon(user.getIcon());
}
查询点赞排行榜前五的用户
/**
* 查询点赞Top5用户
* @param id
* @return
*/
@Override
public Result queryBlogLikes(Long id) {
String key = BLOG_LIKED_KEY + id;
// 1. 查询top5的点赞用户ZRANGE key 0 4
Set<String> top5 = stringRedisTemplate.opsForZSet().range(key, 0, 4);
// 判空,避免空指针异常
if(top5 == null || top5.isEmpty()) {
return Result.ok(Collections.emptyList());
}
// 2. 解析其中的用户id
List<Long> ids = top5.stream().map(Long::valueOf).collect(Collectors.toList());
// 3. 根据用户id查询用户 WHERE id IN (5,1) ORDER BY FIELD (id,5,1)
List<UserDTO> userDTOS = userService.listByIds(ids)
.stream()
.map(user -> BeanUtil.copyProperties(user, UserDTO.class))
.collect(Collectors.toList());
// 4. 返回
return Result.ok(userDTOS);
}
这行代码使用了Stream API来处理top5集合中的每个元素。其中,top5是一个集合,它包含了博客点赞用户的信息。
stream()方法用于将集合转换为流,map(Long::valueOf)
用于将每个元素转换为Long类型,collect(Collectors.toList())
用于将转换后的元素收集到一个新的List中,最终得到用户id列表ids。
根据用户id查询用户也可以写成
// 3. 根据用户id查询用户 WHERE id IN (5,1) ORDER BY FIELD (id,5,1)
List<User> userList = userService.listByIds(ids);
List<UserDTO> userDTOS = userList.stream()
.map(user -> {
UserDTO userDTO = new UserDTO();
BeanUtil.copyProperties(user, userDTO);
return userDTO;
})
.collect(Collectors.toList());