Backend Development 6 min read

Implementing IP‑Based City Detection and Visit Counting in a Spring Backend

This article explains how to obtain a user's city from their IP address in a Java Spring backend, using the QQWry IP database, custom utility classes, and a login interceptor to count visits per city, with complete code examples and deployment considerations.

Top Architect
Top Architect
Top Architect
Implementing IP‑Based City Detection and Visit Counting in a Spring Backend

Significance

In e‑commerce, news, and social sites, understanding the geographic distribution of users is essential for cost‑effective operations, especially when different cities require different handling strategies.

Methods to Obtain City from IP

Public APIs from providers like Taobao or Sina, which may change URLs and have rate limits.

Open‑source pure IP database (e.g., QQWry) that can be downloaded and updated independently.

Implementation Idea

First retrieve the client’s IP address from the HttpServletRequest, then use the QQWry library to map the IP to a city, and finally count visits per city.

IPUtil – Getting Client IP

public class IPUtil {
    public static String getIpAddress(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }
}

IPZone – Parsing QQWry Data

public IPZone findIP(final String ip) {
    final long ipNum = toNumericIP(ip);
    final QIndex idx = searchIndex(ipNum);
    if (idx == null) {
        return new IPZone(ip);
    }
    return readIP(ip, idx);
}

Login Interceptor

The interceptor extracts the IP, resolves the city via QQWry, and updates an in‑memory map of visit counts (in production a Redis store would be used).

@Slf4j
public class MyLoginInterceptor implements HandlerInterceptor {
    private static final String LOGIN_PATH = "/user/login";
    private static Map
visitCount;
    private static final QQWry qqWry;

    static {
        visitCount = new HashMap<>(31);
        qqWry = new QQWry();
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("【MyLoginInterceptor】called: {}", request.getRequestURI());
        if (request.getRequestURI().equals(LOGIN_PATH)) {
            String ipAddress = IPUtil.getIpAddress(request);
            String province = qqWry.findIP(ipAddress).getMainInfo();
            if (visitCount.containsKey(province)) {
                visitCount.put(province, new AtomicInteger(visitCount.get(province).incrementAndGet()));
            } else {
                visitCount.put(province, new AtomicInteger());
            }
        }
        return true;
    }
    // postHandle and afterCompletion omitted for brevity
}

WebMvc Configuration

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyLoginInterceptor());
    }
}

Login Controller Example

@RestController("user")
public class LoginController {
    @GetMapping("login")
    public String login() {
        // login logic
        return "success";
    }
}

In production, the visit counts should be stored in a distributed cache such as Redis, and a separate REST endpoint can expose the per‑city statistics for front‑end visualization.

backendJavaRedisSpringInterceptorIPGeolocation
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.