为什么要做一个访问计数?之前的个人博客用得是卜算子做站点访问计数,用起来挺好,但出现较多次的响应很慢,再其次就是个人博客实在是访问太少,数据不好看😢…
前面一篇博文简单介绍了spring中的redistemplate的配置与使用,那么这篇算是一个简单的应用case了,主要基于redis的计数器来实现统计
i. 设计
一个简单的访问计数器,主要利用redis的hash结构,对应的存储结构如下:
存储结构比较简单,为了扩展,每个应用(or站点)对应一个app,然后根据path路径进行分页统计,最后有一个特殊的用于统计全站的访问计数
ii. 实现
主要就是利用redis的hash结构,然后实现数据统计,并没有太多的难度,spring环境下搭建redis环境可以参考:
1. redis封装类
针对几个常用的做了简单的封装,直接使用redistemplate的excute方法进行的操作,当然也是可以使用 template.opsforvalue() 等便捷方式,这里采用json方式进行对象的序列化和反序列化
public class quickredisclient {
private static final charset code = charset.forname("utf-8");
private static redistemplate template;
public static void register(redistemplate 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 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) 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) con -> {
con.set(tobytes(key), tobytes(value));
return null;
});
}
public static long incr(string key, long add) {
return template.execute((rediscallback) 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) con -> {
long record = con.hincrby(tobytes(key), tobytes(field), add);
return record == null ? 0l : record;
});
}
public static t hget(string key, string field, class clz) {
return template.execute((rediscallback) con -> {
byte[] records = con.hget(tobytes(key), tobytes(field));
if (records == null) {
return null;
}
return json.parseobject(records, clz);
});
}
public static map hmget(string key, list fields, class clz) {
list list =
template.execute((rediscallback>) con -> con.hmget(tobytes(key), tobytes(fields)));
if (collectionutils.isempty(list)) {
return collections.emptymap();
}
map 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;
}
}
对应的配置类
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 redistemplate(redisconnectionfactory redisconnectionfactory) {
redistemplate 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 支持
首先是定义请求参数:
@data
public class webcountreqdo implements serializable {
private string appkey;
private string referer;
}
其次是实现controller接口,稍稍注意下,根据path进行计数的逻辑:
如果请求参数显示指定了referer参数,则用传入的参数进行统计
如果没有显示指定referer,则根据header获取referer
解析referer,分别对path和host进行统计+1,这样站点的统计计数就是根据host来的,而页面的统计计数则是根据path路径来的
@slf4j
@restcontroller
@requestmapping(path = "/count")
public class webcountcontroller {
@requestmapping(path = "cc", method = {requestmethod.get})
public responsewrapper 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);
}
}
}
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持萬仟网。
希望与广大网友互动??
点此进行留言吧!