主题
09 · 与后端框架集成:Spring / Express / NestJS / FastAPI
生活类比: 写 yaml 是"先画菜单再开餐厅"——design-first; 在代码里写注解、自动导出 yaml 是"先开餐厅再贴菜单"——code-first。 两条路都能到罗马,绝大多数后端团队走的是 code-first(写代码顺手出文档)。
1. 两种工作流:Design-first vs Code-first
Design-first Code-first
──────────── ──────────
1. 写 openapi.yaml 1. 写代码 + 加注解
2. 评审 / 改 2. 启动服务,自动暴露 /v3/api-docs
3. 后端 implements 3. yaml 由框架自动生成
4. 前端用 codegen 出 SDK 4. 前端拉 yaml 出 SDK
✅ 大型项目 / 团队 ✅ 个人 / 中小项目
✅ 多语言后端协作 ✅ 改代码不用同步改 yaml
❌ 早期开发慢 ❌ yaml 容易"脏",得 lint推荐:项目早期 design-first 锁定接口;日常迭代 code-first 提效。
2. Spring Boot · springdoc-openapi(推荐)
注:早年用的 springfox 已经不维护了,新项目用 springdoc-openapi。
2.1 加依赖
xml
<!-- pom.xml -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.5.0</version>
</dependency>启动后 零配置:
- yaml:
http://localhost:8080/v3/api-docs.yaml - json:
http://localhost:8080/v3/api-docs - UI:
http://localhost:8080/swagger-ui.html
2.2 全局配置
java
@Configuration
public class OpenApiConfig {
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("电商 API")
.version("1.0.0")
.description("基于 Spring Boot 3 + springdoc-openapi"))
.components(new Components()
.addSecuritySchemes("bearerAuth",
new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")))
.addSecurityItem(new SecurityRequirement().addList("bearerAuth"));
}
}2.3 Controller 注解
java
@RestController
@RequestMapping("/users")
@Tag(name = "用户管理")
public class UserController {
@GetMapping
@Operation(summary = "获取用户列表", operationId = "listUsers")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "成功"),
@ApiResponse(responseCode = "401", description = "未登录")
})
public List<User> list(
@Parameter(description = "页码") @RequestParam(defaultValue = "1") int page) {
return userService.list(page);
}
@PostMapping
@Operation(summary = "创建用户", operationId = "createUser",
security = @SecurityRequirement(name = "bearerAuth"))
public User create(@Valid @RequestBody UserCreate body) {
return userService.create(body);
}
}2.4 DTO 注解
java
@Schema(description = "创建用户请求")
public class UserCreate {
@Schema(description = "用户名", example = "alice", required = true)
@NotBlank
@Size(max = 50)
private String name;
@Schema(description = "邮箱", example = "alice@x.com", required = true)
@Email
private String email;
}Spring
@Valid配合 JSR-303 注解(@NotBlank、@Size、
3. Express + swagger-jsdoc · 手写注释模式
3.1 安装
bash
npm i swagger-jsdoc swagger-ui-express3.2 启动入口
js
// app.js
const express = require('express');
const swaggerJsdoc = require('swagger-jsdoc');
const swaggerUi = require('swagger-ui-express');
const app = express();
const spec = swaggerJsdoc({
definition: {
openapi: '3.0.3',
info: { title: 'Express API', version: '1.0.0' },
servers: [{ url: 'http://localhost:3000' }]
},
apis: ['./routes/*.js'] // 从这些文件里提取 JSDoc 注释
});
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(spec));
app.listen(3000);3.3 路由文件用 JSDoc 注释
js
// routes/users.js
const router = require('express').Router();
/**
* @openapi
* /users:
* get:
* summary: 获取用户列表
* tags: [user]
* parameters:
* - in: query
* name: page
* schema: { type: integer, default: 1 }
* responses:
* 200:
* description: 用户数组
* content:
* application/json:
* schema:
* type: array
* items: { $ref: '#/components/schemas/User' }
*/
router.get('/', (req, res) => res.json(userService.list()));
/**
* @openapi
* components:
* schemas:
* User:
* type: object
* required: [id, name, email]
* properties:
* id: { type: integer }
* name: { type: string }
* email: { type: string, format: email }
*/
module.exports = router;Express 没有反射,所以只能手写注释。看着繁琐,但胜在零侵入业务代码。
4. NestJS · @nestjs/swagger(最优雅)
4.1 安装
bash
npm i @nestjs/swagger4.2 启动入口
ts
// main.ts
import { NestFactory } from '@nestjs/core';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const config = new DocumentBuilder()
.setTitle('NestJS API')
.setVersion('1.0')
.addBearerAuth()
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, document);
await app.listen(3000);
}
bootstrap();4.3 Controller + DTO
ts
// users.controller.ts
import { Controller, Get, Post, Body, Query, Param } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';
@ApiTags('user')
@Controller('users')
export class UsersController {
@Get()
@ApiOperation({ summary: '获取用户列表' })
@ApiResponse({ status: 200, type: [User] })
list(@Query('page') page = 1): User[] {
return [];
}
@Post()
@ApiBearerAuth()
@ApiOperation({ summary: '创建用户' })
create(@Body() dto: CreateUserDto): User {
return this.usersService.create(dto);
}
}
// dto/create-user.dto.ts
import { ApiProperty } from '@nestjs/swagger';
import { IsEmail, IsNotEmpty } from 'class-validator';
export class CreateUserDto {
@ApiProperty({ example: 'Alice' })
@IsNotEmpty()
name: string;
@ApiProperty({ example: 'alice@x.com' })
@IsEmail()
email: string;
}4.4 杀手锏:CLI 插件自动推断装饰器
nest-cli.json:
json
{
"compilerOptions": {
"plugins": [
{
"name": "@nestjs/swagger",
"options": { "introspectComments": true }
}
]
}
}效果:不写 @ApiProperty 也能从 TS 类型自动推断 + 从 JSDoc 注释抽 description。懒人福音。
5. FastAPI · 内置,零配置(最爽)
5.1 装好 FastAPI 就完事
bash
pip install fastapi uvicorn[standard]python
# main.py
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr, Field
app = FastAPI(title="FastAPI 示例", version="1.0.0")
class UserCreate(BaseModel):
name: str = Field(..., max_length=50, example="Alice")
email: EmailStr = Field(..., example="alice@x.com")
class User(BaseModel):
id: int
name: str
email: EmailStr
@app.get("/users", response_model=list[User], tags=["user"], summary="用户列表")
def list_users(page: int = 1):
return []
@app.post("/users", response_model=User, status_code=201, tags=["user"], summary="创建用户")
def create_user(user: UserCreate):
return User(id=1, **user.dict())启动:
bash
uvicorn main:app --reload打开:
- Swagger UI:
http://localhost:8000/docs - Redoc:
http://localhost:8000/redoc - yaml:
http://localhost:8000/openapi.json
零注解、零配置。FastAPI 用 Pydantic 类型提示 直接生成完整的 OpenAPI 文档——这是其他框架做不到的优雅。
6. 其他常见框架速查
6.1 Go (Gin) + swaggo/swag
go
// @title 用户 API
// @version 1.0
func main() { ... }
// @Summary 获取用户
// @Tags user
// @Param id path int true "用户 ID"
// @Success 200 {object} User
// @Router /users/{id} [get]
func getUser(c *gin.Context) { ... }bash
swag init # 生成 docs/ 目录
# 然后挂载 gin-swagger 中间件即可6.2 Django REST + drf-spectacular
python
# settings.py
INSTALLED_APPS = [..., 'drf_spectacular']
REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema'
}
SPECTACULAR_SETTINGS = { 'TITLE': 'My API', 'VERSION': '1.0.0' }
# urls.py
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
urlpatterns = [
path('schema/', SpectacularAPIView.as_view(), name='schema'),
path('docs/', SpectacularSwaggerView.as_view(url_name='schema')),
]6.3 .NET Core + Swashbuckle
csharp
// Program.cs
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
if (app.Environment.IsDevelopment()) {
app.UseSwagger();
app.UseSwaggerUI();
}6.4 Rust (actix) + utoipa
rust
#[utoipa::path(
get,
path = "/users/{id}",
responses((status = 200, body = User))
)]
async fn get_user(id: web::Path<i32>) -> impl Responder { ... }7. 对比一张表
| 框架 | 集成包 | 默认 UI 路径 | 工作方式 | 推荐度 |
|---|---|---|---|---|
| Spring Boot | springdoc-openapi-starter | /swagger-ui.html | 注解 | ⭐⭐⭐⭐⭐ |
| Express | swagger-jsdoc + swagger-ui-express | 自定义 | JSDoc 注释 | ⭐⭐⭐ |
| NestJS | @nestjs/swagger | /api | 装饰器(CLI 插件可省) | ⭐⭐⭐⭐⭐ |
| FastAPI | 内置 | /docs | Pydantic 类型 | ⭐⭐⭐⭐⭐ |
| Go (Gin) | swaggo/swag + gin-swagger | /swagger/index.html | 注释 | ⭐⭐⭐⭐ |
| Django REST | drf-spectacular | /docs/ | 类自动 | ⭐⭐⭐⭐ |
| ASP.NET Core | Swashbuckle | /swagger | 反射 | ⭐⭐⭐⭐ |
| Rust (actix) | utoipa | 自定义 | 宏 | ⭐⭐⭐ |
8. Code-first 的两个常见坑
8.1 注解 vs 真实代码不一致
java
@PostMapping("/users")
@ApiResponse(responseCode = "200") // ← 写错了!应该是 201
public ResponseEntity<User> create(...) {
return ResponseEntity.status(201).body(...);
}解决:CI 里跑接口测试(schemathesis / Newman),用真实响应去校 yaml。
8.2 注解写得满天飞,可读性差
java
@Operation(...) @ApiResponses(...) @Parameter(...)
@SecurityRequirement(...) @Tag(...) @RequestMapping(...)
public User get(...) { /* 业务才 1 行 */ }解决:
- 简单接口靠类型推断(NestJS CLI 插件、FastAPI Pydantic)
- 复杂接口注解抽到一个单独的 Doc 类 / interface
9. Hybrid:design-first + code-first 混合
很多大型团队的做法:
1. 用 Stoplight Studio 设计 openapi.yaml ← design-first
2. 用 openapi-generator 生成 server interface
3. 后端写 implements,业务在实现里
4. CI 检查:导出真实运行时 yaml ↔ 仓库里的 yaml diff,不一致就拒合这样既享受了 design-first 的"先评审后写代码",又有 code-first 的"代码即真相"。
10. 章末面试题速览
- springfox 和 springdoc-openapi 的区别? → springfox 已停止维护,且只支持 OpenAPI 2.0(Swagger 2)。新项目用 springdoc-openapi(支持 OAS 3 + Spring Boot 3)。
- NestJS 的 CLI 插件有什么用? → 不写
@ApiProperty也能从 TS 类型 / JSDoc 自动生成 schema,少写一半注解。 - FastAPI 为什么文档"自动"出来? → Python 类型提示 + Pydantic 模型 = 编译期就能拿到完整 schema,框架直接组装出 OpenAPI yaml。
🎬 实战代码
下方放了 4 个框架的最小可运行示例(同一份 User CRUD 接口),你可以照抄到自己项目。
→ 看 09_integration/code/ 目录
💻 示例代码
javascript
// ============================================================
// Express + swagger-jsdoc + swagger-ui-express 最小可运行示例
// 访问: http://localhost:3000/api-docs
// ============================================================
const express = require('express');
const swaggerJsdoc = require('swagger-jsdoc');
const swaggerUi = require('swagger-ui-express');
const app = express();
app.use(express.json());
const spec = swaggerJsdoc({
definition: {
openapi: '3.0.3',
info: {
title: 'Express 用户 API',
version: '1.0.0',
description: 'Express + swagger-jsdoc 示例',
},
servers: [{ url: 'http://localhost:3000', description: '本地' }],
components: {
securitySchemes: {
bearerAuth: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' },
},
schemas: {
User: {
type: 'object',
required: ['id', 'name', 'email'],
properties: {
id: { type: 'integer', example: 1 },
name: { type: 'string', example: 'Alice' },
email: { type: 'string', format: 'email', example: 'alice@x.com' },
},
},
UserCreate: {
type: 'object',
required: ['name', 'email'],
properties: {
name: { type: 'string', maxLength: 50 },
email: { type: 'string', format: 'email' },
},
},
Error: {
type: 'object',
properties: {
code: { type: 'integer' },
message: { type: 'string' },
},
},
},
},
security: [{ bearerAuth: [] }],
},
apis: ['./app.js', './routes/*.js'],
});
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(spec));
app.get('/openapi.json', (req, res) => res.json(spec));
const store = new Map();
let nextId = 1;
/**
* @openapi
* /users:
* get:
* tags: [user]
* summary: 用户列表
* security: []
* parameters:
* - in: query
* name: page
* schema: { type: integer, default: 1 }
* responses:
* 200:
* description: 成功
* content:
* application/json:
* schema:
* type: array
* items: { $ref: '#/components/schemas/User' }
*/
app.get('/users', (req, res) => {
res.json(Array.from(store.values()));
});
/**
* @openapi
* /users/{id}:
* get:
* tags: [user]
* summary: 查单条
* parameters:
* - in: path
* name: id
* required: true
* schema: { type: integer }
* responses:
* 200:
* description: 成功
* content:
* application/json:
* schema: { $ref: '#/components/schemas/User' }
* 404:
* description: 不存在
* content:
* application/json:
* schema: { $ref: '#/components/schemas/Error' }
*/
app.get('/users/:id', (req, res) => {
const u = store.get(Number(req.params.id));
if (!u) return res.status(404).json({ code: 404, message: 'not found' });
res.json(u);
});
/**
* @openapi
* /users:
* post:
* tags: [user]
* summary: 创建用户
* requestBody:
* required: true
* content:
* application/json:
* schema: { $ref: '#/components/schemas/UserCreate' }
* responses:
* 201:
* description: 创建成功
* content:
* application/json:
* schema: { $ref: '#/components/schemas/User' }
*/
app.post('/users', (req, res) => {
const id = nextId++;
const u = { id, ...req.body };
store.set(id, u);
res.status(201).json(u);
});
/**
* @openapi
* /users/{id}:
* delete:
* tags: [user]
* summary: 删除用户
* parameters:
* - in: path
* name: id
* required: true
* schema: { type: integer }
* responses:
* 204:
* description: 已删除
*/
app.delete('/users/:id', (req, res) => {
store.delete(Number(req.params.id));
res.status(204).send();
});
app.listen(3000, () => {
console.log('🌐 http://localhost:3000/api-docs');
console.log('📋 http://localhost:3000/openapi.json');
});json
{
"name": "swagger-express-demo",
"version": "1.0.0",
"description": "Express + swagger-jsdoc 示例",
"main": "app.js",
"scripts": {
"start": "node app.js",
"dev": "nodemon app.js"
},
"dependencies": {
"express": "^4.19.2",
"swagger-jsdoc": "^6.2.8",
"swagger-ui-express": "^5.0.0"
},
"devDependencies": {
"nodemon": "^3.1.0"
}
}python
# ============================================================
# FastAPI 最小可运行示例(零额外配置!)
# 启动: uvicorn main:app --reload
# 访问: http://localhost:8000/docs (Swagger UI)
# http://localhost:8000/redoc (Redoc)
# http://localhost:8000/openapi.json
# ============================================================
from typing import List, Optional
from fastapi import FastAPI, HTTPException, Path, Query, Depends
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from pydantic import BaseModel, EmailStr, Field
app = FastAPI(
title="FastAPI 用户 API",
version="1.0.0",
description="FastAPI 内置 Swagger UI 示例 —— 几乎零额外注解",
contact={"name": "张三", "email": "dev@example.com"},
servers=[
{"url": "http://localhost:8000", "description": "本地"},
{"url": "https://api.example.com", "description": "生产"},
],
)
bearer = HTTPBearer(auto_error=False)
class UserBase(BaseModel):
name: str = Field(..., max_length=50, examples=["Alice"])
email: EmailStr = Field(..., examples=["alice@x.com"])
class User(UserBase):
id: int = Field(..., examples=[1])
class UserCreate(UserBase):
pass
store: dict[int, User] = {}
next_id = 1
@app.get("/users", response_model=List[User], tags=["user"], summary="用户列表")
def list_users(
page: int = Query(1, ge=1, description="页码(从 1 开始)"),
size: int = Query(20, ge=1, le=100, description="每页大小"),
):
items = list(store.values())
return items[(page - 1) * size : page * size]
@app.get(
"/users/{user_id}",
response_model=User,
tags=["user"],
summary="查单条",
responses={404: {"description": "用户不存在"}},
)
def get_user(user_id: int = Path(..., description="用户 ID", ge=1)):
u = store.get(user_id)
if not u:
raise HTTPException(404, "用户不存在")
return u
@app.post(
"/users",
response_model=User,
status_code=201,
tags=["user"],
summary="创建用户",
dependencies=[Depends(bearer)],
)
def create_user(body: UserCreate):
global next_id
u = User(id=next_id, **body.dict())
store[next_id] = u
next_id += 1
return u
@app.delete(
"/users/{user_id}",
status_code=204,
tags=["user"],
summary="删除用户",
dependencies=[Depends(bearer)],
)
def delete_user(user_id: int):
store.pop(user_id, None)txt
fastapi==0.115.0
uvicorn[standard]==0.30.6
pydantic[email]==2.9.2typescript
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
@Module({
controllers: [UsersController],
})
export class AppModule {}typescript
// ============================================================
// NestJS + @nestjs/swagger 最小可运行示例
// 访问: http://localhost:3000/api
// ============================================================
import { NestFactory } from '@nestjs/core';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { ValidationPipe } from '@nestjs/common';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));
const config = new DocumentBuilder()
.setTitle('NestJS 用户 API')
.setDescription('@nestjs/swagger 完整示例')
.setVersion('1.0.0')
.addServer('http://localhost:3000', '本地')
.addBearerAuth()
.addTag('user', '用户管理')
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, document, {
swaggerOptions: { persistAuthorization: true },
});
await app.listen(3000);
console.log('🌐 http://localhost:3000/api');
}
bootstrap();typescript
import { Controller, Get, Post, Delete, Body, Param, Query, HttpCode, NotFoundException } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth, ApiProperty } from '@nestjs/swagger';
import { IsEmail, IsNotEmpty, IsOptional, MaxLength } from 'class-validator';
export class User {
@ApiProperty({ example: 1 })
id: number;
@ApiProperty({ example: 'Alice' })
name: string;
@ApiProperty({ example: 'alice@x.com', format: 'email' })
email: string;
}
export class CreateUserDto {
@ApiProperty({ example: 'Alice', maxLength: 50 })
@IsNotEmpty()
@MaxLength(50)
name: string;
@ApiProperty({ example: 'alice@x.com', format: 'email' })
@IsEmail()
email: string;
}
@ApiTags('user')
@Controller('users')
export class UsersController {
private store = new Map<number, User>();
private nextId = 1;
@Get()
@ApiOperation({ summary: '用户列表', operationId: 'listUsers' })
@ApiResponse({ status: 200, type: [User] })
list(@Query('page') page = 1, @Query('size') size = 20): User[] {
const all = Array.from(this.store.values());
return all.slice((page - 1) * size, page * size);
}
@Get(':id')
@ApiOperation({ summary: '查单条', operationId: 'getUser' })
@ApiResponse({ status: 200, type: User })
@ApiResponse({ status: 404, description: '不存在' })
get(@Param('id') id: number): User {
const u = this.store.get(Number(id));
if (!u) throw new NotFoundException();
return u;
}
@Post()
@ApiBearerAuth()
@ApiOperation({ summary: '创建用户', operationId: 'createUser' })
@ApiResponse({ status: 201, type: User })
create(@Body() dto: CreateUserDto): User {
const u: User = { id: this.nextId++, ...dto };
this.store.set(u.id, u);
return u;
}
@Delete(':id')
@ApiBearerAuth()
@ApiOperation({ summary: '删除用户', operationId: 'deleteUser' })
@ApiResponse({ status: 204, description: '已删除' })
@HttpCode(204)
delete(@Param('id') id: number): void {
this.store.delete(Number(id));
}
}markdown
# 09 章 · 后端集成示例代码
四个框架,**同一份 User CRUD 接口**,让你横向对比注解 vs 类型推断。code/ ├── spring-boot/ ← Spring Boot 3 + springdoc-openapi │ ├── pom.xml │ ├── UserController.java │ └── OpenApiConfig.java ├── express/ ← Express + swagger-jsdoc │ ├── package.json │ └── app.js ├── nestjs/ ← NestJS + @nestjs/swagger │ ├── main.ts │ ├── app.module.ts │ └── users.controller.ts └── fastapi/ ← FastAPI(内置!) ├── requirements.txt └── main.py
## 启动
| 框架 | 命令 | 文档地址 |
| ----------- | ---------------------------------- | -------------------------------- |
| Spring Boot | `mvn spring-boot:run` | http://localhost:8080/swagger-ui.html |
| Express | `npm i && npm start` | http://localhost:3000/api-docs |
| NestJS | `npm i && npx ts-node src/main.ts` | http://localhost:3000/api |
| FastAPI | `pip install -r requirements.txt && uvicorn main:app --reload` | http://localhost:8000/docs |
## 几个直观对比
### 创建用户接口的"代码量"
| 框架 | 大致行数(含 DTO + 注解) | 主要靠 |
| ----------- | ------------------------- | ------------------- |
| Spring Boot | ~50 行 | 注解 (@Operation 等) |
| Express | ~30 行(含 JSDoc) | JSDoc 手写注释 |
| NestJS | ~30 行 | 装饰器 + 类型 |
| FastAPI | ~10 行 | Pydantic 类型 |
> FastAPI 一个 `def create_user(body: UserCreate) -> User` 就能生成完整文档。这就是"动态语言 + 严格类型提示"的力量。java
package com.example.demo;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.oas.models.servers.Server;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
@Configuration
public class OpenApiConfig {
@Bean
public OpenAPI api() {
return new OpenAPI()
.info(new Info()
.title("电商示例 API")
.version("1.0.0")
.description("Spring Boot 3 + springdoc-openapi 完整示例")
.contact(new Contact().name("张三").email("dev@example.com"))
.license(new License().name("MIT")))
.servers(List.of(
new Server().url("http://localhost:8080").description("本地"),
new Server().url("https://api.example.com").description("生产")
))
.components(new Components()
.addSecuritySchemes("bearerAuth",
new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")))
.addSecurityItem(new SecurityRequirement().addList("bearerAuth"));
}
}xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.0</version>
</parent>
<groupId>com.example</groupId>
<artifactId>swagger-springdoc-demo</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- 关键依赖:自动暴露 /swagger-ui.html 和 /v3/api-docs -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.5.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>java
// ============================================================
// Spring Boot 3 + springdoc-openapi 最小可运行示例
// 访问: http://localhost:8080/swagger-ui.html
// ============================================================
package com.example.demo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;
@RestController
@RequestMapping("/users")
@Tag(name = "用户管理", description = "用户的 CRUD")
public class UserController {
private final Map<Long, User> store = new ConcurrentHashMap<>();
private final AtomicLong idGen = new AtomicLong(1);
@GetMapping
@Operation(summary = "用户列表", operationId = "listUsers")
public List<User> list(
@Parameter(description = "页码(从 1 开始)") @RequestParam(defaultValue = "1") int page,
@Parameter(description = "每页大小") @RequestParam(defaultValue = "20") int size) {
return store.values().stream()
.skip((long)(page - 1) * size)
.limit(size)
.toList();
}
@GetMapping("/{id}")
@Operation(summary = "查单条", operationId = "getUser")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "成功"),
@ApiResponse(responseCode = "404", description = "用户不存在")
})
public ResponseEntity<User> get(@PathVariable Long id) {
User u = store.get(id);
return u == null ? ResponseEntity.notFound().build() : ResponseEntity.ok(u);
}
@PostMapping
@Operation(summary = "创建用户", operationId = "createUser",
security = @SecurityRequirement(name = "bearerAuth"))
@ApiResponse(responseCode = "201", description = "创建成功")
public ResponseEntity<User> create(@Valid @RequestBody UserCreate body) {
long id = idGen.getAndIncrement();
User u = new User(id, body.name, body.email);
store.put(id, u);
return ResponseEntity.status(HttpStatus.CREATED).body(u);
}
@DeleteMapping("/{id}")
@Operation(summary = "删除用户", operationId = "deleteUser",
security = @SecurityRequirement(name = "bearerAuth"))
@ApiResponse(responseCode = "204", description = "已删除")
public ResponseEntity<Void> delete(@PathVariable Long id) {
return store.remove(id) != null
? ResponseEntity.noContent().build()
: ResponseEntity.notFound().build();
}
// ---------------- DTO ----------------
@Schema(description = "用户")
public record User(
@Schema(description = "ID", example = "1") Long id,
@Schema(description = "姓名", example = "Alice") String name,
@Schema(description = "邮箱", example = "alice@x.com") String email
) {}
@Schema(description = "创建用户请求体")
public static class UserCreate {
@Schema(description = "姓名", example = "Alice", required = true)
@NotBlank @Size(max = 50)
public String name;
@Schema(description = "邮箱", example = "alice@x.com", required = true)
@Email @NotBlank
public String email;
}
}express/app.js ↗ · express/package.json ↗ · fastapi/main.py ↗ · fastapi/requirements.txt ↗ · nestjs/app.module.ts ↗ · nestjs/main.ts ↗ · nestjs/users.controller.ts ↗ · README.md ↗ · spring-boot/OpenApiConfig.java ↗ · spring-boot/pom.xml ↗ · spring-boot/UserController.java ↗