《redis实战》之redis文章投票
前言
最近几年,越来越多的网站开始提供部分对网页链接、文章或问题进行投票的功能,这些网站会根据文章的发布时间和文章获得的投票数量计算出一个评分,然后按照这个评分来决定如何排序和展示文章。
一、Springboot整合redis
https://www.cnblogs.com/minmin123/p/13595734.html
二、对文章进行投票
要构建一个文章投票网站,我们首先要做的就是为了这个网站设置一些数值和限制条件:如果文章获得至少200张支持票,那么网站就认为这篇文章是一篇有趣的文章; 加入这个网站每天发布1000篇文章,而其中的50篇符合网站对有趣文章的要求,那么网站要做的就是把50篇文章放到文章列表前100位至少一天;
为了产生一个能够随着时间流逝而不断减少的评分,程序需要根据文章的发布时间和当前时间来计算文章的评分,具体计算方法为:将文章得到的支持票数量乘以一个常量,然后加上文章的发布时间,得出的结果就是文章的评分。
计算评分与支持票数量相乘的常量是432 (一天的秒数【86400】除以一天所需的支持票数量200)文章每获得一张支持票,程序就需要将文章的评分增加432分
1、准备数据
把文章标题,网址,发布文章的用户、文章的发布时间、文章的投票数量存入一个散列中
time:存时间戳
poster: 存用户的id
工具类代码:
1 import org.springframework.data.redis.core.RedisTemplate; 2 import org.springframework.data.redis.core.StringRedisTemplate; 3 import org.springframework.stereotype.Component; 4 import org.springframework.util.CollectionUtils; 5 6 import javax.annotation.Resource; 7 import java.util.List; 8 import java.util.Map; 9 import java.util.Set; 10 import java.util.concurrent.TimeUnit; 11 12 @Component 13 public final class RedisUtil { 14 15 @Resource 16 private RedisTemplate<String, Object> redisTemplate; 17 18 19 public Set<String> keys(String keys){ 20 try { 21 return redisTemplate.keys(keys); 22 }catch (Exception e){ 23 e.printStackTrace(); 24 return null; 25 } 26 } 27 28 /** 29 * 指定缓存失效时间 30 * @param key 键 31 * @param time 时间(秒) 32 * @return boolean 33 */ 34 public boolean expire(String key, long time) { 35 try { 36 if (time > 0) { 37 redisTemplate.expire(key, time, TimeUnit.SECONDS); 38 } 39 return true; 40 } catch (Exception e) { 41 e.printStackTrace(); 42 return false; 43 } 44 } 45 /** 46 * 根据key 获取过期时间 47 * @param key 键 不能为null 48 * @return 时间(秒) 返回0代表为永久有效 49 */ 50 public long getExpire(String key) { 51 return redisTemplate.getExpire(key, TimeUnit.SECONDS); 52 } 53 /** 54 * 判断key是否存在 55 * @param key 键 56 * @return true 存在 false不存在 57 */ 58 public boolean hasKey(String key) { 59 try { 60 return redisTemplate.hasKey(key); 61 } catch (Exception e) { 62 e.printStackTrace(); 63 return false; 64 } 65 } 66 /** 67 * 删除缓存 68 * @param key 可以传一个值 或多个 69 */ 70 @SuppressWarnings("unchecked") 71 public void del(String... key) { 72 if (key != null && key.length > 0) { 73 if (key.length == 1) { 74 redisTemplate.delete(key[0]); 75 } else { 76 redisTemplate.delete(CollectionUtils.arrayToList(key)); 77 } 78 } 79 } 80 /** 81 * 普通缓存获取 82 * @param key 键 83 * @return 值 84 */ 85 public Object get(String key) { 86 return key == null ? null : redisTemplate.opsForValue().get(key); 87 } 88 /** 89 * 普通缓存放入 90 * @param key 键 91 * @param value 值 92 * @return true成功 false失败 93 */ 94 public boolean set(String key, Object value) { 95 try { 96 redisTemplate.opsForValue().set(key, value); 97 return true; 98 } catch (Exception e) { 99 e.printStackTrace(); 100 return false; 101 } 102 } 103 /** 104 * 普通缓存放入, 不存在放入,存在返回 105 * @param key 键 106 * @param value 值 107 * @return true成功 false失败 108 */ 109 public boolean setnx(String key, Object value) { 110 try { 111 redisTemplate.opsForValue().setIfAbsent(key,value); 112 return true; 113 } catch (Exception e) { 114 e.printStackTrace(); 115 return false; 116 } 117 } 118 /** 119 * 普通缓存放入并设置时间 120 * @param key 键 121 * @param value 值 122 * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期 123 * @return true成功 false 失败 124 */ 125 public boolean set(String key, Object value, long time) { 126 try { 127 if (time > 0) { 128 redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS); 129 } else { 130 set(key, value); 131 } 132 return true; 133 } catch (Exception e) { 134 e.printStackTrace(); 135 return false; 136 } 137 } 138 139 /** 140 * 普通缓存放入并设置时间,不存在放入,存在返回 141 * @param key 键 142 * @param value 值 143 * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期 144 * @return true成功 false 失败 145 */ 146 public boolean setnx(String key, Object value, long time) { 147 try { 148 if (time > 0) { 149 redisTemplate.opsForValue().setIfAbsent(key, value, time, TimeUnit.SECONDS); 150 } else { 151 set(key, value); 152 } 153 return true; 154 } catch (Exception e) { 155 e.printStackTrace(); 156 return false; 157 } 158 } 159 160 /** 161 * 递增 162 * @param key 键 163 * @param delta 要增加几(大于0) 164 * @return long 165 */ 166 public long incr(String key, long delta) { 167 if (delta < 0) { 168 throw new RuntimeException("递增因子必须大于0"); 169 } 170 return redisTemplate.opsForValue().increment(key, delta); 171 } 172 /** 173 * 递增 174 * @param key 键 175 * @param delta 要增加几(大于0) 176 * @return double 177 */ 178 public Double zincrby(String key,Object value, long delta) { 179 if (delta < 0) { 180 throw new RuntimeException("递增因子必须大于0"); 181 } 182 return redisTemplate.opsForZSet().incrementScore(key,value,delta); 183 } 184 /** 185 * 递减 186 * @param key 键 187 * @param delta 要减少几(小于0) 188 * @return long 189 */ 190 public long decr(String key, long delta) { 191 if (delta < 0) { 192 throw new RuntimeException("递减因子必须大于0"); 193 } 194 return redisTemplate.opsForValue().increment(key, -delta); 195 } 196 /** 197 * HashGet 198 * @param key 键 不能为null 199 * @param item 项 不能为null 200 * @return 值 201 */ 202 public Object hget(String key, String item) { 203 return redisTemplate.opsForHash().get(key, item); 204 } 205 /** 206 * HashGet 207 * @param key 键 不能为null 208 * @param item 项 不能为null 209 * @return 值 210 */ 211 public Object zget(String key, String item) { 212 return redisTemplate.opsForZSet().score(key,item); 213 } 214 /** 215 * HashGet 216 * @param key 键 不能为null 217 * @param min max 218 * @return 值 219 */ 220 public Set<Object> zrangeByScorce(String key, double min, double max) { 221 return redisTemplate.opsForZSet().rangeByScore(key,min,max); 222 } 223 /** 224 * 获取hashKey对应的所有键值 225 * @param key 键 226 * @return 对应的多个键值 227 */ 228 public Map<Object, Object> hmget(String key) { 229 return redisTemplate.opsForHash().entries(key); 230 } 231 /** 232 * HashSet 233 * @param key 键 234 * @param map 对应多个键值 235 * @return true 成功 false 失败 236 */ 237 public boolean hmset(String key, Map<String, Object> map) { 238 try { 239 redisTemplate.opsForHash().putAll(key, map); 240 return true; 241 } catch (Exception e) { 242 e.printStackTrace(); 243 return false; 244 } 245 } 246 /** 247 * HashSet 并设置时间 248 * @param key 键 249 * @param map 对应多个键值 250 * @param time 时间(秒) 251 * @return true成功 false失败 252 */ 253 public boolean hmset(String key, Map<String, Object> map, long time) { 254 try { 255 redisTemplate.opsForHash().putAll(key, map); 256 if (time > 0) { 257 expire(key, time); 258 } 259 return true; 260 } catch (Exception e) { 261 e.printStackTrace(); 262 return false; 263 } 264 } 265 /** 266 * 向一张hash表中放入数据,如果不存在将创建 267 * @param key 键 268 * @param item 项 269 * @param value 值 270 * @return true 成功 false失败 271 */ 272 public boolean hset(String key, String item, Object value) { 273 try { 274 redisTemplate.opsForHash().put(key, item, value); 275 return true; 276 } catch (Exception e) { 277 e.printStackTrace(); 278 return false; 279 } 280 } 281 282 /** 283 * 向一张hash表中放入数据,如果不存在将创建 284 * @param key 键 285 * @param item 项 286 * @param value 值 287 * @return true 成功 false失败 288 */ 289 public boolean zset(String key, String item, Double value) { 290 try { 291 redisTemplate.opsForZSet().add(key,item,value); 292 return true; 293 } catch (Exception e) { 294 e.printStackTrace(); 295 return false; 296 } 297 } 298 /** 299 * 向一张hash表中放入数据,如果不存在将创建 300 * @param key 键 301 * @param item 项 302 * @param value 值 303 * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间 304 * @return true 成功 false失败 305 */ 306 public boolean hset(String key, String item, Object value, long time) { 307 try { 308 redisTemplate.opsForHash().put(key, item, value); 309 if (time > 0) { 310 expire(key, time); 311 } 312 return true; 313 } catch (Exception e) { 314 e.printStackTrace(); 315 return false; 316 } 317 } 318 /** 319 * 删除hash表中的值 320 * @param key 键 不能为null 321 * @param item 项 可以使多个 不能为null 322 */ 323 public void hdel(String key, Object... item) { 324 redisTemplate.opsForHash().delete(key, item); 325 } 326 /** 327 * 判断hash表中是否有该项的值 328 * @param key 键 不能为null 329 * @param item 项 不能为null 330 * @return true 存在 false不存在 331 */ 332 public boolean hHasKey(String key, String item) { 333 return redisTemplate.opsForHash().hasKey(key, item); 334 } 335 /** 336 * 判断hash表中是否有该项的值 337 * @param key 键 不能为null 338 * @param item 项 不能为null 339 * @return true 存在 false不存在 340 */ 341 public boolean sIsMember(String key, String item) { 342 return redisTemplate.opsForSet().isMember(key,item); 343 } 344 /** 345 * hash递增 如果不存在,就会创建一个 并把新增后的值返回 346 * @param key 键 347 * @param item 项 348 * @param by 要增加几(大于0) 349 * @return double 350 */ 351 public double hincr(String key, String item, double by) { 352 return redisTemplate.opsForHash().increment(key, item, by); 353 } 354 /** 355 * hash递减 356 * @param key 键 357 * @param item 项 358 * @param by 要减少记(小于0) 359 * @return double 360 */ 361 public double hdecr(String key, String item, double by) { 362 return redisTemplate.opsForHash().increment(key, item, -by); 363 } 364 /** 365 * 根据key获取Set中的所有值 366 * @param key 键 367 * @return Set<Object> 368 */ 369 public Set<Object> sGet(String key) { 370 try { 371 return redisTemplate.opsForSet().members(key); 372 } catch (Exception e) { 373 e.printStackTrace(); 374 return null; 375 } 376 } 377 /** 378 * 根据value从一个set中查询,是否存在 379 * @param key 键 380 * @param value 值 381 * @return true 存在 false不存在 382 */ 383 public boolean sHasKey(String key, Object value) { 384 try { 385 return redisTemplate.opsForSet().isMember(key, value); 386 } catch (Exception e) { 387 e.printStackTrace(); 388 return false; 389 } 390 } 391 /** 392 * 将数据放入set缓存 393 * @param key 键 394 * @param values 值 可以是多个 395 * @return 成功个数 396 */ 397 public long sSet(String key, Object... values) { 398 try { 399 return redisTemplate.opsForSet().add(key, values); 400 } catch (Exception e) { 401 e.printStackTrace(); 402 return 0; 403 } 404 } 405 /** 406 * 将set数据放入缓存 407 * @param key 键 408 * @param time 时间(秒) 409 * @param values 值 可以是多个 410 * @return 成功个数 411 */ 412 public long sSetAndTime(String key, long time, Object... values) { 413 try { 414 Long count = redisTemplate.opsForSet().add(key, values); 415 if (time > 0) 416 expire(key, time); 417 return count; 418 } catch (Exception e) { 419 e.printStackTrace(); 420 return 0; 421 } 422 } 423 /** 424 * 获取set缓存的长度 425 * @param key 键 426 * @return long 427 */ 428 public long sGetSetSize(String key) { 429 try { 430 return redisTemplate.opsForSet().size(key); 431 } catch (Exception e) { 432 e.printStackTrace(); 433 return 0; 434 } 435 } 436 /** 437 * 移除值为value的 438 * @param key 键 439 * @param values 值 可以是多个 440 * @return 移除的个数 441 */ 442 public long setRemove(String key, Object... values) { 443 try { 444 Long count = redisTemplate.opsForSet().remove(key, values); 445 return count; 446 } catch (Exception e) { 447 e.printStackTrace(); 448 return 0; 449 } 450 } 451 // ===============================list================================= 452 /** 453 * 获取list缓存的内容 454 * @param key 键 455 * @param start 开始 456 * @param end 结束 0 到 -1代表所有值 457 * @return List<Object> 458 */ 459 public List<Object> lGet(String key, long start, long end) { 460 try { 461 return redisTemplate.opsForList().range(key, start, end); 462 } catch (Exception e) { 463 e.printStackTrace(); 464 return null; 465 } 466 } 467 /** 468 * 获取list缓存的长度 469 * @param key 键 470 * @return long 471 */ 472 public long lGetListSize(String key) { 473 try { 474 return redisTemplate.opsForList().size(key); 475 } catch (Exception e) { 476 e.printStackTrace(); 477 return 0; 478 } 479 } 480 /** 481 * 通过索引 获取list中的值 482 * @param key 键 483 * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推 484 * @return Object 485 */ 486 public Object lGetIndex(String key, long index) { 487 try { 488 return redisTemplate.opsForList().index(key, index); 489 } catch (Exception e) { 490 e.printStackTrace(); 491 return null; 492 } 493 } 494 /** 495 * 将list放入缓存 496 * @param key 键 497 * @param value 值 498 * @return boolean 499 */ 500 public boolean lSet(String key, Object value) { 501 try { 502 redisTemplate.opsForList().rightPush(key, value); 503 return true; 504 } catch (Exception e) { 505 e.printStackTrace(); 506 return false; 507 } 508 } 509 /** 510 * 将list放入缓存 511 * @param key 键 512 * @param value 值 513 * @param time 时间(秒) 514 * @return boolean 515 */ 516 public boolean lSet(String key, Object value, long time) { 517 try { 518 redisTemplate.opsForList().rightPush(key, value); 519 if (time > 0) 520 expire(key, time); 521 return true; 522 } catch (Exception e) { 523 e.printStackTrace(); 524 return false; 525 } 526 } 527 /** 528 * 将list放入缓存 529 * @param key 键 530 * @param value 值 531 * @return boolean 532 */ 533 public boolean lSet(String key, List<Object> value) { 534 try { 535 redisTemplate.opsForList().rightPushAll(key, value); 536 return true; 537 } catch (Exception e) { 538 e.printStackTrace(); 539 return false; 540 } 541 } 542 /** 543 * 将list放入缓存 544 * 545 * @param key 键 546 * @param value 值 547 * @param time 时间(秒) 548 * @return boolean 549 */ 550 public boolean lSet(String key, List<Object> value, long time) { 551 try { 552 redisTemplate.opsForList().rightPushAll(key, value); 553 if (time > 0) 554 expire(key, time); 555 return true; 556 } catch (Exception e) { 557 e.printStackTrace(); 558 return false; 559 } 560 } 561 /** 562 * 根据索引修改list中的某条数据 563 * @param key 键 564 * @param index 索引 565 * @param value 值 566 * @return boolean 567 */ 568 public boolean lUpdateIndex(String key, long index, Object value) { 569 try { 570 redisTemplate.opsForList().set(key, index, value); 571 return true; 572 } catch (Exception e) { 573 e.printStackTrace(); 574 return false; 575 } 576 } 577 /** 578 * 移除N个值为value 579 * @param key 键 580 * @param count 移除多少个 581 * @param value 值 582 * @return 移除的个数 583 */ 584 public long lRemove(String key, long count, Object value) { 585 try { 586 Long remove = redisTemplate.opsForList().remove(key, count, value); 587 return remove; 588 } catch (Exception e) { 589 e.printStackTrace(); 590 return 0; 591 } 592 } 593 /** 594 * 移除N个值为value 595 * @param key 键 596 * @param count 移除多少个 597 * @param value 值 598 * @return 移除的个数 599 */ 600 public long zscore(String key, long count, Object value) { 601 try { 602 Long remove = redisTemplate.opsForList().remove(key, count, value); 603 return remove; 604 } catch (Exception e) { 605 e.printStackTrace(); 606 return 0; 607 } 608 } 609 }
参考代码:
@Autowired
private RedisUtil redisUtil;
@Test public void test1(){ List<ActiveBO> list = new ArrayList<>(); ActiveBO activeBO = new ActiveBO(); activeBO.setId(1L); activeBO.setTitle("肖申克的救赎"); activeBO.setLink("https://www.cnblogs.com/minmin123/p/13595734.html"); activeBO.setPoster("user01"); activeBO.setVotes(0); ActiveBO activeBO1 = new ActiveBO(); activeBO1.setId(1L); activeBO1.setTitle("如何提升工作效率"); activeBO1.setLink("https://www.baidu.com"); activeBO1.setPoster("user02"); activeBO1.setVotes(0); list.add(activeBO); list.add(activeBO1); Integer i = 0; for (ActiveBO activeBO0 :list) { redisUtil.hset("active:"+i, "title", activeBO0.getTitle()); redisUtil.hset("active:"+i, "link", activeBO0.getLink()); redisUtil.hset("active:"+i, "poster", activeBO0.getPoster()); redisUtil.hset("active:"+i, "time",System.currentTimeMillis()); redisUtil.hset("active:"+i, "votes", activeBO0.getVotes()); i++; } }
我们的文章投票网站使用两个有序集合来有序地存储文章;
发布时间:用时间戳
注意:这个用的redis的有序集合zset
参考代码:
1 @Test 2 public void test3(){ 3 redisUtil.zset("time","active:0",1598945519501D); 4 redisUtil.zset("time","active:1",1598945519511D); 5 } 6 @Test 7 public void test4(){ 8 redisUtil.zset("score","active:0",0D); 9 redisUtil.zset("score","active:1",0D); 10 }
为了防止用户对同一篇文章进行多次投票,网站需要为每篇文章记录一个已投票用户名单。创建一个集合来存储。
参考代码:
1 public void test5(){ 2 redisUtil.sSet("voted:0","user03"); 3 redisUtil.sSet("voted:0","user04"); 4 }
为了节约内存,我们规定当一篇文章发布期满一周后,用户将不能再对他进行投票,文章的评分将被固定下来,而记录文章已经投票用户名单的集合也会被删除。
实现对文章投票
1、voted 中增加点赞用户Id
2、评分 增加432分
3、存储文章的投票数量+1
参考代码:
1 private static final Long WEEK_SENDS = 7 * 86400L; 2 private static final Long VOTE_SCORE = 432L; 3 @Autowired 4 RedisUtil redisUtil; 5 6 /** 7 * 对文章进行投票 8 * @param activeId 文章id 9 * @param userId 用户id 10 * @return boolean 是否成功 11 */ 12 public boolean articleVote(String activeId,String userId) { 13 14 15 //1、查看文章是否发表是否已经超过一周 16 Object time = redisUtil.zget("time", "active"+":" + activeId); 17 java.text.NumberFormat nf = java.text.NumberFormat.getInstance(); 18 nf.setGroupingUsed(false); 19 String format = nf.format(time); 20 //2.Long.ValueOf(“String”)与Long.parseLong(“String”)的区别 21 //Long.ValueOf(“String”)返回Long包装类型 22 //Long.parseLong(“String”)返回long基本数据类型 23 24 if (System.currentTimeMillis() - Long.parseLong(format) < WEEK_SENDS) { 25 return false; 26 } 27 28 // 2、用户点击支持,判断该用户时候已经点击过 //3、把用户放在voted中, 29 String voted ="voted"+ ":" + activeId; 30 if (redisUtil.sSet(voted,userId) == 1) { 31 //用户以前没有点赞过 32 33 //4、文章的score分值增加432(常量) 34 redisUtil.zincrby("score", "active"+":" + activeId, VOTE_SCORE); 35 36 //5、用户active:1中 votes 的数值+1 37 redisUtil.hincr( "active"+":" + activeId, "votes", 1); 38 39 } 40 return true; 41 }
测试代码:
activeService.articleVote("2020090211184300005","user:000007");
运行效果如下: