1、Redis概述
• Redis是一款基于键值对的NoSQL数据库,它的值支持多种数据结构: 字符串(strings)、哈希(hashes)、列表(lists)、集合(sets)、有序集合(sorted sets)等。
• Redis将所有的数据都存放在内存中,所以它的读写性能十分惊人。 同时,Redis还可以将内存中的数据以快照或日志的形式保存到硬盘上,以保证数据的安全性。
• Redis典型的应用场景包括:缓存、排行榜、计数器、社交网络、消息队列等。
https://redis.io
https://github.com/microsoftarchive/redis
windows版本下载地址:https://github.com/microsoftarchive/redis/releases/tag/win-3.2.100
1、安装完成后配置环境变量
2、命令行输入redis-cli检验环境变量是否配置完成
2、Redis整合springboot
1、导入依赖pom.xml
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>
2、配置redis
application.properties
#配置redisspring.redis.database=10spring.redis.host=localhostspring.redis.port=6379
3、配置类设置redis属性
@Configurationpublic class RedisConfig {@Beanpublic RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){RedisTemplate<String,Object> template=new RedisTemplate<>();template.setConnectionFactory(factory);//设置key的序列化方式template.setKeySerializer(RedisSerializer.string());//设置value的序列化方式template.setValueSerializer(RedisSerializer.json());//设置hashKey的序列化方式template.setHashKeySerializer(RedisSerializer.string());//设置hashValue的序列化方式template.setHashValueSerializer(RedisSerializer.json());template.afterPropertiesSet();return template;}}
4、测试各种属性
@Testpublic void testStrings() {String redisKey = "test:count";redisTemplate.opsForValue().set(redisKey, 1);System.out.println(redisTemplate.opsForValue().get(redisKey));System.out.println(redisTemplate.opsForValue().increment(redisKey));System.out.println(redisTemplate.opsForValue().decrement(redisKey));}@Testpublic void testHashes() {String redisKey = "test:user";redisTemplate.opsForHash().put(redisKey, "id", 1);redisTemplate.opsForHash().put(redisKey, "username", "zhangsan");System.out.println(redisTemplate.opsForHash().get(redisKey, "id"));System.out.println(redisTemplate.opsForHash().get(redisKey, "username"));}@Testpublic void testLists() {String redisKey = "test:ids";redisTemplate.opsForList().leftPush(redisKey, 101);redisTemplate.opsForList().leftPush(redisKey, 102);redisTemplate.opsForList().leftPush(redisKey, 103);System.out.println(redisTemplate.opsForList().size(redisKey));System.out.println(redisTemplate.opsForList().index(redisKey, 0));System.out.println(redisTemplate.opsForList().range(redisKey, 0, 2));System.out.println(redisTemplate.opsForList().leftPop(redisKey));System.out.println(redisTemplate.opsForList().leftPop(redisKey));System.out.println(redisTemplate.opsForList().leftPop(redisKey));}@Testpublic void testSets() {String redisKey = "test:teachers";redisTemplate.opsForSet().add(redisKey, "刘备", "关羽", "张飞", "赵云", "诸葛亮");System.out.println(redisTemplate.opsForSet().size(redisKey));System.out.println(redisTemplate.opsForSet().pop(redisKey));System.out.println(redisTemplate.opsForSet().members(redisKey));}@Testpublic void testSortedSets() {String redisKey = "test:students";redisTemplate.opsForZSet().add(redisKey, "唐僧", 80);redisTemplate.opsForZSet().add(redisKey, "悟空", 90);redisTemplate.opsForZSet().add(redisKey, "八戒", 50);redisTemplate.opsForZSet().add(redisKey, "沙僧", 70);redisTemplate.opsForZSet().add(redisKey, "白龙马", 60);System.out.println(redisTemplate.opsForZSet().zCard(redisKey));System.out.println(redisTemplate.opsForZSet().score(redisKey, "八戒"));System.out.println(redisTemplate.opsForZSet().reverseRank(redisKey, "八戒"));System.out.println(redisTemplate.opsForZSet().reverseRange(redisKey, 0, 2));}@Testpublic void testKeys() {redisTemplate.delete("test:user");System.out.println(redisTemplate.hasKey("test:user"));redisTemplate.expire("test:students", 10, TimeUnit.SECONDS);}// 批量发送命令,节约网络开销.@Testpublic void testBoundOperations() {String redisKey = "test:count";BoundValueOperations operations = redisTemplate.boundValueOps(redisKey);operations.increment();operations.increment();operations.increment();operations.increment();operations.increment();System.out.println(operations.get());}// 编程式事务@Testpublic void testTransaction() {Object result = redisTemplate.execute(new SessionCallback() {@Overridepublic Object execute(RedisOperations redisOperations) throws DataAccessException {String redisKey = "text:tx";// 启用事务redisOperations.multi();redisOperations.opsForSet().add(redisKey, "zhangsan");redisOperations.opsForSet().add(redisKey, "lisi");redisOperations.opsForSet().add(redisKey, "wangwu");System.out.println(redisOperations.opsForSet().members(redisKey));// 提交事务return redisOperations.exec();}});System.out.println(result);}
3、点赞功能
1、生成redis的key的工具类RedisKeyUtil
public class RedisKeyUtil {private static final String SPLIT = ":";private static final String PREFIX_ENTITY_LIKE = "like:entity";// 某个实体的赞// like:entity:entityType:entityId -> set(userId)public static String getEntityLikeKey(int entityType, int entityId) {return PREFIX_ENTITY_LIKE + SPLIT + entityType + SPLIT + entityId;}}
2、查询点赞数量,点赞状态,以及某实体的点赞数量LikeService
@Servicepublic class LikeService {@Autowiredprivate RedisTemplate redisTemplate;/*** 点赞* @param userId* @param entityType* @param entityId*/public void like(int userId, int entityType, int entityId) {String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId);boolean isMember = redisTemplate.opsForSet().isMember(entityLikeKey, userId);if (isMember) {redisTemplate.opsForSet().remove(entityLikeKey, userId);} else {redisTemplate.opsForSet().add(entityLikeKey, userId);}}/*** 查询某实体点赞的数量* @param entityType* @param entityId* @return*/public long findEntityLikeCount(int entityType, int entityId) {String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId);return redisTemplate.opsForSet().size(entityLikeKey);}/*** 查询某人对某实体的点赞状态* @param userId* @param entityType* @param entityId* @return*/public int findEntityLikeStatus(int userId, int entityType, int entityId) {String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId);return redisTemplate.opsForSet().isMember(entityLikeKey, userId) ? 1 : 0;}}
3、LikeController
@Controllerpublic class LikeController {@Autowiredprivate LikeService likeService;@Autowiredprivate HostHolder hostHolder;/*** 查询点赞数量接口* @param entityType* @param entityId* @return*/@PostMapping("/like")@ResponseBodypublic 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);}}
4、页面(略)
index.html
discuss-detail.html
discuss.js
4、我收到的赞的功能
1、RedisKeyUtil中增加新的key
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";// 某个实体的赞// 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 -> intpublic static String getUserLikeKey(int userId) {return PREFIX_USER_LIKE + SPLIT + userId;}}
2、LikeService
/*** 点赞* @param userId* @param entityType* @param entityId*/public void like(int userId, int entityType, int entityId, int entityUserId) {redisTemplate.execute(new SessionCallback() {@Overridepublic Object execute(RedisOperations operations) throws DataAccessException {String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId);String userLikeKey = RedisKeyUtil.getUserLikeKey(entityUserId);boolean isMember = operations.opsForSet().isMember(entityLikeKey, userId);operations.multi();if (isMember) {operations.opsForSet().remove(entityLikeKey, userId);operations.opsForValue().decrement(userLikeKey);} else {operations.opsForSet().add(entityLikeKey, userId);operations.opsForValue().increment(userLikeKey);}return operations.exec();}});}/*** 查询某个用户获得的赞* @param userId* @return*/public int findUserLikeCount(int userId) {String userLikeKey = RedisKeyUtil.getUserLikeKey(userId);Integer count = (Integer) redisTemplate.opsForValue().get(userLikeKey);return count == null ? 0 : count.intValue();}
3、LikeController
public String like(int entityType, int entityId,int entityUserId) {User user = hostHolder.getUser();// 点赞likeService.like(user.getId(), entityType, entityId, entityUserId);
4、UserController
/*** 个人主页接口* @param userId* @param model* @return*/@GetMapping("/profile/{userId}")public String getProfilePage(@PathVariable("userId") int userId, Model model) {User user = userService.selectUserById(userId);if (user == null) {throw new RuntimeException("该用户不存在!");}// 用户model.addAttribute("user", user);// 点赞数量int likeCount = likeService.findUserLikeCount(userId);model.addAttribute("likeCount", likeCount);return "/site/profile";}
5、页面(略)
index.html
discuss-detail.html
discuss.js
profile.html
5、关注与被关注功能
1、RedisKeyUtil
private static final String PREFIX_FOLLOWEE = "followee";private static final String PREFIX_FOLLOWER = "follower";// 某个用户关注的实体// followee:userId:entityType -> zset(entityId,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;}
2、CommunityConstant
/*** 实体类型: 用户*/int ENTITY_TYPE_USER = 3;
3、FollowService
@Servicepublic class FollowService {@Autowiredprivate RedisTemplate redisTemplate;/*** 关注* @param userId* @param entityType* @param entityId*/public void follow(int userId, int entityType, int entityId) {redisTemplate.execute(new SessionCallback() {@Overridepublic 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();}});}/*** 取消关注* @param userId* @param entityType* @param entityId*/public void unfollow(int userId, int entityType, int entityId) {redisTemplate.execute(new SessionCallback() {@Overridepublic 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();}});}/*** 查询关注的实体的数量* @param userId* @param entityType* @return*/public long findFolloweeCount(int userId, int entityType) {String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType);return redisTemplate.opsForZSet().zCard(followeeKey);}/*** 查询实体的粉丝的数量* @param entityType* @param entityId* @return*/public long findFollowerCount(int entityType, int entityId) {String followerKey = RedisKeyUtil.getFollowerKey(entityType, entityId);return redisTemplate.opsForZSet().zCard(followerKey);}/*** 查询当前用户是否已关注该实体* @param userId* @param entityType* @param entityId* @return*/public boolean hasFollowed(int userId, int entityType, int entityId) {String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType);return redisTemplate.opsForZSet().score(followeeKey, entityId) != null;}}
4、UserController实现CommuniyuConstant接口
// 关注数量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);
5、FollowController
@Controllerpublic class FollowController {@Autowiredprivate FollowService followService;@Autowiredprivate HostHolder hostHolder;/*** 关注接口* @param entityType* @param entityId* @return*/@RequestMapping(path = "/follow", method = RequestMethod.POST)@ResponseBodypublic String follow(int entityType, int entityId) {User user = hostHolder.getUser();followService.follow(user.getId(), entityType, entityId);return CommunityUtil.getJSONString(0, "已关注!");}/*** 取消关注接口* @param entityType* @param entityId* @return*/@RequestMapping(path = "/unfollow", method = RequestMethod.POST)@ResponseBodypublic String unfollow(int entityType, int entityId) {User user = hostHolder.getUser();followService.unfollow(user.getId(), entityType, entityId);return CommunityUtil.getJSONString(0, "已取消关注!");}}
6、页面(略)
index.html
profile.html
discuss.html
6、关注列表与粉丝列表功能
• 业务层
- 查询某个用户关注的人,支持分页。
- 查询某个用户的粉丝,支持分页。
• 表现层
- 处理“查询关注的人”、“查询粉丝”请求。
- 编写“查询关注的人”、“查询粉丝”模板。
1、FollowService
/*** 查询某用户关注的人* @param userId* @param offset* @param limit* @return*/public List<Map<String, Object>> findFollowees(int userId, int offset, int limit) {String followeeKey = RedisKeyUtil.getFolloweeKey(userId, ENTITY_TYPE_USER);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.selectUserById(targetId);map.put("user", user);Double score = redisTemplate.opsForZSet().score(followeeKey, targetId);map.put("followTime", new Date(score.longValue()));list.add(map);}return list;}/*** 查询某用户的粉丝* @param userId* @param offset* @param limit* @return*/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.selectUserById(targetId);map.put("user", user);Double score = redisTemplate.opsForZSet().score(followerKey, targetId);map.put("followTime", new Date(score.longValue()));list.add(map);}return list;}
2、FollowController
/*** 查询关注列表接口* @param userId* @param page* @param model* @return*/@GetMapping("/followees/{userId}")public String getFollowees(@PathVariable("userId") int userId, Page page, Model model) {User user = userService.selectUserById(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";}/*** 查询粉丝列表接口* @param userId* @param page* @param model* @return*/@GetMapping("/followers/{userId}")public String getFollowers(@PathVariable("userId") int userId, Page page, Model model) {User user = userService.selectUserById(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);}
3、页面(略)
discuss-detail.html
followee.html
follower.html
profile.html
7、优化登录模块
使用Redis存储验证码
- 验证码需要频繁的访问与刷新,对性能要求较高。
- 验证码不需永久保存,通常在很短的时间后就会失效。
分布式部署时,存在Session共享的问题。
• 使用Redis存储登录凭证
处理每次请求时,都要查询用户的登录凭证,访问的频率非常高。
• 使用Redis缓存用户信息
1、RedisKeyUtil
2、UserService
3、LoginTicketMapper
4、LoginController
