package rfx.server.util;
/* Lookup geographic location from IP address
* Author: TrucLK
* Idea: http://stackoverflow.com/questions/8622816/redis-or-mongo-for-determining-if-a-number-falls-within-ranges/8624231#8624231
* Sơ lược ý tưởng:
* Mỗi IP address có thể được đổi thành một số nguyên hệ thập phân.
*
* Một dãy IP bao gồm 1 số đầu và số cuối, thể hiện trên dãy đó là mã số của vị trí tỉnh thành.
*
* Cách thức lưu trữ dữ liệu gồm 1 Sorted set của Redis. Mỗi dãy IP sẽ có 2 vị trí trên set này. Với điểm số tương đương với giá trị đầu và giá trị cuối của dãy.
*
* Tìm 2 giá trị gần nhất và xem có cùng thuộc 1 dãy hay không. Nếu thuộc sẽ lấy giá trị Location của dãy ấy.
*
* Khuyết điểm, vẫn chỉ mới hỗ trợ các dãy IP riêng không chồng chéo.
*
* */
import java.util.Date;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Set;
import java.util.TreeMap;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.ShardedJedis;
import redis.clients.jedis.ShardedJedisPool;
import rfx.server.util.redis.RedisManagerUtil;
public class LocationUtil {
final public static String MAXIP = "4294967295";
final public static int LOCATION_VIETNAM_UNDEFINED = 0;
final public static int LOCATION_UNDEFINED = -99;
final public static int LOCATION_NULL = -1;
private static Map<Integer, Boolean> provinceCacheMap = RedisManagerUtil.getRedisPoolConfigs().getProvinceCacheLookup() ; // {24:true,29:true}
private static Map<String, Boolean> countryCacheMap = RedisManagerUtil.getRedisPoolConfigs().getCountryCacheLookup() ; // {VN:true,US:true}
private static NavigableMap<Long, LocationCacheObj> LOCATIONCACHE = new TreeMap<Long, LocationCacheObj>();
private static NavigableMap<Long, LocationCacheObj> COUNTRYCACHE = new TreeMap<Long, LocationCacheObj>();
static int cachedTime = RedisManagerUtil.getRedisPoolConfigs().getCacheTime();
/**
* Lookup province ip, có cache các dãy IP của HCM, HA NOI (request lớn) ---> improve performance đáng kể
* @param ipAdress
* @return
* provinceId
*/
public static LocationCacheObj getVNProvinceFromIp(String ipAdress) {
LocationCacheObj locationCacheObj = null;
long ipLong = 0;
try {
ipLong = StringUtil.Dot2LongIP(ipAdress);
LocationCacheObj floorCacheObj = LOCATIONCACHE.get(LOCATIONCACHE.floorKey(ipLong)); // chận dưới lớn nhất
LocationCacheObj ceilCacheObj = LOCATIONCACHE.get(LOCATIONCACHE.ceilingKey(ipLong)); // chận trên nhỏ nhất
if (floorCacheObj.getProvince() != LOCATION_UNDEFINED) {
if( floorCacheObj.beginIPNumber == ceilCacheObj.beginIPNumber
&& floorCacheObj.endIPNumber == ceilCacheObj.endIPNumber ){
//System.out.println( "return from cache, begin: "+ floorCacheObj.getBeginIPNumber() + ","+floorCacheObj.getEndIPNumber() );
return floorCacheObj;
}
}
} catch (Exception ex) {}
boolean commited = false;
ShardedJedisPool jedisPool = null;
ShardedJedis shardedJedis = null;
Jedis jedis = null;
try {
jedisPool = RedisManagerUtil.getIPLocationPool();
shardedJedis = jedisPool.getResource();
jedis = shardedJedis.getShard(StringPool.BLANK);
Set<String> floors = jedis.zrevrangeByScore("range_index", String.valueOf(ipLong), "0", 0, 1);
Set<String> ceils = jedis.zrangeByScore("range_index", String.valueOf(ipLong), MAXIP, 0, 1);
if (floors.size() == 1 && ceils.size() == 1) {
String floor = floors.iterator().next();
String ceil = ceils.iterator().next();
String f0, f1, c0, c1 = "";
f0 = floor.split("-")[0];
f1 = floor.split("-")[1];
c0 = ceil.split("-")[0];
c1 = ceil.split("-")[1];
// 1: (f0.equals(c1) && f1.equals(c0))==true : Khi startIpNum < ipLong < endIpNum
// 2: (f0.equals(c0) && f1.equals(c1))==true : startIpNum == ipLong AND ipLong == endIpNum
// 2.1 Nếu startIpNum == ipLong, floor = ceil = startIpNum-endIpNum
// 2.2 Nếu endIpNum == ipLong, floor = ceil = endIpNum-startIpNum --> phải đảo lại startIpNum-endIpNum để lấy province,zone
if ( (f0.equals(c1) && f1.equals(c0)) || (f0.equals(c0) && f1.equals(c1)) ) {
int zone = 0 ;
int province = StringUtil.safeParseInt(jedis.hget("province:" + floor,"province"), LOCATION_UNDEFINED);
if( province == LOCATION_UNDEFINED ){
System.out.println( "2.2 endIpNum == ipLong:"+f1+"-"+f0 );
province = StringUtil.safeParseInt(jedis.hget("province:"+f1+"-"+f0,"province"), LOCATION_UNDEFINED);
zone = StringUtil.safeParseInt(jedis.hget("province:"+f1+"-"+f0,"zone"), 0);
}
else{
zone = StringUtil.safeParseInt(jedis.hget("province:" + floor,"zone"), 0);
}
long beginIPNumber = Long.parseLong(f0) ;
long endIPNumber = Long.parseLong(f1) ;
locationCacheObj = new LocationCacheObj(cachedTime);
locationCacheObj.setProvice(province);
locationCacheObj.setZone(zone);
locationCacheObj.setBeginIPNumber(beginIPNumber);
locationCacheObj.setEndIPNumber(endIPNumber);
LocationCacheObj ceilCacheObj = new LocationCacheObj();
ceilCacheObj.setProvice(province);
ceilCacheObj.setZone(zone);
ceilCacheObj.setBeginIPNumber(beginIPNumber);
ceilCacheObj.setEndIPNumber(endIPNumber);
if (LOCATIONCACHE.size() < RedisManagerUtil.getRedisPoolConfigs().getMaxCacheEntry() ) {
if( provinceCacheMap.get(province) != null ){
LOCATIONCACHE.put(beginIPNumber, locationCacheObj);
LOCATIONCACHE.put(endIPNumber, ceilCacheObj);
}
}
}
}
commited = true;
// zrevrangebyscore range_index 5 0 LIMIT 0 1
} catch (Exception e) {
e.printStackTrace();
LogUtil.error("LocationUtil.getLocationFromIp", e.getMessage());
} finally {
if (commited) {
jedisPool.returnResource(shardedJedis);
} else {
jedisPool.returnBrokenResource(shardedJedis);
}
}
if(locationCacheObj == null) {
locationCacheObj = new LocationCacheObj();
}
return locationCacheObj;
}
/**
* Lookup country ip, có cache các dãy IP của VN, US (request lớn) ---> improve performance đáng kể
* @param ipAdress
* @return
* countryCode
*/
public static LocationCacheObj getCountryFromIp(String ipAdress) {
LocationCacheObj locationCacheObj = null;
long ipLong = 0;
try {
ipLong = StringUtil.Dot2LongIP(ipAdress);
LocationCacheObj floorCacheObj = COUNTRYCACHE.get(COUNTRYCACHE.floorKey(ipLong)); // chận dưới lớn nhất
LocationCacheObj ceilCacheObj = COUNTRYCACHE.get(COUNTRYCACHE.ceilingKey(ipLong)); // chận trên nhỏ nhất
if (floorCacheObj.getCountryCode() != null) {
if( floorCacheObj.beginIPNumber == ceilCacheObj.beginIPNumber
&& floorCacheObj.endIPNumber == ceilCacheObj.endIPNumber ){
//System.out.println( "getCountryFromIp return from cache, begin: "+ floorCacheObj.getBeginIPNumber() + ","+floorCacheObj.getEndIPNumber() + ", country: "+ floorCacheObj.getCountryCode() );
return floorCacheObj;
}
}
} catch (Exception ex) {}
boolean commited = false;
ShardedJedisPool jedisPool = null;
ShardedJedis shardedJedis = null;
Jedis jedis = null;
try {
jedisPool = RedisManagerUtil.getIPLocationPool();
shardedJedis = jedisPool.getResource();
jedis = shardedJedis.getShard(StringPool.BLANK);
Set<String> floors = jedis.zrevrangeByScore("c_range_index", String.valueOf(ipLong), "0", 0, 1);
Set<String> ceils = jedis.zrangeByScore("c_range_index", String.valueOf(ipLong), MAXIP, 0, 1);
if (floors.size() == 1 && ceils.size() == 1) {
String floor = floors.iterator().next();
String ceil = ceils.iterator().next();
String f0, f1, c0, c1 = "";
f0 = floor.split("-")[0];
f1 = floor.split("-")[1];
c0 = ceil.split("-")[0];
c1 = ceil.split("-")[1];
// 1: (f0.equals(c1) && f1.equals(c0))==true : Khi startIpNum < ipLong < endIpNum
// 2: (f0.equals(c0) && f1.equals(c1))==true : startIpNum == ipLong AND ipLong == endIpNum
// 2.1 Nếu startIpNum == ipLong, floor = ceil = startIpNum-endIpNum
// 2.2 Nếu endIpNum == ipLong, floor = ceil = endIpNum-startIpNum --> phải đảo lại startIpNum-endIpNum để lấy countryCode
if ( (f0.equals(c1) && f1.equals(c0)) || (f0.equals(c0) && f1.equals(c1)) ) {
String countryCode = jedis.hget("country:"+floor,"code");
if( countryCode==null ){
countryCode = jedis.hget("country:"+f1+"-"+f0,"code");
}
long beginIPNumber = Long.parseLong(f0) ;
long endIPNumber = Long.parseLong(f1) ;
locationCacheObj = new LocationCacheObj(cachedTime);
locationCacheObj.setCountryCode(countryCode);
locationCacheObj.setBeginIPNumber(beginIPNumber);
locationCacheObj.setEndIPNumber(endIPNumber);
LocationCacheObj ceilCacheObj = new LocationCacheObj();
locationCacheObj.setCountryCode(countryCode);
ceilCacheObj.setBeginIPNumber(beginIPNumber);
ceilCacheObj.setEndIPNumber(endIPNumber);
if (COUNTRYCACHE.size() < RedisManagerUtil.getRedisPoolConfigs().getMaxCacheEntry() ) {
if( countryCacheMap.get(countryCode) != null ){
//System.out.println( "COUNTRYCACHE.put "+ beginIPNumber);
COUNTRYCACHE.put(beginIPNumber, locationCacheObj);
COUNTRYCACHE.put(endIPNumber, ceilCacheObj);
}
}
}
}
commited = true;
// zrevrangebyscore range_index 5 0 LIMIT 0 1
} catch (Exception e) {
e.printStackTrace();
LogUtil.error("LocationUtil.getLocationFromIp", e.getMessage());
} finally {
if (commited) {
jedisPool.returnResource(shardedJedis);
} else {
jedisPool.returnBrokenResource(shardedJedis);
}
}
if(locationCacheObj == null) {
locationCacheObj = new LocationCacheObj();
}
return locationCacheObj;
}
@Deprecated
/**
* Not cache version
*
* @param ipAdress
* @return
*/
public static LocationCacheObj getLocationFromIpNotCache(String ipAdress) {
LocationCacheObj locationCacheObj = null;
long ipLong = 0;
try {
ipLong = StringUtil.Dot2LongIP(ipAdress);
} catch (Exception ex) {}
boolean commited = false;
ShardedJedisPool jedisPool = null;
ShardedJedis shardedJedis = null;
Jedis jedis = null;
try {
jedisPool = RedisManagerUtil.getIPLocationPool();
shardedJedis = jedisPool.getResource();
jedis = shardedJedis.getShard(StringPool.BLANK);
Set<String> floors = jedis.zrevrangeByScore("range_index", String.valueOf(ipLong), "0", 0, 1);
Set<String> ceils = jedis.zrangeByScore("range_index", String.valueOf(ipLong), MAXIP, 0, 1);
if (floors.size() == 1 && ceils.size() == 1) {
String floor = floors.iterator().next();
String ceil = ceils.iterator().next();
String f0, f1, c0, c1 = "";
f0 = floor.split("-")[0];
f1 = floor.split("-")[1];
c0 = ceil.split("-")[0];
c1 = ceil.split("-")[1];
if (f0.equals(c1) && f1.equals(c0)) {
int province = StringUtil.safeParseInt(jedis.hget("province:" + floor,"province"), LOCATION_UNDEFINED);
int zone = StringUtil.safeParseInt(jedis.hget("province:" + floor,"zone"), 1);
if (LOCATIONCACHE.size() < RedisManagerUtil.getRedisPoolConfigs().getMaxCacheEntry()) {
locationCacheObj = new LocationCacheObj(cachedTime);
locationCacheObj.setProvice(province);
locationCacheObj.setZone(zone);
}
}
}
commited = true;
// zrevrangebyscore range_index 5 0 LIMIT 0 1
} catch (Exception e) {
e.printStackTrace();
LogUtil.error("LocationUtil.getLocationFromIp", e.getMessage());
} finally {
if (commited) {
jedisPool.returnResource(shardedJedis);
} else {
jedisPool.returnBrokenResource(shardedJedis);
}
}
if(locationCacheObj == null) {
locationCacheObj = new LocationCacheObj();
}
return locationCacheObj;
}
public static class LocationCacheObj {
private long beginIPNumber = 0;
private long endIPNumber = 0;
private String countryCode;
private int province = LOCATION_UNDEFINED;
private int zone = 0;
private long cacheDay = new Date().getTime();
private int liveTime = 30; // minus
public LocationCacheObj() {
}
public LocationCacheObj(int liveTime) {
this.liveTime = liveTime;
}
public int getProvince() {
return province;
}
public void setProvice(int province) {
this.province = province;
}
public int getZone() {
return zone;
}
public void setZone(int zone) {
this.zone = zone;
}
public boolean isExpried() {
Date currentDate = new Date();
return ((currentDate.getTime() - cacheDay) / (60 * 1000)) > liveTime;
}
public void setLiveTime(int mins) {
this.liveTime = mins;
}
public long getBeginIPNumber() {
return beginIPNumber;
}
public void setBeginIPNumber(long beginIPNumber) {
this.beginIPNumber = beginIPNumber;
}
public long getEndIPNumber() {
return endIPNumber;
}
public void setEndIPNumber(long endIPNumber) {
this.endIPNumber = endIPNumber;
}
public String getCountryCode() {
return countryCode;
}
public void setCountryCode(String countryCode) {
this.countryCode = countryCode;
}
}
}