博客浏览量通过redis缓存定时累加(只适用于学习)

1.设计缘由

自己写的博客网站有浏览量这个数据项,可是我也不想每加载一次文章数据就修改浏览量加一,并且自己的博客也没有那么大访问量没必要做时时更新访问量数据。所以构思选用redis缓存定时累加浏览数据的思路。

2.redis存储数据格式

浏览记录(字符串)
因为博客文章访问是开放的所以我设计的是key为(ip::文章id )value值随意我这里是0;因为我这里不保存浏览记录,而且5分钟以内同一ip访问同一条数据不增加访问量所以缓存时间5分钟。

文章浏览量(哈希)
最后会定时对浏览量进行存储所以采用(文章id-访问量)格式

浏览记录(字符串)
ip::文章id   0   缓存时间5分钟

浏览量(哈希)
key 文章id
value 访问量

3.主要代码

获取ip工具类

/**
 * @Description: Ip地址获取工具 如果使用代理,比如nginx一定要配置请求头的转发
 * @Modified By:
 */
@Component
public class IPUtils {
    private static Logger logger = LoggerFactory.getLogger(IPUtils.class);
    private static final String IP_UTILS_FLAG = ",";
    private static final String UNKNOWN = "unknown";
    private static final String LOCALHOST_IP = "0:0:0:0:0:0:0:1";
    private static final String LOCALHOST_IP1 = "127.0.0.1";

    /**
     * 获取IP地址
     * <p>
     * 使用Nginx等反向代理软件, 则不能通过request.getRemoteAddr()获取IP地址
     * 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,X-Forwarded-For中第一个非unknown的有效IP字符串,则为真实IP地址
     */
    public static String getIpAddr(HttpServletRequest request) {
        String ip = null;
        try {
            //以下两个获取在k8s中,将真实的客户端IP,放到了x-Original-Forwarded-For。而将WAF的回源地址放到了 x-Forwarded-For了。
            ip = request.getHeader("X-Original-Forwarded-For");
            if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = request.getHeader("X-Forwarded-For");
            }
            //获取nginx等代理的ip
            if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = request.getHeader("x-forwarded-for");
            }
            if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = request.getHeader("Proxy-Client-IP");
            }
            if (StringUtils.isEmpty(ip) || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = request.getHeader("WL-Proxy-Client-IP");
            }
            if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = request.getHeader("HTTP_CLIENT_IP");
            }
            if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = request.getHeader("HTTP_X_FORWARDED_FOR");
            }
            //兼容k8s集群获取ip
            if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = request.getRemoteAddr();
                if (LOCALHOST_IP1.equalsIgnoreCase(ip) || LOCALHOST_IP.equalsIgnoreCase(ip)) {
                    //根据网卡取本机配置的IP
                    InetAddress iNet = null;
                    try {
                        iNet = InetAddress.getLocalHost();
                    } catch (UnknownHostException e) {
                        logger.error("getClientIp error: {}", e);
                    }
                    ip = iNet.getHostAddress();
                }
            }
        } catch (Exception e) {
            logger.error("IPUtils ERROR ", e);
        }
        //使用代理,则获取第一个IP地址
        if (!StringUtils.isEmpty(ip) && ip.indexOf(IP_UTILS_FLAG) > 0) {
            ip = ip.substring(0, ip.indexOf(IP_UTILS_FLAG));
        }

        return ip;
    }
}

数据工具类

public class BrowseKeyUtils {
    //保存文章被浏览数量的key
    public static final String MAP_KEY_BROWSE_COUNT = "MAP_KEY_BROWSE_COUNT";
    //浏览时间限制
    public static final long  BROWSE_TIME =60*5;

    /**
     * 拼接用户ip和文章id作为key。格式 222222::333333
     * @param ip 用户ip
     * @param aid 文章id
     * @return
     */
    public static String getBrowseKey(String ip, String aid){
        StringBuilder builder = new StringBuilder();
        builder.append(ip);
        builder.append("::");
        builder.append(aid);
        return builder.toString();
    }
}

接口

public interface BrowseService {

    /**
     * @Description: 存入浏览记录 key= ip::aid  value=date
     * @Param ip IP地址
     * @Param aid 文章id
     */
    void saveBrowseRedis(String ip, String aid);

