180626-Spring Design a Simple Access Counter with Redis

Links to articles: https://liuyueyi.github.io/hexblog/2018/06/26/180626-Spring Designs a Simple Access Counter with Redis/

Spring Design a Simple Access Counter with Redis

Why do you want to make an access count? Previous personal blogs used divination operators to do site visits counting, which is good, but the response is very slow, followed by personal blogs are too few visits, data is not good.

The previous blog article briefly introduced the configuration and use of RedisTemplate in Spring, so this is a simple application case, mainly based on Redis counter to achieve statistics.

<!-- more -->

I. design

A simple access counter mainly uses the hash structure of redis. The corresponding storage structure is as follows:

Storage structure is relatively simple, in order to expand, each application (or site) corresponds to an APP, then according to path path path for paging statistics, and finally there is a special access count for the whole station.

II. implementation

It is not too difficult to use Redis hash structure to implement data statistics. It can be used as a reference to build redis environment in Spring environment.

1. Redis encapsulation class

For several commonly used encapsulation, we use RedisTemplate's excute method directly. Of course, we can use template.opsForValue() and other convenient methods. Here we use JSON to serialize and deserialize objects.

public class QuickRedisClient {
    private static final Charset CODE = Charset.forName("UTF-8");
    private static RedisTemplate<String, String> template;

    public static void register(RedisTemplate<String, String> template) {
        QuickRedisClient.template = template;
    }

    public static void nullCheck(Object... args) {
        for (Object obj : args) {
            if (obj == null) {
                throw new IllegalArgumentException("redis argument can not be null!");
            }
        }
    }

    public static byte[] toBytes(String key) {
        nullCheck(key);
        return key.getBytes(CODE);
    }

    public static byte[][] toBytes(List<String> keys) {
        byte[][] bytes = new byte[keys.size()][];
        int index = 0;
        for (String key : keys) {
            bytes[index++] = toBytes(key);
        }
        return bytes;
    }

    public static String getStr(String key) {
        return template.execute((RedisCallback<String>) con -> {
            byte[] val = con.get(toBytes(key));
            return val == null ? null : new String(val);
        });
    }

    public static void putStr(String key, String value) {
        template.execute((RedisCallback<Void>) con -> {
            con.set(toBytes(key), toBytes(value));
            return null;
        });
    }

    public static Long incr(String key, long add) {
        return template.execute((RedisCallback<Long>) con -> {
            Long record = con.incrBy(toBytes(key), add);
            return record == null ? 0L : record;
        });
    }

    public static Long hIncr(String key, String field, long add) {
        return template.execute((RedisCallback<Long>) con -> {
            Long record = con.hIncrBy(toBytes(key), toBytes(field), add);
            return record == null ? 0L : record;
        });
    }

    public static <T> T hGet(String key, String field, Class<T> clz) {
        return template.execute((RedisCallback<T>) con -> {
            byte[] records = con.hGet(toBytes(key), toBytes(field));
            if (records == null) {
                return null;
            }

            return JSON.parseObject(records, clz);
        });
    }

    public static <T> Map<String, T> hMGet(String key, List<String> fields, Class<T> clz) {
        List<byte[]> list =
                template.execute((RedisCallback<List<byte[]>>) con -> con.hMGet(toBytes(key), toBytes(fields)));
        if (CollectionUtils.isEmpty(list)) {
            return Collections.emptyMap();
        }

        Map<String, T> result = new HashMap<>();
        for (int i = 0; i < fields.size(); i++) {
            if (list.get(i) == null) {
                continue;
            }

            result.put(fields.get(i), JSON.parseObject(list.get(i), clz));
        }
        return result;
    }
}

Corresponding configuration classes

package com.git.hui.story.cache.redis;

import com.git.hui.story.cache.redis.serializer.DefaultStrSerializer;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;

/**
 * Created by yihui in 18:45 18/6/11.
 */
@Configuration
@PropertySource(value = "classpath:application.yml")
public class RedisConf {

