(仿牛客社区项目)Java开发笔记4.4:实现关注、取消关注功能
(仿牛客社区项目)Java开发笔记4.4:实现关注、取消关注功能
·
实现关注、取消关注功能
1.util包
RedisKeyUtil类中添加getFollowerKey方法。
package com.gerrard.community.util;
public class RedisKeyUtil {
private static final String SPLIT = ":";
private static final String PREFIX_ENTITY_LIKE = "like:entity";
private static final String PREFIX_USER_LIKE = "like:user";
private static final String PREFIX_FOLLOWEE = "followee";
private static final String PREFIX_FOLLOWER = "follower";
//某个实体的赞
//like:entity:【entityType:entityId】->【set(userId)】
public static String getEntityLikeKey(int entityType, int entityId) {
return PREFIX_ENTITY_LIKE + SPLIT + entityType + SPLIT + entityId;
}
//某个用户的赞
//like:user:【userId】->int
public static String getUserLikeKey(int userId) {
return PREFIX_USER_LIKE + SPLIT + userId;
}
//某个用户关注的实体(键:用户Id,值:实体Id)
//followee:【userId:entityType】->zset(entityId,now)
public static String getFolloweeKey(int userId, int entityType) {
return PREFIX_FOLLOWEE + SPLIT + userId + SPLIT + entityType;
}
//某个实体拥有的粉丝(键:实体Id,值:用户Id)
//follower:【entityType:entityId】->zset(userId,now)
public static String getFollowerKey(int entityType, int entityId) {
return PREFIX_FOLLOWER + SPLIT + entityType + SPLIT + entityId;
}
//entityType不可以删除,userId和entityId值会有重复,需要借助entityType构成键的一部分加以区分,【不对】
//entityType不可以删除,Id值相同的情况下,需要借助entityType以示区分
}
CommunityConstant类中添加实体类型:用户字段。
/**
* 实体类型:用户
*/
int ENTITY_TYPE_USER=3;
2.service层
添加FollowerService类。
package com.gerrard.community.service;
import com.nowcoder.community.util.RedisKeyUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.stereotype.Service;
@Service
public class FollowService {
@Autowired
private RedisTemplate redisTemplate;
//可以将follow好unfollow整合到一块
public void follow(int userId, int entityType, int entityId) {
redisTemplate.execute(new SessionCallback() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType);
String followerKey = RedisKeyUtil.getFollowerKey(entityType, entityId);
operations.multi();
operations.opsForZSet().add(followeeKey, entityId, System.currentTimeMillis());
operations.opsForZSet().add(followerKey, userId, System.currentTimeMillis());
return operations.exec();
}
});
}
public void unfollow(int userId, int entityType, int entityId) {
redisTemplate.execute(new SessionCallback() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType);
String followerKey = RedisKeyUtil.getFollowerKey(entityType, entityId);
operations.multi();
operations.opsForZSet().remove(followeeKey, entityId);
operations.opsForZSet().remove(followerKey, userId);
return operations.exec();
}
});
}
// 查询关注的实体的数量
public long findFolloweeCount(int userId, int entityType) {
String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType);
return redisTemplate.opsForZSet().zCard(followeeKey);
}
// 查询实体的粉丝的数量
public long findFollowerCount(int entityType, int entityId) {
String followerKey = RedisKeyUtil.getFollowerKey(entityType, entityId);
return redisTemplate.opsForZSet().zCard(followerKey);
}
// 查询当前用户是否已关注该实体 用户关注的实体
public boolean hasFollowed(int userId, int entityType, int entityId) {
String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType);
return redisTemplate.opsForZSet().score(followeeKey, entityId) != null;
}
}
3.controller层
添加FollowerController类。
以关注为例:在profile.html点击关注ta按钮,发送/follow ajax请求。
1.先查询到登录用户;
2.再在redis数据库中添加相关键值对;【涉及到多表操作,开启事务操作】
例:id为15z的用户关注了id为152的用户,则redis数据库中插入以下键值对:
//followee:【userId:entityType】->zset(entityId,now)
//follower:【entityType:entityId】->zset(userId,now)
followee:【154:3】->{152,now}
follower:【3:152】->{154,now}
3.最后返回已关注提示信息。
package com.gerrard.community.controller;
import com.gerrard.community.entity.User;
import com.gerrard.community.service.FollowService;
import com.gerrard.community.util.CommunityUtil;
import com.gerrard.community.util.HostHolder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class FollowController {
@Autowired
private FollowService followService;
@Autowired
private HostHolder hostHolder;
@RequestMapping(path = "/follow", method = RequestMethod.POST)
@ResponseBody
public String follow(int entityType, int entityId) {
User user = hostHolder.getUser();
followService.follow(user.getId(), entityType, entityId);
return CommunityUtil.getJSONString(0, "已关注!");
}
@RequestMapping(path = "/unfollow", method = RequestMethod.POST)
@ResponseBody
public String unfollow(int entityType, int entityId) {
User user = hostHolder.getUser();
followService.unfollow(user.getId(), entityType, entityId);
return CommunityUtil.getJSONString(0, "已取消关注!");
}
}
UserController中对getProfilePage方法进行重构。
用户在index页面访问profile页面时,先获取查看用户信息的该用户实体,接着查询redis数据库,调用likeService查询点赞数量,调用followService查询关注数量和粉丝数量,是否已关注作者,并返回至profile页面。
package com.gerrard.community.controller;
import com.gerrard.community.annotation.LoginRequired;
import com.gerrard.community.entity.User;
import com.gerrard.community.service.FollowService;
import com.gerrard.community.service.LikeService;
import com.gerrard.community.service.UserService;
import com.gerrard.community.util.CommunityConstant;
import com.gerrard.community.util.CommunityUtil;
import com.gerrard.community.util.HostHolder;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
@Controller
@RequestMapping("/user")
public class UserController implements CommunityConstant {
private static final Logger logger = LoggerFactory.getLogger(UserController.class);
@Value("${community.path.upload}")
private String uploadPath;
@Value("${community.path.domain}")
private String domain;
@Value("${server.servlet.context-path}")
private String contextPath;
@Autowired
private UserService userService;
@Autowired
private HostHolder hostHolder;
@Autowired
private LikeService likeService;
@Autowired
private FollowService followService;
@LoginRequired
@RequestMapping(path = "/setting", method = RequestMethod.GET)
public String getSettingPage() {
return "/site/setting";
}
@LoginRequired
@RequestMapping(path = "/upload", method = RequestMethod.POST)
public String uploadHeader(MultipartFile headerImage, Model model) {
if (headerImage == null) {
model.addAttribute("error", "您还没有选择图片!");
return "/site/setting";
}
String fileName = headerImage.getOriginalFilename();
String suffix = fileName.substring(fileName.lastIndexOf("."));
if (StringUtils.isBlank(suffix)) {
model.addAttribute("error", "文件的格式不正确!");
return "/site/setting";
}
// 生成随机文件名
fileName = CommunityUtil.generateUUID() + suffix;
// 确定文件存放的路径
File dest = new File(uploadPath + "/" + fileName);
try {
// 存储文件
headerImage.transferTo(dest);
} catch (IOException e) {
logger.error("上传文件失败: " + e.getMessage());
throw new RuntimeException("上传文件失败,服务器发生异常!", e);
}
// 更新当前用户的头像的路径(web访问路径)
// http://localhost:8080/community/user/header/xxx.png
User user = hostHolder.getUser();
String headerUrl = domain + contextPath + "/user/header/" + fileName;
userService.updateHeader(user.getId(), headerUrl);
return "redirect:/index";
}
@RequestMapping(path = "/header/{fileName}", method = RequestMethod.GET)
public void getHeader(@PathVariable("fileName") String fileName, HttpServletResponse response) {
// 服务器存放路径
fileName = uploadPath + "/" + fileName;
// 文件后缀
String suffix = fileName.substring(fileName.lastIndexOf("."));
// 响应图片
response.setContentType("image/" + suffix);
try (
FileInputStream fis = new FileInputStream(fileName);
OutputStream os = response.getOutputStream();
) {
byte[] buffer = new byte[1024];
int b = 0;
while ((b = fis.read(buffer)) != -1) {
os.write(buffer, 0, b);
}
} catch (IOException e) {
logger.error("读取头像失败: " + e.getMessage());
}
}
// 个人主页
@RequestMapping(path = "/profile/{userId}", method = RequestMethod.GET)
public String getProfilePage(@PathVariable("userId") int userId, Model model) {
User user = userService.findUserById(userId);
if (user == null) {
throw new RuntimeException("该用户不存在!");
}
// 用户
model.addAttribute("user", user);
// 点赞数量
int likeCount = likeService.findUserLikeCount(userId);
model.addAttribute("likeCount", likeCount);
// 关注数量
long followeeCount = followService.findFolloweeCount(userId, ENTITY_TYPE_USER);
model.addAttribute("followeeCount", followeeCount);
// 粉丝数量
long followerCount = followService.findFollowerCount(ENTITY_TYPE_USER, userId);
model.addAttribute("followerCount", followerCount);
// 是否已关注
boolean hasFollowed = false;
if (hostHolder.getUser() != null) {
hasFollowed = followService.hasFollowed(hostHolder.getUser().getId(), ENTITY_TYPE_USER, userId);
}
model.addAttribute("hasFollowed", hasFollowed);
return "/site/profile";
}
}
4.view层
profile.html:
动态展示数据:
profile.js:
为关注ta按钮绑定单击事件:发送ajax请求:
5.功能测试
Redis数据库查看:
更多推荐
已为社区贡献2条内容
所有评论(0)