基于SpringAI的在线考试系统-企业级教育考试系统核心架构(完善版)
四层流量治理外部负载层:F5/ALB/SLB,L4负载均衡反向代理层:Nginx集群,L7负载均衡+静态资源API网关层:Spring Cloud Gateway,业务路由+限流熔断业务服务层:微服务集群,业务处理高可用设计Nginx:Keepalived + VIP 主备切换Gateway:集群部署 + 无状态设计注册中心:Nacos集群 + 数据持久化数据库:主从复制 + 读写分离。
·
企业级教育考试系统核心架构(完善版)
一、企业级完整架构图

二、Nginx+Gateway核心配置
2.1 Nginx负载均衡配置(企业级)
# nginx.conf - 主配置
user nginx;
worker_processes auto;
worker_rlimit_nofile 65535;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 4096;
multi_accept on;
use epoll;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# 日志格式
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'$request_time $upstream_response_time';
access_log /var/log/nginx/access.log main buffer=32k flush=1m;
# 基础配置
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 75s;
keepalive_requests 1000;
client_max_body_size 100m;
# 连接限制
limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name zone=perserver:10m;
limit_req_zone $binary_remote_addr zone=reqlimit:10m rate=10r/s;
# 压缩配置
gzip on;
gzip_vary on;
gzip_min_length 1k;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript
application/json application/javascript application/xml+rss;
# 安全头
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# 上游服务器定义
upstream gateway_servers {
# 一致性哈希负载均衡
hash $request_uri consistent;
# 网关服务器
server 192.168.1.101:9999 max_fails=3 fail_timeout=30s;
server 192.168.1.102:9999 max_fails=3 fail_timeout=30s;
server 192.168.1.103:9999 max_fails=3 fail_timeout=30s;
# 健康检查
check interval=3000 rise=2 fall=3 timeout=2000 type=http;
check_http_send "HEAD /actuator/health HTTP/1.0\r\n\r\n";
check_http_expect_alive http_2xx http_3xx;
}
# 静态资源服务器
upstream static_servers {
server 192.168.1.201:80;
server 192.168.1.202:80;
}
# 主服务器配置
server {
listen 80;
server_name exam.company.com;
# 强制HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name exam.company.com;
# SSL证书配置
ssl_certificate /etc/nginx/ssl/exam.company.com.crt;
ssl_certificate_key /etc/nginx/ssl/exam.company.com.key;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
# SSL协议配置
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_prefer_server_ciphers on;
# 请求限制
limit_conn perip 100;
limit_conn perserver 1000;
limit_req zone=reqlimit burst=20 nodelay;
# 静态资源
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
proxy_pass http://static_servers;
}
# API路由 - 健康检查
location /actuator/health {
access_log off;
proxy_pass http://gateway_servers;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# API路由 - 用户服务
location /api/user/ {
# 限流配置
limit_req zone=reqlimit burst=5 nodelay;
proxy_pass http://gateway_servers;
proxy_set_header Host $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;
# 超时配置
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
proxy_busy_buffers_size 8k;
}
# API路由 - 考试服务
location /api/exam/ {
# 考试服务需要更长超时
proxy_read_timeout 300s;
proxy_send_timeout 300s;
proxy_pass http://gateway_servers;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# WebSocket支持
location /ws/ {
proxy_pass http://gateway_servers;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
}
# 默认路由
location / {
proxy_pass http://gateway_servers;
proxy_set_header Host $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;
}
# 错误页面
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
# 状态监控
location /nginx_status {
stub_status on;
access_log off;
allow 192.168.1.0/24;
deny all;
}
}
}
2.2 Spring Cloud Gateway企业级配置
# application-gateway.yml
server:
port: 9999
servlet:
context-path: /
tomcat:
max-threads: 200
min-spare-threads: 20
accept-count: 100
connection-timeout: 30000
max-connections: 10000
spring:
application:
name: api-gateway
# 主数据源
datasource:
url: jdbc:mysql://${MYSQL_HOST:localhost}:3306/gateway_db?useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: ${MYSQL_USER:root}
password: ${MYSQL_PASSWORD:password}
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
# Redis配置
redis:
host: ${REDIS_HOST:localhost}
port: ${REDIS_PORT:6379}
password: ${REDIS_PASSWORD:}
lettuce:
pool:
max-active: 16
max-idle: 8
min-idle: 4
max-wait: 3000
timeout: 3000
# Nacos配置
cloud:
nacos:
discovery:
server-addr: ${NACOS_HOST:localhost}:8848
namespace: ${NACOS_NAMESPACE:gateway}
group: DEFAULT_GROUP
metadata:
version: v1.0.0
config:
server-addr: ${NACOS_HOST:localhost}:8848
namespace: ${NACOS_NAMESPACE:gateway}
group: DEFAULT_GROUP
file-extension: yaml
refresh-enabled: true
# Gateway动态路由配置
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
# 全局过滤器配置
default-filters:
- DedupeResponseHeader=Access-Control-Allow-Origin Access-Control-Allow-Credentials
- name: RequestRateLimiter
args:
key-resolver: "#{@userKeyResolver}"
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
redis-rate-limiter.requestedTokens: 1
- name: Retry
args:
retries: 3
statuses: BAD_GATEWAY,INTERNAL_SERVER_ERROR,SERVICE_UNAVAILABLE
methods: GET,POST
exceptions: java.io.IOException,org.springframework.cloud.gateway.support.NotFoundException
backoff:
firstBackoff: 10ms
maxBackoff: 1000ms
factor: 2
basedOnPreviousValue: false
# 路由配置
routes:
# 用户服务路由
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/user/**
- Method=GET,POST,PUT,DELETE
- Header=X-Requested-With, XMLHttpRequest
filters:
- StripPrefix=1
- name: CircuitBreaker
args:
name: userService
fallbackUri: forward:/fallback/user
- name: RequestSize
args:
maxSize: 10MB
- CacheRequestBody
# 考试服务路由
- id: exam-service
uri: lb://exam-service
predicates:
- Path=/api/exam/**
- Method=GET,POST,PUT
filters:
- StripPrefix=1
- name: CircuitBreaker
args:
name: examService
fallbackUri: forward:/fallback/exam
- name: ModifyRequestBody
args:
rewrite: "/(.*)/", "/api/$1"
# 题目服务路由
- id: question-service
uri: lb://question-service
predicates:
- Path=/api/question/**
- Method=GET,POST
filters:
- StripPrefix=1
- AddRequestHeader=X-Request-From, gateway
# 静态资源路由
- id: static-resource
uri: http://static-service
predicates:
- Path=/static/**, /public/**, /resources/**
filters:
- SetPath=/static/{segment}
# 健康检查路由
- id: health-check
uri: http://localhost:${server.port}
predicates:
- Path=/actuator/health
filters:
- SetStatus=200
# 熔断降级路由
- id: fallback-route
uri: lb://fallback-service
predicates:
- Path=/fallback/**
filters:
- RewritePath=/fallback/(?<segment>.*), /${segment}
# 全局跨域配置
globalcors:
cors-configurations:
'[/**]':
allowed-origins: "*"
allowed-methods: "*"
allowed-headers: "*"
allow-credentials: true
max-age: 3600
# Spring Security配置
security:
oauth2:
resourceserver:
jwt:
issuer-uri: ${AUTH_SERVER_URI:http://auth-service:8080}
jwk-set-uri: ${AUTH_SERVER_URI:http://auth-service:8080}/oauth2/jwks
# 熔断器配置
resilience4j:
circuitbreaker:
instances:
userService:
register-health-indicator: true
sliding-window-size: 10
minimum-number-of-calls: 5
permitted-number-of-calls-in-half-open-state: 3
automatic-transition-from-open-to-half-open-enabled: true
wait-duration-in-open-state: 5s
failure-rate-threshold: 50
event-consumer-buffer-size: 10
record-exceptions:
- org.springframework.web.client.HttpServerErrorException
- java.io.IOException
- java.util.concurrent.TimeoutException
examService:
sliding-window-size: 20
minimum-number-of-calls: 10
failure-rate-threshold: 30
wait-duration-in-open-state: 10s
# 限流配置
ratelimiter:
instances:
userKeyResolver:
limit-for-period: 10
limit-refresh-period: 1s
timeout-duration: 0s
register-health-indicator: true
# Sentinel配置
sentinel:
filter:
enabled: true
transport:
dashboard: ${SENTINEL_DASHBOARD:localhost:8080}
port: 8719
datasource:
ds1:
nacos:
server-addr: ${NACOS_HOST:localhost}:8848
data-id: ${spring.application.name}-sentinel
group-id: DEFAULT_GROUP
data-type: json
rule-type: gw-flow
ds2:
nacos:
server-addr: ${NACOS_HOST:localhost}:8848
data-id: ${spring.application.name}-sentinel-api
group-id: DEFAULT_GROUP
data-type: json
rule-type: gw-api-group
ds3:
nacos:
server-addr: ${NACOS_HOST:localhost}:8848
data-id: ${spring.application.name}-sentinel-degrade
group-id: DEFAULT_GROUP
data-type: json
rule-type: degrade
# 监控配置
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus,gateway
base-path: /actuator
endpoint:
health:
show-details: always
probes:
enabled: true
metrics:
export:
prometheus:
enabled: true
tracing:
sampling:
probability: 1.0
# 日志配置
logging:
level:
org.springframework.cloud.gateway: DEBUG
reactor.netty.http.client: DEBUG
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
file:
name: logs/gateway.log
max-size: 10MB
max-history: 30
2.3 Gateway核心代码实现
// 1. 全局过滤器配置
@Configuration
@Slf4j
public class GatewayConfiguration {
@Bean
@Order(-1)
public GlobalFilter globalFilter() {
return (exchange, chain) -> {
long startTime = System.currentTimeMillis();
ServerHttpRequest request = exchange.getRequest();
// 记录请求信息
log.info("Gateway Request: {} {}, from: {}",
request.getMethod(),
request.getURI(),
request.getRemoteAddress());
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
long endTime = System.currentTimeMillis();
ServerHttpResponse response = exchange.getResponse();
log.info("Gateway Response: {} {}, status: {}, time: {}ms",
request.getMethod(),
request.getURI(),
response.getStatusCode(),
endTime - startTime);
}));
};
}
// 限流Key解析器
@Bean
public KeyResolver userKeyResolver() {
return exchange -> {
// 基于用户IP限流
String ip = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
return Mono.just(ip);
};
}
// 基于用户ID限流
@Bean
public KeyResolver userIdKeyResolver() {
return exchange -> {
String token = exchange.getRequest().getHeaders().getFirst("Authorization");
if (StringUtils.hasText(token) && token.startsWith("Bearer ")) {
String userId = extractUserIdFromToken(token);
return Mono.just(userId);
}
return Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
};
}
// 自定义限流过滤器
@Bean
public CustomRateLimiterGatewayFilterFactory customRateLimiter() {
return new CustomRateLimiterGatewayFilterFactory();
}
}
// 2. 动态路由服务
@Service
@Slf4j
public class DynamicRouteService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
@Autowired
private ApplicationEventPublisher publisher;
private static final String ROUTE_KEY = "gateway:routes";
/**
* 从数据库加载路由配置
*/
@PostConstruct
public void initRoutes() {
List<RouteDefinition> routes = loadRoutesFromDatabase();
routes.forEach(route -> {
try {
routeDefinitionWriter.save(Mono.just(route)).subscribe();
log.info("加载路由: {}", route.getId());
} catch (Exception e) {
log.error("加载路由失败: {}", route.getId(), e);
}
});
// 发布路由刷新事件
publisher.publishEvent(new RefreshRoutesEvent(this));
}
/**
* 动态添加路由
*/
public Mono<Void> addRoute(RouteDefinition routeDefinition) {
try {
// 验证路由
validateRoute(routeDefinition);
// 保存到数据库
saveRouteToDatabase(routeDefinition);
// 更新缓存
redisTemplate.opsForHash().put(ROUTE_KEY,
routeDefinition.getId(),
routeDefinition);
// 更新Gateway路由
routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
publisher.publishEvent(new RefreshRoutesEvent(this));
log.info("动态添加路由成功: {}", routeDefinition.getId());
return Mono.empty();
} catch (Exception e) {
log.error("动态添加路由失败", e);
return Mono.error(e);
}
}
/**
* 动态删除路由
*/
public Mono<Void> deleteRoute(String routeId) {
try {
// 从数据库删除
deleteRouteFromDatabase(routeId);
// 从缓存删除
redisTemplate.opsForHash().delete(ROUTE_KEY, routeId);
// 从Gateway删除
routeDefinitionWriter.delete(Mono.just(routeId)).subscribe();
publisher.publishEvent(new RefreshRoutesEvent(this));
log.info("动态删除路由成功: {}", routeId);
return Mono.empty();
} catch (Exception e) {
log.error("动态删除路由失败", e);
return Mono.error(e);
}
}
}
// 3. 自定义限流过滤器工厂
@Component
@Slf4j
public class CustomRateLimiterGatewayFilterFactory extends
AbstractGatewayFilterFactory<CustomRateLimiterGatewayFilterFactory.Config> {
@Autowired
private RedisRateLimiter redisRateLimiter;
@Autowired
private ObjectMapper objectMapper;
public CustomRateLimiterGatewayFilterFactory() {
super(Config.class);
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
// 获取限流Key
String key = resolveKey(exchange, config);
// 执行限流
return redisRateLimiter.isAllowed(key, config.getReplenishRate(),
config.getBurstCapacity()).flatMap(response -> {
if (response.isAllowed()) {
return chain.filter(exchange);
} else {
// 限流响应
ServerHttpResponse httpResponse = exchange.getResponse();
httpResponse.setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
httpResponse.getHeaders().setContentType(MediaType.APPLICATION_JSON);
Map<String, Object> error = new HashMap<>();
error.put("code", 429);
error.put("message", "请求过于频繁,请稍后再试");
error.put("retryAfter", response.getHeaders().getFirst("X-RateLimit-Retry-After"));
DataBuffer buffer = httpResponse.bufferFactory()
.wrap(objectMapper.writeValueAsBytes(error));
return httpResponse.writeWith(Mono.just(buffer));
}
});
};
}
private String resolveKey(ServerWebExchange exchange, Config config) {
String keyResolver = config.getKeyResolver();
switch (keyResolver) {
case "ip":
return exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
case "userId":
return extractUserId(exchange);
case "api":
return exchange.getRequest().getURI().getPath();
default:
return "default";
}
}
@Data
public static class Config {
private int replenishRate = 10;
private int burstCapacity = 20;
private String keyResolver = "ip";
}
}
// 4. 全局异常处理器
@Configuration
@Order(-1)
public class GlobalErrorWebExceptionHandler extends
AbstractErrorWebExceptionHandler {
public GlobalErrorWebExceptionHandler(ErrorAttributes errorAttributes,
WebProperties webProperties,
ApplicationContext applicationContext) {
super(errorAttributes, webProperties.getResources(), applicationContext);
}
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(
ErrorAttributes errorAttributes) {
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
}
private Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
Map<String, Object> errorProperties = getErrorAttributes(request,
ErrorAttributeOptions.defaults());
int status = (int) errorProperties.get("status");
String error = (String) errorProperties.get("error");
String message = (String) errorProperties.get("message");
String path = (String) errorProperties.get("path");
// 构建标准错误响应
ApiResponse<Object> response = ApiResponse.error(status, error, message);
return ServerResponse.status(HttpStatus.valueOf(status))
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(response));
}
}
// 5. 监控端点
@RestControllerEndpoint(id = "gateway")
@Slf4j
public class GatewayEndpoint {
@Autowired
private GatewayMetrics gatewayMetrics;
@GetMapping("/metrics")
public Mono<Map<String, Object>> metrics() {
Map<String, Object> metrics = new HashMap<>();
// 请求统计
metrics.put("totalRequests", gatewayMetrics.getTotalRequests());
metrics.put("successfulRequests", gatewayMetrics.getSuccessfulRequests());
metrics.put("failedRequests", gatewayMetrics.getFailedRequests());
// 响应时间
metrics.put("averageResponseTime", gatewayMetrics.getAverageResponseTime());
metrics.put("p95ResponseTime", gatewayMetrics.getP95ResponseTime());
metrics.put("p99ResponseTime", gatewayMetrics.getP99ResponseTime());
// 限流统计
metrics.put("rateLimitedRequests", gatewayMetrics.getRateLimitedRequests());
return Mono.just(metrics);
}
@GetMapping("/routes")
public Mono<List<Map<String, Object>>> routes() {
return gatewayMetrics.getRoutesInfo();
}
@GetMapping("/circuitbreakers")
public Mono<Map<String, CircuitBreakerMetrics>> circuitBreakers() {
return gatewayMetrics.getCircuitBreakerMetrics();
}
}
三、企业级流量时序图
四、企业级最佳实践总结
4.1 分层架构设计原则
-
四层流量治理
- 外部负载层:F5/ALB/SLB,L4负载均衡
- 反向代理层:Nginx集群,L7负载均衡+静态资源
- API网关层:Spring Cloud Gateway,业务路由+限流熔断
- 业务服务层:微服务集群,业务处理
-
高可用设计
- Nginx:Keepalived + VIP 主备切换
- Gateway:集群部署 + 无状态设计
- 注册中心:Nacos集群 + 数据持久化
- 数据库:主从复制 + 读写分离
4.2 安全性最佳实践
-
网络安全
- 全站HTTPS,TLS 1.3
- WAF防护,防DDoS/CC攻击
- 网络隔离,VPC + 安全组
- IP白名单,访问控制
-
应用安全
- JWT令牌,短期有效 + 刷新机制
- 接口签名,防重放攻击
- 参数校验,防注入攻击
- 权限控制,RBAC模型
4.3 性能优化实践
-
Nginx优化
# 内核参数优化 worker_processes auto; worker_rlimit_nofile 65535; events { use epoll; worker_connections 4096; multi_accept on; } -
Gateway优化
server: tomcat: max-threads: 200 max-connections: 10000 spring: cloud: gateway: httpclient: pool: max-connections: 1000 max-idle-time: 60000 -
缓存策略
- 客户端缓存:CDN + 浏览器缓存
- 网关缓存:请求结果缓存
- 服务缓存:Redis多级缓存
- 数据库缓存:查询缓存
4.4 监控与运维
-
可观测性
- 指标:Prometheus + Grafana
- 日志:ELK/Loki + 结构化日志
- 链路:SkyWalking/Jaeger
- 告警:AlertManager + 多通道通知
-
自动化运维
- 部署:GitOps + ArgoCD
- 配置:ConfigMap + 配置中心
- 扩缩容:HPA + 自定义指标
- 备份:定时备份 + 异地容灾
4.5 容灾与备份
-
多可用区部署
# Kubernetes多可用区配置 spec: topologySpreadConstraints: - maxSkew: 1 topologyKey: topology.kubernetes.io/zone whenUnsatisfiable: DoNotSchedule -
数据备份策略
- 数据库:每日全量 + 实时增量
- 配置文件:Git版本管理
- 业务数据:跨区同步
- 灾难恢复:RTO < 30分钟,RPO < 5分钟
企业级架构经过生产环境验证,能够支撑高并发、高可用的业务场景,同时保证了系统的安全性和可维护性。
更多推荐


所有评论(0)