Day26: Redis入门、开发点赞功能、开发我收到的赞的功能、重构点赞功能、开发关注、取消关注、开发关注列表、粉丝列表、重构登录功能
Redis入门
简介
- Redis是NoSQL数据库(Not only SQL)
- 值支持多种数据结构(key都是string):字符串、哈希、列表、集合、有序集合
- 把数据存在内存中,速度惊人;
- 同时也可以讲数据快照(数据备份,定时跑一次)/日志**(AOF,实时存命令)**存在硬盘上,保证数据安全性;
- Redis典型的应用场景包括:缓存、排行榜、计数器、社交网络、消息队列等。
Redis安装
- mac端使用homebrew进行安装:
brew install redis
- 安装完成后,你可以使用以下命令来启动Redis服务器:
redis-server /usr/local/etc/redis.conf
- 你也可以设置Redis作为后台服务运行:
brew services start redis
- 运行redis客户端:
iris@MateBook ~ % redis-cli 127.0.0.1:6379> select 1 OK
Redis基本使用
- 相比于python用下划线连接两个单词,redis用冒号:
- 定位数据库:(默认是0)
127.0.0.1:6379> select 1 OK 127.0.0.1:6379[1]> select 2 OK 127.0.0.1:6379[2]> select 10 OK 127.0.0.1:6379[10]> select 0
- 运算strings:
127.0.0.1:6379> set test:count 1 //插入 OK 127.0.0.1:6379> get test:count //查找 "1" 127.0.0.1:6379> incr test:count //加1 (integer) 2 127.0.0.1:6379> get test:count "2" 127.0.0.1:6379> decr test:count //减1 (integer) 1 127.0.0.1:6379> get test:count "1"
- 运算hashes(hget,hset ):可以理解为strings是key是string,value是单个的hashmap,hashes是key是string,value是hashmap的hashmap。
127.0.0.1:6379> hset test:user id 1 //指明field (integer) 1 127.0.0.1:6379> hest test:user name zhangsan (error) ERR unknown command 'hest', with args beginning with: 'test:user' 'name' 'zhangsan' 127.0.0.1:6379> hset test:user name zhangsan (integer) 1 127.0.0.1:6379> hget test:user id "1" 127.0.0.1:6379> hget test:user username //查不到返回nil (nil) 127.0.0.1:6379> hget test:user name
- 运算list:相当于一个双向容器,左近左出就是stack,左进右出就是队列。
127.0.0.1:6379> lpush test:ids 101 102 103 //l表示左,从左边依次插入101 102 103 (integer) 3 127.0.0.1:6379> llen test:ids// llen输出list长度 (integer) 3 127.0.0.1:6379> lindex test:ids 0// 0位置对应的值 "103" 127.0.0.1:6379> lindex test:ids 2 "101" 127.0.0.1:6379> lrange test:ids 0 2 //从0-2位置对应的值 1) "103" 2) "102" 3) "101" 127.0.0.1:6379> rpop test:ids //从右边出队,相当于队列 "101" 127.0.0.1:6379> rpop test:ids "102" 127.0.0.1:6379> rpop test:ids "103"
list作为栈:
127.0.0.1:6379> lpush test:ids 100 (integer) 1 127.0.0.1:6379> lpush test:ids 101 (integer) 2 127.0.0.1:6379> lpush test:ids 102 (integer) 3 127.0.0.1:6379> lpush test:ids 103 (integer) 4 127.0.0.1:6379> lpop test:ids "103" 127.0.0.1:6379> lpop test:ids "102" 127.0.0.1:6379> lpop test:ids "101" 127.0.0.1:6379> lpop test:ids "100"
- 运算set:元素无序且不能重复
127.0.0.1:6379> sadd test:teachers aaa vvv bbb cccc ddd //sadd添加元素 (integer) 5 127.0.0.1:6379> scard test:teachers //查找元素数量 (integer) 5 127.0.0.1:6379> spop test:teachers //随机弹出一个元素(可用于抽奖) "bbb" 127.0.0.1:6379> spop test:teachers "vvv" 127.0.0.1:6379> scard test:teachers (integer) 3 127.0.0.1:6379> smembers test:teachers //列出所有元素 1) "aaa" 2) "cccc" 3) "ddd"
- 运算sorted set:按分数进行排序**(跳表)**
127.0.0.1:6379> zadd test:students 10 aaa 20 bbb 30 ccc 40 ddd 50 eee //值是aaa分数是10 (integer) 5 127.0.0.1:6379> zcard test:students //查找数量 (integer) 5 127.0.0.1:6379> zscore test:students aaa //查找对应值对应的分数 "10" 127.0.0.1:6379> zscore test:students c (nil) 127.0.0.1:6379> zscore test:students ccc "30" 127.0.0.1:6379> zrank test:students ccc //查找对应值对应的分数的排名(默认升序) (integer) 2 127.0.0.1:6379> zrange test:students 0 2 //查找排名在范围内的元素 1) "aaa" 2) "bbb" 3) "ccc"
- 全局命令
127.0.0.1:6379> keys * //列举所有key 1) "test:students" 2) "test:teachers" 3) "test:user" 4) "test:count" 127.0.0.1:6379> keys test* //列出所有test开头的key 1) "test:students" 2) "test:teachers" 3) "test:user" 4) "test:count" 127.0.0.1:6379> type test:user //列出key对应的value的数据类型 hash 127.0.0.1:6379> type test:ids none 127.0.0.1:6379> exists test:user //查找是否存在key,存在1,不存在0 (integer) 1 127.0.0.1:6379> exists test:id (integer) 0 127.0.0.1:6379> del test:user //删除对应的key (integer) 1 127.0.0.1:6379> exists test:user (integer) 0 127.0.0.1:6379> expire test:students 10 //为key设置过期时间,单位为s (integer) 1 127.0.0.1:6379> keys * 1) "test:teachers" 2) "test:count"
整合Redis到Springboot
导包(xml文件)
org.springframework.boot spring-boot-starter-data-redis // 3.2.4
- 这里可以不置顶版本,maven会自动去父pom中找版本,找不到就用最新版本,我的父pom的版本是3.3.0M1
配置Redis(application.properties)
配置属性文件:
# Redis spring.data.redis.database = 11 spring.data.redis.port=6379 spring.data.redis.host=localhost
默认端口6379,指定database是哪一号
编写配置类:Configuration/RedisConfig
@Configuration public class RedisConfig { @Bean public RedisTemplate redisTemplate(RedisConnectionFactory factory){ RedisTemplate template = new RedisTemplate(); template.setConnectionFactory(factory); //序列化的方式(数据转换的方式) //设置key的序列化方式 template.setKeySerializer(RedisSerializer.string()); //设置value的序列化方式 template.setValueSerializer(RedisSerializer.json()); //设置hashes的key的序列化方式 template.setHashKeySerializer(RedisSerializer.string()); //设置hashes的value的序列化方式 template.setHashValueSerializer(RedisSerializer.json()); template.afterPropertiesSet(); return template; } }
- 主要是设置redis的序列化方式(就是查到之后我们用什么样的数据类型去接,调用RedisSerializer.string()等工具类;
- template.afterPropertiesSet(); 执行后触发修改;
- Redis建立连接需要注入RedisConnectionFactory factory 工厂
访问Redis
编写RedisTests测试类
@RunWith(SpringRunner.class) @SpringBootTest @ContextConfiguration(classes = CommunityApplication.class) public class RedisTests { @Autowired private RedisTemplate redisTemplate; @Test public void testStrings() { String redisKey = "test:count";//相当于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)); } @Test public 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")); } @Test public 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)); } @Test public void testSets() { String redisKey = "test:teachers"; redisTemplate.opsForSet().add(redisKey, "刘备", "关羽", "张飞", "赵云", "黄忠"); System.out.println(redisTemplate.opsForSet().size(redisKey)); System.out.println(redisTemplate.opsForSet().members(redisKey)); } @Test public 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)); } @Test public void testKeys() { redisTemplate.delete("test:count"); System.out.println(redisTemplate.hasKey("test:count")); redisTemplate.expire("test:user", 10, TimeUnit.SECONDS); } }
- set->set;
- lpush->leftPush
- lpop->leftPop
- hset->put
- hget->get…
上面的方法每次都要传入key,会很麻烦,有一个方法是设置绑定变量,然后操作跟之前的一样的:
//多次访问同一个key,可以减少网络开销 @Test public void testBoundOperations() { String redisKey = "test:count"; //绑定操作 BoundValueOperations boundValueOperations = redisTemplate.boundValueOps(redisKey); boundValueOperations.increment(); boundValueOperations.increment(); boundValueOperations.increment(); boundValueOperations.increment(); boundValueOperations.increment(); boundValueOperations.get(); }
Redis事务
- 事务管理比较简单;
- 启动事务后讲命令存在队列中,事务提交后统一批量的进行执行;
- 如何在Spring中启用,声明式事务不常用,使用编程式事务:
@Test public void testTransaction() { Object obj = redisTemplate.execute(new SessionCallback() { @Override public Object execute(RedisOperations operations) throws DataAccessException { String redisKey = "test:tx"; //启用事务 operations.multi(); operations.opsForSet().add(redisKey, "zhangsan"); operations.opsForSet().add(redisKey, "lisi"); operations.opsForSet().add(redisKey, "wangwu"); System.out.println(operations.opsForSet().members(redisKey)); return operations.exec(); } }); System.out.println(obj); }
这段代码是在Spring Data Redis中使用编程式事务。它使用redisTemplate.execute()方法执行一个SessionCallback,在这个回调中,它启动一个Redis事务,然后执行一系列的操作。
下面是这段代码的详细解释:
- redisTemplate.execute(new SessionCallback() {...}):这是Spring Data Redis提供的一种执行Redis操作的方式。SessionCallback是一个回调接口,它的execute方法会在Redis操作执行时被调用。
- operations.multi():这行代码启动了一个Redis事务。在事务中,所有的命令都会被序列化和缓存,然后在exec()方法被调用时一次性执行。
- operations.opsForSet().add(redisKey, "zhangsan")、operations.opsForSet().add(redisKey, "lisi")、operations.opsForSet().add(redisKey, "wangwu"):这些代码在Redis的set中添加了三个元素。这些操作都在事务中,所以它们不会立即执行,而是会在exec()方法被调用时执行。
- System.out.println(operations.opsForSet().members(redisKey)):这行代码试图打印出set的所有成员。但是因为这个操作也在事务中,所以在exec()被调用之前,它会返回null。
- return operations.exec():这行代码执行了事务,这意味着所有在multi()和exec()之间的操作都会被一次性执行。exec()方法返回一个List,其中包含了事务中每个操作的结果。
总的来说,这段代码在一个Redis事务中添加了三个元素到一个set中,然后试图打印出这个set的所有成员,但是因为打印操作也在事务中,所以它会返回null。最后,它执行了事务,并返回了事务的结果。
最后的 输出结果:
- 本来查询是空的,这是因为处在redis的事务中的查询不会被立即执行;
- 之后打印出来的内容是obj的结果,也就是事务中每个操作的结果。前面的1,1,1就是插入的时候返回的影响的行数(类似于MySQL)
开发点赞功能
点赞
- 支持对帖子、评论点赞。
- 第1次点赞,第2次取消点赞。
首页点赞数量
- 统计帖子的点赞数量。
详情页点赞数量
- 统计点赞数量。
- 显示点赞状态。
因为实时性要求很高,存到redis中,速度快。
数据访问层
(不用写。比较简单,redis类似于操作map,直接集合到业务层)
业务层
写生成key的工具类
因为会生成很多各种各样的key,写一个工具类生成key:
public class RedisKeyUtil { private static final String SPLIT = ":";//分隔符 //存实体的赞 private static final String PREFIX_ENTITY_LIKE = "like:entity"; //某个实体的赞 //key:like:entity:entityType:entityId -> value:集合set(userId),存哪些人点赞了这个实体而不是直接存数字 public static String getEntityLikeKey(int entityType, int entityId) { return PREFIX_ENTITY_LIKE + SPLIT + entityType + SPLIT + entityId; } }
- //key:{like:entity:entityType:entityId} -> value:{集合set(userId)},存哪些人点赞了这个实体而不是直接存数字
统计点赞数量
@Service public class LikeService { @Autowired private RedisTemplate redisTemplate; //点赞 public void like(int userId, int entityType, int entityId) {//userId是谁点的赞,entityType是点赞的实体类型,entityId是点赞的实体id 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);//点赞 } } }
统计查询某实体被点赞的数量
public long findEntityLikeCount(int entityType, int entityId) { String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId); return redisTemplate.opsForSet().size(entityLikeKey); }
查询某人对某实体的点赞状态(某人对某实体是否点过赞)
public int findEntityLikeStatus(int userId, int entityType, int entityId) { String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId); return redisTemplate.opsForSet().isMember(entityLikeKey, userId) ? 1 : 0; }
表现层
创建一个新的LikeController:
@Controller public class LikeController { @Autowired private LikeService likeService; @Autowired private 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 map = new HashMap(); map.put("likeCount", likeCount); map.put("likeStatus", likeStatus); return CommunityUtil.getJsonString(0, null, map); } }
- 异步请求,页面不刷新(@RequestMapping注解)
- 需要把likeCount和likeStatus传给前端。
修改HomeController(首页帖子有多少赞)
if(list != null) { for (DiscussPost post : list) { Map map = new java.util.HashMap(); map.put("post", post); map.put("user", userService.findUserById(post.getUserId())); //查询帖子的点赞数量 long likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_POST, post.getId()); map.put("likeCount", likeCount); discussPosts.add(map); } }
修改index.html:
寒江雪 发布于 2019-04-15 15:32:18- 赞 11
- |
- 回帖 7
修改帖子详情DiscussPostController
帖子点赞:
.... User user = userService.findUserById(post.getUserId()); model.addAttribute("user", user); //点赞数量 long likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_POST, discussPostId); model.addAttribute("likeCount",likeCount); //点赞状态 int likeStatus = hostHolder.getUser() == null ? 0 ://未登录默认为未点赞 likeService.findEntityLikeStatus(hostHolder.getUser().getId(), ENTITY_TYPE_POST, post.getId()); model.addAttribute("likeStatus",likeStatus);
评论点赞:
commentVo.put("user", userService.findUserById(comment.getUserId())); // 点赞数量 likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_COMMENT, comment.getId()); commentVo.put("likeCount", likeCount); // 点赞状态 likeStatus = hostHolder.getUser() == null ? 0 ://未登录默认为未点赞 likeService.findEntityLikeStatus(hostHolder.getUser().getId(), ENTITY_TYPE_COMMENT, comment.getId()); commentVo.put("likeStatus", likeStatus);
评论的评论点赞:
// 回复目标 User target = reply.getTargetId() == 0 ? null : userService.findUserById(reply.getTargetId()); // 点赞 likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_COMMENT, reply.getId()); //点赞状态 likeStatus = hostHolder.getUser() == null ? 0 ://未登录默认为未点赞 likeService.findEntityLikeStatus(hostHolder.getUser().getId(), ENTITY_TYPE_COMMENT, reply.getId()); replyVo.put("likeCount", likeCount); replyVo.put("likeStatus", likeStatus);
修改DiscussPost.html
- 赞 111
- 以用户为key,记录点赞数量
- increment(key),decrement(key)
开发个人主页
- 以用户为key,查询点赞数量
重构点赞功能
在Util中新添加生成UserKey:
//某个用户的赞 //key:like:user:userId -> value:整数,存这个用户点赞了多少个实体 public static String getUserLikeKey(int userId) { return PREFIX_USER_LIKE + SPLIT + userId; }
重写LikeService的Like函数,将被点赞的人的活动也记录上,而且我们希望活动是不会被打断的,因此需要使用事务:
//点赞 public void like(int userId, int entityType, int entityId, int entityUserId){//userId是谁点的赞,entityType是点赞的实体类型,entityId是点赞的实体id //引入事务 redisTemplate.execute(new SessionCallback() { @Override public Object execute(RedisOperations operations) throws DataAccessException { String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId); String userLikeKey = RedisKeyUtil.getUserLikeKey(entityUserId);//被点赞的用户的key //判断用户是否已经点过赞 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(); } //事务之外查询 }); }
添加查询某用户有多少赞的函数:
//查询某个用户获得的赞 public int findUserLikeCount(int userId) { String userLikeKey = RedisKeyUtil.getUserLikeKey(userId); Integer count = (Integer) redisTemplate.opsForValue().get(userLikeKey); return count == null ? 0 : count.intValue(); }
- 使用intValue把Integer转化为int(开箱)
重构Controller:
@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);//点赞操作
修改discuss-post.html
th:onclick="|like(this,2,${cvo.comment.id},${cvo.comment.userId});|"
加一个userId
(还要修改discuss.js,在前面已经给出)
开发个人主页
UserController,创建个人主页:
//个人主页 @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); return "/site/profile"; }
修改index.html:
- 使用intValue把Integer转化为int(开箱)
- 以用户为key,查询点赞数量
(评论和楼中楼同理)
开发我收到的赞的功能
累加很麻烦,添加新key比较方便
重构点赞功能
- //key:{like:entity:entityType:entityId} -> value:{集合set(userId)},存哪些人点赞了这个实体而不是直接存数字
- 统计帖子的点赞数量。
- 这里可以不置顶版本,maven会自动去父pom中找版本,找不到就用最新版本,我的父pom的版本是3.3.0M1
- 全局命令
- 运算sorted set:按分数进行排序**(跳表)**
- 运算set:元素无序且不能重复
- 运算list:相当于一个双向容器,左近左出就是stack,左进右出就是队列。
- 运算hashes(hget,hset ):可以理解为strings是key是string,value是单个的hashmap,hashes是key是string,value是hashmap的hashmap。
- 运算strings:
- 运行redis客户端:
- 你也可以设置Redis作为后台服务运行:
- 安装完成后,你可以使用以下命令来启动Redis服务器:
- mac端使用homebrew进行安装:
还没有评论,来说两句吧...