优化api文档

This commit is contained in:
yml2213
2026-07-30 15:44:08 +08:00
parent beb171b751
commit 4165fc4d01
4 changed files with 672 additions and 182 deletions
+164
View File
@@ -0,0 +1,164 @@
import type { CSSProperties } from 'react'
import { Alert, Card, Descriptions, Space, Table, Tag, Typography } from 'antd'
import type { ColumnsType } from 'antd/es/table'
import type { EndpointSpec, ParamSpec } from './types'
const { Text, Paragraph } = Typography
const methodColor: Record<string, string> = {
GET: 'blue',
POST: 'green',
PUT: 'orange',
PATCH: 'gold',
DELETE: 'red',
}
const preStyle: CSSProperties = {
margin: 0,
padding: 12,
background: '#f6f8fa',
borderRadius: 6,
fontSize: 12.5,
lineHeight: 1.6,
overflow: 'auto',
whiteSpace: 'pre-wrap',
wordBreak: 'break-all',
}
const paramColumns: ColumnsType<ParamSpec> = [
{
title: '字段',
dataIndex: 'name',
width: 200,
render: (v: string) => <Text code style={{ whiteSpace: 'nowrap' }}>{v}</Text>,
},
{
title: '必填',
dataIndex: 'required',
width: 70,
render: (v?: boolean) =>
v ? <Tag color="red">是</Tag> : <Tag>否</Tag>,
},
{
title: '类型',
dataIndex: 'type',
width: 180,
render: (v: string) => <Text type="secondary" style={{ fontSize: 12 }}>{v}</Text>,
},
{ title: '说明', dataIndex: 'desc' },
{
title: '示例',
dataIndex: 'example',
width: 160,
render: (v?: string) =>
v ? <Text code style={{ fontSize: 12 }}>{v}</Text> : null,
},
]
function ParamTable({ title, params }: { title: string; params: ParamSpec[] }) {
return (
<Card size="small" title={title} style={{ width: '100%' }}>
<Table
size="small"
pagination={false}
rowKey="name"
dataSource={params}
columns={paramColumns}
/>
</Card>
)
}
function CodeBlock({ title, code }: { title: string; code: string }) {
return (
<Card size="small" title={title} style={{ width: '100%' }}>
<pre style={preStyle}>{code}</pre>
</Card>
)
}
/** 通用接口渲染:方法+路径+权限 → 参数表 → 请求/响应示例 → 字段说明 → 注意事项。 */
export default function EndpointDoc({ spec }: { spec: EndpointSpec }) {
return (
<Space direction="vertical" size="middle" style={{ width: '100%' }}>
<Card size="small">
<Space wrap size="middle" align="center">
<Tag color={methodColor[spec.method]} style={{ margin: 0, fontSize: 13, fontWeight: 700 }}>
{spec.method}
</Tag>
<Text code copyable style={{ fontSize: 14 }}>
{spec.path}
</Text>
<Tag color="purple">{spec.scope}</Tag>
</Space>
<Paragraph type="secondary" style={{ margin: '8px 0 0' }}>
{spec.summary}
</Paragraph>
</Card>
{spec.pathParams?.length ? <ParamTable title="路径参数" params={spec.pathParams} /> : null}
{spec.queryParams?.length ? <ParamTable title="查询参数" params={spec.queryParams} /> : null}
{spec.bodyParams?.length ? <ParamTable title="请求体参数" params={spec.bodyParams} /> : null}
{spec.requestExample ? <CodeBlock title="请求示例" code={spec.requestExample} /> : null}
<CodeBlock title="响应示例" code={spec.responseExample} />
{spec.responseFields?.length ? (
<ParamTable title="响应字段说明" params={spec.responseFields} />
) : null}
{spec.notes?.length ? (
<Alert
type="warning"
showIcon
message="注意事项"
description={
<ul style={{ margin: 0, paddingLeft: 20 }}>
{spec.notes.map((n, i) => (
<li key={i}>{n}</li>
))}
</ul>
}
/>
) : null}
</Space>
)
}
/** 通用响应说明卡片,供页面顶部展示统一响应格式与错误码。 */
export function CommonResponseDoc({
fields,
errors,
}: {
fields: ParamSpec[]
errors: ParamSpec[]
}) {
return (
<Space direction="vertical" size="middle" style={{ width: '100%' }}>
<Card size="small" title="统一响应格式">
<Descriptions size="small" column={1} bordered>
<Descriptions.Item label="结构">
<Text code>{`{ "code": 0, "message": "ok", "data": {} }`}</Text>
</Descriptions.Item>
{fields.map((f) => (
<Descriptions.Item key={f.name} label={<Text code>{f.name}</Text>}>
{f.desc}
</Descriptions.Item>
))}
</Descriptions>
</Card>
<Card size="small" title="错误码">
<Table
size="small"
pagination={false}
rowKey="name"
dataSource={errors}
columns={[
{ title: 'HTTP/code', dataIndex: 'name', width: 120, render: (v) => <Text code>{v}</Text> },
{ title: '含义', dataIndex: 'desc' },
]}
/>
</Card>
</Space>
)
}
+252
View File
@@ -0,0 +1,252 @@
import type { EndpointSpec, ParamSpec } from './types'
// 通用响应体字段说明,所有接口共用。
export const commonResponseFields: ParamSpec[] = [
{ name: 'code', type: 'int', required: true, desc: '0 表示成功,非 0 表示失败' },
{ name: 'message', type: 'string', required: true, desc: '成功为 "ok",失败为错误描述' },
{ name: 'data', type: 'object', desc: '业务数据,失败时省略' },
]
// 通用错误码。
export const errorCodes: ParamSpec[] = [
{ name: '400', type: 'int', desc: '参数错误 / 业务校验失败(余额不足、库存不足、状态非法等)' },
{ name: '401', type: 'int', desc: '鉴权失败(Key 缺失、签名错误、时间戳过期、Nonce 重复)' },
{ name: '403', type: 'int', desc: '权限不足(缺少所需 scope 或商户功能未开通)' },
{ name: '404', type: 'int', desc: '资源不存在(订单号不存在等)' },
{ name: '500', type: 'int', desc: '服务端内部错误' },
]
// 订单状态与可履约说明(payment_status / fulfillment_status)。
export const orderStatusTable: ParamSpec[] = [
{ name: 'pending', type: 'fulfillment_status', desc: '待履约,可被履约器接单', example: 'can_fulfill=true' },
{ name: 'processing', type: 'fulfillment_status', desc: '履约中', example: 'can_fulfill=false' },
{ name: 'succeeded', type: 'fulfillment_status', desc: '履约成功', example: 'can_fulfill=false' },
{ name: 'failed', type: 'fulfillment_status', desc: '履约失败,可重试', example: 'can_fulfill=true' },
{ name: 'cancelled', type: 'fulfillment_status', desc: '已取消', example: 'can_fulfill=false' },
]
export const paymentStatusTable: ParamSpec[] = [
{ name: 'pending', type: 'payment_status', desc: '待支付' },
{ name: 'paid', type: 'payment_status', desc: '已支付(下单成功即为 paid)' },
{ name: 'refunded', type: 'payment_status', desc: '已退款(取消订单后)' },
{ name: 'cancelled', type: 'payment_status', desc: '已取消' },
]
// 订单对象公共字段说明,下单/查询/取消/回传响应共用。
const orderFields: ParamSpec[] = [
{ name: 'order_no', type: 'string', required: true, desc: '平台订单号' },
{ name: 'client_order_no', type: 'string', required: true, desc: '调用方传入的幂等单号' },
{ name: 'payment_status', type: 'enum', desc: '支付状态,见「状态说明」' },
{ name: 'fulfillment_status', type: 'enum', desc: '履约状态,见「状态说明」' },
{ name: 'can_fulfill', type: 'bool', desc: '是否允许履约器接单/发货' },
{ name: 'cannot_fulfill_reason', type: 'string', desc: 'can_fulfill=false 时的原因' },
{ name: 'product.sku', type: 'string', desc: '商品标识,履约以此为准' },
{ name: 'product.name', type: 'string', desc: '商品展示名' },
{ name: 'quantity', type: 'int', desc: '数量' },
{ name: 'base_amount', type: 'int', desc: '商品基础金额(最小货币单位)' },
{ name: 'fee_type', type: 'enum(rate|fixed)', desc: '手续费类型' },
{ name: 'service_fee_amount', type: 'int', desc: '手续费金额' },
{ name: 'amount', type: 'int', desc: '实付总额 = base_amount + service_fee_amount' },
{ name: 'currency', type: 'string', desc: '货币,默认 POINT' },
{ name: 'buyer_reference', type: 'string', desc: '买家标识/备注' },
{ name: 'data', type: 'object', desc: '下单时透传的请求数据(原样回显)' },
{ name: 'result', type: 'object', desc: '履约器回传的结果数据(原样回显)' },
{ name: 'provider_order_no', type: 'string', desc: '履约器侧单号' },
{ name: 'failure_reason', type: 'string', desc: '最近一次失败原因' },
{ name: 'created_at', type: 'datetime', desc: '创建时间' },
{ name: 'delivered_at', type: 'datetime', desc: '履约成功时间' },
{ name: 'cancelled_at', type: 'datetime', desc: '取消时间' },
]
const orderResponseExample = `{
"code": 0,
"message": "ok",
"data": {
"order_no": "FO20260730000123",
"client_order_no": "shop-10001",
"payment_status": "paid",
"fulfillment_status": "pending",
"can_fulfill": true,
"cannot_fulfill_reason": "",
"product": { "sku": "suit_pink_sheep", "name": "套装-糯粉咩咩" },
"quantity": 1,
"base_amount": 100,
"fee_type": "rate",
"service_fee_amount": 5,
"amount": 105,
"currency": "POINT",
"buyer_reference": "buyer-001",
"data": { "server": "ios-wechat", "uid": "player-id" },
"provider_order_no": "",
"failure_reason": "",
"created_at": "2026-07-30T10:00:00+08:00",
"delivered_at": null,
"cancelled_at": null
}
}`
export const endpoints: EndpointSpec[] = [
{
key: 'list-products',
method: 'GET',
path: '/api/client/v1/products',
summary: '查询商户已授权且上架的商品列表',
scope: 'products:read',
queryParams: [
{ name: 'page', type: 'int', desc: '页码,默认 1', example: '1' },
{ name: 'size', type: 'int', desc: '每页条数,默认 20', example: '20' },
],
responseExample: `{
"code": 0,
"message": "ok",
"data": {
"list": [
{
"id": 12,
"sku": "suit_pink_sheep",
"display_name": "套装-糯粉咩咩",
"price_amount": 100,
"cost_amount": 80,
"currency": "POINT",
"stock": -1,
"status": "active",
"product": { "code": "suit_pink_sheep", "name": "套装-糯粉咩咩", "category": "和平精英" }
}
],
"total": 1,
"page": 1,
"size": 20
}
}`,
responseFields: [
{ name: 'list[].sku', type: 'string', desc: '商品标识,下单时作为 sku 传入' },
{ name: 'list[].display_name', type: 'string', desc: '商户自定义展示名' },
{ name: 'list[].price_amount', type: 'int', desc: '单价(最小货币单位)' },
{ name: 'list[].stock', type: 'int', desc: '库存,-1 表示不限' },
{ name: 'list[].status', type: 'enum', desc: '商品状态,active 可售' },
{ name: 'list[].product', type: 'object', desc: '平台商品目录快照' },
{ name: 'total', type: 'int', desc: '总条数' },
{ name: 'page', type: 'int', desc: '当前页码' },
{ name: 'size', type: 'int', desc: '每页条数' },
],
},
{
key: 'create-order',
method: 'POST',
path: '/api/client/v1/orders',
summary: '幂等创建订单并扣款(成功返回 201,重复请求返回 200 且 idempotent=true)',
scope: 'orders:write',
bodyParams: [
{ name: 'client_order_no', type: 'string', required: true, desc: '调用方幂等单号,需与 Idempotency-Key 一致', example: 'shop-10001' },
{ name: 'sku', type: 'string', required: true, desc: '商品标识,取自商品列表', example: 'suit_pink_sheep' },
{ name: 'quantity', type: 'int', desc: '数量,默认 1', example: '1' },
{ name: 'buyer_reference', type: 'string', desc: '买家标识/备注', example: 'buyer-001' },
{ name: 'data', type: 'object', desc: '透传给履约器的业务数据(区服、账号等),原样存储并回显' },
],
requestExample: `{
"client_order_no": "shop-10001",
"sku": "suit_pink_sheep",
"quantity": 1,
"buyer_reference": "buyer-001",
"data": { "server": "ios-wechat", "uid": "player-id" }
}`,
responseExample: `{
"code": 0,
"message": "ok",
"data": {
"idempotent": false,
"order": {
"order_no": "FO20260730000123",
"client_order_no": "shop-10001",
"payment_status": "paid",
"fulfillment_status": "pending",
"can_fulfill": true
}
}
}`,
responseFields: [
{ name: 'idempotent', type: 'bool', desc: 'true 表示该 client_order_no 已存在,返回既有订单' },
{ name: 'order', type: 'object', desc: '订单对象,字段见「订单字段说明」' },
],
notes: [
'Header 需带 Idempotency-Key,且必须与 client_order_no 一致。',
'下单即扣款(payment_status=paid),余额不足返回 400。',
'同一 client_order_no 重复请求不会重复扣款,返回 idempotent=true。',
],
},
{
key: 'query-order',
method: 'GET',
path: '/api/client/v1/orders/{order_no}',
summary: '查询订单状态与详情',
scope: 'orders:read',
pathParams: [{ name: 'order_no', type: 'string', required: true, desc: '平台订单号', example: 'FO20260730000123' }],
responseExample: orderResponseExample,
responseFields: orderFields,
},
{
key: 'cancel-order',
method: 'POST',
path: '/api/client/v1/orders/{order_no}/cancel',
summary: '取消未履约订单并退款(仅 pending/failed 可取消)',
scope: 'orders:write',
pathParams: [{ name: 'order_no', type: 'string', required: true, desc: '平台订单号', example: 'FO20260730000123' }],
bodyParams: [{ name: 'reason', type: 'string', desc: '取消原因(可选)', example: '买家主动取消' }],
requestExample: `{ "reason": "买家主动取消" }`,
responseExample: orderResponseExample,
responseFields: orderFields,
notes: [
'取消成功后 payment_status 变为 refunded,金额退回商户钱包。',
'已进入 processing/succeeded 的订单不可取消,返回 400。',
],
},
{
key: 'ship-notify',
method: 'POST',
path: '/api/client/v1/orders/{order_no}/ship-notify',
summary: '履约器回传履约结果(processing/success/failed)',
scope: 'fulfillment:write',
pathParams: [{ name: 'order_no', type: 'string', required: true, desc: '平台订单号', example: 'FO20260730000123' }],
bodyParams: [
{ name: 'ship_status', type: 'enum(processing|success|failed)', required: true, desc: '履约结果', example: 'success' },
{ name: 'provider_order_no', type: 'string', desc: '履约器侧单号', example: 'SRC20260720001' },
{ name: 'fail_reason', type: 'string', desc: '失败原因(failed 时建议填)' },
{ name: 'result', type: 'object', desc: '履约结果数据,原样存储并回显' },
],
requestExample: `{
"ship_status": "success",
"provider_order_no": "SRC20260720001",
"result": { "delivered_at": "2026-07-30T10:05:00+08:00" }
}`,
responseExample: orderResponseExample,
responseFields: orderFields,
notes: [
'processing → fulfillment_status=processing;success → succeeded;failed → failed(可重试)。',
'已 succeeded 的订单再推 success 仍返回成功,不重复处理(幂等)。',
'已 succeeded 推 failed、或 cancelled 订单推送 → 返回 400 拒绝。',
],
},
{
key: 'get-wallet',
method: 'GET',
path: '/api/client/v1/wallet',
summary: '查询商户钱包余额',
scope: 'wallet:read',
responseExample: `{
"code": 0,
"message": "ok",
"data": {
"id": 1,
"merchant_id": 2,
"currency": "POINT",
"available_balance": 895,
"frozen_balance": 0
}
}`,
responseFields: [
{ name: 'currency', type: 'string', desc: '货币,默认 POINT' },
{ name: 'available_balance', type: 'int', desc: '可用余额(最小货币单位)' },
{ name: 'frozen_balance', type: 'int', desc: '冻结余额' },
],
},
]
+45
View File
@@ -0,0 +1,45 @@
// 开放接口文档的数据模型。所有接口信息以数据描述,由 EndpointDoc 统一渲染。
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
/** 单个接口的完整描述。 */
export interface EndpointSpec {
/** 唯一 key,用于折叠面板与锚点 */
key: string
/** HTTP 方法 */
method: HttpMethod
/** 接口路径,路径参数用 {name} 表示 */
path: string
/** 一句话说明 */
summary: string
/** 调用所需权限 scope */
scope: string
/** 路径参数说明(可选) */
pathParams?: ParamSpec[]
/** 查询参数说明(可选) */
queryParams?: ParamSpec[]
/** 请求体字段说明(可选,POST/PUT 用) */
bodyParams?: ParamSpec[]
/** 请求体示例 JSON 字符串(可选) */
requestExample?: string
/** 响应体示例 JSON 字符串 */
responseExample: string
/** 响应体字段说明 */
responseFields?: ParamSpec[]
/** 额外说明(幂等、限制等),渲染为提示框 */
notes?: string[]
}
/** 参数/字段说明。 */
export interface ParamSpec {
/** 字段名 */
name: string
/** 是否必填 */
required?: boolean
/** 类型,如 string / int / object / enum(a|b|c) */
type: string
/** 说明 */
desc: string
/** 示例值(可选) */
example?: string
}