文章目录
Redis基础知识
1 Redis入门
- Redis是一款基于键值对的NoSQL数据库,它的值支持多种数据结构:字符串(strings)、哈希(hashes)、列表(lists)、集合(sets)、有序集合(sorted sets)等。
- Redis将所有的数据都存放在内存中,所以它的读写性能十分惊人。同时,Redis还可以将内存中的数据以快照或日志的形式保存到硬盘上,以保证数据的安全性。
- Redis典型的应用场景包括:缓存、排行榜、计数器、社交网络、消息队列等。
2 Spring整合Redis
- 引入依赖
spring-boot-starter-data-redis
- 配置Redis
配置数据库参数
编写配置类,构造RedisTemplate
# redis
spring.redis.database=0
spring.redis.host=localhost
spring.redis.port=6379
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(factory);
// 设置key序列化的方式
redisTemplate.setKeySerializer(RedisSerializer.string());
// 设置value序列化方式
redisTemplate.setValueSerializer(RedisSerializer.json());
// 设置hashKey序列化方式
redisTemplate.setHashKeySerializer(RedisSerializer.string());
// 设置hashValue序列化方式
redisTemplate.setHashValueSerializer(RedisSerializer.json());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
- 访问Redis - redisTemplate.opsForValue()
redisTemplate.opsForHash()
redisTemplate.opsForList()
redisTemplate.opsForSet()
redisTemplate.opsForZSet()
Redis实现点赞
1 点赞
-
支持对帖子、评论点赞。
- 第1次点赞,第2次取消点赞。
-
首页点赞数量
- 统计帖子的点赞数量。
-
详情页点赞数量
- 统计点赞数量。
- 显示点赞状态。
- 创建Key
public class RedisKeyUtil {
private static final String SPLIT = ":";
private static final String LIKE_KEY = "like:entity";
// set(userIds)
public static String getLikeKey(int entityType, int entityId) {
return LIKE_KEY + SPLIT + entityType + SPLIT + entityId;
}
}
- 点赞Service层
@Service
public class LikeService {
@Autowired
RedisTemplate redisTemplate;
// 点赞
public void like(int userId, int entityType, int entityId) {
// key
String likeKey = RedisKeyUtil.getLikeKey(entityType, entityId);
// 查看是否已经点赞
Boolean member = redisTemplate.opsForSet().isMember(likeKey, userId);
if (member) {
redisTemplate.opsForSet().remove(likeKey, userId);
} else {
redisTemplate.opsForSet().add(likeKey, userId);
}
}
// 查询点赞数量
public long findEntityLikeCount(int entityType, int entityId) {
// key
String likeKey = RedisKeyUtil.getLikeKey(entityType, entityId);
return redisTemplate.opsForSet().size(likeKey);
}
// 查询某人点赞状态
public int findEntityLikeStatus(int userId, int entityType, int entityId) {
// key
String likeKey = RedisKeyUtil.getLikeKey(entityType, entityId);
// 1 是点赞 0 是没点赞
return redisTemplate.opsForSet().isMember(likeKey, userId) ? 1 : 0;
}
}
- 点赞Controller层
@Controller
public class LikeController {
@Autowired
LikeService likeService;
@Autowired
HostHolder hostHolder;
@RequestMapping(path = "/like", method = RequestMethod.POST)
@ResponseBody
public String like(int entityType, int entityId) {
// 需要拦截器
// 当前用户
User user = hostHolder.getUser();
// 点赞
likeService.like(user.getId(), entityType, entityId);
// 点赞数量
long likeCount = likeService.findEntityLikeCount(entityType, entityId);
// 点赞状态
int likeStatus = likeService.findEntityLikeStatus(user.getId(), entityType, entityId);
// 返回结果
Map<String, Object> map = new HashMap<>();
map.put("likeCount", likeCount);
map.put("likeStatus", likeStatus);
return CommunityUtil.getJSONString(0,null,map);
}
}
2 我收到的赞
- 重构点赞功能
- 以用户为key,记录点赞数量
- increment(key),decrement(key)
- 开发个人主页
- 以用户为key,查询点赞数量
- 创建Key
public class RedisKeyUtil {
private static final String SPLIT = ":";
private static final String PREFIX_LIKE_KEY = "like:entity";
private static final String PREFIX_USER_LIKE = "like:user";
// set(userIds)
public static String getLikeKey(int entityType, int entityId) {
return PREFIX_LIKE_KEY + SPLIT + entityType + SPLIT + entityId;
}
// 某个用户的赞
// like:user:userId -> int
public static String getUserLikeKey(int userId) {
return PREFIX_USER_LIKE + SPLIT + userId;
}
}
- 我收到的赞Service层
@Service
public class LikeService{
@Autowired
RedisTemplate redisTemplate;
// 点赞
public void like(int userId, int entityType, int entityId, int entityUserId) {
/**
* userId是当前点赞的那个人,entityUserId是被赞的那个人
*/
redisTemplate.execute(new SessionCallback() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
// key
String entityLikeKey = RedisKeyUtil.getLikeKey(entityType, entityId);
// 用户收到的赞
String userLikeKey = RedisKeyUtil.getUserLikeKey(entityUserId);
// 查看是否已经点赞
Boolean member = operations.opsForSet().isMember(entityLikeKey, userId);
operations.multi();
if (member) {
operations.opsForSet().remove(entityLikeKey, userId);
operations.opsForValue().decrement(userLikeKey);
} else {
operations.opsForSet().add(entityLikeKey, userId);
operations.opsForValue().increment(userLikeKey);
}
return operations.exec();
}
});
}
// 统计某人收到点赞的数量
public int findUserLikeCount(int entityUserId) {
String likeKey = RedisKeyUtil.getUserLikeKey(entityUserId);
Integer count = (Integer) redisTemplate.opsForValue().get(likeKey);
return count == null ? 0 : count.intValue();
}
}
- controller层
@Controller
public class LikeController {
@Autowired
LikeService likeService;
@Autowired
HostHolder hostHolder;
@RequestMapping(path = "/like", method = RequestMethod.POST)
@ResponseBody
public String like(int entityType, int entityId, int entityUserId) {
// 需要拦截器
// 当前用户
User user = hostHolder.getUser();
// 点赞
likeService.like(user.getId(), entityType, entityId, entityUserId);
// 点赞数量
long likeCount = likeService.findEntityLikeCount(entityType, entityId);
// 点赞状态
int likeStatus = likeService.findEntityLikeStatus(user.getId(), entityType, entityId);
// 返回结果
Map<String, Object> map = new HashMap<>();
map.put("likeCount", likeCount);
map.put("likeStatus", likeStatus);
return CommunityUtil.getJSONString(0,null,map);
}
}
Redis实现关注
1 关注、取消关注
- 需求
- 开发关注、取消关注功能。
- 统计用户的关注数、粉丝数。
- 关键
- 若A关注了B,则A是B的Follower(粉丝),B是A的Followee(目标)。
- 关注的目标可以是用户、帖子、题目等,在实现时将这些目标抽象为实体。
- Key
public class RedisKeyUtil {
// 粉丝
private static final String PREFIX_FOLLOWER = "follower";
// 关注者
private static final String PREFIX_FOLLOWEE = "followee";
// 关注者
public static String getFolloweeKey(int entityType, int entityUserId) {
return PREFIX_FOLLOWEE + SPLIT + entityUserId + SPLIT + entityType;
}
// 粉丝
public static String getFollowerKey(int entityType, int entityUserId) {
return PREFIX_FOLLOWER + SPLIT + entityType + SPLIT + entityUserId;
}
}
- Service,关注用有序集合保存,将日期保存为分数,这样可以实现按时间排序
@Service
public class FollowService{
@Autowired
RedisTemplate redisTemplate;
// 关注
public void follow(int entityType, int entityUserId, int userId) {
redisTemplate.execute(new SessionCallback() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
// 粉丝,该用户是否包含当前用户的id
String followerKey = RedisKeyUtil.getFollowerKey(entityType, entityUserId);
// 关注者,当前用户是否关注该用户
String followeeKey = RedisKeyUtil.getFolloweeKey(entityType, userId);
operations.multi();
operations.opsForZSet().add(followeeKey, entityUserId, System.currentTimeMillis());
operations.opsForZSet().add(followerKey, userId, System.currentTimeMillis());
return operations.exec();
}
});
}
// 取消关注
public void unfollow(int entityType, int entityUserId, int userId) {
redisTemplate.execute(new SessionCallback() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
// 粉丝,该用户是否包含当前用户的id
String followerKey = RedisKeyUtil.getFollowerKey(entityType, entityUserId);
// 关注者,当前用户是否关注该用户
String followeeKey = RedisKeyUtil.getFolloweeKey(entityType, userId);
operations.multi();
operations.opsForZSet().remove(followeeKey, entityUserId);
operations.opsForZSet().remove(followerKey, userId);
return operations.exec();
}
});
}
// 关注数量
public long findFollowerCount(int entityType, int entityUserId) {
// 关注者,当前用户是否关注该用户
String followeeKey = RedisKeyUtil.getFolloweeKey(entityType, entityUserId);
return redisTemplate.opsForZSet().zCard(followeeKey);
}
// 粉丝数量
public long findFolloweeCount(int entityType, int entityUserId) {
// 粉丝,该用户是否包含当前用户的id
String followerKey = RedisKeyUtil.getFollowerKey(entityType, entityUserId);
return redisTemplate.opsForZSet().zCard(followerKey);
}
// 是否关注
public boolean hasFollow(int entityType, int entityUserId, int userId) {
String followeeKey = RedisKeyUtil.getFolloweeKey(entityType, userId);
return redisTemplate.opsForZSet().score(followeeKey, entityUserId) != null;
}
}
- Controller
2 关注、粉丝列表
- 业务层
- 查询某个用户关注的人,支持分页。
- 查询某个用户的粉丝,支持分页。
- 表现层
- 处理“查询关注的人”、“查询粉丝”请求。
- 编写“查询关注的人”、“查询粉丝”模板。
- Service
// 关注列表
public List<Map<String, Object>> followees(int userId, int offset, int limit) {
String followeeKey = RedisKeyUtil.getFolloweeKey(ENTITY_TYPE_USER, userId);
// 取
Set<Integer> set = redisTemplate.opsForZSet().reverseRange(followeeKey, offset, offset + limit - 1);
if (set == null) {
return null;
}
List<Map<String, Object>> list = new ArrayList<>();
for (Integer id : set) {
Map<String, Object> map = new HashMap<>();
User user = userService.findUserById(id);
Double score = redisTemplate.opsForZSet().score(followeeKey, id);
map.put("user", user);
map.put("followTime", new Date(score.longValue()));
list.add(map);
}
return list;
}
// 粉丝列表
public List<Map<String, Object>> followers(int userId, int offset, int limit) {
String followerKey = RedisKeyUtil.getFollowerKey(ENTITY_TYPE_USER, userId);
Set<Integer> set = redisTemplate.opsForZSet().reverseRange(followerKey, offset, offset + limit - 1);
if (set == null) {
return null;
}
List<Map<String, Object>> list = new ArrayList<>();
for (Integer id : set) {
Map<String, Object> map = new HashMap<>();
User user = userService.findUserById(id);
Double score = redisTemplate.opsForZSet().score(followerKey, id);
map.put("user", user);
map.put("followTime", new Date(score.longValue()));
list.add(map);
}
return list;
}
- Controller
@RequestMapping(path = "/followees/{userId}", method = RequestMethod.GET)
public String followees(@PathVariable("userId") int userId, Page page, Model model) {
User user = userService.findUserById(userId);
if (user == null) {
throw new RuntimeException("该用户不存在!");
}
model.addAttribute("user", user);
page.setPath("/followees/" + userId);
page.setRows((int)followService.findFolloweeCount(ENTITY_TYPE_USER, userId));
page.setLimit(5);
// 判断用户是否关注状态
List<Map<String, Object>> userList = followService.followees(userId, page.getOffset(), page.getLimit());
for (Map<String, Object> map : userList) {
User u = (User) map.get("user");
map.put("hasFollowed", hasFollow(u.getId()));
}
model.addAttribute("users", userList);
return "/site/followee";
}
@RequestMapping(path = "/followers/{userId}", method = RequestMethod.GET)
public String followers(@PathVariable("userId") int userId, Page page, Model model) {
User user = userService.findUserById(userId);
if (user == null) {
throw new RuntimeException("该用户不存在!");
}
model.addAttribute("user", user);
page.setPath("/followers/" + userId);
page.setRows((int)followService.findFollowerCount(ENTITY_TYPE_USER, userId));
page.setLimit(5);
// 判断用户是否关注状态
List<Map<String, Object>> userList = followService.followers(userId, page.getOffset(), page.getLimit());
for (Map<String, Object> map : userList) {
User u = (User) map.get("user");
map.put("hasFollowed", hasFollow(u.getId()));
}
model.addAttribute("users", userList);
return "/site/follower";
}
private boolean hasFollow(int userId) {
if (hostHolder.getUser() == null) {
return false;
}
return followService.hasFollowed(ENTITY_TYPE_USER, userId, hostHolder.getUser().getId());
}
使用Redis优化登陆模块
- 使用Redis存储验证码
- 验证码需要频繁的访问与刷新,对性能要求较高。
- 验证码不需永久保存,通常在很短的时间后就会失效。
- 分布式部署时,存在Session共享的问题。
- 使用Redis存储登录凭证
- 处理每次请求时,都要查询用户的登录凭证,访问的频率非常高。
- 使用Redis缓存用户信息
- 处理每次请求时,都要根据凭证查询用户信息,访问的频率非常高。