4.5 关注、取消关注
关注、取消关注
在 RedisKeyUtil 里增加两个方法去生成key
因为关注、取关比较高频,所以我们存到key里,所以我们在 RedisKeyUtil 里增加两个方法去生成key
private static final String PREFIX_FOLLOWEE = "followee";
private static final String PREFIX_FOLLOWER = "follower";
// 某个用户关注的实体
// followee:userId:entityType -> zset(entityId,now) now代表以当前时间作为分数排序
public static String getFolloweeKey(int userId, int entityType) {
return PREFIX_FOLLOWEE + SPLIT + userId + SPLIT + entityType;
}
// 某个实体拥有的粉丝
// follower:entityType:entityId -> zset(userId,now)
public static String getFollowerKey(int entityType, int entityId) {
return PREFIX_FOLLOWER + SPLIT + entityType + SPLIT + entityId;
}
业务层(Service)
FollowService处理关注、取消关注业务:
@Service
public class FollowService {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private UserService userService;
// 关注
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();
}
});
}
}
在常量接口CommunityConstant中定义一个常量表示实体类型是User
表现层(Controller和themeleaf模板)
然后是处理展示个人主页profile.html的模板
然后是处理上面关注按钮的profile.js
在用户主页显示该用户关注数、粉丝数
在主页显示正确的状态和数量,主页是通过UserController访问的
展示个人主页的模板profile.html:
4.6 关注列表、粉丝列表
因为关注列表和粉丝列表开发过程很相似,所以这里一起开发
业务层(Service)
FollowService:
实现接口
注入
@Autowired
private UserService userService;
// 查询某用户关注的人
public List<Map<String, Object>> findFollowees(int userId, int offset, int limit) {
String followeeKey = RedisKeyUtil.getFolloweeKey(userId, ENTITY_TYPE_USER);
// Set是一个接口,但是后面的实现类是有序的 起始索引 结束索引
Set<Integer> targetIds = redisTemplate.opsForZSet().reverseRange(followeeKey, offset, offset + limit - 1);
if (targetIds == null) {
return null;
}
List<Map<String, Object>> list = new ArrayList<>();
for (Integer targetId : targetIds) {
Map<String, Object> map = new HashMap<>();
User user = userService.findUserById(targetId);
map.put("user", user);
Double score = redisTemplate.opsForZSet().score(followeeKey, targetId);//score当是存的是毫秒数,可以还原回日期
map.put("followTime", new Date(score.longValue()));
list.add(map);
}
return list;
}
// 查询某用户的粉丝
public List<Map<String, Object>> findFollowers(int userId, int offset, int limit) {
String followerKey = RedisKeyUtil.getFollowerKey(ENTITY_TYPE_USER, userId);
Set<Integer> targetIds = redisTemplate.opsForZSet().reverseRange(followerKey, offset, offset + limit - 1);
if (targetIds == null) {
return null;
}
List<Map<String, Object>> list = new ArrayList<>();
for (Integer targetId : targetIds) {
Map<String, Object> map = new HashMap<>();
User user = userService.findUserById(targetId);
map.put("user", user);
Double score = redisTemplate.opsForZSet().score(followerKey, targetId);
map.put("followTime", new Date(score.longValue()));
list.add(map);
}
return list;
}
表现层
FollowController:
@Autowired
private UserService userService;
@RequestMapping(path = "/followees/{userId}", method = RequestMethod.GET)
public String getFollowees(@PathVariable("userId") int userId, Page page, Model model) {
User user = userService.findUserById(userId);
if (user == null) {
throw new RuntimeException("该用户不存在!");
}
model.addAttribute("user", user);
page.setLimit(5);
page.setPath("/followees/" + userId);
page.setRows((int) followService.findFolloweeCount(userId, ENTITY_TYPE_USER));
List<Map<String, Object>> userList = followService.findFollowees(userId, page.getOffset(), page.getLimit());
if (userList != null) {
for (Map<String, Object> map : userList) {
User u = (User) map.get("user");
// 判断当前用户是否关注了该人
map.put("hasFollowed", hasFollowed(u.getId()));
}
}
model.addAttribute("users", userList);
return "/site/followee";
}
@RequestMapping(path = "/followers/{userId}", method = RequestMethod.GET)
public String getFollowers(@PathVariable("userId") int userId, Page page, Model model) {
User user = userService.findUserById(userId);
if (user == null) {
throw new RuntimeException("该用户不存在!");
}
model.addAttribute("user", user);
page.setLimit(5);
page.setPath("/followers/" + userId);
page.setRows((int) followService.findFollowerCount(ENTITY_TYPE_USER, userId));
List<Map<String, Object>> userList = followService.findFollowers(userId, page.getOffset(), page.getLimit());
if (userList != null) {
for (Map<String, Object> map : userList) {
User u = (User) map.get("user");
// 判断当前用户是否关注了该人
map.put("hasFollowed", hasFollowed(u.getId()));
}
}
model.addAttribute("users", userList);
return "/site/follower";
}
private boolean hasFollowed(int userId) {
if (hostHolder.getUser() == null) {
return false;
}
return followService.hasFollowed(hostHolder.getUser().getId(), ENTITY_TYPE_USER, userId);
}
接下来就该处理themeleaf模板了
首先我们要处理 profile.html 页面,因为我们是通过这个页面跳转到关注列表和粉丝列表的
然后处理关注页面followee.html
然后是粉丝页面follower.html
4.7 优化登录模块
1.重构: 使用Redis存储验证码
RedisKeyUtil:
首先要在redis里存验证码,所以我们现在 RedisKeyUtil 里面加方法定义key
private static final String PREFIX_KAPTCHA = "kaptcha"; // 验证码
// 存到redis里的验证码的key 参数表示用户临时的凭证(不是登录凭证),很快过期,为了在验证码时表示这个用户
public static String getKaptchaKey(String owner) {
return PREFIX_KAPTCHA + SPLIT + owner;
}
LoginController:
2. 重构:使用Redis存储登录凭证
RedisKeyUtil:
在redis里存登录凭证,所以我们现在 RedisKeyUtil 里面加方法定义key
private static final String PREFIX_TICKET = "ticket"; // 登录凭证
// 存的redis的登录凭证的key 把登录凭证传进来
public static String getTicketKey(String ticket) {
return PREFIX_TICKET + SPLIT + ticket;
}
LoginTicketMapper:
然后我们要使用reid存储凭证,所以我们可以把之前的LoginTicketMapper废弃掉,在类上加上 @Deprecated 表示不推荐使用
UserService:
在UserService里重构一下用到LoginTicketMapper的地方
3. 重构:使用Redis缓存用户信息
RedisKeyUtil:
在redis里存缓存信息,所以我们现在 RedisKeyUtil 里面加方法定义key
private static final String PREFIX_USER = "user";
// 用户
public static String getUserKey(int userId) {
return PREFIX_USER + SPLIT + userId;
}
UserService
然后我们还需要在UserService里修改一些方法