跳到主要内容

达人探店点赞实现

在当今的社交和电商领域中,达人探店功能成为了吸引用户、增加用户互动的重要手段。本文将深入探讨达人探店相关功能的实现,包括发布探店笔记、查看探店笔记、点赞功能、点赞排行榜、点赞查询列表等方面。

发布探店笔记

数据库设计

表结构设计

  • tb_blog表:用于存储探店笔记的基本信息,包括商户 id、用户 id、标题、照片、文字描述、点赞数量、评论数量、创建时间和更新时间等字段。
  • tb_blog_comments表:用于存储探店笔记的评论信息,包括用户 id、探店 id、父级评论 id、回复评论 id、回复内容、点赞数、状态和创建时间等字段。
create table tb_blog
(
id bigint unsigned auto_increment comment '主键'
primary key,
shop_id bigint not null comment '商户id',
user_id bigint unsigned not null comment '用户id',
title varchar(255) collate utf8mb4_unicode_ci not null comment '标题',
images varchar(2048) not null comment '探店的照片,最多9张,多张以","隔开',
content varchar(2048) collate utf8mb4_unicode_ci not null comment '探店的文字描述',
liked int unsigned default '0' null comment '点赞数量',
comments int unsigned null comment '评论数量',
create_time timestamp default CURRENT_TIMESTAMP not null comment '创建时间',
update_time timestamp default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间'
)
collate = utf8mb4_general_ci
row_format = COMPACT;

create table tb_blog_comments
(
id bigint unsigned auto_increment comment '主键'
primary key,
user_id bigint unsigned not null comment '用户id',
blog_id bigint unsigned not null comment '探店id',
parent_id bigint unsigned not null comment '关联的1级评论id,如果是一级评论,则值为0',
answer_id bigint unsigned not null comment '回复的评论id',
content varchar(255) not null comment '回复的内容',
liked int unsigned null comment '点赞数',
status tinyint unsigned null comment '状态,0:正常,1:被举报,2:禁止查看',
create_time timestamp default CURRENT_TIMESTAMP not null comment '创建时间',
update_time timestamp default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间'
)
collate = utf8mb4_general_ci
row_format = COMPACT;

具体实现

上传文件接口:

@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);
}
}

修改路径为 自己的nginx前端里面的img目录:

public static final String IMAGE_UPLOAD_DIR = "/opt/homebrew/var/www/hmdp/imgs/";

发布博客 :

    @PostMapping
public Result saveBlog(@RequestBody Blog blog) {
// 获取登录用户
UserDTO user = UserHolder.getUser();
blog.setUserId(user.getId());
// 保存探店博文
blogService.save(blog);
// 返回id
return Result.ok(blog.getId());
}

在发布博客时,首先获取当前登录用户的信息,并将其设置为博客的用户 id。然后,调用博客服务的保存方法将博客信息保存到数据库中。最后,返回博客的 id 作为响应。

查看探店笔记:

@Override
public Result queryBlogById(Long id) {
// 根据id查询
Blog blog = this.getById(id);
if (blog == null) {
return Result.fail("博文不存在");
}
// 查询用户
queryBlogUser(blog);
return Result.ok(blog);
}

private void queryBlogUser(Blog blog) {
Long userId = blog.getUserId();
User user = userService.getById(userId);
blog.setName(user.getNickName());
blog.setIcon(user.getIcon());
}

点赞功能实现

需求分析

  • 同一个用户只能点赞一次,再次点击则取消点赞。
  • 如果当前用户已经点赞,则点赞按钮高亮显示(前端已实现,判断字段 Blog 类的 isLike 属性)。

实现步骤

  • 给 Blog 类中添加一个 isLike 字段,标示是否被当前用户点赞。
  • 修改点赞功能,利用 Redis 的 set 集合判断是否点赞过,未点赞过则点赞数 + 1,已点赞过则点赞数 - 1。
  • 修改根据 id 查询 Blog 的业务,判断当前登录用户是否点赞过,赋值给 isLike 字段。
  • 修改分页查询 Blog 业务,判断当前登录用户是否点赞过,赋值给 isLike 字段。

给 Blog 添加字段:

    /**
* 是否点赞过了
*/
@TableField(exist = false)
private Boolean isLike;

