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

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-data-redis</artifactId>
  4. </dependency>

2、配置redis
application.properties

  1. #配置redis
  2. spring.redis.database=10
  3. spring.redis.host=localhost
  4. spring.redis.port=6379

3、配置类设置redis属性

  1. @Configuration
  2. public class RedisConfig {
  3. @Bean
  4. public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){
  5. RedisTemplate<String,Object> template=new RedisTemplate<>();
  6. template.setConnectionFactory(factory);
  7. //设置key的序列化方式
  8. template.setKeySerializer(RedisSerializer.string());
  9. //设置value的序列化方式
  10. template.setValueSerializer(RedisSerializer.json());
  11. //设置hashKey的序列化方式
  12. template.setHashKeySerializer(RedisSerializer.string());
  13. //设置hashValue的序列化方式
  14. template.setHashValueSerializer(RedisSerializer.json());
  15. template.afterPropertiesSet();
  16. return template;
  17. }
  18. }

4、测试各种属性

  1. @Test
  2. public void testStrings() {
  3. String redisKey = "test:count";
  4. redisTemplate.opsForValue().set(redisKey, 1);
  5. System.out.println(redisTemplate.opsForValue().get(redisKey));
  6. System.out.println(redisTemplate.opsForValue().increment(redisKey));
  7. System.out.println(redisTemplate.opsForValue().decrement(redisKey));
  8. }
  9. @Test
  10. public void testHashes() {
  11. String redisKey = "test:user";
  12. redisTemplate.opsForHash().put(redisKey, "id", 1);
  13. redisTemplate.opsForHash().put(redisKey, "username", "zhangsan");
  14. System.out.println(redisTemplate.opsForHash().get(redisKey, "id"));
  15. System.out.println(redisTemplate.opsForHash().get(redisKey, "username"));
  16. }
  17. @Test
  18. public void testLists() {
  19. String redisKey = "test:ids";
  20. redisTemplate.opsForList().leftPush(redisKey, 101);
  21. redisTemplate.opsForList().leftPush(redisKey, 102);
  22. redisTemplate.opsForList().leftPush(redisKey, 103);
  23. System.out.println(redisTemplate.opsForList().size(redisKey));
  24. System.out.println(redisTemplate.opsForList().index(redisKey, 0));
  25. System.out.println(redisTemplate.opsForList().range(redisKey, 0, 2));
  26. System.out.println(redisTemplate.opsForList().leftPop(redisKey));
  27. System.out.println(redisTemplate.opsForList().leftPop(redisKey));
  28. System.out.println(redisTemplate.opsForList().leftPop(redisKey));
  29. }
  30. @Test
  31. public void testSets() {
  32. String redisKey = "test:teachers";
  33. redisTemplate.opsForSet().add(redisKey, "刘备", "关羽", "张飞", "赵云", "诸葛亮");
  34. System.out.println(redisTemplate.opsForSet().size(redisKey));
  35. System.out.println(redisTemplate.opsForSet().pop(redisKey));
  36. System.out.println(redisTemplate.opsForSet().members(redisKey));
  37. }
  38. @Test
  39. public void testSortedSets() {
  40. String redisKey = "test:students";
  41. redisTemplate.opsForZSet().add(redisKey, "唐僧", 80);
  42. redisTemplate.opsForZSet().add(redisKey, "悟空", 90);
  43. redisTemplate.opsForZSet().add(redisKey, "八戒", 50);
  44. redisTemplate.opsForZSet().add(redisKey, "沙僧", 70);
  45. redisTemplate.opsForZSet().add(redisKey, "白龙马", 60);
  46. System.out.println(redisTemplate.opsForZSet().zCard(redisKey));
  47. System.out.println(redisTemplate.opsForZSet().score(redisKey, "八戒"));
  48. System.out.println(redisTemplate.opsForZSet().reverseRank(redisKey, "八戒"));
  49. System.out.println(redisTemplate.opsForZSet().reverseRange(redisKey, 0, 2));
  50. }
  51. @Test
  52. public void testKeys() {
  53. redisTemplate.delete("test:user");
  54. System.out.println(redisTemplate.hasKey("test:user"));
  55. redisTemplate.expire("test:students", 10, TimeUnit.SECONDS);
  56. }
  57. // 批量发送命令,节约网络开销.
  58. @Test
  59. public void testBoundOperations() {
  60. String redisKey = "test:count";
  61. BoundValueOperations operations = redisTemplate.boundValueOps(redisKey);
  62. operations.increment();
  63. operations.increment();
  64. operations.increment();
  65. operations.increment();
  66. operations.increment();
  67. System.out.println(operations.get());
  68. }
  69. // 编程式事务
  70. @Test
  71. public void testTransaction() {
  72. Object result = redisTemplate.execute(new SessionCallback() {
  73. @Override
  74. public Object execute(RedisOperations redisOperations) throws DataAccessException {
  75. String redisKey = "text:tx";
  76. // 启用事务
  77. redisOperations.multi();
  78. redisOperations.opsForSet().add(redisKey, "zhangsan");
  79. redisOperations.opsForSet().add(redisKey, "lisi");
  80. redisOperations.opsForSet().add(redisKey, "wangwu");
  81. System.out.println(redisOperations.opsForSet().members(redisKey));
  82. // 提交事务
  83. return redisOperations.exec();
  84. }
  85. });
  86. System.out.println(result);
  87. }

