活动公告

系统通知
07-14 23:24
系统通知
通知:本站资源由网友上传分享,如有违规等问题请到版务模块进行投诉,资源失效请在帖子内回复要求补档,会尽快处理!
10-23 09:31

掌握Swagger实现跨域请求的技巧与配置方法让前后端分离项目中的API调用不再受同源策略限制轻松解决跨域访问问题

SunJu_FaceMall

3万

主题

3063

科技点

3万

积分

执行版主

碾压王

积分
32876

塔罗立华奏

执行版主 发表于 2025-10-1 00:10:01 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
引言

在现代Web开发中,前后端分离架构已成为主流。这种架构模式下,前端和后端分别独立开发,通过API进行数据交互。然而,这种架构也带来了一个常见的技术挑战——跨域请求问题。当前端应用与后端API不在同一个域、端口或协议下时,浏览器的同源策略会阻止前端直接访问后端API,导致请求失败。

Swagger作为一个强大的API开发工具,不仅可以帮助我们设计和文档化API,还可以通过适当的配置解决跨域请求问题。本文将详细介绍如何利用Swagger实现跨域请求,让前后端分离项目中的API调用不再受同源策略限制,轻松解决跨域访问问题。

跨域请求与同源策略基础

什么是同源策略

同源策略(Same-Origin Policy)是Web浏览器的一种重要安全机制,它规定了一个源的文档或脚本如何能与另一个源的资源进行交互。所谓”同源”或”同站点”指的是三个相同的部分:协议(protocol)、域名(domain)和端口(port)。

例如,以下URL被视为不同源:

• http://example.com/app1和http://example.com/app2(同源)
• http://example.com/app1和https://example.com/app2(不同源,协议不同)
• http://example.com/app1和http://api.example.com/app2(不同源,域名不同)
• http://example.com:8080/app1和http://example.com:8081/app2(不同源,端口不同)

跨域请求的问题

当浏览器中的JavaScript代码尝试访问不同源的资源时,就会触发同源策略的限制。对于AJAX请求,这意味着:

1. 请求会成功发送到服务器
2. 服务器会处理请求并返回响应
3. 浏览器会拦截响应,不让JavaScript代码访问返回的数据

这种限制对于保护用户数据安全非常重要,但在前后端分离的开发模式中,它常常成为开发的障碍。

Swagger简介及其在API开发中的作用

Swagger是什么

Swagger(现为OpenAPI规范)是一套围绕OpenAPI规范构建的开源工具,用于设计、构建、记录和使用RESTful Web服务。它主要包括:

• Swagger Editor:一个基于浏览器的编辑器,用于编写OpenAPI规范。
• Swagger UI:一个交互式的API文档UI,可以可视化API资源并执行API调用。
• Swagger Codegen:一个代码生成工具,可以根据OpenAPI规范生成服务器存根和客户端SDK。

Swagger在API开发中的作用

Swagger在API开发中扮演着多重角色:

1. API设计工具:帮助开发者在编码前设计API接口。
2. 交互式文档:自动生成可交互的API文档,方便前端开发者了解和使用API。
3. API测试工具:提供直接在浏览器中测试API的功能。
4. 客户端代码生成:可以根据API定义生成各种语言的客户端代码。

在前后端分离项目中,Swagger UI通常部署在后端服务器上,而前端应用则部署在另一个域或端口上,这就产生了跨域访问的问题。

跨域问题的常见解决方案

在介绍如何使用Swagger解决跨域问题之前,我们先了解一下常见的跨域解决方案:

1. JSONP(JSON with Padding)

JSONP是一种利用<script>标签不受同源策略限制的特性来实现跨域通信的方法。但JSONP只支持GET请求,且存在安全风险,现在已经不是推荐的解决方案。

2. 代理服务器

通过在同源下设置一个代理服务器,将前端的请求转发到目标服务器。这种方法在开发环境中常用,但在生产环境中会增加服务器的负担。

3. CORS(Cross-Origin Resource Sharing)

CORS是W3C推荐的一种跨域解决方案,它通过在HTTP响应头中添加特定字段来告诉浏览器允许哪些域的网页访问资源。这是目前最标准和安全的跨域解决方案。

4. WebSocket

WebSocket是一种通信协议,它不受同源策略的限制。但WebSocket主要用于实时通信,不适合普通的API请求。

5. postMessage

window.postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信。但它主要用于窗口间的通信,不适合API请求。

在以上解决方案中,CORS是最标准和安全的方法,也是我们将重点讨论的解决方案。

使用Swagger配置跨域请求的详细方法

Swagger本身并不直接处理跨域问题,但我们可以通过配置后端服务器和Swagger来支持跨域请求。下面,我将介绍几种常见框架中配置Swagger支持跨域请求的方法。

基于Spring Boot的配置

Spring Boot是Java生态中最流行的框架之一,下面介绍如何在Spring Boot项目中配置Swagger支持跨域请求。

