FlagCounter被封杀?自己实现一个简单的多国访客计数器
起因
前段时间发现博客右边的FlagCounter计数器突然没了,又看到了博客园封杀了FlagCounter的消息,有点摸不着头脑。于是上FlagCounter的网站上看了一眼,发现最近出现的来自新国家访问居然来自台湾。又经过一轮百度,看到有博主发表声明说由于国家立场拒绝使用FlagCounter了。于是我赶紧把公告栏清空了,又苦于没有替代品,就想着干脆自己写一个。
前端显示的是HTML,比FlagCounter的图片格式要清晰很多。
开发环境以及线上环境
- SpringBoot 2.1.8
- Redis 5.0.x
- 域名 + SSL证书 (博客园需要https)
使用到的接口及开源数据
- www.taobao.com/help/getip.php 用于获取IP地址(可以直接用下面的接口获取,之前疏忽了)
- http://ip-api.com/json/ 用于解析IP地理位置(只提供HTTP,所以请求需要通过后端发送)
- https://github.com/mukeshsolanki/country-picker-android 国旗图片来源
开发思路
用户访问时通过淘宝接口在前端获取访客IP地址,传给后端。后端向ip-api请求ip地址解析后的国家代码,并记录到Redis数据库。同时向后端请求访客数据,并显示在页面上。
其实在找国家代码标准的数据的时候,发现有两种标准:一种是ISO-3166标准,还有一种是GB/T 2659-2000。区别在于,前者把香港、台湾等地区标为HK、TW,后者直接都标记为CN。可能FlagCounter也只是被这个标准坑了一波,把所有的国家代码标注的地区都当做了国家处理。
关键代码
package com.qf; import com.alibaba.fastjson.JSON; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.ZSetOperations; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import java.util.HashMap; import java.util.Set; @CrossOrigin @RestController @Transactional public class Controller { @Autowired private StringRedisTemplate redisTemplate; private RestTemplate restTemplate = new RestTemplate(); // 接口隐藏 @RequestMapping("xxxxxx") public String getVisitor(){ // 取出排名前20的国家以及total(总数),所以是0-20共21个 Set<ZSetOperations.TypedTuple<String>> typedTupleSet = redisTemplate.opsForZSet().reverseRangeWithScores("visitor", 0, 20); return JSON.toJSON(typedTupleSet).toString(); } // 接口隐藏 @RequestMapping("xxxxxx") public void addVisitor(@PathVariable String ip){ // 请求ip-api接口,获取ip所属地信息 IPaddr iPaddr = restTemplate.getForObject("http://ip-api.com/json/"+ip, IPaddr.class, new HashMap<>()); String code = iPaddr.getCountryCode(); // 转换为GB/T 2659-2000标准 if (code.equals("HK") || code.equals("TW") || code.equals("MO")) { code = "CN"; } // 更新Redis中的记录 redisTemplate.opsForZSet().incrementScore("visitor", code, 1); redisTemplate.opsForZSet().incrementScore("visitor", "total", 1); } }