3、点赞功能

1、生成redis的key的工具类RedisKeyUtil

  1. public class RedisKeyUtil {
  2. private static final String SPLIT = ":";
  3. private static final String PREFIX_ENTITY_LIKE = "like:entity";
  4. // 某个实体的赞
  5. // like:entity:entityType:entityId -> set(userId)
  6. public static String getEntityLikeKey(int entityType, int entityId) {
  7. return PREFIX_ENTITY_LIKE + SPLIT + entityType + SPLIT + entityId;
  8. }
  9. }

2、查询点赞数量,点赞状态,以及某实体的点赞数量LikeService

  1. @Service
  2. public class LikeService {
  3. @Autowired
  4. private RedisTemplate redisTemplate;
  5. /**
  6. * 点赞
  7. * @param userId
  8. * @param entityType
  9. * @param entityId
  10. */
  11. public void like(int userId, int entityType, int entityId) {
  12. String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId);
  13. boolean isMember = redisTemplate.opsForSet().isMember(entityLikeKey, userId);
  14. if (isMember) {
  15. redisTemplate.opsForSet().remove(entityLikeKey, userId);
  16. } else {
  17. redisTemplate.opsForSet().add(entityLikeKey, userId);
  18. }
  19. }
  20. /**
  21. * 查询某实体点赞的数量
  22. * @param entityType
  23. * @param entityId
  24. * @return
  25. */
  26. public long findEntityLikeCount(int entityType, int entityId) {
  27. String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId);
  28. return redisTemplate.opsForSet().size(entityLikeKey);
  29. }
  30. /**
  31. * 查询某人对某实体的点赞状态
  32. * @param userId
  33. * @param entityType
  34. * @param entityId
  35. * @return
  36. */
  37. public int findEntityLikeStatus(int userId, int entityType, int entityId) {
  38. String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId);
  39. return redisTemplate.opsForSet().isMember(entityLikeKey, userId) ? 1 : 0;
  40. }
  41. }

3、LikeController

  1. @Controller
  2. public class LikeController {
  3. @Autowired
  4. private LikeService likeService;
  5. @Autowired
  6. private HostHolder hostHolder;
  7. /**
  8. * 查询点赞数量接口
  9. * @param entityType
  10. * @param entityId
  11. * @return
  12. */
  13. @PostMapping("/like")
  14. @ResponseBody
  15. public String like(int entityType, int entityId) {
  16. User user = hostHolder.getUser();
  17. // 点赞
  18. likeService.like(user.getId(), entityType, entityId);
  19. // 数量
  20. long likeCount = likeService.findEntityLikeCount(entityType, entityId);
  21. // 状态
  22. int likeStatus = likeService.findEntityLikeStatus(user.getId(), entityType, entityId);
  23. // 返回的结果
  24. Map<String, Object> map = new HashMap<>();
  25. map.put("likeCount", likeCount);
  26. map.put("likeStatus", likeStatus);
  27. return CommunityUtil.getJSONString(0, null, map);
  28. }
  29. }

4、页面(略)
index.html
discuss-detail.html
discuss.js

4、我收到的赞的功能

1、RedisKeyUtil中增加新的key

  1. public class RedisKeyUtil {
  2. private static final String SPLIT = ":";
  3. private static final String PREFIX_ENTITY_LIKE = "like:entity";
  4. private static final String PREFIX_USER_LIKE = "like:user";
  5. // 某个实体的赞
  6. // like:entity:entityType:entityId -> set(userId)
  7. public static String getEntityLikeKey(int entityType, int entityId) {
  8. return PREFIX_ENTITY_LIKE + SPLIT + entityType + SPLIT + entityId;
  9. }
  10. // 某个用户的赞
  11. // like:user:userId -> int
  12. public static String getUserLikeKey(int userId) {
  13. return PREFIX_USER_LIKE + SPLIT + userId;
  14. }
  15. }

