SpringBoot 微服务拦截器与负载均衡实践

目录

一、项目整体架构

二、数据模型设计

[2.1 用户模型(User.java)](#2.1 用户模型(User.java))

[2.2 黑名单模型(BlackList.java)](#2.2 黑名单模型(BlackList.java))

[2.3 接口耗时模型(FunTime.java)](#2.3 接口耗时模型(FunTime.java))

三、数据访问层(DAO)

[3.1 用户数据操作(UserDao.java)](#3.1 用户数据操作(UserDao.java))

[3.2 拦截器相关数据操作(AopDao.java)](#3.2 拦截器相关数据操作(AopDao.java))

四、工具类实现

[4.1 响应信息工具类(SendMsg.java)](#4.1 响应信息工具类(SendMsg.java))

[4.2 用户代理识别工具类(UserAgentUtils.java)](#4.2 用户代理识别工具类(UserAgentUtils.java))

五、控制器实现

六、拦截器核心实现

6.1拦截器配置(InterceptorConfig.java)

[6.2 核心拦截器(CoreHandlerInterceptor.java)](#6.2 核心拦截器(CoreHandlerInterceptor.java))

[七、Nginx 负载均衡配置](#七、Nginx 负载均衡配置)

7.1多节点配置

八、全局异常处理

九、前端视图(lists.html)

十、测试环节与结果验证

[10.1 拦截器核心功能测试](#10.1 拦截器核心功能测试)

[10.2 负载均衡测试](#10.2 负载均衡测试)

[10.3 多终端适配测试](#10.3 多终端适配测试)


在微服务架构中,拦截器和负载均衡是两个核心组件。拦截器负责请求的预处理与后处理,实现权限控制、日志记录等横切关注点;负载均衡则实现请求的合理分发,提升系统可用性与吞吐量。本篇博客将结合实际项目,讲解如何在 SpringBoot 中实现自定义拦截器,并通过 Nginx 配置负载均衡。

一、项目整体架构

本项目基于 SpringBoot 2.7.1,主要实现以下功能:

  • 自定义拦截器:实现访问时间控制、爬虫识别、黑名单校验
  • 负载均衡:通过 Nginx 实现多节点请求分发
  • 数据持久化:使用 MyBatis 操作 MySQL 数据库
  • 全局异常处理:统一异常响应机制

项目结构如下:

核心依赖配置(pom.xml):

复制代码
<dependencies>
    <!-- Spring Web核心依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.2.1</version> 
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.24</version>
    </dependency>

    <!-- Thymeleaf模板引擎 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>

</dependencies>

二、数据模型设计

2.1 用户模型(User.java)

对应数据库t_user表,存储用户基本信息:

java 复制代码
package com.pp.interceptor.model;

public class User {
    private int uid;
    private String uname;
    private String upwd;

    // getter/setter
    public int getUid() { return uid; }
    public void setUid(int uid) { this.uid = uid; }
    public String getUname() { return uname; }
    public void setUname(String uname) { this.uname = uname; }
    public String getUpwd() { return upwd; }
    public void setUpwd(String upwd) { this.upwd = upwd; }
}

2.2 黑名单模型(BlackList.java)

对应数据库t_black表,存储被禁止访问的用户信息:

java 复制代码
package com.pp.interceptor.model;

public class BlackList {
    private int tid;
    private String bname;

    // getter/setter
    public int getTid() { return tid; }
    public void setTid(int tid) { this.tid = tid; }
    public String getBname() { return bname; }
    public void setBname(String bname) { this.bname = bname; }
}

2.3 接口耗时模型(FunTime.java)

对应数据库t_funtime表,记录接口调用耗时:

java 复制代码
package com.pp.interceptor.model;

public class FunTime {
    private int fid;
    private String fname;  // 接口标识(包含节点ID)
    private long subtime;  // 耗时(毫秒)

    // getter/setter
    public int getFid() { return fid; }
    public void setFid(int fid) { this.fid = fid; }
    public String getFname() { return fname; }
    public void setFname(String fname) { this.fname = fname; }
    public long getSubtime() { return subtime; }
    public void setSubtime(long subtime) { this.subtime = subtime; }
}

三、数据访问层(DAO)

3.1 用户数据操作(UserDao.java)

提供用户查询功能:

java 复制代码
package com.pp.interceptor.dao;

import com.pp.interceptor.model.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import java.util.List;

@Mapper     // MyBatis映射接口标识
public interface UserDao {

    @Select("select  *   from  t_user")
    public List<User> queryUsers();
}

3.2 拦截器相关数据操作(AopDao.java)

提供黑名单查询和接口耗时记录功能:

java 复制代码
package com.pp.interceptor.dao;

import com.pp.interceptor.model.FunTime;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface AopDao {
    // 查询用户是否在黑名单中
    @Select("select count(*) from t_black where bname=#{name}")
    public int queryBlackName(String name);

    // 记录接口耗时
    @Insert("insert into t_funtime(fname,subtime) values(#{fname},#{subtime})")
    public void insertTime(FunTime ft);
}

四、工具类实现

4.1 响应信息工具类(SendMsg.java)

统一处理响应信息,确保编码一致:

java 复制代码
package com.pp.interceptor.utils;

import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;

@Component
public class SendMsg {

    public void send(HttpServletResponse response, String content) {
        try {
            response.setCharacterEncoding("UTF-8");   // 设置响应编码为UTF-8
            response.setContentType("text/plain;charset=UTF-8");   // 设置响应内容类型为纯文本

            // 使用OutputStream避免与其他输出流冲突
            OutputStream os = response.getOutputStream();
            os.write(content.getBytes(StandardCharsets.UTF_8));
            os.flush();
            os.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

4.2 用户代理识别工具类(UserAgentUtils.java)

识别爬虫请求和鸿蒙终端:

java 复制代码
package com.pp.interceptor.utils;

import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

@Component
public class UserAgentUtils {

    // 爬虫关键词库(强化规则)
    private static final String[] CRAWLER_KEYWORDS = {"python", "scrapy", "curl", "wget", "spider", "bot", "crawler", "scraper"};
    // 鸿蒙终端标识
    private static final String HARMONY_OS_FLAG = "HarmonyOS";

    /**
     * 判断是否为爬虫请求
     */
    public boolean isCrawler(HttpServletRequest request) {
        String userAgent = request.getHeader("User-Agent");
        System.out.println("User-Agent: " + userAgent);

        if (userAgent == null || userAgent.isEmpty()) {
            return true; // 空UA判定为异常爬虫
        }
        String lowerUA = userAgent.toLowerCase();
        for (String keyword : CRAWLER_KEYWORDS) {
            if (lowerUA.contains(keyword)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 判断是否为鸿蒙终端请求
     */
    public boolean isHarmonyOs(HttpServletRequest request) {
        String userAgent = request.getHeader("User-Agent");
        if (userAgent == null) {
            return false;
        }
        return userAgent.contains(HARMONY_OS_FLAG);
    }
}

五、控制器实现

UserController.java 处理用户相关请求,包括用户列表查询和登录:

java 复制代码
package com.pp.interceptor.controller;

import com.pp.interceptor.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import java.util.List;

@Controller
@RequestMapping("/users")
public class UserController {

    @Autowired
    private UserDao userDao;

    @Value("${node.id}")
    private String nodeId;  // 从配置文件获取节点标识

    /**
     * 查询用户列表
     */
    @RequestMapping("/queryList")
    public ModelAndView queryList(HttpServletRequest request) {
        System.out.printf("[%s] 处理用户列表查询请求%n", nodeId);
        ModelAndView mav = new ModelAndView();
        List userList = userDao.queryUsers();
        mav.setViewName("lists");  // 指定模板页面
        mav.addObject("datas", userList);  // 传递数据到视图
        return mav;
    }

    /**
     * 登录接口(不被拦截)
     */
    @RequestMapping("/login")
    public String login(HttpServletRequest request) {
        System.out.printf("[%s] 处理登录请求%n", nodeId);
        return "【" + nodeId + "】登录业务处理成功(负载均衡节点响应)";
    }
}

关键说明

  • 基于@Controller标识为 MVC 控制器,@RequestMapping("/users")定义基础路径
  • queryList方法查询用户列表并通过ModelAndView传递数据到lists.html视图,login方法直接返回字符串响应
  • 注入nodeId用于标识当前服务节点(配合多端口部署实现负载均衡演示)

六、拦截器核心实现

6.1拦截器配置(InterceptorConfig.java)

注册拦截器并配置拦截规则:

java 复制代码
package com.pp.interceptor.config;

import com.pp.interceptor.interceptor.CoreHandlerInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Autowired
    private CoreHandlerInterceptor coreHandlerInterceptor;  // 这里用接口的好处:可以更换绑定的拦截器类,具体看@Component修饰哪个类(IOC)

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册核心拦截器,拦截所有请求,排除登录接口
        registry.addInterceptor(coreHandlerInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns(
                        "/users/login",
                        "/static/**",
                        "/favicon.ico"  // 新增:排除图标请求,因为浏览器会自动发送/favicon.ico请求(获取网站图标)
                );
    }
}

6.2 核心拦截器(CoreHandlerInterceptor.java)

实现请求拦截逻辑,包括前置检查、后置处理和完成后操作:

java 复制代码
package com.pp.interceptor.interceptor;

import com.pp.interceptor.dao.AopDao;
import com.pp.interceptor.model.FunTime;
import com.pp.interceptor.model.User;
import com.pp.interceptor.utils.SendMsg;
import com.pp.interceptor.utils.UserAgentUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Calendar;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

@Component  // 通用组件标记,告诉Spring:这个类要被扫描并加入IOC容器
public class CoreHandlerInterceptor implements HandlerInterceptor {

    @Autowired
    private AopDao aopDao;
    @Autowired
    private SendMsg sendMsg;
    @Autowired
    private UserAgentUtils userAgentUtils;

    // 从配置文件中读取节点ID
    @Value("${node.id}")  // 将配置文件中的值注入到Spring管理的Bean中
    private String nodeId; // 节点标识

    private long startTime;

    /**
     * 前置拦截:爬虫识别、黑名单、访问时间控制
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        startTime = System.currentTimeMillis();
        String requestUri = request.getRequestURI();
        System.out.printf("[%s] 前置拦截开始 - 请求地址:%s%n", nodeId, requestUri);

        // 1. 访问时间控制(12-14点禁止访问)
        int currentHour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
        if (currentHour >= 12 && currentHour <= 14) {
            sendMsg.send(response, "【" + nodeId + "】现在是休息时间(12-14点),系统暂不服务,请稍后访问");
            return false;
        }

        // 2. 爬虫识别(强化规则)
        if (userAgentUtils.isCrawler(request)) {
            sendMsg.send(response, "【" + nodeId + "】检测到爬虫请求,禁止访问(接口防护)");
            return false;
        }

        // 3. 黑名单校验
        String uname = request.getParameter("name");
        if (uname != null && !uname.isEmpty()) {
            int count = aopDao.queryBlackName(uname);
            if (count > 0) {
                sendMsg.send(response, "【" + nodeId + "】您在黑名单中,请申请解除后访问");
                return false;
            }
        }

        // 鸿蒙终端适配提示
        if (userAgentUtils.isHarmonyOs(request)) {
            System.out.printf("[%s] 检测到鸿蒙终端请求,已适配响应规则%n", nodeId);
        }

        return true;
    }

    /**
     * 后置拦截:数据处理、耗时日志记录
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        String requestUri = request.getRequestURI();
        System.out.printf("[%s] 后置拦截开始 - 请求地址:%s%n", nodeId, requestUri);

        // 增加非空判断,避免空指针异常
        if (modelAndView != null) {
            modelAndView.addObject("heads", "【" + nodeId + "】用户信息表(负载均衡节点返回)");

            //建立在@Controller注解机制
            Map<String, Object> maps = modelAndView.getModel();
            List<User> lists = (List<User>) maps.get("datas");
            //lists.remove(0)报错;

            System.out.println(lists.size());

            Iterator its = lists.iterator();
            while (its.hasNext()) {
                User u = (User) its.next();
                System.out.println(u.getUname());
                if (u.getUname().contains("海")) {
                    its.remove();
                }
            }
            modelAndView.addObject("datas", lists);
        }

    }

    /**
     * 完成拦截:接口耗时日志记录(存入数据库)
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        long endTime = System.currentTimeMillis();
        long costTime = endTime - startTime;
        String requestUri = request.getRequestURI();

        // 记录接口耗时(关联节点标识)
        FunTime funTime = new FunTime();
        funTime.setFname(nodeId + "-" + requestUri);
        funTime.setSubtime(costTime);
        aopDao.insertTime(funTime);

        // 异常日志打印
        if (ex != null) {
            System.err.printf("[%s] 请求异常 - 地址:%s,异常信息:%s%n", nodeId, requestUri, ex.getMessage());
        } else {
            System.out.printf("[%s] 请求完成 - 地址:%s,耗时:%dms%n", nodeId, requestUri, costTime);
        }
    }
}

七、Nginx 负载均衡配置

通过 Nginx 实现请求分发,配置如下:

java 复制代码
http {
    # 后端服务集群配置
    upstream backend-servers { 
        server 192.168.247.1:8990 weight=2;  # 权重2,接收更多请求
        server 192.168.247.1:8900 weight=1;  # 权重1
    }

    server {
        listen       89;  # Nginx监听端口
        server_name  localhost;

        location / {
            proxy_pass http://backend-servers;  # 转发到后端集群
            # 解决400错误的核心配置
            proxy_set_header Host $proxy_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }
}

负载均衡解析

  • upstream:定义后端服务集群,weight表示权重(数值越大,分配到的请求越多)
  • proxy_pass:将请求转发到backend-servers集群
  • 额外请求头配置:解决反向代理导致的请求信息丢失问题

7.1多节点配置

为实现负载均衡,需配置多个服务节点,通过不同端口区分:

application-8990.properties(节点 1):

复制代码
# 节点1端口
server.port=8990
# 节点标识(用于日志区分)
node.id=server-8990

application-8900.properties(节点 2):

复制代码
# 节点2端口
server.port=8900
# 节点标识(用于日志区分)
node.id=server-8900

启动前要选择允许多个实例,并分别启动两个节点:

八、全局异常处理

统一处理项目中抛出的异常,返回标准化的错误信息:

java 复制代码
package com.pp.interceptor.config;

import com.pp.interceptor.utils.SendMsg;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

// 全局控制器通知注解  @ControllerAdvice + @ResponseBody
@RestControllerAdvice
public class GlobalExceptionHandler {

    @Autowired
    private SendMsg sendMsg;

    @Value("${node.id}")
    private String nodeId;  // 从配置文件中读取节点标识

    // 异常处理方法注解,声明此方法处理指定类型的异常
    @ExceptionHandler(Exception.class)      // Exception.class表示处理所有异常
    public void handleGlobalException(HttpServletRequest request, HttpServletResponse response, Exception e) {

        String errorMsg = String.format("[%s] 请求发生异常:%s,已触发容错机制,请稍后重试", nodeId, e.getMessage());
        sendMsg.send(response, errorMsg);
        // 打印异常栈(便于排查)
        e.printStackTrace();

    }
}

关键说明

  • 使用@RestControllerAdvice实现全局异常拦截,无需在每个控制器中重复处理异常
  • 通过@ExceptionHandler(Exception.class)捕获所有异常,结合SendMsg工具类向客户端返回包含节点标识的错误信息,便于问题定位

九、前端视图(lists.html)

基于 Thymeleaf 的用户列表展示页面:

java 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div th:text="${heads}"></div> <!-- 显示节点标识标题 -->

<ul th:each="u : ${datas}"> <!-- 遍历用户列表 -->
    <li th:text="${u.uname}"></li> <!-- 显示用户名 -->
</ul>
</body>
</html>

十、测试环节与结果验证

10.1 拦截器核心功能测试

(1)爬虫识别拦截

(2)黑名单拦截

(3)正常请求(非爬虫 / 黑名单,非 12-14 点)

10.2 负载均衡测试

(1)多节点分发

通过 Nginx(代理 8900/8990 节点)多次请求,响应交替显示 "【server-8900】登录业务处理成功" 和 "【server-8990】登录业务处理成功",但8990节点所占权重更大。

10.3 多终端适配测试

模拟鸿蒙终端识别:

相关推荐
yaoxin5211231 小时前
295. Java Stream API - 选择适用于并行计算的 BinaryOperator
java·开发语言
冬至喵喵1 小时前
RoaringBitmap与传统Bitmap
java·开发语言
better_liang2 小时前
Java技术栈中的MySQL数据结构应用与优化
java·数据结构·mysql·性能调优·索引优化
Swift社区2 小时前
Spring Boot 配置文件未生效
java·spring boot·后端
计算机程序设计小李同学2 小时前
基于Web和Android的漫画阅读平台
java·前端·vue.js·spring boot·后端·uniapp
沛沛老爹2 小时前
从Web到AI:Agent Skills CI/CD流水线集成实战指南
java·前端·人工智能·ci/cd·架构·llama·rag
ゞ 正在缓冲99%…2 小时前
2025.12.17华为软开
java·算法
好大哥呀2 小时前
Java 中的 Spring 框架
java·开发语言·spring
计算机毕设指导62 小时前
基于微信小程序技术校园拼车系统【源码文末联系】
java·spring boot·mysql·微信小程序·小程序·tomcat·maven