    /**
     * @Description: 文章浏览数+1
     * @Param aid 文章id
     */
    void incrementCount( String aid);

    /**
     * 获取Redis中存储的所有浏览数量
     * @return
     */
    List<Articles> getBrowseCountFromRedis();
}

实现类
redisUtil是我自己封装的redisTemplate工具类当时以为写错了没有使用里面的哈希操作,直接用的redisTemplate。你可以全部使用redisTemplate或自己的工具类

public class BrowseServiceImpl implements  BrowseService{

    @Autowired
    RedisUtil redisUtil;
    @Autowired
    RedisTemplate redisTemplate;

    /**
     * @Description: 如果没有记录则添加,+1;有记录不处理
     * @Auther: dxf
     * @Param ip
     * @Param aid
     */
    @Override
    public void saveBrowseRedis(String ip, String aid) {
        String key = BrowseKeyUtils.getBrowseKey(ip, aid);
        System.out.println(key);
        if(ObjectUtil.isEmpty(redisUtil.get(key))){
            System.out.println(key+"  添加");
            redisUtil.set(key,0,BrowseKeyUtils.BROWSE_TIME);
            incrementCount(aid);
        }

    }

    @Override
    public void incrementCount(String aid) {
        System.out.println(aid+"+1");
        redisTemplate.opsForHash().increment(BrowseKeyUtils.MAP_KEY_BROWSE_COUNT, aid, 1);
    }

    @Override
    public List<Articles> getBrowseCountFromRedis() {
        Cursor<Map.Entry<Object, Object>> cursor = redisTemplate.opsForHash().scan(BrowseKeyUtils.MAP_KEY_BROWSE_COUNT, ScanOptions.NONE);
        List<Articles> list = new ArrayList<>();
        while (cursor.hasNext()){
            Map.Entry<Object, Object> entry = cursor.next();
            Long key =  Long.valueOf((String) entry.getKey());
            //组装成 UserLike 对象
            Articles articles = new Articles();
            articles.setId(key);
            articles.setBrowseCount( Long.valueOf(String.valueOf(entry.getValue())) );
            list.add(articles);
            
            //存到 list 后从 Redis 中删除
            redisTemplate.opsForHash().delete(BrowseKeyUtils.MAP_KEY_BROWSE_COUNT,  entry.getKey());
            System.out.println(redisTemplate.opsForHash().size(BrowseKeyUtils.MAP_KEY_BROWSE_COUNT));
        }
        return list;
    }
}

4.定时任务和批量更新

定时任务

@Autowired
BrowseService browseService;
@Autowired
ArticlesService articlesService;

@Scheduled(cron = "0 0 10 * * ? ")//每天10点更新
private void detectDeviceStatus() {
    log.info("更新浏览数量");
    List<Articles> browseCount = browseService.getBrowseCountFromRedis();
    if(CollectionUtils.isNotEmpty(browseCount)){//不为空才更新
        articlesService.addBrowseCount(browseCount);
    }

}

批量更新
MySQL没有提供直接的方法来实现批量更新,但可以使用case when语法来实现这个功能。

UPDATE course
    SET name = CASE id 
        WHEN 1 THEN 'name1'
        WHEN 2 THEN 'name2'
        WHEN 3 THEN 'name3'
    END, 
    title = CASE id 
        WHEN 1 THEN 'New Title 1'
        WHEN 2 THEN 'New Title 2'
        WHEN 3 THEN 'New Title 3'
    END
WHERE id IN (1,2,3)

这条sql的意思是,如果id为1,则name的值为name1,title的值为New Title1;依此类推。

<update id="addBrowseCount" parameterType="java.util.List" >
    update blog_articles
    <trim prefix="set" suffixOverrides=",">
        <trim prefix="browse_count =case" suffix="end ">
            <foreach collection="list" item="item" index="index">
                <if test="item.browseCount !=null and item.id !=null">
                    when id=#{item.id}
                    then browse_count+#{item.browseCount}
                </if>
            </foreach>
        </trim>
    </trim>
    where id in
    <foreach collection="list" item="item" index="index" separator="," open="(" close=")">
        #{item.id}
    </foreach>
</update>
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值