2、LikeService

  1. /**
  2. * 点赞
  3. * @param userId
  4. * @param entityType
  5. * @param entityId
  6. */
  7. public void like(int userId, int entityType, int entityId, int entityUserId) {
  8. redisTemplate.execute(new SessionCallback() {
  9. @Override
  10. public Object execute(RedisOperations operations) throws DataAccessException {
  11. String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId);
  12. String userLikeKey = RedisKeyUtil.getUserLikeKey(entityUserId);
  13. boolean isMember = operations.opsForSet().isMember(entityLikeKey, userId);
  14. operations.multi();
  15. if (isMember) {
  16. operations.opsForSet().remove(entityLikeKey, userId);
  17. operations.opsForValue().decrement(userLikeKey);
  18. } else {
  19. operations.opsForSet().add(entityLikeKey, userId);
  20. operations.opsForValue().increment(userLikeKey);
  21. }
  22. return operations.exec();
  23. }
  24. });
  25. }
  26. /**
  27. * 查询某个用户获得的赞
  28. * @param userId
  29. * @return
  30. */
  31. public int findUserLikeCount(int userId) {
  32. String userLikeKey = RedisKeyUtil.getUserLikeKey(userId);
  33. Integer count = (Integer) redisTemplate.opsForValue().get(userLikeKey);
  34. return count == null ? 0 : count.intValue();
  35. }

3、LikeController

  1. public String like(int entityType, int entityId,int entityUserId) {
  2. User user = hostHolder.getUser();
  3. // 点赞
  4. likeService.like(user.getId(), entityType, entityId, entityUserId);

4、UserController

  1. /**
  2. * 个人主页接口
  3. * @param userId
  4. * @param model
  5. * @return
  6. */
  7. @GetMapping("/profile/{userId}")
  8. public String getProfilePage(@PathVariable("userId") int userId, Model model) {
  9. User user = userService.selectUserById(userId);
  10. if (user == null) {
  11. throw new RuntimeException("该用户不存在!");
  12. }
  13. // 用户
  14. model.addAttribute("user", user);
  15. // 点赞数量
  16. int likeCount = likeService.findUserLikeCount(userId);
  17. model.addAttribute("likeCount", likeCount);
  18. return "/site/profile";
  19. }

5、页面(略)
index.html
discuss-detail.html
discuss.js
profile.html

5、关注与被关注功能

1、RedisKeyUtil

  1. private static final String PREFIX_FOLLOWEE = "followee";
  2. private static final String PREFIX_FOLLOWER = "follower";
  3. // 某个用户关注的实体
  4. // followee:userId:entityType -> zset(entityId,now)
  5. public static String getFolloweeKey(int userId, int entityType) {
  6. return PREFIX_FOLLOWEE + SPLIT + userId + SPLIT + entityType;
  7. }
  8. // 某个实体拥有的粉丝
  9. // follower:entityType:entityId -> zset(userId,now)
  10. public static String getFollowerKey(int entityType, int entityId) {
  11. return PREFIX_FOLLOWER + SPLIT + entityType + SPLIT + entityId;
  12. }

2、CommunityConstant

  1. /**
  2. * 实体类型: 用户
  3. */
  4. int ENTITY_TYPE_USER = 3;

3、FollowService

  1. @Service
  2. public class FollowService {
  3. @Autowired
  4. private RedisTemplate redisTemplate;
  5. /**
  6. * 关注
  7. * @param userId
  8. * @param entityType
  9. * @param entityId
  10. */
  11. public void follow(int userId, int entityType, int entityId) {
  12. redisTemplate.execute(new SessionCallback() {
  13. @Override
  14. public Object execute(RedisOperations operations) throws DataAccessException {
  15. String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType);
  16. String followerKey = RedisKeyUtil.getFollowerKey(entityType, entityId);
  17. operations.multi();
  18. operations.opsForZSet().add(followeeKey, entityId, System.currentTimeMillis());
  19. operations.opsForZSet().add(followerKey, userId, System.currentTimeMillis());
  20. return operations.exec();
  21. }
  22. });
  23. }
  24. /**
  25. * 取消关注
  26. * @param userId
  27. * @param entityType
  28. * @param entityId
  29. */
  30. public void unfollow(int userId, int entityType, int entityId) {
  31. redisTemplate.execute(new SessionCallback() {
  32. @Override
  33. public Object execute(RedisOperations operations) throws DataAccessException {
  34. String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType);
  35. String followerKey = RedisKeyUtil.getFollowerKey(entityType, entityId);
  36. operations.multi();
  37. operations.opsForZSet().remove(followeeKey, entityId);
  38. operations.opsForZSet().remove(followerKey, userId);
  39. return operations.exec();
  40. }
  41. });
  42. }
  43. /**
  44. * 查询关注的实体的数量
  45. * @param userId
  46. * @param entityType
  47. * @return
  48. */
  49. public long findFolloweeCount(int userId, int entityType) {
  50. String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType);
  51. return redisTemplate.opsForZSet().zCard(followeeKey);
  52. }
  53. /**
  54. * 查询实体的粉丝的数量
  55. * @param entityType
  56. * @param entityId
  57. * @return
  58. */
  59. public long findFollowerCount(int entityType, int entityId) {
  60. String followerKey = RedisKeyUtil.getFollowerKey(entityType, entityId);
  61. return redisTemplate.opsForZSet().zCard(followerKey);
  62. }
  63. /**
  64. * 查询当前用户是否已关注该实体
  65. * @param userId
  66. * @param entityType
  67. * @param entityId
  68. * @return
  69. */
  70. public boolean hasFollowed(int userId, int entityType, int entityId) {
  71. String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType);
  72. return redisTemplate.opsForZSet().score(followeeKey, entityId) != null;
  73. }
  74. }