首先,确保你的项目中包含Swagger和CORS相关的依赖:
  1. <!-- Spring Boot Starter Web -->
  2. <dependency>
  3.     <groupId>org.springframework.boot</groupId>
  4.     <artifactId>spring-boot-starter-web</artifactId>
  5. </dependency>
  6. <!-- Springfox Swagger2 -->
  7. <dependency>
  8.     <groupId>io.springfox</groupId>
  9.     <artifactId>springfox-swagger2</artifactId>
  10.     <version>3.0.0</version>
  11. </dependency>
  12. <!-- Springfox Swagger UI -->
  13. <dependency>
  14.     <groupId>io.springfox</groupId>
  15.     <artifactId>springfox-swagger-ui</artifactId>
  16.     <version>3.0.0</version>
  17. </dependency>
  18. <!-- Spring Boot Starter Actuator (可选,用于监控) -->
  19. <dependency>
  20.     <groupId>org.springframework.boot</groupId>
  21.     <artifactId>spring-boot-starter-actuator</artifactId>
  22. </dependency>
复制代码

创建一个Swagger配置类:
  1. import org.springframework.context.annotation.Bean;
  2. import org.springframework.context.annotation.Configuration;
  3. import springfox.documentation.builders.ApiInfoBuilder;
  4. import springfox.documentation.builders.PathSelectors;
  5. import springfox.documentation.builders.RequestHandlerSelectors;
  6. import springfox.documentation.service.ApiInfo;
  7. import springfox.documentation.service.Contact;
  8. import springfox.documentation.spi.DocumentationType;
  9. import springfox.documentation.spring.web.plugins.Docket;
  10. import springfox.documentation.swagger2.annotations.EnableSwagger2;
  11. @Configuration
  12. @EnableSwagger2
  13. public class SwaggerConfig {
  14.     @Bean
  15.     public Docket api() {
  16.         return new Docket(DocumentationType.SWAGGER_2)
  17.                 .select()
  18.                 .apis(RequestHandlerSelectors.basePackage("com.example.demo.controller"))
  19.                 .paths(PathSelectors.any())
  20.                 .build()
  21.                 .apiInfo(apiInfo());
  22.     }
  23.     private ApiInfo apiInfo() {
  24.         return new ApiInfoBuilder()
  25.                 .title("API文档")
  26.                 .description("API文档描述")
  27.                 .version("1.0")
  28.                 .contact(new Contact("作者名", "作者网址", "作者邮箱"))
  29.                 .build();
  30.     }
  31. }
复制代码

在Spring Boot中,有多种方式配置CORS:

方法一:使用@CrossOrigin注解

可以在Controller类或具体的方法上添加@CrossOrigin注解:
  1. import org.springframework.web.bind.annotation.CrossOrigin;
  2. import org.springframework.web.bind.annotation.GetMapping;
  3. import org.springframework.web.bind.annotation.RequestMapping;
  4. import org.springframework.web.bind.annotation.RestController;
  5. @RestController
  6. @RequestMapping("/api")
  7. @CrossOrigin(origins = "http://localhost:3000") // 允许特定源
  8. public class ApiController {
  9.     @GetMapping("/data")
  10.     public String getData() {
  11.         return "Hello, World!";
  12.     }
  13. }
复制代码

方法二:全局CORS配置

创建一个配置类,全局配置CORS:
  1. import org.springframework.context.annotation.Bean;
  2. import org.springframework.context.annotation.Configuration;
  3. import org.springframework.web.cors.CorsConfiguration;
  4. import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
  5. import org.springframework.web.filter.CorsFilter;
  6. @Configuration
  7. public class CorsConfig {
  8.     @Bean
  9.     public CorsFilter corsFilter() {
  10.         UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
  11.         CorsConfiguration config = new CorsConfiguration();
  12.         config.setAllowCredentials(true); // 允许发送Cookie信息
  13.         config.addAllowedOrigin("http://localhost:3000"); // 允许前端域名
  14.         config.addAllowedHeader("*"); // 允许任何头
  15.         config.addAllowedMethod("*"); // 允许任何方法(GET、POST等)
  16.         source.registerCorsConfiguration("/**", config);
  17.         return new CorsFilter(source);
  18.     }
  19. }
复制代码

方法三:实现WebMvcConfigurer接口
  1. import org.springframework.context.annotation.Configuration;
  2. import org.springframework.web.servlet.config.annotation.CorsRegistry;
  3. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  4. @Configuration
  5. public class WebMvcConfig implements WebMvcConfigurer {
  6.     @Override
  7.     public void addCorsMappings(CorsRegistry registry) {
  8.         registry.addMapping("/**")
  9.                 .allowedOrigins("http://localhost:3000")
  10.                 .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
  11.                 .allowedHeaders("*")
  12.                 .allowCredentials(true)
  13.                 .maxAge(3600);
  14.     }
  15. }
复制代码