修改业务代码:

    @Override
public Result likeBlog(Long id) {
// 判断当前用户是否已经点赞
Long userId = UserHolder.getUser().getId();
String key = BLOG_LIKED_KEY + id;
Boolean isMember = stringRedisTemplate.opsForSet().isMember(key, userId.toString());
if (BooleanUtil.isFalse(isMember)) {
// 没有点赞,点赞数+1,保存到redis
boolean isSuccess = this.update().setSql("like=like + 1").eq("id", id).update();
if (isSuccess) {
stringRedisTemplate.opsForSet().add(key, userId.toString());
}
} else {
// 已经点赞,点赞数-1,从redis删除
boolean isSuccess = this.update().setSql("like=like - 1").eq("id", id).update();
if (isSuccess) {
stringRedisTemplate.opsForSet().remove(key, userId.toString());
}
}
return Result.ok();
}

修改关于博客是否被当前用户点赞:

    @Override
public Result queryBlogById(Long id) {
// 根据id查询
Blog blog = this.getById(id);
if (blog == null) {
return Result.fail("博文不存在");
}
// 查询用户
queryBlogUser(blog);
isBlogLiked(blog);
return Result.ok(blog);
}

private void isBlogLiked(Blog blog) {
Long userId = UserHolder.getUser().getId();
String key = BLOG_LIKED_KEY + blog.getId();
Boolean isMember = stringRedisTemplate.opsForSet().isMember(key, userId.toString());
blog.setIsLike(BooleanUtil.isTrue(isMember));
}

@Override
public Result queryHotBlog(Integer current) {
// 根据用户查询
Page<Blog> page = this.query()
.orderByDesc("liked")
.page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE));
// 获取当前页数据
List<Blog> records = page.getRecords();
// 查询用户
records.forEach(blog -> {
this.isBlogLiked(blog);
this.queryBlogUser(blog);
});
return Result.ok(records);
}

点赞排行榜

需求分析 在探店笔记的详情页面,需要展示给该笔记点赞的人,例如最早点赞的 TOP5,形成点赞排行榜。

实现思路 之前的点赞是放到 set 集合中,但 set 集合不能排序。因此,这里使用 sortedSet 来实现可排序的点赞集合。

修改 likeblogs:

    @Override
public Result likeBlog(Long id) {
// 判断当前用户是否已经点赞
Long userId = UserHolder.getUser().getId();
String key = BLOG_LIKED_KEY + id;
Double isMember = stringRedisTemplate.opsForZSet().score(key, userId.toString());
if (isMember == null) {
// 没有点赞,点赞数+1,保存到redis
boolean isSuccess = this.update().setSql("liked=liked + 1").eq("id", id).update();
// zadd key value score
if (isSuccess) {
stringRedisTemplate.opsForZSet().add(key, userId.toString(), System.currentTimeMillis());
}
} else {
// 已经点赞,点赞数-1,从redis删除
boolean isSuccess = this.update().setSql("liked=liked - 1").eq("id", id).update();
if (isSuccess) {
stringRedisTemplate.opsForZSet().remove(key, userId.toString());
}
}
return Result.ok();
}

private void isBlogLiked(Blog blog) {
Long userId = UserHolder.getUser().getId();
String key = BLOG_LIKED_KEY + blog.getId();
Double isMember = stringRedisTemplate.opsForZSet().score(key, userId.toString());
blog.setIsLike(isMember!= null);
}

点赞列表查询

    @Override
public Result queryBlogLikes(Long id) {
// 查询top5点赞用户
String key = BLOG_LIKED_KEY + id;
Set<String> top5 = stringRedisTemplate.opsForZSet().range(key, 0, 4);
if (top5 == null || top5.isEmpty()) {
return Result.ok(Collections.emptyList());
}
// 解析出用户的id
List<Long> ids = top5.stream().map(Long::valueOf).collect(Collectors.toList());
// 根据用户id查询用户信息
List<UserDTO> userDTOS = userService.query().in("id", ids)
.last("ORDER BY FIELD(id," + StringUtil.join(ids, ",") + ")")
.list()
.stream()
.map(user -> {
return BeanUtil.copyProperties(user, UserDTO.class);
})
.collect(Collectors.toList());
return Result.ok(userDTOS);
}