openapi: 3.1.0 info: title: Mach-CMS API description: | Mach-CMS 后端 API 规范。前后端契约文件,任何接口变更必须同步修改此文件。 - 所有业务接口返回统一包装体 `Response` - 列表查询返回 `PageResult` - 认证方式: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 有效期 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'