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

本文介绍了一个基于Redis的访问计数器设计,利用Redis的Hash结构存储每个应用的访问数据,包括全站和各页面统计。通过Spring的RedisTemplate进行操作,提供了一组便捷的方法进行增减操作。在Controller中,根据请求参数或Header的referer信息更新计数,确保准确统计。

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

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

I. 设计

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

6e950884e676e53ad6eefe24bf20d606.png

存储结构比较简单,为了扩展,每个应用(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);

}

}

}

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

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值