服务器之家

服务器之家 > 正文

Spring之借助Redis设计一个简单访问计数器的示例

时间:2021-05-11 15:12     来源/作者:一灰灰Blog

为什么要做一个访问计数?之前的个人博客用得是卜算子做站点访问计数,用起来挺好,但出现较多次的响应很慢,再其次就是个人博客实在是访问太少,数据不好看…

前面一篇博文简单介绍了spring中的redistemplate的配置与使用,那么这篇算是一个简单的应用case了,主要基于redis的计数器来实现统计

i. 设计

一个简单的访问计数器,主要利用redis的hash结构,对应的存储结构如下:

Spring之借助Redis设计一个简单访问计数器的示例

存储结构比较简单,为了扩展,每个应用(or站点)对应一个app,然后根据path路径进行分页统计,最后有一个特殊的用于统计全站的访问计数

ii. 实现

主要就是利用redis的hash结构,然后实现数据统计,并没有太多的难度,spring环境下搭建redis环境可以参考:

spring之redistemplate配置与使用

1. redis封装类

针对几个常用的做了简单的封装,直接使用redistemplate的excute方法进行的操作,当然也是可以使用 template.opsforvalue() 等便捷方式,这里采用json方式进行对象的序列化和反序列化

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
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;
  }
}

对应的配置类

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
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 支持

首先是定义请求参数:

?
1
2
3
4
5
@data
public class webcountreqdo implements serializable {
  private string appkey;
  private string referer;
}

其次是实现controller接口,稍稍注意下,根据path进行计数的逻辑:

  1. 如果请求参数显示指定了referer参数,则用传入的参数进行统计
  2. 如果没有显示指定referer,则根据header获取referer
  3. 解析referer,分别对path和host进行统计+1,这样站点的统计计数就是根据host来的,而页面的统计计数则是根据path路径来的
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@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, "请指定appkey!");
    }
 
    string referer = reqinfocontext.getreqinfo().getreferer();
    if (stringutils.isblank(referer)) {
      referer = webcountreqdo.getreferer();
    }
 
    if (stringutils.isblank(referer)) {
      return responsewrapper.errorreturnmix(status.statusenum.fail_mix, "无法获取请求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);
    }
  }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。

原文链接:https://liuyueyi.github.io/hexblog/

相关文章

热门资讯

2020微信伤感网名听哭了 让对方看到心疼的伤感网名大全
2020微信伤感网名听哭了 让对方看到心疼的伤感网名大全 2019-12-26
yue是什么意思 网络流行语yue了是什么梗
yue是什么意思 网络流行语yue了是什么梗 2020-10-11
背刺什么意思 网络词语背刺是什么梗
背刺什么意思 网络词语背刺是什么梗 2020-05-22
苹果12mini价格表官网报价 iPhone12mini全版本价格汇总
苹果12mini价格表官网报价 iPhone12mini全版本价格汇总 2020-11-13
2021德云社封箱演出完整版 2021年德云社封箱演出在线看
2021德云社封箱演出完整版 2021年德云社封箱演出在线看 2021-03-15
返回顶部