4、UserController实现CommuniyuConstant接口

  1. // 关注数量
  2. long followeeCount = followService.findFolloweeCount(userId, ENTITY_TYPE_USER);
  3. model.addAttribute("followeeCount", followeeCount);
  4. // 粉丝数量
  5. long followerCount = followService.findFollowerCount(ENTITY_TYPE_USER, userId);
  6. model.addAttribute("followerCount", followerCount);
  7. // 是否已关注
  8. boolean hasFollowed = false;
  9. if (hostHolder.getUser() != null) {
  10. hasFollowed = followService.hasFollowed(hostHolder.getUser().getId(), ENTITY_TYPE_USER, userId);
  11. }
  12. model.addAttribute("hasFollowed", hasFollowed);

5、FollowController

  1. @Controller
  2. public class FollowController {
  3. @Autowired
  4. private FollowService followService;
  5. @Autowired
  6. private HostHolder hostHolder;
  7. /**
  8. * 关注接口
  9. * @param entityType
  10. * @param entityId
  11. * @return
  12. */
  13. @RequestMapping(path = "/follow", method = RequestMethod.POST)
  14. @ResponseBody
  15. public String follow(int entityType, int entityId) {
  16. User user = hostHolder.getUser();
  17. followService.follow(user.getId(), entityType, entityId);
  18. return CommunityUtil.getJSONString(0, "已关注!");
  19. }
  20. /**
  21. * 取消关注接口
  22. * @param entityType
  23. * @param entityId
  24. * @return
  25. */
  26. @RequestMapping(path = "/unfollow", method = RequestMethod.POST)
  27. @ResponseBody
  28. public String unfollow(int entityType, int entityId) {
  29. User user = hostHolder.getUser();
  30. followService.unfollow(user.getId(), entityType, entityId);
  31. return CommunityUtil.getJSONString(0, "已取消关注!");
  32. }
  33. }

6、页面(略)
index.html
profile.html
discuss.html

6、关注列表与粉丝列表功能

• 业务层

  • 查询某个用户关注的人,支持分页。
  • 查询某个用户的粉丝,支持分页。

• 表现层

  • 处理“查询关注的人”、“查询粉丝”请求。
  • 编写“查询关注的人”、“查询粉丝”模板。