为了让Swagger UI能够跨域访问API,我们需要在Swagger配置中添加额外的设置:
  1. import org.springframework.context.annotation.Bean;
  2. import org.springframework.context.annotation.Configuration;
  3. import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
  4. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  5. import springfox.documentation.builders.ApiInfoBuilder;
  6. import springfox.documentation.builders.PathSelectors;
  7. import springfox.documentation.builders.RequestHandlerSelectors;
  8. import springfox.documentation.service.ApiInfo;
  9. import springfox.documentation.service.Contact;
  10. import springfox.documentation.spi.DocumentationType;
  11. import springfox.documentation.spring.web.plugins.Docket;
  12. import springfox.documentation.swagger2.annotations.EnableSwagger2;
  13. @Configuration
  14. @EnableSwagger2
  15. public class SwaggerConfig implements WebMvcConfigurer {
  16.     @Bean
  17.     public Docket api() {
  18.         return new Docket(DocumentationType.SWAGGER_2)
  19.                 .select()
  20.                 .apis(RequestHandlerSelectors.basePackage("com.example.demo.controller"))
  21.                 .paths(PathSelectors.any())
  22.                 .build()
  23.                 .apiInfo(apiInfo());
  24.     }
  25.     private ApiInfo apiInfo() {
  26.         return new ApiInfoBuilder()
  27.                 .title("API文档")
  28.                 .description("API文档描述")
  29.                 .version("1.0")
  30.                 .contact(new Contact("作者名", "作者网址", "作者邮箱"))
  31.                 .build();
  32.     }
  33.     @Override
  34.     public void addResourceHandlers(ResourceHandlerRegistry registry) {
  35.         registry.addResourceHandler("swagger-ui.html")
  36.                 .addResourceLocations("classpath:/META-INF/resources/");
  37.         registry.addResourceHandler("/webjars/**")
  38.                 .addResourceLocations("classpath:/META-INF/resources/webjars/");
  39.     }
  40. }
复制代码

下面是一个完整的Spring Boot项目示例,展示如何配置Swagger和CORS:
  1. // 主应用类
  2. package com.example.demo;
  3. import org.springframework.boot.SpringApplication;
  4. import org.springframework.boot.autoconfigure.SpringBootApplication;
  5. @SpringBootApplication
  6. public class DemoApplication {
  7.     public static void main(String[] args) {
  8.         SpringApplication.run(DemoApplication.class, args);
  9.     }
  10. }
复制代码
  1. // Swagger配置类
  2. package com.example.demo.config;
  3. import org.springframework.context.annotation.Bean;
  4. import org.springframework.context.annotation.Configuration;
  5. import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
  6. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  7. import springfox.documentation.builders.ApiInfoBuilder;
  8. import springfox.documentation.builders.PathSelectors;
  9. import springfox.documentation.builders.RequestHandlerSelectors;
  10. import springfox.documentation.service.ApiInfo;
  11. import springfox.documentation.service.Contact;
  12. import springfox.documentation.spi.DocumentationType;
  13. import springfox.documentation.spring.web.plugins.Docket;
  14. import springfox.documentation.swagger2.annotations.EnableSwagger2;
  15. @Configuration
  16. @EnableSwagger2
  17. public class SwaggerConfig implements WebMvcConfigurer {
  18.     @Bean
  19.     public Docket api() {
  20.         return new Docket(DocumentationType.SWAGGER_2)
  21.                 .select()
  22.                 .apis(RequestHandlerSelectors.basePackage("com.example.demo.controller"))
  23.                 .paths(PathSelectors.any())
  24.                 .build()
  25.                 .apiInfo(apiInfo());
  26.     }
  27.     private ApiInfo apiInfo() {
  28.         return new ApiInfoBuilder()
  29.                 .title("API文档")
  30.                 .description("API文档描述")
  31.                 .version("1.0")
  32.                 .contact(new Contact("作者名", "作者网址", "作者邮箱"))
  33.                 .build();
  34.     }
  35.     @Override
  36.     public void addResourceHandlers(ResourceHandlerRegistry registry) {
  37.         registry.addResourceHandler("swagger-ui.html")
  38.                 .addResourceLocations("classpath:/META-INF/resources/");
  39.         registry.addResourceHandler("/webjars/**")
  40.                 .addResourceLocations("classpath:/META-INF/resources/webjars/");
  41.     }
  42. }
复制代码
  1. // CORS配置类
  2. package com.example.demo.config;
  3. import org.springframework.context.annotation.Bean;
  4. import org.springframework.context.annotation.Configuration;
  5. import org.springframework.web.cors.CorsConfiguration;
  6. import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
  7. import org.springframework.web.filter.CorsFilter;
  8. @Configuration
  9. public class CorsConfig {
  10.     @Bean
  11.     public CorsFilter corsFilter() {
  12.         UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
  13.         CorsConfiguration config = new CorsConfiguration();
  14.         config.setAllowCredentials(true); // 允许发送Cookie信息
  15.         config.addAllowedOrigin("http://localhost:3000"); // 允许前端域名
  16.         config.addAllowedHeader("*"); // 允许任何头
  17.         config.addAllowedMethod("*"); // 允许任何方法(GET、POST等)
  18.         source.registerCorsConfiguration("/**", config);
  19.         return new CorsFilter(source);
  20.     }
  21. }
复制代码
  1. // 示例Controller
  2. package com.example.demo.controller;
  3. import io.swagger.annotations.Api;
  4. import io.swagger.annotations.ApiOperation;
  5. import org.springframework.web.bind.annotation.GetMapping;
  6. import org.springframework.web.bind.annotation.RequestMapping;
  7. import org.springframework.web.bind.annotation.RestController;
  8. @RestController
  9. @RequestMapping("/api")
  10. @Api(tags = "示例API")
  11. public class ApiController {
  12.     @GetMapping("/hello")
  13.     @ApiOperation("获取问候语")
  14.     public String sayHello() {
  15.         return "Hello, World!";
  16.     }
  17.     @GetMapping("/data")
  18.     @ApiOperation("获取数据")
  19.     public Object getData() {
  20.         return new Object() {
  21.             public String name = "示例数据";
  22.             public int value = 100;
  23.         };
  24.     }
  25. }
