当前位置:网站首页>[Niuke discussion area] Chapter 4: redis
[Niuke discussion area] Chapter 4: redis
2022-06-28 01:38:00 【Yard animals also have dreams】
Catalog
1. Redis introduction
- Redis It's based on key value pairs NoSQL database , Its value supports a variety of data structures : character string (strings)、 Hash (hashes)、 list (lists)、 aggregate (sets)、 Ordered set (sorted sets) etc. .
- Redis Store all data in memory , So its read-write performance is amazing . meanwhile ,Redis You can also save the data in memory to the hard disk in the form of snapshot or log , To ensure data security .
- Redis Typical application scenarios include : cache 、 Ranking List 、 Counter 、 Social networks 、 Message queuing, etc
https://redis.io
https://github.com/microsoftarchive/redis
install Redis
double-click “Redis-x64-3.2.100.msi”, Set the installation directory to the environment variable , open cmd, Input redis-cli
, Connect Redis:
String Type of access
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
(integer) 2
127.0.0.1:6379> decr test:count
(integer) 1
Hash type access
127.0.0.1:6379> hset test:user id 1
(integer) 1
127.0.0.1:6379> hset test:user username zhangsan
(integer) 1
127.0.0.1:6379> hget test:user id
"1"
127.0.0.1:6379> hget test:user username
"zhangsan"
list Type of access
127.0.0.1:6379> lpush test:ids 101 102 103
(integer) 3
127.0.0.1:6379> llen test:ids
(integer) 3
127.0.0.1:6379> lindex test:ids 0
"103"
127.0.0.1:6379> lindex test:ids 2
"101"
127.0.0.1:6379> lrange test:ids 0 2
1) "103"
2) "102"
3) "101"
127.0.0.1:6379> rpop test:ids
"101"
Access to collection types
disorder scard
Count the number of elements in the set spop
Pop an element at random smembers
View the remaining elements of the collection
127.0.0.1:6379> sadd test:teachers aaa bbb ccc ddd eee
(integer) 5
127.0.0.1:6379> scard test:teachers
(integer) 5
127.0.0.1:6379> spop test:teachers
"eee"
127.0.0.1:6379> smembers test:teachers
1) "aaa"
2) "ddd"
3) "bbb"
4) "ccc"
Orderly zscore
View the score of an element zrank
View the ranking of an element
127.0.0.1:6379> zadd test:students 10 aaa 20 bbb 30 ccc 40 ddd 50 eee
(integer) 5
127.0.0.1:6379> zcard test:students
(integer) 5
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"
Global command
Valid for all data types
View all... In the library key
127.0.0.1:6379> keys *
1) "test:ids"
2) "test:user"
3) "test:students"
4) "test:teachers"
5) "test:count"
All with test At the beginning key
127.0.0.1:6379> keys test*
1) "test:ids"
2) "test:user"
3) "test:students"
4) "test:teachers"
5) "test:count"
View a certain key The type of
127.0.0.1:6379> type test:user
hash
View a certain key Whether there is ,1 Indicates presence
127.0.0.1:6379> exists test:user
(integer) 1
Delete a key
127.0.0.1:6379> del test:user
(integer) 1
127.0.0.1:6379> exists test:user
(integer) 0
Set up a key The expiration time of ( second )
127.0.0.1:6379> expire test:stundets 10
(integer) 0
10 Seconds later test:stundets, Does not exist.
127.0.0.1:6379> exists test:stundets
(integer) 0
2. SpringBoot Integrate Redis
pom.xml
The version is already in the parent pom Specified in , So you can not write
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
application.properties
# redis
spring.redis.database=11
spring.redis.host=localhost
spring.redis.port=6379
Configuration class
package com.nowcoder.community.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// Set up key How to serialize
template.setKeySerializer(RedisSerializer.string());
// Set up value How to serialize
template.setValueSerializer(RedisSerializer.json());
// Set up hash Of key How to serialize
template.setHashKeySerializer(RedisSerializer.string());
// Set up hash Of value How to serialize
template.setHashValueSerializer(RedisSerializer.json());
template.afterPropertiesSet();
return template;
}
}
Test it
Innovative testing
package com.nowcoder.community;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
@SpringBootTest
public class RedisTests {
@Resource
private RedisTemplate redisTemplate;
@Test
public void testStrings() {
String redisKey = "test:count";
redisTemplate.opsForValue().set(redisKey, 1);
System.out.println(redisTemplate.opsForValue().get(redisKey));// Value
System.out.println(redisTemplate.opsForValue().increment(redisKey));// increase
System.out.println(redisTemplate.opsForValue().decrement(redisKey));// Reduce
}
@Test
public void testHashes() {
String redisKey = "test:user";
redisTemplate.opsForHash().put(redisKey, "id", 1);
redisTemplate.opsForHash().put(redisKey, "username", " Zhang San ");
// Value
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);
// Value
System.out.println(redisTemplate.opsForList().size(redisKey));
System.out.println(redisTemplate.opsForList().index(redisKey, 0));// Left subscript 0 The data of
System.out.println(redisTemplate.opsForList().range(redisKey, 0, 2));// On the left [0,2] The data of
System.out.println(redisTemplate.opsForList().leftPop(redisKey));// Pop... From the left
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, " Liu bei ", " Guan yu ", " Zhang Fei ");
// Value
System.out.println(redisTemplate.opsForSet().size(redisKey));
System.out.println(redisTemplate.opsForSet().pop(redisKey));// Pop up one randomly
System.out.println(redisTemplate.opsForSet().members(redisKey));// The remaining elements
}
@Test
public void testSortedSets() {
String redisKey = "test:students";
redisTemplate.opsForZSet().add(redisKey, " Tang's monk ", 80);//80 It is the score of Tang monk
redisTemplate.opsForZSet().add(redisKey, " The wu is empty ", 90);
redisTemplate.opsForZSet().add(redisKey, " Monk sha ", 50);
redisTemplate.opsForZSet().add(redisKey, " Bajie ", 60);
System.out.println(redisTemplate.opsForZSet().zCard(redisKey));// Number of statistical elements
System.out.println(redisTemplate.opsForZSet().score(redisKey, " Bajie "));// Count the score of an element
// Count the ranking of an element , Default from small to large
System.out.println(redisTemplate.opsForZSet().rank(redisKey, " Bajie "));
// From big to small
System.out.println(redisTemplate.opsForZSet().reverseRank(redisKey, " Bajie "));
// The smallest top three ( The default is from small to large )
System.out.println(redisTemplate.opsForZSet().range(redisKey, 0, 2));
// The largest top three
System.out.println(redisTemplate.opsForZSet().reverseRange(redisKey, 0, 2));
}
@Test
public void testKeys() {
// Test global commands
redisTemplate.delete("test:user");
System.out.println(redisTemplate.hasKey("test:user"));
redisTemplate.expire("test:students", 10, TimeUnit.SECONDS);
}
// Visit the same one many times key, Use bindings , Simplify the code
@Test
public void testBoundOperations() {
String redisKey = "test:count";
BoundValueOperations operations = redisTemplate.boundValueOps(redisKey);
operations.increment();
operations.increment();
operations.increment();
System.out.println(operations.get());
}
// Programming transactions
@Test
public void testTransactional() {
Object obj = redisTemplate.execute(new SessionCallback() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
String redisKey = "test:tx";
operations.multi();// Start transaction
operations.opsForSet().add(redisKey, " Zhang San ");
operations.opsForSet().add(redisKey, " Li Si ");
operations.opsForSet().add(redisKey, " Wang Wu ");
// No data , Because it has not been implemented yet add
System.out.println(operations.opsForSet().members(redisKey));
return operations.exec();// Commit transaction
}
});
System.out.println(obj);
}
}
3. give the thumbs-up
- give the thumbs-up
- Support for post 、 Comment like .
- The first 1 I like it next time , The first 2 I like it twice .
- Number of likes on the home page
- Count the number of likes of Posts .
- Number of likes on the details page
- Count the number of likes .
- Show like status .
Because like is a very frequent operation , So save the likes data to Redis Improve performance in
Write a tool class
package com.nowcoder.community.util;
public class RedisKeyUtil {
public static final String SPLIT = ":";
public static final String PREFIX_ENTITY_LIKE = "like:entity";
// An entity ( post 、 Comment on ) Like
//like:entity:entityType:entityId-->set(userId)
public static String getEntityLikeKey(int entityType, int entityId) {
return PREFIX_ENTITY_LIKE + SPLIT + entityType + SPLIT + entityId;
}
}
service
package com.nowcoder.community.service;
import com.nowcoder.community.util.RedisKeyUtil;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class LikeService {
@Resource
private RedisTemplate redisTemplate;
// give the thumbs-up
public void like(int userId, int entityType, int entityId) {
String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId);
Boolean isMember = redisTemplate.opsForSet().isMember(entityLikeKey, userId);
if (isMember) {
// That's great , Then cancel
redisTemplate.opsForSet().remove(entityLikeKey, userId);
} else {
redisTemplate.opsForSet().add(entityLikeKey, userId);
}
}
// Query the number of likes of an entity
public long findEntityLikeCount(int entityType, int entityId) {
String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId);
return redisTemplate.opsForSet().size(entityLikeKey);
}
// Query someone's like status for an entity
public int findEntityLikeStatus(int userId, int entityType, int entityId) {
String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId);
return redisTemplate.opsForSet().isMember(entityLikeKey, userId) ? 1 : 0;
}
}
controller
package com.nowcoder.community.controller;
import com.nowcoder.community.entity.User;
import com.nowcoder.community.service.LikeService;
import com.nowcoder.community.util.CommunityUtil;
import com.nowcoder.community.util.HostHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
@Controller
public class LikeController {
@Resource
private LikeService likeService;
@Resource
private HostHolder hostHolder;
@PostMapping("/like")
@ResponseBody
public String like(int entityType, int entityId) {
User user = hostHolder.getUser();
// give the thumbs-up
likeService.like(user.getId(), entityType, entityId);
// Number
long likeCount = likeService.findEntityLikeCount(entityType, entityId);
// state
int likeStatus = likeService.findEntityLikeStatus(user.getId(), entityType, entityId);
// The result returned
Map<String, Object> map = new HashMap<>();
map.put("likeCount", likeCount);
map.put("likeStatus", likeStatus);
return CommunityUtil.getJSONString(0, null, map);
}
}
discuss-detail.html
86 That's ok
<li class="d-inline ml-2">
<a href="javascript:;" th:onclick="|like(this, 1, ${post.id});|" class="text-primary">
<b> Fabulous </b> <i>11</i>
</a>
</li>
138 That's ok
<li class="d-inline ml-2">
<a href="javascript:;" th:onclick="|like(this, 2, ${cvo.comment.id})|" class="text-primary">
<b> Fabulous </b>(<i>1</i>)
</a>
</li>
164 That's ok
<li class="d-inline ml-2">
<a href="javascript:;" th:onclick="|like(this, 2, ${rvo.reply.id})|" class="text-primary">
<b> Fabulous </b>(<i>1</i>)
</a>
</li>
Add one at the end script label
<script th:src="@{/js/discuss.js}"></script>
resources / static / js Under the new discuss.js
function like(btn, entityType, entityId) {
$.post(
CONTEXT_PATH + "/like",
{
"entityType":entityType, "entityId":entityId},
function (data) {
data = $.parseJSON(data);
if (data.code == 0) {
$(btn).children("i").text(data.likeCount);
$(btn).children("b").text(data.likeStatus==1?' Liked ':' Fabulous ');
} else {
alert(data.msg);
}
}
);
}
start-up , test : After logging in, find a post to like . Click like again , To cancel the , Then try to like the comments and their replies
Correct the problem that the number of initial likes is incorrect :
1. Deal with home page
perfect HomeController Of getIndexPage() Method
public class HomeController implements CommunityConstant {
// To use constants
@Resource
private LikeService likeService;
@GetMapping("/index")
public String getIndexPage(Model model, Page page) {
// Before method call , SpringMVC Will automatically instantiate Model and Page, And will Page Inject Model.
// therefore , stay thymeleaf Can be accessed directly in Page Data in object .
page.setRows(discussPostService.findDiscussPostRows(0));
page.setPath("/index");
// This list The post in contains foreign keys userId, We need to find out userName Splice to the post
List<DiscussPost> list = discussPostService.findDiscussPosts(0, page.getOffset(), page.getLimit());
List<Map<String, Object>> discussPosts = new ArrayList<>();
if (list != null) {
for (DiscussPost post : list) {
Map<String, Object> map = new HashMap<>();
map.put("post", post);
User user = userService.findUserById(post.getUserId());
map.put("user", user);
// Add these two sentences
long likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_POST, post.getId());
map.put("likeCount", likeCount);
discussPosts.add(map);
}
}
model.addAttribute("discussPosts", discussPosts);
return "/index";
}
index.html
134 That's ok
<li class="d-inline ml-2"> Fabulous <span th:text="${map.likeCount}">11</span></li>
2. Process the post details page
DiscussPostController Of getDiscussPost() Method
@Resource
private LikeService likeService;
@GetMapping("/detail/{discussPostId}")
public String getDiscussPost(@PathVariable("discussPostId") int discussPostId, Model model, Page page) {
// post
DiscussPost post = discussPostService.findDiscussPostById(discussPostId);
model.addAttribute("post", post);
// author
User user = userService.findUserById(post.getUserId());
model.addAttribute("user", user);
// Thumb up number
long likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_POST, discussPostId);
model.addAttribute("likeCount", likeCount);
// Like status
int likeStatus = hostHolder.getUser() == null ? 0 :
likeService.findEntityLikeStatus(hostHolder.getUser().getId(), ENTITY_TYPE_POST, discussPostId);
model.addAttribute("likeStatus", likeStatus);
// Paging information of comments
page.setLimit(5);
page.setPath("/discuss/detail/" + discussPostId);
page.setRows(post.getCommentCount());
// Comment on : Comments to posts
// reply : Comments for comments
// Comment list
List<Comment> commentList = commentService.findCommentsByEntity(
ENTITY_TYPE_POST, post.getId(), page.getOffset(), page.getLimit());
// Comment on VO list
List<Map<String, Object>> commentVoList = new ArrayList<>();
if (commentList != null) {
for (Comment comment : commentList) {
// Comment on VO
Map<String, Object> commentVo = new HashMap<>();
// Go to VO Add comments to
commentVo.put("comment", comment);
// Add the author
commentVo.put("user", userService.findUserById(comment.getUserId()));
// Thumb up number
likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_COMMENT, comment.getId());
commentVo.put("likeCount", likeCount);
// Like status
likeStatus = hostHolder.getUser() == null ? 0 :
likeService.findEntityLikeStatus(hostHolder.getUser().getId(), ENTITY_TYPE_COMMENT, comment.getId());
commentVo.put("likeStatus", likeStatus);
// Query reply list
List<Comment> replyList = commentService.findCommentsByEntity(
ENTITY_TYPE_COMMENT, comment.getId(), 0, Integer.MAX_VALUE);
// reply VO list
List<Map<String, Object>> replyVoList = new ArrayList<>();
if (replyList != null) {
for (Comment reply : replyList) {
Map<String, Object> replyVo = new HashMap<>();
// reply
replyVo.put("reply", reply);
// author
replyVo.put("user", userService.findUserById(reply.getUserId()));
// Target of reply
User target = reply.getTargetId() == 0 ? null : userService.findUserById(reply.getTargetId());
replyVo.put("target", target);
// Thumb up number
likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_COMMENT, reply.getId());
replyVo.put("likeCount", likeCount);
// Like status
likeStatus = hostHolder.getUser() == null ? 0 :
likeService.findEntityLikeStatus(hostHolder.getUser().getId(), ENTITY_TYPE_COMMENT, reply.getId());
replyVo.put("likeStatus", likeStatus);
replyVoList.add(replyVo);
}
}
commentVo.put("replys", replyVoList);
// Number of replies
int replyCount = commentService.findCommentCount(ENTITY_TYPE_COMMENT, comment.getId());
commentVo.put("replyCount", replyCount);
commentVoList.add(commentVo);
}
}
model.addAttribute("comments", commentVoList);
return "site/discuss-detail";
}
discuss-detail.html
88 That's ok
<b th:text="${likeStatus==1?' Liked ':' Fabulous '}"> Fabulous </b> <i th:text="${likeCount}">11</i>
140 That's ok
<b th:text="${cvo.likeStatus==1?' Liked ':' Fabulous '}"> Fabulous </b>(<i th:text="${cvo.likeCount}">1</i>)
166 That's ok
<b th:text="${rvo.likeStatus==1?' Liked ':' Fabulous '}"> Fabulous </b>(<i th:text="${rvo.likeCount}">1</i>)
4. I got a great
Refactoring the like function
- User oriented key, Record the number of likes
- increment(key),decrement(key)
Develop a personal home page
- User oriented key, Query the number of likes
RedisKeyUtil Add attributes and methods
public static final String PREFIX_USER_LIKE = "like:user";
// A user's favorite
// like:user:userId -> int
public static String getUserLikeKey(int userId) {
return PREFIX_USER_LIKE + SPLIT + userId;
}
LikeService
restructure like() Method
// give the thumbs-up
public void like(int userId, int entityType, int entityId, int entityUserId) {
redisTemplate.execute(new SessionCallback() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId);
String userLikeKey = RedisKeyUtil.getUserLikeKey(entityUserId);
// This query cannot be placed inside a transaction
boolean isMember = operations.opsForSet().isMember(entityLikeKey, userId);
// Open transaction
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();
}
});
}
Add a way , Count the number of likes
// Query the number of likes a user gets
public int findUserLikeCount(int userId) {
String userLikeKey = RedisKeyUtil.getUserLikeKey(userId);
Integer count = (Integer) redisTemplate.opsForValue().get(userLikeKey);
return count == null ? 0 : count.intValue();
}
LikeController
like() Method Add a formal parameter entityUserId
public String like(int entityType, int entityId, int entityUserId) {
...
// give the thumbs-up
likeService.like(user.getId(), entityType, entityId, entityUserId);
discuss-detail.html
78 That's ok
<a th:href="@{|/user/profile/${user.id}|}">
87 That's ok
<a href="javascript:;" th:onclick="|like(this, 1, ${post.id}, ${post.userId});|" class="text-primary">
139 That's ok
<a href="javascript:;" th:onclick="|like(this, 2, ${cvo.comment.id},${cvo.comment.userId})|" class="text-primary">
165 That's ok
<a href="javascript:;" th:onclick="|like(this, 2, ${rvo.reply.id}, ${rvo.reply.userId})|" class="text-primary">
discuss.js
function like(btn, entityType, entityId, entityUserId) {
$.post(
CONTEXT_PATH + "/like",
{
"entityType":entityType, "entityId":entityId, "entityUserId":entityUserId},
function (data) {
data = $.parseJSON(data);
if (data.code == 0) {
$(btn).children("i").text(data.likeCount);
$(btn).children("b").text(data.likeStatus==1?' Liked ':' Fabulous ');
} else {
alert(data.msg);
}
}
);
}
UserController
@Resource
private LikeService likeService;
// Personal home page
@GetMapping("/profile/{userId}")
public String getProfilePage(@PathVariable("userId") int userId, Model model) {
User user = userService.findUserById(userId);
if (user == null) {
throw new RuntimeException(" The user does not exist !");
}
// user
model.addAttribute("user", user);
// Number of favors
int likeCount = likeService.findUserLikeCount(userId);
model.addAttribute("likeCount", likeCount);
return "/site/profile";
}
index.html
43 That's ok
<a class="dropdown-item text-center" th:href="@{|/user/profile/${loginUser.id}|}"> Personal home page </a>
122 That's ok
<a th:href="@{|/user/profile/${map.user.id}|}">
profile.html
2 That's ok
<html lang="en" xmlns:th="http://www.thymeleaf.org">
8 That's ok
<link rel="stylesheet" th:href="@{/css/global.css}" />
14 That's ok
<!-- Head -->
<header class="bg-dark sticky-top" th:replace="index::header">
165-166 That's ok
<script th:src="@{/js/global.js}"></script>
<script th:src="@{/js/profile.js}"></script>
80 That's ok
<img th:src="${user.headerUrl}" class="align-self-start mr-4 rounded-circle" alt=" The avatars " style="width:50px;">
83 That's ok
<span th:utext="${user.username}">nowcoder</span>
87 That's ok
<span> Registered at <i class="text-muted" th:text="${#dates.format(user.createTime, 'yyyy-MM-dd HH:mm:ss')}">2015-06-12 15:20:12</i></span>
92 That's ok
<span class="ml-4"> To obtain the <i class="text-danger" th:text="${likeCount}">87</i> A great </span>
Start the test , Delete the previous likes data first
C:\Users\15642>redis-cli
127.0.0.1:6379> select 11
OK
127.0.0.1:6379[11]> keys *
1) "like:entity:2:94"
2) "like:entity:1:234"
3) "test:teachers"
4) "test:tx"
5) "test:count"
127.0.0.1:6379[11]> flushdb
OK
127.0.0.1:6379[11]> keys *
(empty list or set)
After landing , Randomly choose a personal post to like , Give him some praise for his comments , Then go to his homepage to see if the number of likes received is correct
5. Focus on 、 Cancel the attention
demand
- Development concerns 、 Remove the focus function .
- Count the number of users' attention 、 Number of fans .
The key
- if A Pay attention to the B, be A yes B Of Follower( fans ),B yes A Of Followee( The goal is ).
- The focus can be on users 、 post 、 Title, etc , Abstract these goals into entities when they are implemented .
RedisKeyUtil
Add attributes and methods
public static final String PREFIX_FOLLOWEE = "followee";
public static final String PREFIX_FOLLOWER = "follower";
// An entity concerned by a user
//followee:userId:entityType -> zSet(entityId, now)
public static String getFolloweeKey(int userId, int entityType) {
return PREFIX_FOLLOWEE + SPLIT + userId + SPLIT + entityType;
}
// Fans owned by a user
//follower:entityType:entityId -> zSet(userId, now)
public static String getFollowerKey(int entityType, int entityId) {
return PREFIX_FOLLOWER + SPLIT + entityType + SPLIT + entityId;
}
newly build FollowService
package com.nowcoder.community.service;
import com.nowcoder.community.util.RedisKeyUtil;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class FollowService {
@Resource
private RedisTemplate redisTemplate;
// Focus on
public void follow(int userId, int entityType, int entityId) {
redisTemplate.execute(new SessionCallback() {
@Override
public 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();
}
});
}
// Cancel the attention
public void unfollow(int userId, int entityType, int entityId) {
redisTemplate.execute(new SessionCallback() {
@Override
public 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();
}
});
}
}
newly build FollowController
package com.nowcoder.community.controller;
import com.nowcoder.community.entity.User;
import com.nowcoder.community.service.FollowService;
import com.nowcoder.community.util.CommunityUtil;
import com.nowcoder.community.util.HostHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
@Controller
public class FollowController {
@Resource
private FollowService followService;
@Resource
private HostHolder hostHolder;
@PostMapping("/follow")
@ResponseBody
public String follow(int entityType, int entityId) {
User user = hostHolder.getUser();
followService.follow(user.getId(), entityType, entityId);
return CommunityUtil.getJSONString(0, " Followed !");
}
@PostMapping("/unfollow")
@ResponseBody
public String unfollow(int entityType, int entityId) {
User user = hostHolder.getUser();
followService.unfollow(user.getId(), entityType, entityId);
return CommunityUtil.getJSONString(0, " Cancelled attention !");
}
}
CommunityConstant
// Entity type : user
int ENTITY_TYPE_USER = 3;
profile.html
84 That's ok “ Focus on TA” Add... To the last line of :
<input type="hidden" id="entityId" th:value="${user.id}">
The modified profile.js
$(function(){
$(".follow-btn").click(follow);
});
function follow() {
var btn = this;
if($(btn).hasClass("btn-info")) {
// Focus on TA
$.post(
CONTEXT_PATH + "/follow",
{
"entityType":3, "entityId":$(btn).prev().val()},
function (data) {
data = $.parseJSON(data);
if (data.code == 0) {
window.location.reload();
} else {
alert(data.msg);
}
}
);
// $(btn).text(" Followed ").removeClass("btn-info").addClass("btn-secondary");
} else {
// Cancel the attention
$.post(
CONTEXT_PATH + "/unfollow",
{
"entityType":3, "entityId":$(btn).prev().val()},
function (data) {
data = $.parseJSON(data);
if (data.code == 0) {
window.location.reload();
} else {
alert(data.msg);
}
}
);
// $(btn).text(" Focus on TA").removeClass("btn-secondary").addClass("btn-info");
}
}
FollowService
Increase method
// Query the number of entities of interest
public long findFolloweeCount(int userId, int entityType) {
String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType);
return redisTemplate.opsForZSet().zCard(followeeKey);
}
// Query the number of followers of the entity
public long findFollowerCount(int entityType, int entityId) {
String followerKey = RedisKeyUtil.getFollowerKey(entityType, entityId);
return redisTemplate.opsForZSet().zCard(followerKey);
}
// Query whether the current user has paid attention to the entity
public boolean hasFollowed(int userId, int entityType, int entityId) {
String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType);
return redisTemplate.opsForZSet().score(followeeKey, entityId) != null;
}
UserController
perfect getProfilePage Method
public class UserController implements CommunityConstant {
@Resource
private FollowService followService;
// Personal home page
@GetMapping("/profile/{userId}")
public String getProfilePage(@PathVariable("userId") int userId, Model model) {
User user = userService.findUserById(userId);
if (user == null) {
throw new RuntimeException(" The user does not exist !");
}
// user
model.addAttribute("user", user);
// Number of favors
int likeCount = likeService.findUserLikeCount(userId);
model.addAttribute("likeCount", likeCount);
// Focus on quantity
long followeeCount = followService.findFolloweeCount(userId, ENTITY_TYPE_USER);
model.addAttribute("followeeCount", followeeCount);
// Number of fans
long followerCount = followService.findFollowerCount(ENTITY_TYPE_USER, userId);
model.addAttribute("followerCount", followerCount);
// Have you paid attention to
boolean hasFollowed = false;
if (hostHolder.getUser() != null) {
hasFollowed = followService.hasFollowed(hostHolder.getUser().getId(), ENTITY_TYPE_USER, userId);
}
model.addAttribute("hasFollowed", hasFollowed);
return "/site/profile";
}
profile.html
91 That's ok
<span> Pay attention to the <a class="text-primary" href="followee.html" th:text="${followeeCount}">5</a> people </span>
92 That's ok
<span class="ml-4"> Concern <a class="text-primary" href="follower.html" th:text="${followerCount}">123</a> people </span>
85 That's ok
<button type="button" class="btn btn-info btn-sm float-right mr-5 follow-btn" th:text="${hasFollowed?' Followed ':' Focus on TA'}" th:if="${loginUser!=null&&loginUser.id!=user.id}"> Focus on TA</button>
start-up , test
6. Attention list 、 Fans list
The business layer
- Query a user's attention , Support paging .
- Query a user's fans , Support paging .
The presentation layer
- Handle “ Query the people you follow ”、“ Query fans ” request .
- To write “ Query the people you follow ”、“ Query fans ” Templates .
FollowService
Increase method
public class FollowService implements CommunityConstant {
@Resource
private UserService userService;
// Query a user's attention
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.findUserById(targetId);
map.put("user", user);
Double score = redisTemplate.opsForZSet().score(followeeKey, targetId);
map.put("followTime", new Date(score.longValue()));
list.add(map);
}
return list;
}
// Query a user's fans
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.findUserById(targetId);
map.put("user", user);
Double score = redisTemplate.opsForZSet().score(followerKey, targetId);
map.put("followTime", new Date(score.longValue()));
list.add(map);
}
return list;
}
FollowController
Increase method
public class FollowController implements CommunityConstant {
@Resource
private UserService userService;
@GetMapping("/followees/{userId}")
public String getFollowees(@PathVariable("userId") int userId, Page page, Model model) {
User user = userService.findUserById(userId);
if (user == null) {
throw new RuntimeException(" The user does not exist !");
}
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";
}
@GetMapping("/followers/{userId}")
public String getFollowers(@PathVariable("userId") int userId, Page page, Model model) {
User user = userService.findUserById(userId);
if (user == null) {
throw new RuntimeException(" The user does not exist !");
}
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);
}
profile.html
92 That's ok
<span> Pay attention to the <a class="text-primary" th:href="@{|/followees/${user.id}|}" th:text="${followeeCount}">5</a> people </span>
93 That's ok
<span class="ml-4"> Concern <a class="text-primary" th:href="@{|/followers/${user.id}|}" th:text="${followerCount}">123</a> people </span>
followee.html
2 That's ok
<html lang="en" xmlns:th="http://www.thymeleaf.org">
8 That's ok
<link rel="stylesheet" th:href="@{/css/global.css}" />
14 That's ok
<!-- Head -->
<header class="bg-dark sticky-top" th:replace="index::header">
233-234 That's ok
<script th:src="@{/js/global.js}"></script>
<script th:src="@{/js/profile.js}"></script>
Delete 93-148 That's ok , Just one li Labels can be
64 Yes div label
<div class="position-relative">
<!-- Options -->
<ul class="nav nav-tabs mb-3">
<li class="nav-item">
<a class="nav-link position-relative active" th:href="@{|/followees/${user.id}|}">
<i class="text-info" th:utext="${user.username}">Nowcoder</i> Pay attention to the people
</a>
</li>
<li class="nav-item">
<a class="nav-link position-relative" th:href="@{|/followers/${user.id}|}">
Focus on <i class="text-info" th:utext="${user.username}">Nowcoder</i> People who
</a>
</li>
</ul>
<a th:href="@{|/user/profile/${user.id}|}" class="text-muted position-absolute rt-0"> Return to your home page ></a>
</div>
83 That's ok
<li class="media pb-3 pt-3 mb-3 border-bottom position-relative" th:each="map:${users}">
84 That's ok a label
<a th:href="@{|/user/profile/${map.user.id}|}">
<img th:src="${map.user.headerUrl}" class="mr-4 rounded-circle user-header" alt=" The avatars " >
</a>
89 That's ok
<span class="text-success" th:utext="${map.user.username}"> Idle people in the Rockies </span>
90 That's ok span label
<span class="float-right text-muted font-size-12">
Focus on <i th:text="${#dates.format(map.followTime, 'yyyy-MM-dd HH:mm:ss')}">2019-04-28 14:13:25</i>
</span>
101 That's ok
<!-- Pagination -->
<nav class="mt-5" th:replace="index::pagination">
95 Add a line before the line
<input type="hidden" id="entityId" th:value="${map.user.id}">
96 That's ok button label
<button type="button" th:class="|btn ${map.hasFollowed?'btn-secondary':'btn-info'} btn-sm float-right follow-btn|" th:if="${loginUser!=null&&loginUser.id!=map.user.id}" th:text="${map.hasFollowed?' Followed ':' Focus on TA'}"> Focus on TA
</button>
follower.html Do exactly the same thing !
start-up , test , Log in to an account , Just pay attention to someone , Then log in to that person's account , Check whether you are a fan +1
7. Optimize login module
Use Redis Store verification code
- Captcha needs frequent access and refresh , High performance requirements .
- Verification code does not need to be saved permanently , It usually fails after a short time .
- Distributed deployment , There is Session The problem of sharing .
Use Redis Store login credentials
- When processing each request , Query the user's login credentials , The frequency of visits is very high .
Use Redis Cache user information
- When processing each request , Query user information according to the voucher , The frequency of visits is very high .
7.1 Use Redis Store verification code
RedisKeyUtil
Add attributes and methods
public static final String PREFIX_KAPTCHA = "kaptcha";
// Login verification code key
public static String getKaptchaKey(String owner) {
return PREFIX_KAPTCHA + SPLIT + owner;
}
LoginController
restructure getKaptcha() Method 、login() Method
@Resource
private RedisTemplate redisTemplate;
@GetMapping("/kaptcha")
public void getKaptcha(HttpServletResponse response) {
// Generate verification code
String text = kaptchaProducer.createText();
BufferedImage image = kaptchaProducer.createImage(text);
// Ownership of verification
String kaptchaOwner = CommunityUtil.generateUUID();
Cookie cookie = new Cookie("kaptchaOwner", kaptchaOwner);
cookie.setMaxAge(60);
cookie.setPath(contextPath);
response.addCookie(cookie);
// Store the verification code in Redis
String redisKey = RedisKeyUtil.getKaptchaKey(kaptchaOwner);
redisTemplate.opsForValue().set(redisKey, text, 60, TimeUnit.SECONDS);
// Output image to browser
response.setContentType("image/png");
try {
OutputStream os = response.getOutputStream();
ImageIO.write(image, "png", os);
} catch (IOException e) {
logger.error(" Response verification code failed :" + e.getMessage());
}
}
@PostMapping("/login")
public String login(String username, String password, String code,
boolean rememberme, Model model, HttpServletResponse response,
@CookieValue("kaptchaOwner") String kaptchaOwner) {
// Check the verification code
String kaptcha = null;
if (StringUtils.isNotBlank(kaptchaOwner)) {
String redisKey = RedisKeyUtil.getKaptchaKey(kaptchaOwner);
kaptcha = (String) redisTemplate.opsForValue().get(redisKey);
}
// The rest is the same
if (StringUtils.isBlank(kaptcha) || StringUtils.isBlank(code) || !kaptcha.equalsIgnoreCase(code)) {
model.addAttribute("codeMsg", " The verification code is incorrect !");
return "/site/login";
}
// Check the account number 、 password
int expiredSeconds = rememberme ? REMEMBER_EXPIRED_SECONDS : DEFAULT_EXPIRED_SECONDS;
Map<String, Object> map = userService.login(username, password, expiredSeconds);
if (map.containsKey("ticket")) {
// Only after successful login will it be saved ticket
Cookie cookie = new Cookie("ticket", map.get("ticket").toString());
cookie.setPath(contextPath);
cookie.setMaxAge(expiredSeconds);
response.addCookie(cookie);
return "redirect:/index";
} else {
model.addAttribute("usernameMsg", map.get("usernameMsg"));
model.addAttribute("passwordMsg", map.get("passwordMsg"));
return "/site/login";
}
}
7.2 Use Redis Store login credentials
RedisKeyUtil
public static final String PREFIX_TICKET = "ticket";
// Login credentials
public static String getTicketKey(String ticket) {
return PREFIX_TICKET + SPLIT + ticket;
}
LoginTicketMapper It can be discarded , Annotate the class @Deprecated
UserService
// @Resource
// private LoginTicketMapper loginTicketMapper;
@Resource
private RedisTemplate redisTemplate;
modify login() The second half of the method
// Generate login credentials
LoginTicket loginTicket = new LoginTicket();
loginTicket.setUserId(user.getId());
loginTicket.setTicket(CommunityUtil.generateUUID());
loginTicket.setStatus(0);
loginTicket.setExpired(new Date(System.currentTimeMillis() + expiredSeconds * 1000));
// loginTicketMapper.insertLoginTicket(loginTicket);
String redisKey = RedisKeyUtil.getTicketKey(loginTicket.getTicket());
redisTemplate.opsForValue().set(redisKey, loginTicket);
map.put("ticket", loginTicket.getTicket());
return map;
logout() Method
public void logout(String ticket) {
// loginTicketMapper.updateStatus(ticket, 1);
String redisKey = RedisKeyUtil.getTicketKey(ticket);
LoginTicket loginTicket = (LoginTicket) redisTemplate.opsForValue().get(redisKey);
loginTicket.setStatus(1);// Status as 1, Said to delete
redisTemplate.opsForValue().set(redisKey, loginTicket);
}
findLoginTicket() Method
public LoginTicket findLoginTicket(String ticket) {
// return loginTicketMapper.selectByTicket(ticket);
String redisKey = RedisKeyUtil.getTicketKey(ticket);
return (LoginTicket) redisTemplate.opsForValue().get(redisKey);
}
7.3 Use Redis Cache user information
RedisKeyUtil
public static final String PREFIX_USER = "user";
// user
public static String getUserKey(int userId) {
return PREFIX_USER + SPLIT + userId;
}
UserService
Add three methods
// 1. Priority value from cache
private User getCache(int userId) {
String redisKey = RedisKeyUtil.getUserKey(userId);
return (User) redisTemplate.opsForValue().get(redisKey);
}
// 2. Initialize cache data if not available
private User initCache(int userId) {
User user = userMapper.selectById(userId);
String redisKey = RedisKeyUtil.getUserKey(userId);
redisTemplate.opsForValue().set(redisKey, user, 3600, TimeUnit.SECONDS);
return user;
}
// 3. Clear cache data when data changes
private void clearCache(int userId) {
String redisKey = RedisKeyUtil.getUserKey(userId);
redisTemplate.delete(redisKey);
}
modify findUserById() Method
public User findUserById(int id) {
// return userMapper.selectById(id);
User user = getCache(id);
if (user == null) {
user = initCache(id);
}
return user;
}
modify activation() Method
public int activation(int userId, String code) {
User user = userMapper.selectById(userId);
if (user.getStatus() == 1) {
return ACTIVATION_REPEAT;
} else if (user.getActivationCode().equals(code)) {
userMapper.updateStatus(userId, 1);
clearCache(userId);
return ACTIVATION_SUCCESS;
} else {
return ACTIVATION_FAILURE;
}
}
modify updateHeader() Method
public int updateHeader(int userId, String headerUrl) {
int rows = userMapper.updateHeader(userId, headerUrl);
clearCache(userId);
return rows;
}
边栏推荐
- 基于可学习尺寸自适应分子亚结构的药物相互作用预测
- Modular development
- Taro---day2---编译运行
- 網頁鼠標點擊特效案例收集(直播間紅心同理)
- Summary of attack methods of attack team
- Ten MySQL locks, one article will give you full analysis
- 怎样能低成本构建一个电商平台
- Arrays.asList()坑
- Overview and deployment of GFS distributed file system
- Latest MySQL advanced SQL statement Encyclopedia
猜你喜欢
The research group of Xuyong and duanwenhui of Tsinghua University has developed an efficient and accurate first principles electronic structure deep learning method and program
药物发现综述-01-药物发现概述
基于可学习尺寸自适应分子亚结构的药物相互作用预测
Official announcement! Apache Doris graduated from the Apache incubator and officially became the top project of Apache!
What are cookies and the security risks of v-htm
【说明】Jmeter乱码的解决方法
评价——秩和比综合评价
Taro---day1---搭建项目
Deepmind | pre training of molecular property prediction through noise removal
电商转化率这么抽象,到底是个啥?
随机推荐
基于可学习尺寸自适应分子亚结构的药物相互作用预测
Comprehensive evaluation of free, easy-to-use and powerful open source note taking software
想开户买股票,在网上办理股票开户安全吗?
Acwing game 57 [unfinished]
去哪儿网(Qunar) DevOps 实践分享
PostgreSQL设置自增字段
Deepmind | pre training of molecular property prediction through noise removal
The flutter slivereappbar is fully parsed. All the effects you want are here!
Proe/Creo产品结构设计-钻研不断
Set collection usage
Solve storage problems? WMS warehouse management system solution
MySQL - function
What is the application scope and function of the conductive slip ring of single crystal furnace
如何理解 Transformer 中的 Query、Key 与 Value
攻击队攻击方式复盘总结
如何高效读书学习
Drug interaction prediction based on learning size adaptive molecular substructure
PostgreSQL setting auto increment field
【开源】开源系统整理-考试问卷等
同花顺上能炒股开户吗?安全吗?