This is the fifth day of my participation in the August More text Challenge. For details, see: August More Text Challenge

@TOC

Thinking about

Thought analysis

Do weekly hot talk, should use the cache to do, if directly check the database, will cause pressure on the database. If you’re doing it with a cache, Redis is a better way to do it.

Add data using Redsi
# Day :1 refers to 10 comments added to the first post at Post :1 on the 1st.
6 post:2 refers to the 6 comments added to the second post on # 1
zadd day:1 10 post:1 6 post:2
zadd day:2 10 post:1 6 post:2
zadd day:3 10 post:1 6 post:2
....
zadd day:8 10 post:1 6 post:2
# This completes 7 days of recording

Copy the code

The above command will help us keep track of the total number of comments for 7 days. But it hasn’t helped us figure out who gets the most reviews. Just look at Redis’ sorted set. There’s a command that does this for you.

This command can help us achieve union, we just need to make a union of 7 days of comments to find,.

# Redis command
We're going to put the 7 days into a new set called week: Rank and then we're going to have ours in the new set
#7 days of recordunion week:rank 7 day:1... day:8Copy the code

Put the Redis command into practice

Local command line tests

127.0.0.1:6379> ping
PONG
127.0.0.1:6379> zadd day:1 10 post:1
(integer) 1
127.0.0.1:6379> zadd day:2 10 post:1
(integer) 1
127.0.0.1:6379> zadd day:3 10 post:1
(integer) 1
127.0.0.1:6379> zadd day:1 5 post:2
(integer) 1
127.0.0.1:6379> zadd day:2 5 post:2
(integer) 1
127.0.0.1:6379> zadd day:3 5 post:2
(integer) 1
127.0.0.1:6379> keys *
1) "day:1"
2) "day:2"
3) "day:3"
Copy the code

View the leaderboard command ZRevrange for the day

127.0.0.1:6379> zrevrange day:1 0-1 withscores 1)"post:1"
2) "10"
3) "post:2"
4) "5"127.0.0.1:6379 >Copy the code

A weekly review list. Because I only have three days, so I only wrote three days

127.0.0.1:6379> zrevrange week:rank 0-1 withscores 1)"post:1"
2) "30"
3) "post:2"
4) "15"127.0.0.1:6379 >Copy the code

There is no error in the record above. The above command can help us to implement our idea easily. Let’s do this in code. There is also a small bug on it when the day:1 May appear but it is not possible to pass it directly. It is possible to increase one by one, and you should use the increment command to solve this problem.

# When +1 when -1 is when you add a comment you add 1 and when you delete it you subtract 1
ZINCRBY day:1 10 post:1
Copy the code

Code to implement

The current style of the front end, so that we need to start this feature at the beginning of the project

@Component
// Implement the boot class, as well as the context servlect
public class ContextStartup implements ApplicationRunner.ServletContextAware {
    / / into the categoryService
    @Autowired
    IMCategoryService categoryService;
    ServletContext servletContext;
    // Inject the post service class
    @Autowired
    IMPostService postService;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        // Call the full lookup method
        List<MCategory> list = categoryService.list(new QueryWrapper<MCategory>().eq("status".0));
        servletContext.setAttribute("List", list);
        // Call the weekly heat review method
        postService.initweek();
    }

    @Override
    public void setServletContext(ServletContext servletContext) {
        this.servletContext = servletContext; }}Copy the code

This is the general idea of the ServiceImpl class

2, initialize the total number of articles read // Cache the basic information of the article (ID, title, number of comments, author ID) // so as to avoid library lookup. We can just use our cache. 3, // do unionCopy the code

Here we need a Redis utility class, which I found on the Internet, not written by me. Lots of them on the Internet. Just use it

package com.example.springbootblog.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@Component
public class RedisUtil {

    @Autowired
    private RedisTemplate redisTemplate;

