|
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
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相关的依赖:
- <!-- Spring Boot Starter Web -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <!-- Springfox Swagger2 -->
- <dependency>
- <groupId>io.springfox</groupId>
- <artifactId>springfox-swagger2</artifactId>
- <version>3.0.0</version>
- </dependency>
- <!-- Springfox Swagger UI -->
- <dependency>
- <groupId>io.springfox</groupId>
- <artifactId>springfox-swagger-ui</artifactId>
- <version>3.0.0</version>
- </dependency>
- <!-- Spring Boot Starter Actuator (可选,用于监控) -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-actuator</artifactId>
- </dependency>
复制代码
创建一个Swagger配置类:
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import springfox.documentation.builders.ApiInfoBuilder;
- import springfox.documentation.builders.PathSelectors;
- import springfox.documentation.builders.RequestHandlerSelectors;
- import springfox.documentation.service.ApiInfo;
- import springfox.documentation.service.Contact;
- import springfox.documentation.spi.DocumentationType;
- import springfox.documentation.spring.web.plugins.Docket;
- import springfox.documentation.swagger2.annotations.EnableSwagger2;
- @Configuration
- @EnableSwagger2
- public class SwaggerConfig {
- @Bean
- public Docket api() {
- return new Docket(DocumentationType.SWAGGER_2)
- .select()
- .apis(RequestHandlerSelectors.basePackage("com.example.demo.controller"))
- .paths(PathSelectors.any())
- .build()
- .apiInfo(apiInfo());
- }
- private ApiInfo apiInfo() {
- return new ApiInfoBuilder()
- .title("API文档")
- .description("API文档描述")
- .version("1.0")
- .contact(new Contact("作者名", "作者网址", "作者邮箱"))
- .build();
- }
- }
复制代码
在Spring Boot中,有多种方式配置CORS:
方法一:使用@CrossOrigin注解
可以在Controller类或具体的方法上添加@CrossOrigin注解:
- import org.springframework.web.bind.annotation.CrossOrigin;
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RestController;
- @RestController
- @RequestMapping("/api")
- @CrossOrigin(origins = "http://localhost:3000") // 允许特定源
- public class ApiController {
- @GetMapping("/data")
- public String getData() {
- return "Hello, World!";
- }
- }
复制代码
方法二:全局CORS配置
创建一个配置类,全局配置CORS:
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.web.cors.CorsConfiguration;
- import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
- import org.springframework.web.filter.CorsFilter;
- @Configuration
- public class CorsConfig {
- @Bean
- public CorsFilter corsFilter() {
- UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
- CorsConfiguration config = new CorsConfiguration();
- config.setAllowCredentials(true); // 允许发送Cookie信息
- config.addAllowedOrigin("http://localhost:3000"); // 允许前端域名
- config.addAllowedHeader("*"); // 允许任何头
- config.addAllowedMethod("*"); // 允许任何方法(GET、POST等)
- source.registerCorsConfiguration("/**", config);
- return new CorsFilter(source);
- }
- }
复制代码
方法三:实现WebMvcConfigurer接口
- import org.springframework.context.annotation.Configuration;
- import org.springframework.web.servlet.config.annotation.CorsRegistry;
- import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
- @Configuration
- public class WebMvcConfig implements WebMvcConfigurer {
- @Override
- public void addCorsMappings(CorsRegistry registry) {
- registry.addMapping("/**")
- .allowedOrigins("http://localhost:3000")
- .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
- .allowedHeaders("*")
- .allowCredentials(true)
- .maxAge(3600);
- }
- }
复制代码
为了让Swagger UI能够跨域访问API,我们需要在Swagger配置中添加额外的设置:
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
- import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
- import springfox.documentation.builders.ApiInfoBuilder;
- import springfox.documentation.builders.PathSelectors;
- import springfox.documentation.builders.RequestHandlerSelectors;
- import springfox.documentation.service.ApiInfo;
- import springfox.documentation.service.Contact;
- import springfox.documentation.spi.DocumentationType;
- import springfox.documentation.spring.web.plugins.Docket;
- import springfox.documentation.swagger2.annotations.EnableSwagger2;
- @Configuration
- @EnableSwagger2
- public class SwaggerConfig implements WebMvcConfigurer {
- @Bean
- public Docket api() {
- return new Docket(DocumentationType.SWAGGER_2)
- .select()
- .apis(RequestHandlerSelectors.basePackage("com.example.demo.controller"))
- .paths(PathSelectors.any())
- .build()
- .apiInfo(apiInfo());
- }
- private ApiInfo apiInfo() {
- return new ApiInfoBuilder()
- .title("API文档")
- .description("API文档描述")
- .version("1.0")
- .contact(new Contact("作者名", "作者网址", "作者邮箱"))
- .build();
- }
- @Override
- public void addResourceHandlers(ResourceHandlerRegistry registry) {
- registry.addResourceHandler("swagger-ui.html")
- .addResourceLocations("classpath:/META-INF/resources/");
- registry.addResourceHandler("/webjars/**")
- .addResourceLocations("classpath:/META-INF/resources/webjars/");
- }
- }
复制代码
下面是一个完整的Spring Boot项目示例,展示如何配置Swagger和CORS:
- // 主应用类
- package com.example.demo;
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
- @SpringBootApplication
- public class DemoApplication {
- public static void main(String[] args) {
- SpringApplication.run(DemoApplication.class, args);
- }
- }
复制代码- // Swagger配置类
- package com.example.demo.config;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
- import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
- import springfox.documentation.builders.ApiInfoBuilder;
- import springfox.documentation.builders.PathSelectors;
- import springfox.documentation.builders.RequestHandlerSelectors;
- import springfox.documentation.service.ApiInfo;
- import springfox.documentation.service.Contact;
- import springfox.documentation.spi.DocumentationType;
- import springfox.documentation.spring.web.plugins.Docket;
- import springfox.documentation.swagger2.annotations.EnableSwagger2;
- @Configuration
- @EnableSwagger2
- public class SwaggerConfig implements WebMvcConfigurer {
- @Bean
- public Docket api() {
- return new Docket(DocumentationType.SWAGGER_2)
- .select()
- .apis(RequestHandlerSelectors.basePackage("com.example.demo.controller"))
- .paths(PathSelectors.any())
- .build()
- .apiInfo(apiInfo());
- }
- private ApiInfo apiInfo() {
- return new ApiInfoBuilder()
- .title("API文档")
- .description("API文档描述")
- .version("1.0")
- .contact(new Contact("作者名", "作者网址", "作者邮箱"))
- .build();
- }
- @Override
- public void addResourceHandlers(ResourceHandlerRegistry registry) {
- registry.addResourceHandler("swagger-ui.html")
- .addResourceLocations("classpath:/META-INF/resources/");
- registry.addResourceHandler("/webjars/**")
- .addResourceLocations("classpath:/META-INF/resources/webjars/");
- }
- }
复制代码- // CORS配置类
- package com.example.demo.config;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.web.cors.CorsConfiguration;
- import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
- import org.springframework.web.filter.CorsFilter;
- @Configuration
- public class CorsConfig {
- @Bean
- public CorsFilter corsFilter() {
- UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
- CorsConfiguration config = new CorsConfiguration();
- config.setAllowCredentials(true); // 允许发送Cookie信息
- config.addAllowedOrigin("http://localhost:3000"); // 允许前端域名
- config.addAllowedHeader("*"); // 允许任何头
- config.addAllowedMethod("*"); // 允许任何方法(GET、POST等)
- source.registerCorsConfiguration("/**", config);
- return new CorsFilter(source);
- }
- }
复制代码- // 示例Controller
- package com.example.demo.controller;
- import io.swagger.annotations.Api;
- import io.swagger.annotations.ApiOperation;
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RestController;
- @RestController
- @RequestMapping("/api")
- @Api(tags = "示例API")
- public class ApiController {
- @GetMapping("/hello")
- @ApiOperation("获取问候语")
- public String sayHello() {
- return "Hello, World!";
- }
- @GetMapping("/data")
- @ApiOperation("获取数据")
- public Object getData() {
- return new Object() {
- public String name = "示例数据";
- public int value = 100;
- };
- }
- }
复制代码
基于Node.js和Express的配置
如果你使用Node.js和Express框架,下面是如何配置Swagger和CORS的示例:
- npm install express cors swagger-jsdoc swagger-ui-express
复制代码- const express = require('express');
- const cors = require('cors');
- const swaggerJsdoc = require('swagger-jsdoc');
- const swaggerUi = require('swagger-ui-express');
- const app = express();
- // 配置CORS
- app.use(cors({
- origin: 'http://localhost:3000', // 允许的前端域名
- methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
- allowedHeaders: ['Content-Type', 'Authorization'],
- credentials: true
- }));
- // 解析JSON请求体
- app.use(express.json());
- // Swagger配置
- const options = {
- definition: {
- openapi: '3.0.0',
- info: {
- title: 'API文档',
- version: '1.0.0',
- description: 'API文档描述'
- },
- servers: [
- {
- url: 'http://localhost:8080',
- description: '开发服务器'
- }
- ]
- },
- apis: ['./routes/*.js'] // 包含API注解的文件路径
- };
- const specs = swaggerJsdoc(options);
- app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs));
- // 示例路由
- app.get('/api/hello', (req, res) => {
- /**
- * @swagger
- * /api/hello:
- * get:
- * summary: 获取问候语
- * responses:
- * 200:
- * description: 成功响应
- */
- res.json({ message: 'Hello, World!' });
- });
- app.get('/api/data', (req, res) => {
- /**
- * @swagger
- * /api/data:
- * get:
- * summary: 获取数据
- * responses:
- * 200:
- * description: 成功响应
- * content:
- * application/json:
- * schema:
- * type: object
- * properties:
- * name:
- * type: string
- * value:
- * type: integer
- */
- res.json({ name: '示例数据', value: 100 });
- });
- const PORT = process.env.PORT || 8080;
- app.listen(PORT, () => {
- console.log(`服务器运行在端口 ${PORT}`);
- console.log(`Swagger文档地址: http://localhost:${PORT}/api-docs`);
- });
复制代码
基于Django REST framework的配置
对于Python的Django框架,配置Swagger和CORS的方法如下:
- pip install django django-rest-framework django-cors-headers drf-yasg
复制代码
在settings.py中添加必要的配置:
- # settings.py
- INSTALLED_APPS = [
- # ...
- 'rest_framework',
- 'corsheaders',
- 'drf_yasg',
- # ...
- ]
- # 添加CORS中间件
- MIDDLEWARE = [
- 'corsheaders.middleware.CorsMiddleware', # 应该尽可能放在顶部
- # ...
- ]
- # CORS配置
- CORS_ALLOWED_ORIGINS = [
- "http://localhost:3000",
- "http://127.0.0.1:3000",
- ]
- CORS_ALLOW_CREDENTIALS = True
- # Swagger配置
- SWAGGER_SETTINGS = {
- 'SECURITY_DEFINITIONS': {
- 'Basic': {
- 'type': 'basic'
- },
- 'Bearer': {
- 'type': 'apiKey',
- 'name': 'Authorization',
- 'in': 'header'
- }
- }
- }
复制代码
在urls.py中添加Swagger UI的URL:
- # urls.py
- from django.urls import path, include
- from rest_framework import permissions
- from drf_yasg.views import get_schema_view
- from drf_yasg import openapi
- schema_view = get_schema_view(
- openapi.Info(
- title="API文档",
- default_version='v1',
- description="API文档描述",
- ),
- public=True,
- permission_classes=(permissions.AllowAny,),
- )
- urlpatterns = [
- path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
- path('redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
- path('api/', include('api.urls')),
- ]
复制代码- # api/views.py
- from rest_framework.decorators import api_view
- from rest_framework.response import Response
- from drf_yasg.utils import swagger_auto_schema
- @swagger_auto_schema(
- method='get',
- operation_description='获取问候语',
- responses={200: '成功响应'}
- )
- @api_view(['GET'])
- def hello(request):
- return Response({'message': 'Hello, World!'})
- @swagger_auto_schema(
- method='get',
- operation_description='获取数据',
- responses={200: '成功响应'}
- )
- @api_view(['GET'])
- def get_data(request):
- return Response({'name': '示例数据', 'value': 100})
复制代码- # api/urls.py
- from django.urls import path
- from . import views
- urlpatterns = [
- path('hello/', views.hello),
- path('data/', views.get_data),
- ]
复制代码
实际案例分析
让我们通过一个实际的前后端分离项目案例,来展示如何使用Swagger解决跨域问题。
项目背景
假设我们正在开发一个博客系统,前端使用React(运行在http://localhost:3000),后端使用Spring Boot(运行在http://localhost:8080)。前端需要调用后端的API来获取文章列表、文章详情等数据。
后端配置
- <dependencies>
- <!-- Spring Boot Starter Web -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <!-- Springfox Swagger2 -->
- <dependency>
- <groupId>io.springfox</groupId>
- <artifactId>springfox-swagger2</artifactId>
- <version>3.0.0</version>
- </dependency>
- <!-- Springfox Swagger UI -->
- <dependency>
- <groupId>io.springfox</groupId>
- <artifactId>springfox-swagger-ui</artifactId>
- <version>3.0.0</version>
- </dependency>
- <!-- Spring Data JPA -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-jpa</artifactId>
- </dependency>
- <!-- H2 Database -->
- <dependency>
- <groupId>com.h2database</groupId>
- <artifactId>h2</artifactId>
- <scope>runtime</scope>
- </dependency>
- </dependencies>
复制代码- package com.example.blog.config;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.web.servlet.config.annotation.CorsRegistry;
- import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
- import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
- import springfox.documentation.builders.ApiInfoBuilder;
- import springfox.documentation.builders.PathSelectors;
- import springfox.documentation.builders.RequestHandlerSelectors;
- import springfox.documentation.service.ApiInfo;
- import springfox.documentation.service.Contact;
- import springfox.documentation.spi.DocumentationType;
- import springfox.documentation.spring.web.plugins.Docket;
- import springfox.documentation.swagger2.annotations.EnableSwagger2;
- @Configuration
- @EnableSwagger2
- public class WebConfig implements WebMvcConfigurer {
- @Bean
- public Docket api() {
- return new Docket(DocumentationType.SWAGGER_2)
- .select()
- .apis(RequestHandlerSelectors.basePackage("com.example.blog.controller"))
- .paths(PathSelectors.any())
- .build()
- .apiInfo(apiInfo());
- }
- private ApiInfo apiInfo() {
- return new ApiInfoBuilder()
- .title("博客系统API")
- .description("博客系统的API文档")
- .version("1.0")
- .contact(new Contact("开发团队", "https://example.com", "team@example.com"))
- .build();
- }
- @Override
- public void addResourceHandlers(ResourceHandlerRegistry registry) {
- registry.addResourceHandler("swagger-ui.html")
- .addResourceLocations("classpath:/META-INF/resources/");
- registry.addResourceHandler("/webjars/**")
- .addResourceLocations("classpath:/META-INF/resources/webjars/");
- }
- @Override
- public void addCorsMappings(CorsRegistry registry) {
- registry.addMapping("/**")
- .allowedOrigins("http://localhost:3000")
- .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
- .allowedHeaders("*")
- .allowCredentials(true)
- .maxAge(3600);
- }
- }
复制代码- package com.example.blog.entity;
- import javax.persistence.*;
- import java.time.LocalDateTime;
- @Entity
- @Table(name = "posts")
- public class Post {
- @Id
- @GeneratedValue(strategy = GenerationType.IDENTITY)
- private Long id;
- @Column(nullable = false)
- private String title;
- @Column(nullable = false, columnDefinition = "TEXT")
- private String content;
- @Column(name = "created_at")
- private LocalDateTime createdAt;
- @Column(name = "updated_at")
- private LocalDateTime updatedAt;
- // 构造函数、getter和setter
- public Post() {
- this.createdAt = LocalDateTime.now();
- this.updatedAt = LocalDateTime.now();
- }
- public Post(String title, String content) {
- this();
- this.title = title;
- this.content = content;
- }
- // 省略getter和setter...
- }
复制代码- package com.example.blog.repository;
- import com.example.blog.entity.Post;
- import org.springframework.data.jpa.repository.JpaRepository;
- import org.springframework.stereotype.Repository;
- @Repository
- public interface PostRepository extends JpaRepository<Post, Long> {
- }
复制代码- package com.example.blog.controller;
- import com.example.blog.entity.Post;
- import com.example.blog.repository.PostRepository;
- import io.swagger.annotations.Api;
- import io.swagger.annotations.ApiOperation;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.http.HttpStatus;
- import org.springframework.http.ResponseEntity;
- import org.springframework.web.bind.annotation.*;
- import java.util.List;
- import java.util.Optional;
- @RestController
- @RequestMapping("/api/posts")
- @Api(tags = "文章管理")
- public class PostController {
- @Autowired
- private PostRepository postRepository;
- @GetMapping
- @ApiOperation("获取所有文章")
- public List<Post> getAllPosts() {
- return postRepository.findAll();
- }
- @GetMapping("/{id}")
- @ApiOperation("根据ID获取文章")
- public ResponseEntity<Post> getPostById(@PathVariable Long id) {
- Optional<Post> post = postRepository.findById(id);
- return post.map(ResponseEntity::ok)
- .orElseGet(() -> ResponseEntity.notFound().build());
- }
- @PostMapping
- @ApiOperation("创建新文章")
- public ResponseEntity<Post> createPost(@RequestBody Post post) {
- Post savedPost = postRepository.save(post);
- return new ResponseEntity<>(savedPost, HttpStatus.CREATED);
- }
- @PutMapping("/{id}")
- @ApiOperation("更新文章")
- public ResponseEntity<Post> updatePost(@PathVariable Long id, @RequestBody Post postDetails) {
- return postRepository.findById(id)
- .map(post -> {
- post.setTitle(postDetails.getTitle());
- post.setContent(postDetails.getContent());
- post.setUpdatedAt(LocalDateTime.now());
- Post updatedPost = postRepository.save(post);
- return ResponseEntity.ok(updatedPost);
- })
- .orElseGet(() -> ResponseEntity.notFound().build());
- }
- @DeleteMapping("/{id}")
- @ApiOperation("删除文章")
- public ResponseEntity<Void> deletePost(@PathVariable Long id) {
- return postRepository.findById(id)
- .map(post -> {
- postRepository.delete(post);
- return ResponseEntity.ok().<Void>build();
- })
- .orElseGet(() -> ResponseEntity.notFound().build());
- }
- }
复制代码
前端配置
- npx create-react-app blog-frontend
- cd blog-frontend
- npm install axios
复制代码- // src/services/api.js
- import axios from 'axios';
- const api = axios.create({
- baseURL: 'http://localhost:8080/api',
- timeout: 10000,
- headers: {
- 'Content-Type': 'application/json'
- }
- });
- export default api;
复制代码- // src/services/postService.js
- import api from './api';
- export const getAllPosts = () => {
- return api.get('/posts');
- };
- export const getPostById = (id) => {
- return api.get(`/posts/${id}`);
- };
- export const createPost = (post) => {
- return api.post('/posts', post);
- };
- export const updatePost = (id, post) => {
- return api.put(`/posts/${id}`, post);
- };
- export const deletePost = (id) => {
- return api.delete(`/posts/${id}`);
- };
复制代码- // src/components/PostList.js
- import React, { useState, useEffect } from 'react';
- import { getAllPosts, deletePost } from '../services/postService';
- import PostForm from './PostForm';
- const PostList = () => {
- const [posts, setPosts] = useState([]);
- const [editingPost, setEditingPost] = useState(null);
- const [loading, setLoading] = useState(true);
- const [error, setError] = useState(null);
- useEffect(() => {
- fetchPosts();
- }, []);
- const fetchPosts = async () => {
- try {
- setLoading(true);
- const response = await getAllPosts();
- setPosts(response.data);
- setError(null);
- } catch (err) {
- setError('获取文章列表失败');
- console.error('Error fetching posts:', err);
- } finally {
- setLoading(false);
- }
- };
- const handleDelete = async (id) => {
- if (window.confirm('确定要删除这篇文章吗?')) {
- try {
- await deletePost(id);
- setPosts(posts.filter(post => post.id !== id));
- } catch (err) {
- setError('删除文章失败');
- console.error('Error deleting post:', err);
- }
- }
- };
- const handleEdit = (post) => {
- setEditingPost(post);
- };
- const handleFormSubmit = () => {
- setEditingPost(null);
- fetchPosts();
- };
- if (loading) return <div>加载中...</div>;
- if (error) return <div>{error}</div>;
- return (
- <div className="post-list">
- <h1>博客文章</h1>
- <PostForm post={editingPost} onSubmit={handleFormSubmit} />
- <ul>
- {posts.map(post => (
- <li key={post.id}>
- <h2>{post.title}</h2>
- <p>{post.content.substring(0, 100)}...</p>
- <button onClick={() => handleEdit(post)}>编辑</button>
- <button onClick={() => handleDelete(post.id)}>删除</button>
- </li>
- ))}
- </ul>
- </div>
- );
- };
- export default PostList;
复制代码- // src/components/PostForm.js
- import React, { useState } from 'react';
- import { createPost, updatePost } from '../services/postService';
- const PostForm = ({ post, onSubmit }) => {
- const [formData, setFormData] = useState({
- title: post ? post.title : '',
- content: post ? post.content : ''
- });
- const [loading, setLoading] = useState(false);
- const [error, setError] = useState(null);
- const handleChange = (e) => {
- const { name, value } = e.target;
- setFormData(prev => ({
- ...prev,
- [name]: value
- }));
- };
- const handleSubmit = async (e) => {
- e.preventDefault();
- setLoading(true);
- setError(null);
- try {
- if (post) {
- // 更新文章
- await updatePost(post.id, formData);
- } else {
- // 创建新文章
- await createPost(formData);
- }
- setFormData({ title: '', content: '' });
- onSubmit();
- } catch (err) {
- setError(post ? '更新文章失败' : '创建文章失败');
- console.error('Error submitting form:', err);
- } finally {
- setLoading(false);
- }
- };
- return (
- <div className="post-form">
- <h2>{post ? '编辑文章' : '添加新文章'}</h2>
- {error && <div className="error">{error}</div>}
- <form onSubmit={handleSubmit}>
- <div>
- <label htmlFor="title">标题</label>
- <input
- type="text"
- id="title"
- name="title"
- value={formData.title}
- onChange={handleChange}
- required
- />
- </div>
- <div>
- <label htmlFor="content">内容</label>
- <textarea
- id="content"
- name="content"
- value={formData.content}
- onChange={handleChange}
- required
- />
- </div>
- <button type="submit" disabled={loading}>
- {loading ? '提交中...' : (post ? '更新' : '创建')}
- </button>
- {post && (
- <button type="button" onClick={() => onSubmit()}>
- 取消
- </button>
- )}
- </form>
- </div>
- );
- };
- export default PostForm;
复制代码- // src/App.js
- import React from 'react';
- import PostList from './components/PostList';
- import './App.css';
- function App() {
- return (
- <div className="App">
- <header className="App-header">
- <h1>博客系统</h1>
- </header>
- <main>
- <PostList />
- </main>
- </div>
- );
- }
- 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. 安全性考虑
• 限制允许的源:在生产环境中,不要使用*作为允许的源,而是明确指定允许访问的前端域名。
- // 不推荐
- config.addAllowedOrigin("*");
-
- // 推荐
- config.addAllowedOrigin("https://your-frontend-domain.com");
复制代码
• 谨慎使用allowCredentials:只有在前端确实需要发送Cookie等凭证信息时,才设置allowCredentials(true)。
• 限制HTTP方法:根据API的实际需求,只允许必要的HTTP方法。
谨慎使用allowCredentials:只有在前端确实需要发送Cookie等凭证信息时,才设置allowCredentials(true)。
限制HTTP方法:根据API的实际需求,只允许必要的HTTP方法。
- config.addAllowedMethod("GET");
- config.addAllowedMethod("POST");
- // 而不是
- config.addAllowedMethod("*");
复制代码
2. 性能优化
• 缓存CORS配置:通过设置maxAge,可以让浏览器缓存CORS配置,减少预检请求的次数。
- config.setMaxAge(3600); // 缓存1小时
复制代码
• 合理使用预检请求:对于简单的GET、POST请求(不包含自定义头和复杂内容),浏览器不会发送预检请求。设计API时,尽量遵循简单请求的原则。
3. 开发与生产环境区分
• 环境变量配置:使用环境变量来区分开发和生产环境的CORS配置。
- @Value("${cors.allowed-origins}")
- private String allowedOrigins;
-
- // 在application.properties中
- // 开发环境: cors.allowed-origins=http://localhost:3000
- // 生产环境: cors.allowed-origins=https://your-frontend-domain.com
复制代码
• 多环境配置文件:在Spring Boot中,可以使用不同的配置文件来区分环境。
- # application-dev.properties
- cors.allowed-origins=http://localhost:3000
-
- # application-prod.properties
- cors.allowed-origins=https://your-frontend-domain.com
复制代码
4. Swagger配置最佳实践
• API版本控制:在Swagger配置中添加API版本控制,方便管理不同版本的API。
- @Bean
- public Docket apiV1() {
- return new Docket(DocumentationType.SWAGGER_2)
- .groupName("v1")
- .select()
- .apis(RequestHandlerSelectors.basePackage("com.example.blog.controller.v1"))
- .paths(PathSelectors.ant("/api/v1/**"))
- .build()
- .apiInfo(apiInfoV1());
- }
-
- @Bean
- public Docket apiV2() {
- return new Docket(DocumentationType.SWAGGER_2)
- .groupName("v2")
- .select()
- .apis(RequestHandlerSelectors.basePackage("com.example.blog.controller.v2"))
- .paths(PathSelectors.ant("/api/v2/**"))
- .build()
- .apiInfo(apiInfoV2());
- }
复制代码
• API分组:使用@Api注解对API进行分组,提高文档的可读性。
- @RestController
- @RequestMapping("/api/posts")
- @Api(tags = "文章管理")
- public class PostController {
- // ...
- }
复制代码
• 详细文档:使用@ApiOperation和@ApiParam等注解提供详细的API文档。
- @GetMapping("/{id}")
- @ApiOperation("根据ID获取文章")
- @ApiImplicitParams({
- @ApiImplicitParam(name = "id", value = "文章ID", required = true, dataType = "Long", paramType = "path")
- })
- public ResponseEntity<Post> getPostById(@PathVariable Long id) {
- // ...
- }
复制代码
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调用不再受同源策略限制,轻松解决跨域访问问题。这不仅提高了开发效率,也改善了开发体验,使前后端团队能够更加顺畅地协作。 |
|