当前位置:网站首页>[Niuke discussion area] Chapter 4: redis

[Niuke discussion area] Chapter 4: redis

2022-06-28 01:38:00 Yard animals also have dreams

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:

 Insert picture description here

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

 Insert picture description here


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

 Insert picture description here

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 &gt;</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;
}
原网站

版权声明
本文为[Yard animals also have dreams]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/179/202206272311408120.html