    /** * specifies the cache expiration time **@paramThe key key *@paramTime Time (seconds) x@return* /
    public boolean expire(String key, long time) {
        try {
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false; }}/** * Get expiration time based on key **@paramThe key cannot be null *@returnTime (seconds) returns 0 for permanent */
    public long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    /** * Determine whether the key has **@paramThe key key *@returnTrue Exists false Does not exist */
    public boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false; }}/** * Delete cache **@paramThe key can pass one value or more */
    @SuppressWarnings("unchecked")
    public void del(String... key) {
        if(key ! =null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else{ redisTemplate.delete(CollectionUtils.arrayToList(key)); }}}//============================String=============================

    /** * Get ** from normal cache@paramThe key key *@returnValue * /
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /** * Normal cache put **@paramThe key key *@paramThe value value *@returnTrue Succeeded false Failed */
    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false; }}/** * Normal cache put and set time **@paramThe key key *@paramThe value value *@paramTime Time (seconds) Time must be greater than 0. If time is less than or equal to 0, set indefinite *@returnTrue Succeeded false Failed */
    public boolean set(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false; }}/** * Increments **@paramThe key key *@paramHow much more delta (greater than 0) *@return* /
    public long incr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("The increment factor must be greater than zero.");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }

    /** **@paramThe key key *@paramDelta to be reduced by (less than 0) *@return* /
    public long decr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("The decreasing factor must be greater than zero.");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }

    //================================Map=================================

    /**
     * HashGet
     *
     * @paramThe key cannot be null *@paramItem Items cannot be null *@returnValue * /
    public Object hget(String key, String item) {
        return redisTemplate.opsForHash().get(key, item);
    }

    /** * Get all key values for a hashKey **@paramThe key key *@returnMultiple corresponding keys */
    public Map<Object, Object> hmget(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * HashSet
     *
     * @paramThe key key *@paramMap corresponds to multiple keys *@returnTrue Succeeded false Failed */
    public boolean hmset(String key, Map<String, Object> map) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false; }}/** * HashSet and set time **@paramThe key key *@paramMap corresponds to multiple keys *@paramTime Time (seconds) x@returnTrue Succeeded false Failed */
    public boolean hmset(String key, Map<String, Object> map, long time) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false; }}/** * puts data into a hash table, creating a hash table if it does not exist@paramThe key key *@paramItem item *@paramThe value value *@returnTrue Succeeded false Failed */
    public boolean hset(String key, String item, Object value) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false; }}/** * puts data into a hash table, creating a hash table if it does not exist@paramThe key key *@paramItem item *@paramThe value value *@paramTime Time (seconds) Note: If an existing hash table has a time, the original time * will be replaced@returnTrue Succeeded false Failed */
    public boolean hset(String key, String item, Object value, long time) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false; }}/** * Delete the hash table value **@paramThe key cannot be null *@paramItem can make multiple items not null */
    public void hdel(String key, Object... item) {
        redisTemplate.opsForHash().delete(key, item);
    }

    /** * Determines whether the hash table has a value for this entry **@paramThe key cannot be null *@paramItem Items cannot be null *@returnTrue Exists false Does not exist */
    public boolean hHasKey(String key, String item) {
        return redisTemplate.opsForHash().hasKey(key, item);
    }

    /** * If the hash increment does not exist, one is created and the new value is returned@paramThe key key *@paramItem item *@paramHow much do I have to increase by *@return* /
    public double hincr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, by);
    }

    /** * Hash decrement **@paramThe key key *@paramItem item *@paramBy is going to be reduced by (less than 0) *@return* /
    public double hdecr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, -by);
    }

    //============================set=============================

    /** * get all values in the Set based on key **@paramThe key key *@return* /
    public Set<Object> sGet(String key) {
        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null; }}/** * select ** from set@paramThe key key *@paramThe value value *@returnTrue Exists false Does not exist */
    public boolean sHasKey(String key, Object value) {
        try {
            return redisTemplate.opsForSet().isMember(key, value);
        } catch (Exception e) {
            e.printStackTrace();
            return false; }}/** * Put the data in the set cache **@paramThe key key *@paramValues can be multiple *'s@returnNumber of successes */
    public long sSet(String key, Object... values) {
        try {
            return redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
            e.printStackTrace();
            return 0; }}/** * Put the set data in cache **@paramThe key key *@paramTime Time (seconds) x@paramValues can be multiple *'s@returnNumber of successes */
    public long sSetAndTime(String key, long time, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().add(key, values);
            if (time > 0) expire(key, time);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0; }}/** * Get the length of the set cache **@paramThe key key *@return* /
    public long sGetSetSize(String key) {
        try {
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0; }}/** * Remove ** of value@paramThe key key *@paramValues can be multiple *'s@returnNumber of removed */
    public long setRemove(String key, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().remove(key, values);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0; }}//===============================list=================================

    /** * Get the contents of the list cache **@paramThe key key *@paramStart to *@param0 to -1 represents all values *@return* /
    public List<Object> lGet(String key, long start, long end) {
        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
            return null; }}/** * Get the length of the list cache **@paramThe key key *@return* /
    public long lGetListSize(String key) {
        try {
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0; }}/** * Get the value in the list by index **@paramThe key key *@paramIndex >=0; index>=0; If index<0, -1 is the end of the table, -2 is the penultimate element, and so on *@return* /
    public Object lGetIndex(String key, long index) {
        try {
            return redisTemplate.opsForList().index(key, index);
        } catch (Exception e) {
            e.printStackTrace();
            return null; }}/** * Put the list in cache **@paramThe key key *@paramThe value value *@return* /
    public boolean lSet(String key, Object value) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false; }}/** * Put the list in cache **@paramThe key key *@paramThe value value *@paramTime Time (seconds) x@return* /
    public boolean lSet(String key, Object value, long time) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            if (time > 0) expire(key, time);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false; }}/** * Put the list in cache **@paramThe key key *@paramThe value value *@return* /
    public boolean lSet(String key, List<Object> value) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false; }}/** * Put the list in cache **@paramThe key key *@paramThe value value *@paramTime Time (seconds) x@return* /
    public boolean lSet(String key, List<Object> value, long time) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            if (time > 0) expire(key, time);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false; }}/** * modify a list of data according to the index **@paramThe key key *@paramThe index index *@paramThe value value *@return* /
    public boolean lUpdateIndex(String key, long index, Object value) {
        try {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false; }}/** * Remove N values to value **@paramThe key key *@paramCount How many stars to remove@paramThe value value *@returnNumber of removed */
    public long lRemove(String key, long count, Object value) {
        try {
            Long remove = redisTemplate.opsForList().remove(key, count, value);
            return remove;
        } catch (Exception e) {
            e.printStackTrace();
            return 0; }}/ / = = = = = = = = = = = = = = = = ordered set sort set = = = = = = = = = = = = = = = = = = =
    /** * Add element ** to ordered set@param key
     * @param value
     * @param score
     * @return* /
    public boolean zSet(String key, Object value, double score) {
        return redisTemplate.opsForZSet().add(key, value, score);
    }

    public long batchZSet(String key, Set<ZSetOperations.TypedTuple> typles) {
        return redisTemplate.opsForZSet().add(key, typles);
    }

    public void zIncrementScore(String key, Object value, long delta) {
        redisTemplate.opsForZSet().incrementScore(key, value, delta);
    }

    public void zUnionAndStore(String key, Collection otherKeys, String destKey) {
        redisTemplate.opsForZSet().unionAndStore(key, otherKeys, destKey);
    }

    /** * Obtain the number of zsets *@param key
     * @param value
     * @return* /
    public long getZsetScore(String key, Object value) {
        Double score = redisTemplate.opsForZSet().score(key, value);
        if(score==null) {return 0;
        }else{
            returnscore.longValue(); }}/** * Obtain the rank of members in the ordered set key. * Where the members of the ordered set are sorted in descending order by the score value. *@param key
     * @param start
     * @param end
     * @return* /
    public Set<ZSetOperations.TypedTuple> getZSetRank(String key, long start, long end) {
        returnredisTemplate.opsForZSet().reverseRangeWithScores(key, start, end); }}Copy the code

The code that implements the class


    // Weekly reviews
    @Override
    public void initweek(a) {
        // Get 7 days of articles
        List<MPost> posts = this.list(new QueryWrapper<MPost>().ge("created", DateUtil.lastWeek())
                .select("id"."title"."user_id"."comment_count"."view_count"."created"));// Retrieve the query from 7 days ago and according to these queries, not all queries are required
        // Initialize the general comment for the article
        for (MPost post : posts) {
            / / set the key
            String key = "day:rank:" + DateUtil.format(post.getCreated(), DatePattern.PURE_DATE_FORMAT);
            // The number of cached comments
            redisUtil.zSet(key, post.getId(), post.getCommentCount());
            // Set the automatic expiration date to 7 days
            long between = DateUtil.between(new Date(), post.getCreated(), DateUnit.DAY);
            long expireTime = (7 - between) * 24 * 60 * 60; // The valid time

            redisUtil.expire(key, expireTime);
            // Cache some basic information about the article
            this.hashCachePost(post, expireTime);
        }
        / / and set
        this.zunionAndStore();
    }

    /** * Article number of comments per week and set operation **/
    private void zunionAndStore(a) {
        String destkey = "day:rank:" + DateUtil.format(new Date(), DatePattern.PURE_DATE_FORMAT);
        // Set the new key after the union
        String newkey = "week:rank";
        ArrayList<String> otherKeys = new ArrayList<>();
        // Count 7 days
        for (int i = -6; i < 0; i++) {
            String temp = "day:rank:" + DateUtil.format(DateUtil.offsetDay(new Date(), i), DatePattern.PURE_DATE_FORMAT);
            otherKeys.add(temp);
        }
        redisUtil.zUnionAndStore(destkey, otherKeys, newkey);
    }

    /** * Article author cache **/
    private void hashCachePost(MPost post, long expireTime) {
        / / set the key
        String key = "rank:post:" + post.getId();
        // Determine whether there is an existing or not
        boolean hasKey = redisUtil.hasKey(key);
        if(! hasKey) {// Store it in the cache
            redisUtil.hset(key, "post:id", post.getId(), expireTime);
            redisUtil.hset(key, "post:title", post.getTitle(), expireTime);
            redisUtil.hset(key, "post:commentCount", post.getCommentCount(), expireTime); }}}Copy the code