复制代码

基于Node.js和Express的配置

如果你使用Node.js和Express框架,下面是如何配置Swagger和CORS的示例:
  1. npm install express cors swagger-jsdoc swagger-ui-express
复制代码
  1. const express = require('express');
  2. const cors = require('cors');
  3. const swaggerJsdoc = require('swagger-jsdoc');
  4. const swaggerUi = require('swagger-ui-express');
  5. const app = express();
  6. // 配置CORS
  7. app.use(cors({
  8.     origin: 'http://localhost:3000', // 允许的前端域名
  9.     methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
  10.     allowedHeaders: ['Content-Type', 'Authorization'],
  11.     credentials: true
  12. }));
  13. // 解析JSON请求体
  14. app.use(express.json());
  15. // Swagger配置
  16. const options = {
  17.     definition: {
  18.         openapi: '3.0.0',
  19.         info: {
  20.             title: 'API文档',
  21.             version: '1.0.0',
  22.             description: 'API文档描述'
  23.         },
  24.         servers: [
  25.             {
  26.                 url: 'http://localhost:8080',
  27.                 description: '开发服务器'
  28.             }
  29.         ]
  30.     },
  31.     apis: ['./routes/*.js'] // 包含API注解的文件路径
  32. };
  33. const specs = swaggerJsdoc(options);
  34. app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs));
  35. // 示例路由
  36. app.get('/api/hello', (req, res) => {
  37.     /**
  38.      * @swagger
  39.      * /api/hello:
  40.      *   get:
  41.      *     summary: 获取问候语
  42.      *     responses:
  43.      *       200:
  44.      *         description: 成功响应
  45.      */
  46.     res.json({ message: 'Hello, World!' });
  47. });
  48. app.get('/api/data', (req, res) => {
  49.     /**
  50.      * @swagger
  51.      * /api/data:
  52.      *   get:
  53.      *     summary: 获取数据
  54.      *     responses:
  55.      *       200:
  56.      *         description: 成功响应
  57.      *         content:
  58.      *           application/json:
  59.      *             schema:
  60.      *               type: object
  61.      *               properties:
  62.      *                 name:
  63.      *                   type: string
  64.      *                 value:
  65.      *                   type: integer
  66.      */
  67.     res.json({ name: '示例数据', value: 100 });
  68. });
  69. const PORT = process.env.PORT || 8080;
  70. app.listen(PORT, () => {
  71.     console.log(`服务器运行在端口 ${PORT}`);
  72.     console.log(`Swagger文档地址: http://localhost:${PORT}/api-docs`);
  73. });
复制代码

基于Django REST framework的配置

对于Python的Django框架,配置Swagger和CORS的方法如下:
  1. pip install django django-rest-framework django-cors-headers drf-yasg
复制代码

在settings.py中添加必要的配置:
  1. # settings.py
  2. INSTALLED_APPS = [
  3.     # ...
  4.     'rest_framework',
  5.     'corsheaders',
  6.     'drf_yasg',
  7.     # ...
  8. ]
  9. # 添加CORS中间件
  10. MIDDLEWARE = [
  11.     'corsheaders.middleware.CorsMiddleware',  # 应该尽可能放在顶部
  12.     # ...
  13. ]
  14. # CORS配置
  15. CORS_ALLOWED_ORIGINS = [
  16.     "http://localhost:3000",
  17.     "http://127.0.0.1:3000",
  18. ]
  19. CORS_ALLOW_CREDENTIALS = True
  20. # Swagger配置
  21. SWAGGER_SETTINGS = {
  22.     'SECURITY_DEFINITIONS': {
  23.         'Basic': {
  24.             'type': 'basic'
  25.         },
  26.         'Bearer': {
  27.             'type': 'apiKey',
  28.             'name': 'Authorization',
  29.             'in': 'header'
  30.         }
  31.     }
  32. }
复制代码

