The article links: liuyueyi. Making. IO/hexblog / 201…

Spring uses Redis to design a simple access counter

Why do an access count? Before the personal blog is using bu operator to do site access count, it is good to use, but the response is very slow, followed by personal blog is too little access, the data is not good 😢…

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

I. design

A simple access counter, mainly using redis hash structure, the corresponding storage structure is as follows:

The storage structure is relatively simple. In order to expand, each application (or site) corresponds to an APP, and then carries out paging statistics according to the path path. Finally, there is a special count for counting the access of the whole site

II. The implementation

It is mainly to use Redis hash structure, and then achieve data statistics, there is not too much difficulty, in the Spring environment to build a Redis environment can refer to:

  • 180611-Spring RedisTemplate configuration and use

1. Redis encapsulates classes

We can use template.opsForValue() and other convenient methods to serialize and deserialize objects using JSON

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));
        }
        returnresult; }}Copy the code

Corresponding configuration class

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(a) {
        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(a) {
        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();
        returnfac; }}Copy the code

2. Controller support

The first is to define the request parameters:

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

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

  • If the request parameter display specifies the referer parameter, the statistics are used with the passed parameter
  • If the specified referer is not displayed, the referer is obtained based on the header
  • Parse the referer and count +1 for path and host respectively, so that site counts are based on host and page counts 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, "Request referer cannot be obtained!");
        }

        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); }}}Copy the code

Example 3.

For this simple Redis count, you can now see the count in the footer of your mWeb and ZWeb pages. The count is +1 for each refresh

  • mweb: liuyueyi.gitee.io/mweb/#/
  • zweb: liuyueyi.gitee.io/zweb/#/

III. The other

0. Related blog posts

  • 180611-Spring RedisTemplate configuration and use

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

A gray personal blog, recording all the study and work in the blog, welcome everyone to go to stroll

2. Statement

As far as the letter is not as good as, has been the content, purely one’s own words, because of the limited personal ability, it is hard to avoid omissions and mistakes, such as finding bugs or better suggestions, welcome criticism and correction, not grudging gratitude

  • Micro Blog address: Small Gray Blog
  • QQ: a gray /3302797840

3. Scan attention