    private final Environment environment;

    public RedisConf(Environment environment) {
        this.environment = environment;
    }

    @Bean
    public CacheManager cacheManager() {
        return RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(redisConnectionFactory()).build();
    }

    @Bean
    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);

        DefaultStrSerializer serializer = new DefaultStrSerializer();
        redisTemplate.setValueSerializer(serializer);
        redisTemplate.setHashValueSerializer(serializer);
        redisTemplate.setKeySerializer(serializer);
        redisTemplate.setHashKeySerializer(serializer);

        redisTemplate.afterPropertiesSet();

        QuickRedisClient.register(redisTemplate);
        return redisTemplate;
    }


    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        LettuceConnectionFactory fac = new LettuceConnectionFactory();
        fac.getStandaloneConfiguration().setHostName(environment.getProperty("spring.redis.host"));
        fac.getStandaloneConfiguration().setPort(Integer.parseInt(environment.getProperty("spring.redis.port")));
        fac.getStandaloneConfiguration()
                .setPassword(RedisPassword.of(environment.getProperty("spring.redis.password")));
        fac.afterPropertiesSet();
        return fac;
    }
}

2. Controller support

First, define the request parameters:

@Data
public class WebCountReqDO implements Serializable {
    private String appKey;
    private String referer;
}

The second is to implement the Controller interface, with a little attention to the logic of counting according to path:

  • If the referer parameter is specified in the request parameter display, the input parameter is used for statistics.
  • If no referer is specified, the referer is retrieved from the header
  • Resolve referer, and count path and host + 1, respectively, so that the statistics of the site are based on host, while the statistics of the page are based on path path.
@Slf4j
@RestController
@RequestMapping(path = "/count")
public class WebCountController {

    @RequestMapping(path = "cc", method = {RequestMethod.GET})
    public ResponseWrapper<CountDTO> addCount(WebCountReqDO webCountReqDO) {
        String appKey = webCountReqDO.getAppKey();
        if (StringUtils.isBlank(appKey)) {
            return ResponseWrapper.errorReturnMix(Status.StatusEnum.ILLEGAL_PARAMS_MIX, "Please specify APPKEY!");
        }

        String referer = ReqInfoContext.getReqInfo().getReferer();
        if (StringUtils.isBlank(referer)) {
            referer = webCountReqDO.getReferer();
        }

        if (StringUtils.isBlank(referer)) {
            return ResponseWrapper.errorReturnMix(Status.StatusEnum.FAIL_MIX, "Failure to obtain requests referer!");
        }

        return ResponseWrapper.successReturn(doUpdateCnt(appKey, referer));
    }


    private CountDTO doUpdateCnt(String appKey, String referer) {
        try {
            if (!referer.startsWith("http")) {
                referer = "https://" + referer;
            }

            URI uri = new URI(referer);
            String host = uri.getHost();
            String path = uri.getPath();
            long count = QuickRedisClient.hIncr(appKey, path, 1);
            long total = QuickRedisClient.hIncr(appKey, host, 1);
            return new CountDTO(count, total);
        } catch (Exception e) {
            log.error("get referer path error! referer: {}, e: {}", referer, e);
            return new CountDTO(1L, 1L);
        }
    }
}

3. examples

For this simple redis count, currently the individual mweb and zweb pages have been accessed, and the corresponding count can be seen at the footer. Each refresh count will be + 1

III. other

0. Relevant blog posts

1. A Grey Blog: https://liuyueyi.github.io/hexblog

A grey personal blog, record all the study and work of the blog, welcome to visit

2. statement

Letters are not as good as letters. They are purely family statements. Due to limited personal abilities, there are inevitably omissions and errors. If bug s are found or better suggestions are available, you are welcome to criticize and correct them. Thank you very much.

3. Scanning attention

Keywords: Redis Spring JSON github

Added by Axcelcius on Wed, 15 May 2019 07:21:53 +0300