feat(wecom): 企微 MCP 增加开发计划版本与任务查询

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-08 17:37:41 +08:00
parent d0d5c9ac56
commit 5d0beb5733
13 changed files with 386 additions and 4 deletions
+45
View File
@@ -130,6 +130,7 @@ HQ 页也会展示 Base URL 与 Header 名(不含密钥)。
| `promo.read` | `/promo-codes``/promo-codes/{code}/stats` | `query_promo_codes``query_promo_code_stats` |
| `store.audit.read` | `/store-audits``/store-info-audits``/store-info-audits/{id}``/store-package-audits``/store-package-audits/{id}` | `query_store_audits` 等 5 个 |
| `partner.read` | `/partners``/partners/{partnerId}/users|stores|orders` | `query_partners` 等 4 个 |
| `dev_plan.read` | `/versions``/tasks` | `query_versions``query_tasks` |
**实例建议:**
@@ -138,6 +139,7 @@ HQ 页也会展示 Base URL 与 Header 名(不含密钥)。
| 日常运营 | metrics + order + user + store + promo + redeem |
| 审核值班 | 上表 + store.audit.read |
| 合伙人分析 | 上表 + partner.read |
| 开发计划 | `dev_plan.read`(已有实例需在 HQ 补勾才会出现这两个工具) |
---
@@ -379,6 +381,49 @@ HQ 页也会展示 Base URL 与 Header 名(不含密钥)。
---
### 5.16 查询开发版本
| 项 | 值 |
|----|-----|
| 权限 | `dev_plan.read` |
| 路径 | `/versions` |
| MCP | `query_versions` |
**输入(均可选):**
| 参数 | 位置 | 类型 | 模型可见 | 说明 |
|------|------|------|----------|------|
| q | Query | String | 是 | 版本号或说明,如 `v3.5.18` |
| status | Query | String | 是 | `PENDING` 待启动 / `IN_PROGRESS` 开发中 / `TESTING` 测试 / `RELEASED` 已上线 / `STOPPED` 已停止 |
| page / pageSize | Query | Integer | 否 | 分页 |
**`data.items[]`** versionNo、content、status、statusLabel、createdAt、releasedAt
---
### 5.17 查询开发任务
| 项 | 值 |
|----|-----|
| 权限 | `dev_plan.read` |
| 路径 | `/tasks` |
| MCP | `query_tasks` |
按**任务状态**筛选。可同时用 `q``versionNo`
**输入(均可选):**
| 参数 | 位置 | 类型 | 模型可见 | 说明 |
|------|------|------|----------|------|
| status | Query | String | **是** | `TODO` 待开发 / `IN_PROGRESS` 开发中 / `DEVELOPED` 已开发 / `RELEASED` 已上线 / `STOPPED` 已停止 |
| q | Query | String | 是 | 任务编号或内容 |
| versionNo | Query | String | 是 | 精确版本号,如 `v3.5.18`;传入则只返回该版本下任务 |
| page / pageSize | Query | Integer | 否 | 分页 |
**`data.items[]`** taskNo、content、type、typeLabel、status、statusLabel、creatorName、supportTicketNo、versions[]、createdAt、completedAt。不含附件 URL。
---
## 7. OpenAPI 导入
| 项 | 值 |
+1
View File
@@ -74,6 +74,7 @@
| 日期 | 说明 |
|------|------|
| 2026-09-08 | 企微 MCP/REST 插件增加 `dev_plan.read``query_versions` / `query_tasks`(按任务状态筛选) |
| 2026-09-08 | v3.5.18:企微插件增加 Streamable HTTP MCP 端点 `/api/v1/wecom/plugin/mcp`,与 REST/OpenAPI 并存 |
| 2026-09-08 | HQ「门店账户」主账号详情可 CRUD 店员子账号(`/admin/store-accounts/:id/staff` |
| 2026-09-06 | v3.5.17:企微 API 插件迁入 HQ「企微机器人 → API 插件」;多实例 Key + 工具权限;不再依赖 `WECOM_PLUGIN_*` env |
@@ -56,6 +56,7 @@
| `store.read` | `/stores`(名称搜索 + 评分/核销/累计核销/合伙人) |
| `store.audit.read` | `/store-audits``/store-info-audits``/store-package-audits``/{id}` 对比详情 |
| `partner.read` | `/partners``/partners/{id}/users|stores|orders`(可选 `from`/`to` 时间筛选) |
| `dev_plan.read` | `/versions``/tasks`MCP`query_versions``query_tasks`,任务可按 status 筛选) |
---
@@ -23,6 +23,7 @@
| 响应 | JSON-RPC**无** `{code,message,data}` 信封 |
| 工具结果 | `JSON.stringify(查询结果)` |
| 权限 | `tools/list` 只注册已勾选权限;QueryService 仍 `requirePerm` |
| 开发计划 | `dev_plan.read``query_versions``query_tasks`(任务按 TODO/IN_PROGRESS/DEVELOPED/RELEASED/STOPPED 筛选) |
生产:`https://api.dukanghaoke.com/api/v1/wecom/plugin/mcp`
测试:`https://api-test.dukanghaoke.com/api/v1/wecom/plugin/mcp`
+1 -1
View File
@@ -48,7 +48,7 @@ C 端购酒核销 · 门店扫码核销+打款 · 合伙人拓店履约 · WebAd
**HQ 企微报告(v3.5.15**:企微机器人下「报告」与「消息推送」分开。日报/周报/月报各配 Webhook 与发送时刻;走群机器人 markdown。账期截在发送日北京 0 点(前一天 24 点),不含发送当天:日报=昨日存量+当日新增;周报/月报=上一自然周/月期末存量+本期新增。用户=有效未合并;合伙人=主账号;订单笔数与金额均排除待支付 / 已取消 / 退款中;订单金额=已付 `payAmount``paidAt`);核销=`RedeemRecord`
**企微 API 插件(v3.5.17 / MCP v3.5.18**:实例在 HQ「企微机器人 → API 插件」维护;共用 Header `X-Api-Key`。REST `GET /api/v1/wecom/plugin/*` 与 MCP `POST /api/v1/wecom/plugin/mcp`Streamable HTTP 无状态)并存;MCP 按权限自动 `tools/list`。经营指标含新增门店(`newStores`);门店搜索含经营数据;支持门店/信息/套餐审核对照只读查询;支持按合伙人查关联用户/门店/订单(`from`/`to` 时间筛选)。
**企微 API 插件(v3.5.17 / MCP v3.5.18**:实例在 HQ「企微机器人 → API 插件」维护;共用 Header `X-Api-Key`。REST `GET /api/v1/wecom/plugin/*` 与 MCP `POST /api/v1/wecom/plugin/mcp`Streamable HTTP 无状态)并存;MCP 按权限自动 `tools/list`。经营指标含新增门店(`newStores`);门店搜索含经营数据;支持门店/信息/套餐审核对照只读查询;支持按合伙人查关联用户/门店/订单(`from`/`to` 时间筛选)`dev_plan.read` 可查开发版本(`query_versions`)与按任务状态筛选的任务列表(`query_tasks`
**HQ 列表(v3.5.9**:主表不省略号、可横滑;最左序号;列设置(显隐/顺序)与列宽(拖表头)存 `hq_account.list_column_prefs`。主展示列下划线,点击进编辑或详情。门店列表「累计核销好客权益」= 该店 `RedeemRecord.amount` 合计。用户列表昵称只读(点击进详情);双击「备注」离开即保存(`hq_remark`);列表手机号不脱敏。
+1 -1
View File
@@ -151,7 +151,7 @@ HQ 账号/角色(`hq-permissions`,生效=(角色∪追加)−撤销;可绑
|------|------|
| 智能机器人 | `/wecom/bots` 长连接指令 |
| API 插件 | HQ `/wecom/plugins` 多实例;`/api/v1/wecom/plugin` Header `X-Api-Key` 只读查询(与长连接独立) |
| MCP 插件 | 同上 Key`POST /api/v1/wecom/plugin/mcp` Streamable HTTP`tools/list` 按权限过滤 |
| MCP 插件 | 同上 Key`POST /api/v1/wecom/plugin/mcp` Streamable HTTP`tools/list` 按权限过滤`dev_plan.read``query_versions` / `query_tasks` |
| 消息推送 | `/wecom/pushes` Webhook+eventKey |
| 日志 | `/logs/wecom-bots` |
| C 端微信客服 | 系统设置 `CUSTOMER_SERVICE_WECOM_URL` + `WECOM_CORP_ID`;小程序须已关联该企业微信客服 |
@@ -103,5 +103,7 @@ describe('maskWecomPluginApiKey / path permission map', () => {
]);
expect(allowedWecomPluginMcpTools(['partner.read'])).toContain('query_partners');
expect(allowedWecomPluginMcpTools(['metrics.read'])).not.toContain('query_orders');
expect(allowedWecomPluginOpenApiPaths(['dev_plan.read'])).toEqual(['/versions', '/tasks']);
expect(allowedWecomPluginMcpTools(['dev_plan.read'])).toEqual(['query_versions', 'query_tasks']);
});
});
+22
View File
@@ -26,6 +26,7 @@ export const WECOM_PLUGIN_PERMISSIONS = [
'metrics.read',
'store.audit.read',
'partner.read',
'dev_plan.read',
] as const;
export type WecomPluginPermission = (typeof WECOM_PLUGIN_PERMISSIONS)[number];
@@ -39,6 +40,7 @@ export const WECOM_PLUGIN_PERMISSION_LABELS: Record<WecomPluginPermission, strin
'metrics.read': '经营指标',
'store.audit.read': '门店/套餐审核',
'partner.read': '合伙人关联查询',
'dev_plan.read': '查询版本与任务',
};
export const WECOM_PLUGIN_PERMISSION_GROUPS: Array<{
@@ -64,6 +66,11 @@ export const WECOM_PLUGIN_PERMISSION_GROUPS: Array<{
label: '审核对照',
permissions: ['store.audit.read'],
},
{
key: 'dev_plan',
label: '开发计划',
permissions: ['dev_plan.read'],
},
];
/** OpenAPI path → 工具权限 */
@@ -84,6 +91,8 @@ export const WECOM_PLUGIN_PATH_PERMISSION: Record<string, WecomPluginPermission>
'/partners/{partnerId}/users': 'partner.read',
'/partners/{partnerId}/stores': 'partner.read',
'/partners/{partnerId}/orders': 'partner.read',
'/versions': 'dev_plan.read',
'/tasks': 'dev_plan.read',
};
export const WECOM_PLUGIN_MCP_TOOL_NAMES = [
@@ -103,6 +112,8 @@ export const WECOM_PLUGIN_MCP_TOOL_NAMES = [
'query_partner_users',
'query_partner_stores',
'query_partner_orders',
'query_versions',
'query_tasks',
] as const;
export type WecomPluginMcpToolName = (typeof WECOM_PLUGIN_MCP_TOOL_NAMES)[number];
@@ -142,6 +153,15 @@ export const WECOM_PLUGIN_MCP_TOOL_META: Record<
permission: 'partner.read',
description: '查询佣金归属该合伙人的订单,可选 from/to',
},
query_versions: {
permission: 'dev_plan.read',
description: '按版本号关键词查询开发计划版本;可选 status 筛选版本状态 PENDING/IN_PROGRESS/TESTING/RELEASED/STOPPED',
},
query_tasks: {
permission: 'dev_plan.read',
description:
'查询开发任务列表。status 按任务状态筛选:TODO 待开发、IN_PROGRESS 开发中、DEVELOPED 已开发、RELEASED 已上线、STOPPED 已停止;可选 q 搜任务编号/内容,versionNo 限定某版本',
},
};
export const WECOM_PLUGIN_MCP_TOOLS: Record<WecomPluginPermission, WecomPluginMcpToolName[]> = {
@@ -159,6 +179,7 @@ export const WECOM_PLUGIN_MCP_TOOLS: Record<WecomPluginPermission, WecomPluginMc
'query_store_package_audit_detail',
],
'partner.read': ['query_partners', 'query_partner_users', 'query_partner_stores', 'query_partner_orders'],
'dev_plan.read': ['query_versions', 'query_tasks'],
};
export const WECOM_PLUGIN_TOOL_PATHS: Record<WecomPluginPermission, string[]> = {
@@ -181,6 +202,7 @@ export const WECOM_PLUGIN_TOOL_PATHS: Record<WecomPluginPermission, string[]> =
'/partners/{partnerId}/stores',
'/partners/{partnerId}/orders',
],
'dev_plan.read': ['/versions', '/tasks'],
};
export function parseWecomPluginPermissions(
@@ -43,4 +43,33 @@ describe('WECOM_PLUGIN_MCP_TOOL_DEFS', () => {
expect(result.isError).toBe(true);
expect(result.content[0]).toMatchObject({ type: 'text', text: '请提供查询关键词 q' });
});
it('invokes query_versions and query_tasks', async () => {
const query = {
queryVersions: vi.fn().mockResolvedValue({ total: 1, items: [{ versionNo: 'v3.5.18' }] }),
queryTasks: vi.fn().mockResolvedValue({ total: 1, items: [{ taskNo: 'T1', status: 'TODO' }] }),
} as unknown as WecomPluginQueryService;
const versions = WECOM_PLUGIN_MCP_TOOL_DEFS.find((d) => d.name === 'query_versions');
const tasks = WECOM_PLUGIN_MCP_TOOL_DEFS.find((d) => d.name === 'query_tasks');
expect(versions).toBeTruthy();
expect(tasks).toBeTruthy();
await versions!.invoke(
{ query, plugin, wecomUserId: 'alice' },
{ q: 'v3.5', status: 'IN_PROGRESS', page: 1, pageSize: 5 },
);
expect(query.queryVersions).toHaveBeenCalledWith(plugin, 'alice', 'v3.5', 'IN_PROGRESS', '1', '5');
await tasks!.invoke(
{ query, plugin, wecomUserId: 'alice' },
{ status: 'TODO', q: '核销', versionNo: 'v3.5.18', page: 1, pageSize: 5 },
);
expect(query.queryTasks).toHaveBeenCalledWith(
plugin,
'alice',
'核销',
'TODO',
'v3.5.18',
'1',
'5',
);
});
});
@@ -201,4 +201,47 @@ export const WECOM_PLUGIN_MCP_TOOL_DEFS: ToolDef[] = [
),
),
},
{
name: 'query_versions',
description: WECOM_PLUGIN_MCP_TOOL_META.query_versions.description,
inputSchema: {
q: z.string().optional().describe('版本号关键词,如 v3.5.18'),
status: z
.enum(['PENDING', 'IN_PROGRESS', 'TESTING', 'RELEASED', 'STOPPED'])
.optional()
.describe('版本状态:待启动/开发中/测试/已上线/已停止'),
page,
pageSize,
},
invoke: (ctx, args) =>
jsonResult(() =>
ctx.query.queryVersions(ctx.plugin, ctx.wecomUserId, str(args, 'q'), str(args, 'status'), str(args, 'page'), str(args, 'pageSize')),
),
},
{
name: 'query_tasks',
description: WECOM_PLUGIN_MCP_TOOL_META.query_tasks.description,
inputSchema: {
status: z
.enum(['TODO', 'IN_PROGRESS', 'DEVELOPED', 'RELEASED', 'STOPPED'])
.optional()
.describe('任务状态:TODO 待开发、IN_PROGRESS 开发中、DEVELOPED 已开发、RELEASED 已上线、STOPPED 已停止'),
q: z.string().optional().describe('任务编号或内容关键词'),
versionNo: z.string().optional().describe('限定某版本号,如 v3.5.18'),
page,
pageSize,
},
invoke: (ctx, args) =>
jsonResult(() =>
ctx.query.queryTasks(
ctx.plugin,
ctx.wecomUserId,
str(args, 'q'),
str(args, 'status'),
str(args, 'versionNo'),
str(args, 'page'),
str(args, 'pageSize'),
),
),
},
];
@@ -1,4 +1,10 @@
import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from '@nestjs/common';
import {
BadRequestException,
ForbiddenException,
Inject,
Injectable,
NotFoundException,
} from '@nestjs/common';
import {
clampWecomPluginPage,
clampWecomPluginPageSize,
@@ -19,11 +25,21 @@ import {
allowedWecomPluginMcpTools,
wecomPluginHasPermission,
STORE_INFO_CHANGEABLE_FIELD_LABELS,
DEV_PLAN_TASK_STATUSES,
DEV_PLAN_TASK_STATUS_LABELS,
DEV_PLAN_TASK_TYPE_LABELS,
DEV_PLAN_VERSION_STATUSES,
DEV_PLAN_VERSION_STATUS_LABELS,
type StoreInfoChangeableField,
type WecomPluginPermission,
type DevPlanTaskDto,
type DevPlanVersionDto,
type DevPlanTaskStatusDto,
type DevPlanVersionStatusDto,
} from '@dukang/shared-types';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../../common/prisma/prisma.module';
import { DevPlanService } from '../../modules/dev-plan/dev-plan.service';
import { PromoCodeService } from '../../modules/promo/promo-code.service';
import { WecomBotAuditService } from './wecom-bot-audit.service';
import { wecomPluginAuditBot, type WecomPluginRuntime } from './wecom-plugin.types';
@@ -41,18 +57,68 @@ function requireQuery(q?: string): string {
return v;
}
function parseDevPlanTaskStatus(raw?: string | null): DevPlanTaskStatusDto | null {
const v = String(raw ?? '').trim().toUpperCase();
if (!v) return null;
if (!(DEV_PLAN_TASK_STATUSES as readonly string[]).includes(v)) {
throw new BadRequestException(
`任务状态须为 ${DEV_PLAN_TASK_STATUSES.join(' | ')}(待开发/开发中/已开发/已上线/已停止)`,
);
}
return v as DevPlanTaskStatusDto;
}
function parseDevPlanVersionStatus(raw?: string | null): DevPlanVersionStatusDto | null {
const v = String(raw ?? '').trim().toUpperCase();
if (!v) return null;
if (!(DEV_PLAN_VERSION_STATUSES as readonly string[]).includes(v)) {
throw new BadRequestException(
`版本状态须为 ${DEV_PLAN_VERSION_STATUSES.join(' | ')}(待启动/开发中/测试/已上线/已停止)`,
);
}
return v as DevPlanVersionStatusDto;
}
function toWecomPluginTaskView(t: DevPlanTaskDto) {
return {
taskNo: t.taskNo,
content: t.content,
type: t.type,
typeLabel: DEV_PLAN_TASK_TYPE_LABELS[t.type] ?? t.type,
status: t.status,
statusLabel: DEV_PLAN_TASK_STATUS_LABELS[t.status] ?? t.status,
creatorName: t.creatorName?.trim() || '—',
supportTicketNo: t.supportTicketNo ?? null,
versions: (t.versions ?? []).map((v) => v.versionNo),
createdAt: t.createdAt,
completedAt: t.completedAt ?? null,
};
}
function toWecomPluginVersionView(v: DevPlanVersionDto) {
return {
versionNo: v.versionNo,
content: v.content ?? null,
status: v.status,
statusLabel: DEV_PLAN_VERSION_STATUS_LABELS[v.status] ?? v.status,
createdAt: v.createdAt,
releasedAt: v.releasedAt ?? null,
};
}
@Injectable()
export class WecomPluginQueryService {
constructor(
private readonly prisma: PrismaService,
private readonly promo: PromoCodeService,
private readonly audit: WecomBotAuditService,
@Inject(DevPlanService) private readonly devPlan: DevPlanService,
) {}
info(plugin: WecomPluginRuntime) {
return {
name: plugin.name,
description: '企微智能机器人只读插件。查询订单、用户、门店经营、核销、推广码、经营指标、审核对照合伙人关联数据。',
description: '企微智能机器人只读插件。查询订单、用户、门店经营、核销、推广码、经营指标、审核对照合伙人关联数据、开发计划版本与任务。',
auth: { header: 'X-Api-Key' },
permissions: plugin.permissions,
tools: plugin.permissions.flatMap((p) => WECOM_PLUGIN_TOOL_PATHS[p]),
@@ -1007,6 +1073,106 @@ export class WecomPluginQueryService {
);
}
queryVersions(
plugin: WecomPluginRuntime,
wecomUserId: string,
q?: string,
status?: string,
page?: string,
pageSize?: string,
) {
this.requirePerm(plugin, 'dev_plan.read');
const versionStatus = parseDevPlanVersionStatus(status);
const take = clampWecomPluginPageSize(pageSize);
const pg = clampWecomPluginPage(page);
const keyword = String(q ?? '').trim();
return this.audit.run(
{
bot: wecomPluginAuditBot(plugin),
wecomUserId,
action: 'plugin.dev_plan.versions',
permission: 'dev_plan.read',
inputSummary: `${keyword || '*'} ${versionStatus || ''}`.trim(),
},
async () => {
const data = (await this.devPlan.listVersions({
keyword: keyword || undefined,
status: versionStatus ?? undefined,
page: pg,
pageSize: take,
})) as { items: DevPlanVersionDto[]; total: number };
return {
total: data.total,
items: data.items.map(toWecomPluginVersionView),
};
},
);
}
queryTasks(
plugin: WecomPluginRuntime,
wecomUserId: string,
q?: string,
status?: string,
versionNo?: string,
page?: string,
pageSize?: string,
) {
this.requirePerm(plugin, 'dev_plan.read');
const taskStatus = parseDevPlanTaskStatus(status);
const take = clampWecomPluginPageSize(pageSize);
const pg = clampWecomPluginPage(page);
const keyword = String(q ?? '').trim();
const ver = String(versionNo ?? '').trim();
return this.audit.run(
{
bot: wecomPluginAuditBot(plugin),
wecomUserId,
action: 'plugin.dev_plan.tasks',
permission: 'dev_plan.read',
inputSummary: `${ver || keyword || '*'} ${taskStatus || ''}`.trim(),
},
async () => {
if (ver) {
const listed = (await this.devPlan.listVersions({
keyword: ver,
page: 1,
pageSize: 10,
})) as { items: DevPlanVersionDto[] };
const match = listed.items.find((v) => v.versionNo.toLowerCase() === ver.toLowerCase());
if (!match) throw new NotFoundException(`未找到版本:${ver}`);
const detail = await this.devPlan.getVersion(BigInt(match.id));
let tasks = (detail.tasks ?? []).map(toWecomPluginTaskView);
if (taskStatus) tasks = tasks.filter((t) => t.status === taskStatus);
if (keyword) {
const kw = keyword.toLowerCase();
tasks = tasks.filter(
(t) => t.taskNo.toLowerCase().includes(kw) || t.content.toLowerCase().includes(kw),
);
}
const total = tasks.length;
const start = (pg - 1) * take;
return {
versionNo: match.versionNo,
versionStatus: match.status,
total,
items: tasks.slice(start, start + take),
};
}
const data = (await this.devPlan.listTasks({
status: taskStatus ?? undefined,
keyword: keyword || undefined,
page: pg,
pageSize: take,
})) as { items: DevPlanTaskDto[]; total: number };
return {
total: data.total,
items: data.items.map(toWecomPluginTaskView),
};
},
);
}
private mapStoreInfoAuditRow(
row: {
id: bigint;
@@ -219,4 +219,29 @@ export class WecomPluginController {
) {
return this.query.queryMetrics(plugin, wecomPluginCaller(req), kind);
}
@Get('versions')
versions(
@CurrentWecomPlugin() plugin: WecomPluginRuntime,
@Req() req: Request,
@Query('q') q?: string,
@Query('status') status?: string,
@Query('page') page?: string,
@Query('pageSize') pageSize?: string,
) {
return this.query.queryVersions(plugin, wecomPluginCaller(req), q, status, page, pageSize);
}
@Get('tasks')
tasks(
@CurrentWecomPlugin() plugin: WecomPluginRuntime,
@Req() req: Request,
@Query('q') q?: string,
@Query('status') status?: string,
@Query('versionNo') versionNo?: string,
@Query('page') page?: string,
@Query('pageSize') pageSize?: string,
) {
return this.query.queryTasks(plugin, wecomPluginCaller(req), q, status, versionNo, page, pageSize);
}
}
@@ -328,5 +328,52 @@ export const WECOM_PLUGIN_OPENAPI = {
},
},
},
'/versions': {
get: {
summary: '查询开发版本',
description: '按版本号或说明模糊查询;status 为版本状态',
operationId: '查询开发版本',
parameters: [
{ name: 'q', in: 'query', required: false, schema: { type: 'string' }, description: '版本号关键词,如 v3.5.18' },
{
name: 'status',
in: 'query',
required: false,
schema: { type: 'string', enum: ['PENDING', 'IN_PROGRESS', 'TESTING', 'RELEASED', 'STOPPED'] },
},
...pageParams,
],
responses: {
200: { description: '版本列表', content: { 'application/json': { schema: envelope({ type: 'object' }) } } },
401: unauthorized,
},
},
},
'/tasks': {
get: {
summary: '查询开发任务',
description:
'按任务状态筛选。statusTODO 待开发 / IN_PROGRESS 开发中 / DEVELOPED 已开发 / RELEASED 已上线 / STOPPED 已停止。可选 q、versionNo。',
operationId: '查询开发任务',
parameters: [
{ name: 'q', in: 'query', required: false, schema: { type: 'string' }, description: '任务编号或内容' },
{
name: 'status',
in: 'query',
required: false,
schema: {
type: 'string',
enum: ['TODO', 'IN_PROGRESS', 'DEVELOPED', 'RELEASED', 'STOPPED'],
},
},
{ name: 'versionNo', in: 'query', required: false, schema: { type: 'string' }, description: '限定某版本号' },
...pageParams,
],
responses: {
200: { description: '任务列表', content: { 'application/json': { schema: envelope({ type: 'object' }) } } },
401: unauthorized,
},
},
},
},
} as const;