This converts our command line into code form. We can store our database data in the cache first. The effect

127.0.0.1:6379 > keys * 1)"rank:post:4"
2) "week:rank"
3) "day:rank:20210724"
4) "rank:post:3"
5) "rank:post:2"
6) "day:rank:20210726"
Select * from '3' where id = 3 where id = 3 '127.0.0.1:6379> zrevrange week:rank 0-1 withscores 1)"3"
2) "1"
3) "2"
4) "1"
5) "4"
6) "0"127.0.0.1:6379 >Copy the code

There is one comment in the database with id 3 There was really only one comment

The front end is displayed

So the idea here is pretty simple, just get our data out of the cache. Freemarker allows you to customize your tags. I have custom tags. Hosttemplate

/** * This week's hot topic */
@Component
public class HotsTemplate extends TemplateDirective {

    @Autowired
    RedisUtil redisUtil;

    @Override
    public String getName(a) {
        return "hots";
    }

    @Override
    public void execute(DirectiveHandler handler) throws Exception {
/ / set the key
        String key ="week:rank";
        Set<ZSetOperations.TypedTuple> zSetRank = redisUtil.getZSetRank(key, 0.6);
        ArrayList<Map> maps = new ArrayList<>();

        / / convenience
        for (ZSetOperations.TypedTuple typedTuple : zSetRank) {
            / / create a Map
            HashMap<String, Object> map = new HashMap<>();
            Object post_id = typedTuple.getValue();
            String PostHashKey = "rank:post:" +post_id;
            map.put("id",post_id);
            map.put("title",redisUtil.hget(PostHashKey,"post:title"));
            map.put("commentCount",typedTuple.getScore()); maps.add(map); } handler.put(RESULTS,maps).render(); }}Copy the code

To use our custom tags, inject our notes into FreemarkerConfig

@Configuration
public class FreemarkerConfig {

    @Autowired
    private freemarker.template.Configuration configuration;
    @Autowired
    PostsTemplate postsTemplate;
    @Autowired
    HotsTemplate hotsTemplate;
    @PostConstruct
    public void setUp(a) {
        configuration.setSharedVariable("timeAgo".new TimeAgoMethod());
        configuration.setSharedVariable("posts", postsTemplate);
        configuration.setSharedVariable("hosts", hotsTemplate); }}Copy the code

Front page access The effect

conclusion

When I’m doing this feature. Not comprehensive. Although finished, but should be to get a review within 7 days. I got 7 days of articles. It’s a bug but I don’t want to fix it. That’s it. It works for now. It’s the same thing. There will be time to change the problem. Tired, ruthless code machine record code in life…