Spring之借助Redis设计一个简单访问计数器的示例
程序员文章站
2023-12-19 17:46:46
为什么要做一个访问计数?之前的个人博客用得是卜算子做站点访问计数,用起来挺好,但出现较多次的响应很慢,再其次就是个人博客实在是访问太少,数据不好看????…
前...
为什么要做一个访问计数?之前的个人博客用得是卜算子做站点访问计数,用起来挺好,但出现较多次的响应很慢,再其次就是个人博客实在是访问太少,数据不好看????…
前面一篇博文简单介绍了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<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; } }
对应的配置类
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 支持
首先是定义请求参数:
@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<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); } } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。