在urls.py中添加Swagger UI的URL:
  1. # urls.py
  2. from django.urls import path, include
  3. from rest_framework import permissions
  4. from drf_yasg.views import get_schema_view
  5. from drf_yasg import openapi
  6. schema_view = get_schema_view(
  7.     openapi.Info(
  8.         title="API文档",
  9.         default_version='v1',
  10.         description="API文档描述",
  11.     ),
  12.     public=True,
  13.     permission_classes=(permissions.AllowAny,),
  14. )
  15. urlpatterns = [
  16.     path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
  17.     path('redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
  18.     path('api/', include('api.urls')),
  19. ]
复制代码
  1. # api/views.py
  2. from rest_framework.decorators import api_view
  3. from rest_framework.response import Response
  4. from drf_yasg.utils import swagger_auto_schema
  5. @swagger_auto_schema(
  6.     method='get',
  7.     operation_description='获取问候语',
  8.     responses={200: '成功响应'}
  9. )
  10. @api_view(['GET'])
  11. def hello(request):
  12.     return Response({'message': 'Hello, World!'})
  13. @swagger_auto_schema(
  14.     method='get',
  15.     operation_description='获取数据',
  16.     responses={200: '成功响应'}
  17. )
  18. @api_view(['GET'])
  19. def get_data(request):
  20.     return Response({'name': '示例数据', 'value': 100})
复制代码
  1. # api/urls.py
  2. from django.urls import path
  3. from . import views
  4. urlpatterns = [
  5.     path('hello/', views.hello),
  6.     path('data/', views.get_data),
  7. ]
复制代码

实际案例分析

让我们通过一个实际的前后端分离项目案例,来展示如何使用Swagger解决跨域问题。

项目背景

假设我们正在开发一个博客系统,前端使用React(运行在http://localhost:3000),后端使用Spring Boot(运行在http://localhost:8080)。前端需要调用后端的API来获取文章列表、文章详情等数据。

后端配置
  1. <dependencies>
  2.     <!-- Spring Boot Starter Web -->
  3.     <dependency>
  4.         <groupId>org.springframework.boot</groupId>
  5.         <artifactId>spring-boot-starter-web</artifactId>
  6.     </dependency>
  7.     <!-- Springfox Swagger2 -->
  8.     <dependency>
  9.         <groupId>io.springfox</groupId>
  10.         <artifactId>springfox-swagger2</artifactId>
  11.         <version>3.0.0</version>
  12.     </dependency>
  13.     <!-- Springfox Swagger UI -->
  14.     <dependency>
  15.         <groupId>io.springfox</groupId>
  16.         <artifactId>springfox-swagger-ui</artifactId>
  17.         <version>3.0.0</version>
  18.     </dependency>
  19.     <!-- Spring Data JPA -->
  20.     <dependency>
  21.         <groupId>org.springframework.boot</groupId>
  22.         <artifactId>spring-boot-starter-data-jpa</artifactId>
  23.     </dependency>
  24.     <!-- H2 Database -->
  25.     <dependency>
  26.         <groupId>com.h2database</groupId>
  27.         <artifactId>h2</artifactId>
  28.         <scope>runtime</scope>
  29.     </dependency>
  30. </dependencies>
复制代码
  1. package com.example.blog.config;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.web.servlet.config.annotation.CorsRegistry;
  5. import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
  6. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  7. import springfox.documentation.builders.ApiInfoBuilder;
  8. import springfox.documentation.builders.PathSelectors;
  9. import springfox.documentation.builders.RequestHandlerSelectors;
  10. import springfox.documentation.service.ApiInfo;
  11. import springfox.documentation.service.Contact;
  12. import springfox.documentation.spi.DocumentationType;
  13. import springfox.documentation.spring.web.plugins.Docket;
  14. import springfox.documentation.swagger2.annotations.EnableSwagger2;
  15. @Configuration
  16. @EnableSwagger2
  17. public class WebConfig implements WebMvcConfigurer {
  18.     @Bean
  19.     public Docket api() {
  20.         return new Docket(DocumentationType.SWAGGER_2)
  21.                 .select()
  22.                 .apis(RequestHandlerSelectors.basePackage("com.example.blog.controller"))
  23.                 .paths(PathSelectors.any())
  24.                 .build()
  25.                 .apiInfo(apiInfo());
  26.     }
  27.     private ApiInfo apiInfo() {
  28.         return new ApiInfoBuilder()
  29.                 .title("博客系统API")
  30.                 .description("博客系统的API文档")
  31.                 .version("1.0")
  32.                 .contact(new Contact("开发团队", "https://example.com", "team@example.com"))
  33.                 .build();
  34.     }
  35.     @Override
  36.     public void addResourceHandlers(ResourceHandlerRegistry registry) {
  37.         registry.addResourceHandler("swagger-ui.html")
  38.                 .addResourceLocations("classpath:/META-INF/resources/");
  39.         registry.addResourceHandler("/webjars/**")
  40.                 .addResourceLocations("classpath:/META-INF/resources/webjars/");
  41.     }
  42.     @Override
  43.     public void addCorsMappings(CorsRegistry registry) {
  44.         registry.addMapping("/**")
  45.                 .allowedOrigins("http://localhost:3000")
  46.                 .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
  47.                 .allowedHeaders("*")
  48.                 .allowCredentials(true)
  49.                 .maxAge(3600);
  50.     }
  51. }
复制代码
  1. package com.example.blog.entity;
  2. import javax.persistence.*;
  3. import java.time.LocalDateTime;
  4. @Entity
  5. @Table(name = "posts")
  6. public class Post {
  7.     @Id
  8.     @GeneratedValue(strategy = GenerationType.IDENTITY)
  9.     private Long id;
  10.     @Column(nullable = false)
  11.     private String title;
  12.     @Column(nullable = false, columnDefinition = "TEXT")
  13.     private String content;
  14.     @Column(name = "created_at")
  15.     private LocalDateTime createdAt;
  16.     @Column(name = "updated_at")
  17.     private LocalDateTime updatedAt;
  18.     // 构造函数、getter和setter
  19.     public Post() {
  20.         this.createdAt = LocalDateTime.now();
  21.         this.updatedAt = LocalDateTime.now();
  22.     }
  23.     public Post(String title, String content) {
  24.         this();
  25.         this.title = title;
  26.         this.content = content;
  27.     }
  28.     // 省略getter和setter...
  29. }
复制代码
  1. package com.example.blog.repository;
  2. import com.example.blog.entity.Post;
  3. import org.springframework.data.jpa.repository.JpaRepository;
  4. import org.springframework.stereotype.Repository;
  5. @Repository
  6. public interface PostRepository extends JpaRepository<Post, Long> {
  7. }
复制代码
  1. package com.example.blog.controller;
  2. import com.example.blog.entity.Post;
  3. import com.example.blog.repository.PostRepository;
  4. import io.swagger.annotations.Api;
  5. import io.swagger.annotations.ApiOperation;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.http.HttpStatus;
  8. import org.springframework.http.ResponseEntity;
  9. import org.springframework.web.bind.annotation.*;
  10. import java.util.List;
  11. import java.util.Optional;
  12. @RestController
  13. @RequestMapping("/api/posts")
  14. @Api(tags = "文章管理")
  15. public class PostController {
  16.     @Autowired
  17.     private PostRepository postRepository;
  18.     @GetMapping
  19.     @ApiOperation("获取所有文章")
  20.     public List<Post> getAllPosts() {
  21.         return postRepository.findAll();
  22.     }
  23.     @GetMapping("/{id}")
  24.     @ApiOperation("根据ID获取文章")
  25.     public ResponseEntity<Post> getPostById(@PathVariable Long id) {
  26.         Optional<Post> post = postRepository.findById(id);
  27.         return post.map(ResponseEntity::ok)
  28.                 .orElseGet(() -> ResponseEntity.notFound().build());
  29.     }
  30.     @PostMapping
  31.     @ApiOperation("创建新文章")
  32.     public ResponseEntity<Post> createPost(@RequestBody Post post) {
  33.         Post savedPost = postRepository.save(post);
  34.         return new ResponseEntity<>(savedPost, HttpStatus.CREATED);
  35.     }
  36.     @PutMapping("/{id}")
  37.     @ApiOperation("更新文章")
  38.     public ResponseEntity<Post> updatePost(@PathVariable Long id, @RequestBody Post postDetails) {
  39.         return postRepository.findById(id)
  40.                 .map(post -> {
  41.                     post.setTitle(postDetails.getTitle());
  42.                     post.setContent(postDetails.getContent());
  43.                     post.setUpdatedAt(LocalDateTime.now());
  44.                     Post updatedPost = postRepository.save(post);
  45.                     return ResponseEntity.ok(updatedPost);
  46.                 })
  47.                 .orElseGet(() -> ResponseEntity.notFound().build());
  48.     }
  49.     @DeleteMapping("/{id}")
  50.     @ApiOperation("删除文章")
  51.     public ResponseEntity<Void> deletePost(@PathVariable Long id) {
  52.         return postRepository.findById(id)
  53.                 .map(post -> {
  54.                     postRepository.delete(post);
  55.                     return ResponseEntity.ok().<Void>build();
  56.                 })
  57.                 .orElseGet(() -> ResponseEntity.notFound().build());
  58.     }
  59. }
复制代码

前端配置
  1. npx create-react-app blog-frontend
  2. cd blog-frontend
  3. npm install axios
复制代码
  1. // src/services/api.js
  2. import axios from 'axios';
  3. const api = axios.create({
  4.     baseURL: 'http://localhost:8080/api',
  5.     timeout: 10000,
  6.     headers: {
  7.         'Content-Type': 'application/json'
  8.     }
  9. });
  10. export default api;
复制代码
  1. // src/services/postService.js
  2. import api from './api';
  3. export const getAllPosts = () => {
  4.     return api.get('/posts');
  5. };
  6. export const getPostById = (id) => {
  7.     return api.get(`/posts/${id}`);
  8. };
  9. export const createPost = (post) => {
  10.     return api.post('/posts', post);
  11. };
  12. export const updatePost = (id, post) => {
  13.     return api.put(`/posts/${id}`, post);
  14. };
  15. export const deletePost = (id) => {
  16.     return api.delete(`/posts/${id}`);
  17. };
复制代码
  1. // src/components/PostList.js
  2. import React, { useState, useEffect } from 'react';
  3. import { getAllPosts, deletePost } from '../services/postService';
  4. import PostForm from './PostForm';
  5. const PostList = () => {
  6.     const [posts, setPosts] = useState([]);
  7.     const [editingPost, setEditingPost] = useState(null);
  8.     const [loading, setLoading] = useState(true);
  9.     const [error, setError] = useState(null);
  10.     useEffect(() => {
  11.         fetchPosts();
  12.     }, []);
  13.     const fetchPosts = async () => {
  14.         try {
  15.             setLoading(true);
  16.             const response = await getAllPosts();
  17.             setPosts(response.data);
  18.             setError(null);
  19.         } catch (err) {
  20.             setError('获取文章列表失败');
  21.             console.error('Error fetching posts:', err);
  22.         } finally {
  23.             setLoading(false);
  24.         }
  25.     };
  26.     const handleDelete = async (id) => {
  27.         if (window.confirm('确定要删除这篇文章吗?')) {
  28.             try {
  29.                 await deletePost(id);
  30.                 setPosts(posts.filter(post => post.id !== id));
  31.             } catch (err) {
  32.                 setError('删除文章失败');
  33.                 console.error('Error deleting post:', err);
  34.             }
  35.         }
  36.     };
  37.     const handleEdit = (post) => {
  38.         setEditingPost(post);
  39.     };
  40.     const handleFormSubmit = () => {
  41.         setEditingPost(null);
  42.         fetchPosts();
  43.     };
  44.     if (loading) return <div>加载中...</div>;
  45.     if (error) return <div>{error}</div>;
  46.     return (
  47.         <div className="post-list">
  48.             <h1>博客文章</h1>
  49.             <PostForm post={editingPost} onSubmit={handleFormSubmit} />
  50.             <ul>
  51.                 {posts.map(post => (
  52.                     <li key={post.id}>
  53.                         <h2>{post.title}</h2>
  54.                         <p>{post.content.substring(0, 100)}...</p>
  55.                         <button onClick={() => handleEdit(post)}>编辑</button>
  56.                         <button onClick={() => handleDelete(post.id)}>删除</button>
  57.                     </li>
  58.                 ))}
  59.             </ul>
  60.         </div>
  61.     );
  62. };
  63. export default PostList;
复制代码
  1. // src/components/PostForm.js
  2. import React, { useState } from 'react';
  3. import { createPost, updatePost } from '../services/postService';
  4. const PostForm = ({ post, onSubmit }) => {
  5.     const [formData, setFormData] = useState({
  6.         title: post ? post.title : '',
  7.         content: post ? post.content : ''
  8.     });
  9.     const [loading, setLoading] = useState(false);
  10.     const [error, setError] = useState(null);
  11.     const handleChange = (e) => {
  12.         const { name, value } = e.target;
  13.         setFormData(prev => ({
  14.             ...prev,
  15.             [name]: value
  16.         }));
  17.     };
  18.     const handleSubmit = async (e) => {
  19.         e.preventDefault();
  20.         setLoading(true);
  21.         setError(null);
  22.         try {
  23.             if (post) {
  24.                 // 更新文章
  25.                 await updatePost(post.id, formData);
  26.             } else {
  27.                 // 创建新文章
  28.                 await createPost(formData);
  29.             }
  30.             setFormData({ title: '', content: '' });
  31.             onSubmit();
  32.         } catch (err) {
  33.             setError(post ? '更新文章失败' : '创建文章失败');
  34.             console.error('Error submitting form:', err);
  35.         } finally {
  36.             setLoading(false);
  37.         }
  38.     };
  39.     return (
  40.         <div className="post-form">
  41.             <h2>{post ? '编辑文章' : '添加新文章'}</h2>
  42.             {error && <div className="error">{error}</div>}
  43.             <form onSubmit={handleSubmit}>
  44.                 <div>
  45.                     <label htmlFor="title">标题</label>
  46.                     <input
  47.                         type="text"
  48.                         id="title"
  49.                         name="title"
  50.                         value={formData.title}
  51.                         onChange={handleChange}
  52.                         required
  53.                     />
  54.                 </div>
  55.                 <div>
  56.                     <label htmlFor="content">内容</label>
  57.                     <textarea
  58.                         id="content"
  59.                         name="content"
  60.                         value={formData.content}
  61.                         onChange={handleChange}
  62.                         required
  63.                     />
  64.                 </div>
  65.                 <button type="submit" disabled={loading}>
  66.                     {loading ? '提交中...' : (post ? '更新' : '创建')}
  67.                 </button>
  68.                 {post && (
  69.                     <button type="button" onClick={() => onSubmit()}>
  70.                         取消
  71.                     </button>
  72.                 )}
  73.             </form>
  74.         </div>
  75.     );
  76. };
  77. export default PostForm;
复制代码
  1. // src/App.js
  2. import React from 'react';
  3. import PostList from './components/PostList';
  4. import './App.css';
  5. function App() {
  6.     return (
  7.         <div className="App">
  8.             <header className="App-header">
  9.                 <h1>博客系统</h1>
  10.             </header>
  11.             <main>
  12.                 <PostList />
  13.             </main>
  14.         </div>
  15.     );
  16. }
  17. export default App;
复制代码

测试跨域请求

1. 启动后端Spring Boot应用,访问http://localhost:8080/swagger-ui.html,可以查看API文档并测试API。
2. 启动前端React应用,访问http://localhost:3000,可以查看文章列表并进行增删改查操作。

启动后端Spring Boot应用,访问http://localhost:8080/swagger-ui.html,可以查看API文档并测试API。

启动前端React应用,访问http://localhost:3000,可以查看文章列表并进行增删改查操作。

由于我们在后端配置了CORS,前端应用可以正常调用后端API,不会遇到跨域问题。

最佳实践和注意事项

在使用Swagger解决跨域请求问题时,以下是一些最佳实践和注意事项:

1. 安全性考虑

• 限制允许的源:在生产环境中,不要使用*作为允许的源,而是明确指定允许访问的前端域名。
  1. // 不推荐
  2.   config.addAllowedOrigin("*");
  3.   
  4.   // 推荐
  5.   config.addAllowedOrigin("https://your-frontend-domain.com");
复制代码

• 谨慎使用allowCredentials:只有在前端确实需要发送Cookie等凭证信息时,才设置allowCredentials(true)。
• 限制HTTP方法:根据API的实际需求,只允许必要的HTTP方法。

谨慎使用allowCredentials:只有在前端确实需要发送Cookie等凭证信息时,才设置allowCredentials(true)。

限制HTTP方法:根据API的实际需求,只允许必要的HTTP方法。
  1. config.addAllowedMethod("GET");
  2.   config.addAllowedMethod("POST");
  3.   // 而不是
  4.   config.addAllowedMethod("*");
复制代码

2. 性能优化

• 缓存CORS配置:通过设置maxAge,可以让浏览器缓存CORS配置,减少预检请求的次数。
  1. config.setMaxAge(3600); // 缓存1小时
复制代码

• 合理使用预检请求:对于简单的GET、POST请求(不包含自定义头和复杂内容),浏览器不会发送预检请求。设计API时,尽量遵循简单请求的原则。

3. 开发与生产环境区分

• 环境变量配置:使用环境变量来区分开发和生产环境的CORS配置。
  1. @Value("${cors.allowed-origins}")
  2.   private String allowedOrigins;
  3.   
  4.   // 在application.properties中
  5.   // 开发环境: cors.allowed-origins=http://localhost:3000
  6.   // 生产环境: cors.allowed-origins=https://your-frontend-domain.com
复制代码

• 多环境配置文件:在Spring Boot中,可以使用不同的配置文件来区分环境。
  1. # application-dev.properties
  2.   cors.allowed-origins=http://localhost:3000
  3.   
  4.   # application-prod.properties
  5.   cors.allowed-origins=https://your-frontend-domain.com
复制代码

4. Swagger配置最佳实践

• API版本控制:在Swagger配置中添加API版本控制,方便管理不同版本的API。
  1. @Bean
  2.   public Docket apiV1() {
  3.       return new Docket(DocumentationType.SWAGGER_2)
  4.               .groupName("v1")
  5.               .select()
  6.               .apis(RequestHandlerSelectors.basePackage("com.example.blog.controller.v1"))
  7.               .paths(PathSelectors.ant("/api/v1/**"))
  8.               .build()
  9.               .apiInfo(apiInfoV1());
  10.   }
  11.   
  12.   @Bean
  13.   public Docket apiV2() {
  14.       return new Docket(DocumentationType.SWAGGER_2)
  15.               .groupName("v2")
  16.               .select()
  17.               .apis(RequestHandlerSelectors.basePackage("com.example.blog.controller.v2"))
  18.               .paths(PathSelectors.ant("/api/v2/**"))
  19.               .build()
  20.               .apiInfo(apiInfoV2());
  21.   }
复制代码

• API分组:使用@Api注解对API进行分组,提高文档的可读性。
  1. @RestController
  2.   @RequestMapping("/api/posts")
  3.   @Api(tags = "文章管理")
  4.   public class PostController {
  5.       // ...
  6.   }
复制代码

• 详细文档:使用@ApiOperation和@ApiParam等注解提供详细的API文档。
  1. @GetMapping("/{id}")
  2.   @ApiOperation("根据ID获取文章")
  3.   @ApiImplicitParams({
  4.       @ApiImplicitParam(name = "id", value = "文章ID", required = true, dataType = "Long", paramType = "path")
  5.   })
  6.   public ResponseEntity<Post> getPostById(@PathVariable Long id) {
  7.       // ...
  8.   }
复制代码

5. 常见问题排查

• CORS错误:如果前端仍然遇到CORS错误,检查:后端CORS配置是否正确请求的URL是否正确服务器是否正确响应了预检请求(OPTIONS请求)
• 后端CORS配置是否正确
• 请求的URL是否正确
• 服务器是否正确响应了预检请求(OPTIONS请求)
• Swagger UI无法访问:如果Swagger UI无法访问,检查:是否正确添加了Swagger依赖是否正确配置了资源处理器是否有其他拦截器阻止了对Swagger UI资源的访问
• 是否正确添加了Swagger依赖
• 是否正确配置了资源处理器
• 是否有其他拦截器阻止了对Swagger UI资源的访问
• API文档不完整:如果API文档不完整或缺少某些接口,检查:是否正确使用了Swagger注解扫描的包路径是否正确是否有条件配置(如Profile)导致某些Bean未被加载
• 是否正确使用了Swagger注解
• 扫描的包路径是否正确
• 是否有条件配置(如Profile)导致某些Bean未被加载

CORS错误:如果前端仍然遇到CORS错误,检查:

• 后端CORS配置是否正确
• 请求的URL是否正确
• 服务器是否正确响应了预检请求(OPTIONS请求)

Swagger UI无法访问:如果Swagger UI无法访问,检查:

• 是否正确添加了Swagger依赖
• 是否正确配置了资源处理器
• 是否有其他拦截器阻止了对Swagger UI资源的访问

API文档不完整:如果API文档不完整或缺少某些接口,检查:

• 是否正确使用了Swagger注解
• 扫描的包路径是否正确
• 是否有条件配置(如Profile)导致某些Bean未被加载

总结

在前后端分离的项目中,跨域请求是一个常见的问题,但通过正确配置Swagger和CORS,我们可以轻松解决这个问题。本文详细介绍了如何在Spring Boot、Node.js/Express和Django框架中配置Swagger支持跨域请求,并通过一个实际的博客系统案例展示了完整的实现过程。

关键要点包括:

1. 理解同源策略和跨域请求的基本概念
2. 掌握Swagger的基本配置和使用方法
3. 学习如何在不同框架中配置CORS支持
4. 通过实际案例了解前后端分离项目的完整实现
5. 遵循最佳实践,确保API的安全性和性能

通过本文的指导,你应该能够掌握使用Swagger实现跨域请求的技巧与配置方法,让前后端分离项目中的API调用不再受同源策略限制,轻松解决跨域访问问题。这不仅提高了开发效率,也改善了开发体验,使前后端团队能够更加顺畅地协作。
「七転び八起き(ななころびやおき)」
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则