1、FollowService

  1. /**
  2. * 查询某用户关注的人
  3. * @param userId
  4. * @param offset
  5. * @param limit
  6. * @return
  7. */
  8. public List<Map<String, Object>> findFollowees(int userId, int offset, int limit) {
  9. String followeeKey = RedisKeyUtil.getFolloweeKey(userId, ENTITY_TYPE_USER);
  10. Set<Integer> targetIds = redisTemplate.opsForZSet().reverseRange(followeeKey, offset, offset + limit - 1);
  11. if (targetIds == null) {
  12. return null;
  13. }
  14. List<Map<String, Object>> list = new ArrayList<>();
  15. for (Integer targetId : targetIds) {
  16. Map<String, Object> map = new HashMap<>();
  17. User user = userService.selectUserById(targetId);
  18. map.put("user", user);
  19. Double score = redisTemplate.opsForZSet().score(followeeKey, targetId);
  20. map.put("followTime", new Date(score.longValue()));
  21. list.add(map);
  22. }
  23. return list;
  24. }
  25. /**
  26. * 查询某用户的粉丝
  27. * @param userId
  28. * @param offset
  29. * @param limit
  30. * @return
  31. */
  32. public List<Map<String, Object>> findFollowers(int userId, int offset, int limit) {
  33. String followerKey = RedisKeyUtil.getFollowerKey(ENTITY_TYPE_USER, userId);
  34. Set<Integer> targetIds = redisTemplate.opsForZSet().reverseRange(followerKey, offset, offset + limit - 1);
  35. if (targetIds == null) {
  36. return null;
  37. }
  38. List<Map<String, Object>> list = new ArrayList<>();
  39. for (Integer targetId : targetIds) {
  40. Map<String, Object> map = new HashMap<>();
  41. User user = userService.selectUserById(targetId);
  42. map.put("user", user);
  43. Double score = redisTemplate.opsForZSet().score(followerKey, targetId);
  44. map.put("followTime", new Date(score.longValue()));
  45. list.add(map);
  46. }
  47. return list;
  48. }

2、FollowController

  1. /**
  2. * 查询关注列表接口
  3. * @param userId
  4. * @param page
  5. * @param model
  6. * @return
  7. */
  8. @GetMapping("/followees/{userId}")
  9. public String getFollowees(@PathVariable("userId") int userId, Page page, Model model) {
  10. User user = userService.selectUserById(userId);
  11. if (user == null) {
  12. throw new RuntimeException("该用户不存在!");
  13. }
  14. model.addAttribute("user", user);
  15. page.setLimit(5);
  16. page.setPath("/followees/" + userId);
  17. page.setRows((int) followService.findFolloweeCount(userId, ENTITY_TYPE_USER));
  18. List<Map<String, Object>> userList = followService.findFollowees(userId, page.getOffset(), page.getLimit());
  19. if (userList != null) {
  20. for (Map<String, Object> map : userList) {
  21. User u = (User) map.get("user");
  22. map.put("hasFollowed", hasFollowed(u.getId()));
  23. }
  24. }
  25. model.addAttribute("users", userList);
  26. return "/site/followee";
  27. }
  28. /**
  29. * 查询粉丝列表接口
  30. * @param userId
  31. * @param page
  32. * @param model
  33. * @return
  34. */
  35. @GetMapping("/followers/{userId}")
  36. public String getFollowers(@PathVariable("userId") int userId, Page page, Model model) {
  37. User user = userService.selectUserById(userId);
  38. if (user == null) {
  39. throw new RuntimeException("该用户不存在!");
  40. }
  41. model.addAttribute("user", user);
  42. page.setLimit(5);
  43. page.setPath("/followers/" + userId);
  44. page.setRows((int) followService.findFollowerCount(ENTITY_TYPE_USER, userId));
  45. List<Map<String, Object>> userList = followService.findFollowers(userId, page.getOffset(), page.getLimit());
  46. if (userList != null) {
  47. for (Map<String, Object> map : userList) {
  48. User u = (User) map.get("user");
  49. map.put("hasFollowed", hasFollowed(u.getId()));
  50. }
  51. }
  52. model.addAttribute("users", userList);
  53. return "/site/follower";
  54. }
  55. private boolean hasFollowed(int userId) {
  56. if (hostHolder.getUser() == null) {
  57. return false;
  58. }
  59. return followService.hasFollowed(hostHolder.getUser().getId(), ENTITY_TYPE_USER, userId);
  60. }

3、页面(略)
discuss-detail.html
followee.html
follower.html
profile.html

7、优化登录模块

使用Redis存储验证码

  • 验证码需要频繁的访问与刷新,对性能要求较高。
  • 验证码不需永久保存,通常在很短的时间后就会失效。

分布式部署时,存在Session共享的问题。
• 使用Redis存储登录凭证
处理每次请求时,都要查询用户的登录凭证,访问的频率非常高。
• 使用Redis缓存用户信息
1、RedisKeyUtil
2、UserService
3、LoginTicketMapper
4、LoginController