Files
dukang/server/dukang-api/src/integrations/wecom/wecom-plugin.openapi.ts
T

333 lines
10 KiB
TypeScript

import type { WecomPluginPermission } from '@dukang/shared-types';
import { allowedWecomPluginOpenApiPaths } from '@dukang/shared-types';
const envelope = (dataSchema: Record<string, unknown>) => ({
type: 'object',
properties: {
code: { type: 'integer', example: 0 },
message: { type: 'string', example: 'ok' },
data: dataSchema,
},
required: ['code', 'message', 'data'],
});
const qParam = {
name: 'q',
in: 'query',
required: true,
schema: { type: 'string' },
description: '查询关键词',
};
const pageParams = [
{
name: 'page',
in: 'query',
required: false,
schema: { type: 'integer', default: 1, minimum: 1 },
},
{
name: 'pageSize',
in: 'query',
required: false,
schema: { type: 'integer', default: 5, minimum: 1, maximum: 10 },
description: '默认 5,最大 10',
},
];
const dateRangeParams = [
{
name: 'from',
in: 'query',
required: false,
schema: { type: 'string' },
description: '起始日期 YYYY-MM-DD 或 ISO',
},
{
name: 'to',
in: 'query',
required: false,
schema: { type: 'string' },
description: '结束日期 YYYY-MM-DD 或 ISO(含当天)',
},
];
const unauthorized = {
description: '缺少或错误的 X-Api-Key,或插件未启用',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
code: { type: 'integer' },
message: { type: 'string' },
},
},
},
},
};
function listPath(summary: string, description: string, qDescription: string) {
return {
get: {
summary,
description,
operationId: summary,
parameters: [{ ...qParam, description: qDescription }, ...pageParams],
responses: {
200: {
description: '查询结果',
content: {
'application/json': {
schema: envelope({
type: 'object',
properties: {
total: { type: 'integer' },
items: { type: 'array', items: { type: 'object' } },
},
}),
},
},
},
401: unauthorized,
},
},
};
}
/** 按实例权限过滤 paths,供企微第 2 步对照配置 */
export function filterWecomPluginOpenApi(
spec: typeof WECOM_PLUGIN_OPENAPI,
permissions: WecomPluginPermission[],
title?: string,
) {
const allowedPaths = new Set(allowedWecomPluginOpenApiPaths(permissions));
const paths: Record<string, unknown> = {};
for (const [path, def] of Object.entries(spec.paths)) {
if (allowedPaths.has(path)) paths[path] = def;
}
return {
...spec,
info: {
...spec.info,
title: title || spec.info.title,
},
paths,
};
}
/** OpenAPI 3.0:企微「添加插件工具」可导入。须原样返回,不要套 {code,message,data}。 */
export const WECOM_PLUGIN_OPENAPI = {
openapi: '3.0.3',
info: {
title: '杜康好客运营查询',
description:
'企业内部只读查询。手机号已脱敏。鉴权:Header X-Api-Key。响应除本文件外均为 { code, message, data }。',
version: '1.0.0',
},
servers: [
{ url: 'https://api.dukanghaoke.com/api/v1/wecom/plugin', description: '生产' },
{ url: 'https://api-test.dukanghaoke.com/api/v1/wecom/plugin', description: '测试' },
],
security: [{ ApiKeyAuth: [] }],
components: {
securitySchemes: {
ApiKeyAuth: {
type: 'apiKey',
in: 'header',
name: 'X-Api-Key',
},
},
},
paths: {
'/orders': listPath('查询订单', '按订单号模糊查询', '订单号,如 DK20260903xxxx'),
'/users': listPath('查询用户', '按用户号或 11 位手机号查询;手机号脱敏', '用户号或手机号'),
'/stores': listPath(
'查询门店',
'按门店名称模糊查询,返回经营数据:评分、核销笔数、累计核销好客权益、合伙人、审核状态',
'门店名称关键词',
),
'/store-audits': {
get: {
summary: '门店入驻审核列表',
description: '默认 status=PENDING;可按门店名筛选',
operationId: '查询门店入驻审核',
parameters: [
{ name: 'q', in: 'query', required: false, schema: { type: 'string' }, description: '门店名称' },
{
name: 'status',
in: 'query',
required: false,
schema: { type: 'string', enum: ['PENDING', 'APPROVED', 'REJECTED'], default: 'PENDING' },
},
...pageParams,
],
responses: {
200: { description: '审核列表', content: { 'application/json': { schema: envelope({ type: 'object' }) } } },
401: unauthorized,
},
},
},
'/store-info-audits': {
get: {
summary: '门店信息变更审核列表',
operationId: '查询门店信息变更审核',
parameters: [
{
name: 'status',
in: 'query',
required: false,
schema: { type: 'string', enum: ['PENDING', 'APPROVED', 'REJECTED'], default: 'PENDING' },
},
...pageParams,
],
responses: {
200: { description: '列表', content: { 'application/json': { schema: envelope({ type: 'object' }) } } },
401: unauthorized,
},
},
},
'/store-info-audits/{id}': {
get: {
summary: '门店信息变更审核对比',
description: '返回变更字段 live vs proposed 对照',
operationId: '查询门店信息变更详情',
parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }],
responses: {
200: { description: '详情含 diffs', content: { 'application/json': { schema: envelope({ type: 'object' }) } } },
401: unauthorized,
},
},
},
'/store-package-audits': {
get: {
summary: '门店套餐审核列表',
operationId: '查询门店套餐审核',
parameters: [
{
name: 'status',
in: 'query',
required: false,
schema: { type: 'string', enum: ['PENDING', 'APPROVED', 'REJECTED'], default: 'PENDING' },
},
...pageParams,
],
responses: {
200: { description: '列表', content: { 'application/json': { schema: envelope({ type: 'object' }) } } },
401: unauthorized,
},
},
},
'/store-package-audits/{id}': {
get: {
summary: '门店套餐审核对比',
description: '返回 proposedPackages 与 livePackages 对照',
operationId: '查询门店套餐审核详情',
parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }],
responses: {
200: { description: '详情含套餐对比', content: { 'application/json': { schema: envelope({ type: 'object' }) } } },
401: unauthorized,
},
},
},
'/partners': listPath('查询合伙人', '按姓名、公司名、手机号或 ID 搜索主账号', '合伙人关键词'),
'/partners/{partnerId}/users': {
get: {
summary: '合伙人关联用户',
operationId: '查询合伙人关联用户',
parameters: [
{ name: 'partnerId', in: 'path', required: true, schema: { type: 'string' } },
...dateRangeParams,
...pageParams,
],
responses: {
200: { description: '关联用户列表', content: { 'application/json': { schema: envelope({ type: 'object' }) } } },
401: unauthorized,
},
},
},
'/partners/{partnerId}/stores': {
get: {
summary: '合伙人名下门店',
operationId: '查询合伙人门店',
parameters: [
{ name: 'partnerId', in: 'path', required: true, schema: { type: 'string' } },
...dateRangeParams,
...pageParams,
],
responses: {
200: { description: '门店及经营数据', content: { 'application/json': { schema: envelope({ type: 'object' }) } } },
401: unauthorized,
},
},
},
'/partners/{partnerId}/orders': {
get: {
summary: '合伙人相关订单',
description: '佣金归属 partnerAccountIdAtPay 的订单',
operationId: '查询合伙人订单',
parameters: [
{ name: 'partnerId', in: 'path', required: true, schema: { type: 'string' } },
...dateRangeParams,
...pageParams,
],
responses: {
200: { description: '订单列表', content: { 'application/json': { schema: envelope({ type: 'object' }) } } },
401: unauthorized,
},
},
},
'/redeems': listPath('查询核销', '按核销单号或门店名查询', '核销单号或门店名'),
'/promo-codes': listPath('查询推广码', '按推广码 code 或名称查询', '推广码或名称'),
'/promo-codes/{code}/stats': {
get: {
summary: '推广码统计',
operationId: '查询推广码统计',
parameters: [
{
name: 'code',
in: 'path',
required: true,
schema: { type: 'string' },
description: '推广码 code',
},
],
responses: {
200: {
description: '扫码/成交统计',
content: { 'application/json': { schema: envelope({ type: 'object' }) } },
},
401: unauthorized,
},
},
},
'/metrics': {
get: {
summary: '经营指标',
description:
'today=今日截至当前;daily/weekly/monthly 与企微经营报告同一口径。stats 含 users/partners/stores/orders 存量与增量;storesIncrement 与 newStores 均为新增门店数。',
operationId: '查询经营指标',
parameters: [
{
name: 'kind',
in: 'query',
required: false,
schema: {
type: 'string',
enum: ['today', 'daily', 'weekly', 'monthly'],
default: 'today',
},
},
],
responses: {
200: {
description: '存量与新增',
content: { 'application/json': { schema: envelope({ type: 'object' }) } },
},
401: unauthorized,
},
},
},
},
} as const;