You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
820 lines
19 KiB
820 lines
19 KiB
openapi: 3.1.0
|
|
info:
|
|
title: Mach-CMS API
|
|
description: |
|
|
Mach-CMS 后端 API 规范。前后端契约文件,任何接口变更必须同步修改此文件。
|
|
|
|
- 所有业务接口返回统一包装体 `Response<T>`
|
|
- 列表查询返回 `PageResult<T>`
|
|
- 认证方式:JWT Bearer Token
|
|
- 时间格式:ISO-8601(`2024-01-15T08:30:00Z`)
|
|
version: 0.1.0
|
|
contact:
|
|
name: Mach-CMS Team
|
|
|
|
servers:
|
|
- url: http://localhost:8080/api
|
|
description: 本地开发环境
|
|
|
|
security:
|
|
- bearerAuth: []
|
|
|
|
tags:
|
|
- name: Auth
|
|
description: 认证与授权
|
|
- name: Public
|
|
description: 公开接口(无需认证)
|
|
- name: Article
|
|
description: 文章(前台只读)
|
|
- name: ArticleAdmin
|
|
description: 文章管理(需 ADMIN/EDITOR)
|
|
- name: Tag
|
|
description: 标签(前台只读)
|
|
- name: TagAdmin
|
|
description: 标签管理(需 ADMIN)
|
|
- name: Media
|
|
description: 媒体文件上传
|
|
- name: Search
|
|
description: 全文搜索
|
|
- name: User
|
|
description: 用户管理(需 ADMIN)
|
|
|
|
paths:
|
|
# ==================== Public ====================
|
|
/public/health:
|
|
get:
|
|
tags: [Public]
|
|
summary: 健康检查
|
|
security: []
|
|
responses:
|
|
'200':
|
|
description: 服务正常
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ResponseString'
|
|
example:
|
|
code: 0
|
|
message: success
|
|
data: UP
|
|
|
|
# ==================== Auth ====================
|
|
/auth/register:
|
|
post:
|
|
tags: [Auth]
|
|
summary: 用户注册
|
|
security: []
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/RegisterRequest'
|
|
responses:
|
|
'200':
|
|
description: 注册成功
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ResponseTokenPair'
|
|
|
|
/auth/login:
|
|
post:
|
|
tags: [Auth]
|
|
summary: 用户登录
|
|
security: []
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/LoginRequest'
|
|
responses:
|
|
'200':
|
|
description: 登录成功,返回双 Token
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ResponseTokenPair'
|
|
|
|
/auth/refresh:
|
|
post:
|
|
tags: [Auth]
|
|
summary: 刷新 Access Token
|
|
security: []
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/RefreshTokenRequest'
|
|
responses:
|
|
'200':
|
|
description: 刷新成功
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ResponseTokenPair'
|
|
|
|
/auth/logout:
|
|
post:
|
|
tags: [Auth]
|
|
summary: 退出登录(吊销 Refresh Token)
|
|
responses:
|
|
'200':
|
|
description: 退出成功
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ResponseVoid'
|
|
|
|
# ==================== Article (Public) ====================
|
|
/articles:
|
|
get:
|
|
tags: [Article]
|
|
summary: 文章列表(分页)
|
|
security: []
|
|
parameters:
|
|
- name: page
|
|
in: query
|
|
schema:
|
|
type: integer
|
|
default: 1
|
|
- name: size
|
|
in: query
|
|
schema:
|
|
type: integer
|
|
default: 10
|
|
maximum: 100
|
|
- name: status
|
|
in: query
|
|
schema:
|
|
$ref: '#/components/schemas/ArticleStatus'
|
|
default: PUBLISHED
|
|
- name: tag
|
|
in: query
|
|
description: 标签 slug 过滤
|
|
schema:
|
|
type: string
|
|
responses:
|
|
'200':
|
|
description: 文章列表
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ResponsePageArticleListItem'
|
|
|
|
/articles/{slug}:
|
|
get:
|
|
tags: [Article]
|
|
summary: 文章详情(按 slug)
|
|
security: []
|
|
parameters:
|
|
- name: slug
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
responses:
|
|
'200':
|
|
description: 文章详情
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ResponseArticleDetail'
|
|
'404':
|
|
description: 文章不存在
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ResponseVoid'
|
|
|
|
/articles/search:
|
|
get:
|
|
tags: [Search]
|
|
summary: 全文搜索文章
|
|
security: []
|
|
parameters:
|
|
- name: q
|
|
in: query
|
|
required: true
|
|
description: 搜索关键词
|
|
schema:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 100
|
|
- name: page
|
|
in: query
|
|
schema:
|
|
type: integer
|
|
default: 1
|
|
- name: size
|
|
in: query
|
|
schema:
|
|
type: integer
|
|
default: 10
|
|
maximum: 50
|
|
responses:
|
|
'200':
|
|
description: 搜索结果
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ResponsePageArticleListItem'
|
|
|
|
# ==================== ArticleAdmin ====================
|
|
/admin/articles:
|
|
post:
|
|
tags: [ArticleAdmin]
|
|
summary: 创建文章
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ArticleCreateRequest'
|
|
responses:
|
|
'200':
|
|
description: 创建成功
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ResponseArticleDetail'
|
|
|
|
/admin/articles/{id}:
|
|
put:
|
|
tags: [ArticleAdmin]
|
|
summary: 更新文章
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: integer
|
|
format: int64
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ArticleUpdateRequest'
|
|
responses:
|
|
'200':
|
|
description: 更新成功
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ResponseArticleDetail'
|
|
|
|
delete:
|
|
tags: [ArticleAdmin]
|
|
summary: 删除文章
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: integer
|
|
format: int64
|
|
responses:
|
|
'200':
|
|
description: 删除成功
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ResponseVoid'
|
|
|
|
/admin/articles/{id}/publish:
|
|
patch:
|
|
tags: [ArticleAdmin]
|
|
summary: 发布文章(状态机:DRAFT → PUBLISHED)
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: integer
|
|
format: int64
|
|
responses:
|
|
'200':
|
|
description: 发布成功
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ResponseArticleDetail'
|
|
|
|
/admin/articles/{id}/archive:
|
|
patch:
|
|
tags: [ArticleAdmin]
|
|
summary: 归档文章
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: integer
|
|
format: int64
|
|
responses:
|
|
'200':
|
|
description: 归档成功
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ResponseArticleDetail'
|
|
|
|
# ==================== Tag (Public) ====================
|
|
/tags:
|
|
get:
|
|
tags: [Tag]
|
|
summary: 标签列表(带文章计数)
|
|
security: []
|
|
responses:
|
|
'200':
|
|
description: 标签列表
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ResponseListTagWithCount'
|
|
|
|
# ==================== TagAdmin ====================
|
|
/admin/tags:
|
|
post:
|
|
tags: [TagAdmin]
|
|
summary: 创建标签
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/TagCreateRequest'
|
|
responses:
|
|
'200':
|
|
description: 创建成功
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ResponseTag'
|
|
|
|
/admin/tags/{id}:
|
|
put:
|
|
tags: [TagAdmin]
|
|
summary: 更新标签
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: integer
|
|
format: int64
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/TagUpdateRequest'
|
|
responses:
|
|
'200':
|
|
description: 更新成功
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ResponseTag'
|
|
|
|
delete:
|
|
tags: [TagAdmin]
|
|
summary: 删除标签
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: integer
|
|
format: int64
|
|
responses:
|
|
'200':
|
|
description: 删除成功
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ResponseVoid'
|
|
|
|
# ==================== Media ====================
|
|
/media/upload:
|
|
post:
|
|
tags: [Media]
|
|
summary: 上传文件
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
multipart/form-data:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
file:
|
|
type: string
|
|
format: binary
|
|
description: 文件内容
|
|
directory:
|
|
type: string
|
|
default: images
|
|
description: 存储目录
|
|
responses:
|
|
'200':
|
|
description: 上传成功
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ResponseStoredFile'
|
|
|
|
# ==================== User ====================
|
|
/admin/users:
|
|
get:
|
|
tags: [User]
|
|
summary: 用户列表(分页)
|
|
parameters:
|
|
- name: page
|
|
in: query
|
|
schema:
|
|
type: integer
|
|
default: 1
|
|
- name: size
|
|
in: query
|
|
schema:
|
|
type: integer
|
|
default: 20
|
|
responses:
|
|
'200':
|
|
description: 用户列表
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ResponsePageUserInfo'
|
|
|
|
components:
|
|
securitySchemes:
|
|
bearerAuth:
|
|
type: http
|
|
scheme: bearer
|
|
bearerFormat: JWT
|
|
description: |
|
|
在请求头中携带 `Authorization: Bearer <access_token>`。
|
|
Access Token 有效期 15 分钟,过期后用 Refresh Token 调用 `/auth/refresh` 换取新的 Access Token。
|
|
|
|
schemas:
|
|
# ---------- 通用包装体 ----------
|
|
Response:
|
|
type: object
|
|
properties:
|
|
code:
|
|
type: integer
|
|
description: 0 表示成功,非 0 为业务错误码
|
|
message:
|
|
type: string
|
|
data:
|
|
description: 业务数据,类型由具体接口决定
|
|
required: [code, message]
|
|
|
|
PageResult:
|
|
type: object
|
|
properties:
|
|
list:
|
|
type: array
|
|
items: {}
|
|
total:
|
|
type: integer
|
|
format: int64
|
|
page:
|
|
type: integer
|
|
size:
|
|
type: integer
|
|
totalPages:
|
|
type: integer
|
|
required: [list, total, page, size, totalPages]
|
|
|
|
# ---------- 枚举 ----------
|
|
ArticleStatus:
|
|
type: string
|
|
enum: [DRAFT, PUBLISHED, ARCHIVED]
|
|
description: |
|
|
- DRAFT: 草稿,仅后台可见
|
|
- PUBLISHED: 已发布,前台可见
|
|
- ARCHIVED: 已归档,前台不可见但保留数据
|
|
|
|
UserRole:
|
|
type: string
|
|
enum: [ADMIN, EDITOR, VISITOR]
|
|
|
|
# ---------- Auth ----------
|
|
RegisterRequest:
|
|
type: object
|
|
properties:
|
|
username:
|
|
type: string
|
|
minLength: 3
|
|
maxLength: 50
|
|
password:
|
|
type: string
|
|
minLength: 6
|
|
maxLength: 100
|
|
email:
|
|
type: string
|
|
format: email
|
|
required: [username, password]
|
|
|
|
LoginRequest:
|
|
type: object
|
|
properties:
|
|
username:
|
|
type: string
|
|
password:
|
|
type: string
|
|
required: [username, password]
|
|
|
|
RefreshTokenRequest:
|
|
type: object
|
|
properties:
|
|
refreshToken:
|
|
type: string
|
|
required: [refreshToken]
|
|
|
|
TokenPair:
|
|
type: object
|
|
properties:
|
|
accessToken:
|
|
type: string
|
|
refreshToken:
|
|
type: string
|
|
expiresIn:
|
|
type: integer
|
|
description: Access Token 有效期(秒)
|
|
required: [accessToken, refreshToken, expiresIn]
|
|
|
|
# ---------- Article ----------
|
|
ArticleCreateRequest:
|
|
type: object
|
|
properties:
|
|
title:
|
|
type: string
|
|
maxLength: 200
|
|
summary:
|
|
type: string
|
|
maxLength: 500
|
|
content:
|
|
type: string
|
|
coverImage:
|
|
type: string
|
|
description: 封面图 URL
|
|
tagIds:
|
|
type: array
|
|
items:
|
|
type: integer
|
|
format: int64
|
|
required: [title, content]
|
|
|
|
ArticleUpdateRequest:
|
|
type: object
|
|
properties:
|
|
title:
|
|
type: string
|
|
maxLength: 200
|
|
summary:
|
|
type: string
|
|
maxLength: 500
|
|
content:
|
|
type: string
|
|
coverImage:
|
|
type: string
|
|
tagIds:
|
|
type: array
|
|
items:
|
|
type: integer
|
|
format: int64
|
|
required: [title, content]
|
|
|
|
ArticleListItem:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: integer
|
|
format: int64
|
|
title:
|
|
type: string
|
|
slug:
|
|
type: string
|
|
summary:
|
|
type: string
|
|
coverImage:
|
|
type: string
|
|
status:
|
|
$ref: '#/components/schemas/ArticleStatus'
|
|
publishedAt:
|
|
type: string
|
|
format: date-time
|
|
createdAt:
|
|
type: string
|
|
format: date-time
|
|
authorName:
|
|
type: string
|
|
tagNames:
|
|
type: array
|
|
items:
|
|
type: string
|
|
required: [id, title, slug, status, createdAt, authorName, tagNames]
|
|
|
|
ArticleDetail:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: integer
|
|
format: int64
|
|
title:
|
|
type: string
|
|
slug:
|
|
type: string
|
|
summary:
|
|
type: string
|
|
content:
|
|
type: string
|
|
coverImage:
|
|
type: string
|
|
status:
|
|
$ref: '#/components/schemas/ArticleStatus'
|
|
viewCount:
|
|
type: integer
|
|
format: int64
|
|
publishedAt:
|
|
type: string
|
|
format: date-time
|
|
createdAt:
|
|
type: string
|
|
format: date-time
|
|
updatedAt:
|
|
type: string
|
|
format: date-time
|
|
authorName:
|
|
type: string
|
|
tagNames:
|
|
type: array
|
|
items:
|
|
type: string
|
|
required: [id, title, slug, content, status, viewCount, createdAt, updatedAt, authorName, tagNames]
|
|
|
|
# ---------- Tag ----------
|
|
Tag:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: integer
|
|
format: int64
|
|
name:
|
|
type: string
|
|
slug:
|
|
type: string
|
|
description:
|
|
type: string
|
|
articleCount:
|
|
type: integer
|
|
description: 关联文章数量(查询时计算)
|
|
createdAt:
|
|
type: string
|
|
format: date-time
|
|
required: [id, name, slug, articleCount, createdAt]
|
|
|
|
TagCreateRequest:
|
|
type: object
|
|
properties:
|
|
name:
|
|
type: string
|
|
maxLength: 50
|
|
description:
|
|
type: string
|
|
maxLength: 200
|
|
required: [name]
|
|
|
|
TagUpdateRequest:
|
|
type: object
|
|
properties:
|
|
name:
|
|
type: string
|
|
maxLength: 50
|
|
description:
|
|
type: string
|
|
maxLength: 200
|
|
required: [name]
|
|
|
|
# ---------- Media ----------
|
|
StoredFile:
|
|
type: object
|
|
properties:
|
|
originalName:
|
|
type: string
|
|
storedPath:
|
|
type: string
|
|
url:
|
|
type: string
|
|
size:
|
|
type: integer
|
|
format: int64
|
|
contentType:
|
|
type: string
|
|
required: [originalName, storedPath, url, size]
|
|
|
|
# ---------- User ----------
|
|
UserInfo:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: integer
|
|
format: int64
|
|
username:
|
|
type: string
|
|
email:
|
|
type: string
|
|
avatar:
|
|
type: string
|
|
role:
|
|
$ref: '#/components/schemas/UserRole'
|
|
enabled:
|
|
type: boolean
|
|
createdAt:
|
|
type: string
|
|
format: date-time
|
|
required: [id, username, role, enabled, createdAt]
|
|
|
|
# ---------- 包装体实例化 ----------
|
|
ResponseString:
|
|
allOf:
|
|
- $ref: '#/components/schemas/Response'
|
|
properties:
|
|
data:
|
|
type: string
|
|
|
|
ResponseVoid:
|
|
allOf:
|
|
- $ref: '#/components/schemas/Response'
|
|
properties:
|
|
data:
|
|
type: "null"
|
|
|
|
ResponseTokenPair:
|
|
allOf:
|
|
- $ref: '#/components/schemas/Response'
|
|
properties:
|
|
data:
|
|
$ref: '#/components/schemas/TokenPair'
|
|
|
|
ResponseArticleDetail:
|
|
allOf:
|
|
- $ref: '#/components/schemas/Response'
|
|
properties:
|
|
data:
|
|
$ref: '#/components/schemas/ArticleDetail'
|
|
|
|
ResponsePageArticleListItem:
|
|
allOf:
|
|
- $ref: '#/components/schemas/Response'
|
|
properties:
|
|
data:
|
|
allOf:
|
|
- $ref: '#/components/schemas/PageResult'
|
|
properties:
|
|
list:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/ArticleListItem'
|
|
|
|
ResponseTag:
|
|
allOf:
|
|
- $ref: '#/components/schemas/Response'
|
|
properties:
|
|
data:
|
|
$ref: '#/components/schemas/Tag'
|
|
|
|
ResponseListTagWithCount:
|
|
allOf:
|
|
- $ref: '#/components/schemas/Response'
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/Tag'
|
|
|
|
ResponseStoredFile:
|
|
allOf:
|
|
- $ref: '#/components/schemas/Response'
|
|
properties:
|
|
data:
|
|
$ref: '#/components/schemas/StoredFile'
|
|
|
|
ResponsePageUserInfo:
|
|
allOf:
|
|
- $ref: '#/components/schemas/Response'
|
|
properties:
|
|
data:
|
|
allOf:
|
|
- $ref: '#/components/schemas/PageResult'
|
|
properties:
|
|
list:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/UserInfo'
|