v4.0.17企业微信API插件优化

This commit is contained in:
2026-09-06 09:36:49 +08:00
parent 0e711be6c6
commit 192a401227
36 changed files with 2416 additions and 103 deletions
@@ -2,7 +2,12 @@ import { describe, expect, it } from 'vitest';
import {
WECOM_PLUGIN_API_KEY_HEADER,
WECOM_PLUGIN_BASE_PATH,
WECOM_PLUGIN_PATH_PERMISSION,
allowedWecomPluginOpenApiPaths,
maskWecomPluginApiKey,
parseWecomPluginPermissions,
resolveWecomPluginPublicUrl,
wecomPluginHasPermission,
} from './wecom-plugin';
describe('resolveWecomPluginPublicUrl', () => {
@@ -31,3 +36,44 @@ describe('resolveWecomPluginPublicUrl', () => {
expect(WECOM_PLUGIN_API_KEY_HEADER).toBe('X-Api-Key');
});
});
describe('parseWecomPluginPermissions', () => {
it('keeps catalog keys and drops unknown', () => {
expect(parseWecomPluginPermissions(['order.read', 'bogus', 'metrics.read'])).toEqual([
'order.read',
'metrics.read',
]);
expect(parseWecomPluginPermissions('["user.read","store.read"]')).toEqual([
'user.read',
'store.read',
]);
});
});
describe('maskWecomPluginApiKey / path permission map', () => {
it('masks middle of the key', () => {
expect(maskWecomPluginApiKey('WAv2xxxxYb9')).toBe('WAv2••••xYb9');
});
it('maps openapi paths to tool permissions', () => {
expect(WECOM_PLUGIN_PATH_PERMISSION['/orders']).toBe('order.read');
expect(WECOM_PLUGIN_PATH_PERMISSION['/promo-codes/{code}/stats']).toBe('promo.read');
});
it('filters openapi paths by instance permissions', () => {
expect(allowedWecomPluginOpenApiPaths(['order.read', 'user.read'])).toEqual([
'/orders',
'/users',
]);
expect(allowedWecomPluginOpenApiPaths(['store.audit.read'])).toEqual([
'/store-audits',
'/store-info-audits',
'/store-info-audits/{id}',
'/store-package-audits',
'/store-package-audits/{id}',
]);
expect(allowedWecomPluginOpenApiPaths(['partner.read'])).toContain('/partners');
expect(wecomPluginHasPermission(['order.read'], 'order.read')).toBe(true);
expect(wecomPluginHasPermission(['order.read'], 'metrics.read')).toBe(false);
});
});
+182
View File
@@ -12,6 +12,188 @@ export const WECOM_PLUGIN_METRICS_KINDS = ['today', 'daily', 'weekly', 'monthly'
export type WecomPluginMetricsKind = (typeof WECOM_PLUGIN_METRICS_KINDS)[number];
export const WECOM_PLUGIN_PERMISSIONS = [
'order.read',
'user.read',
'store.read',
'redeem.read',
'promo.read',
'metrics.read',
'store.audit.read',
'partner.read',
] as const;
export type WecomPluginPermission = (typeof WECOM_PLUGIN_PERMISSIONS)[number];
export const WECOM_PLUGIN_PERMISSION_LABELS: Record<WecomPluginPermission, string> = {
'order.read': '查询订单',
'user.read': '查询用户',
'store.read': '查询门店',
'redeem.read': '查询核销',
'promo.read': '查询推广码',
'metrics.read': '经营指标',
'store.audit.read': '门店/套餐审核',
'partner.read': '合伙人关联查询',
};
export const WECOM_PLUGIN_PERMISSION_GROUPS: Array<{
key: string;
label: string;
permissions: WecomPluginPermission[];
}> = [
{
key: 'query',
label: '只读查询',
permissions: [
'order.read',
'user.read',
'store.read',
'redeem.read',
'promo.read',
'metrics.read',
'partner.read',
],
},
{
key: 'audit',
label: '审核对照',
permissions: ['store.audit.read'],
},
];
/** OpenAPI path → 工具权限 */
export const WECOM_PLUGIN_PATH_PERMISSION: Record<string, WecomPluginPermission> = {
'/orders': 'order.read',
'/users': 'user.read',
'/stores': 'store.read',
'/redeems': 'redeem.read',
'/promo-codes': 'promo.read',
'/promo-codes/{code}/stats': 'promo.read',
'/metrics': 'metrics.read',
'/store-audits': 'store.audit.read',
'/store-info-audits': 'store.audit.read',
'/store-info-audits/{id}': 'store.audit.read',
'/store-package-audits': 'store.audit.read',
'/store-package-audits/{id}': 'store.audit.read',
'/partners': 'partner.read',
'/partners/{partnerId}/users': 'partner.read',
'/partners/{partnerId}/stores': 'partner.read',
'/partners/{partnerId}/orders': 'partner.read',
};
export const WECOM_PLUGIN_TOOL_PATHS: Record<WecomPluginPermission, string[]> = {
'order.read': ['/orders'],
'user.read': ['/users'],
'store.read': ['/stores'],
'redeem.read': ['/redeems'],
'promo.read': ['/promo-codes', '/promo-codes/{code}/stats'],
'metrics.read': ['/metrics'],
'store.audit.read': [
'/store-audits',
'/store-info-audits',
'/store-info-audits/{id}',
'/store-package-audits',
'/store-package-audits/{id}',
],
'partner.read': [
'/partners',
'/partners/{partnerId}/users',
'/partners/{partnerId}/stores',
'/partners/{partnerId}/orders',
],
};
export function parseWecomPluginPermissions(
raw?: string | string[] | null,
): WecomPluginPermission[] {
const valid = new Set<string>(WECOM_PLUGIN_PERMISSIONS);
let list: string[];
if (Array.isArray(raw)) {
list = raw.map(String);
} else {
const text = String(raw ?? '').trim();
if (!text) {
list = [];
} else if (text.startsWith('[')) {
try {
const parsed: unknown = JSON.parse(text);
list = Array.isArray(parsed) ? parsed.map(String) : [];
} catch {
list = text.split(/[,\s]+/).map((s) => s.trim()).filter(Boolean);
}
} else {
list = text.split(/[,\s]+/).map((s) => s.trim()).filter(Boolean);
}
}
return [...new Set(list.filter((s): s is WecomPluginPermission => valid.has(s)))];
}
export function wecomPluginHasPermission(
permissions: WecomPluginPermission[],
permission: WecomPluginPermission,
): boolean {
return permissions.includes(permission);
}
/** 当前实例可暴露给企微第 2 步的 OpenAPI paths */
export function allowedWecomPluginOpenApiPaths(
permissions: WecomPluginPermission[],
): string[] {
const allowed = new Set(permissions);
return Object.entries(WECOM_PLUGIN_PATH_PERMISSION)
.filter(([, perm]) => allowed.has(perm))
.map(([path]) => path);
}
export function maskWecomPluginApiKey(apiKey?: string | null): string {
const v = String(apiKey ?? '').trim();
if (!v) return '未配置';
if (v.length <= 8) return '••••';
return `${v.slice(0, 4)}••••${v.slice(-4)}`;
}
export type WecomApiPluginDto = {
id: string;
name: string;
apiKeyConfigured: boolean;
apiKeyMasked: string;
permissions: WecomPluginPermission[];
remark: string | null;
enabled: boolean;
sortOrder: number;
createdAt: string;
updatedAt: string;
};
export type CreateWecomApiPluginRequest = {
name: string;
apiKey?: string;
permissions: WecomPluginPermission[];
remark?: string | null;
enabled?: boolean;
sortOrder?: number;
};
export type UpdateWecomApiPluginRequest = {
name?: string;
apiKey?: string;
permissions?: WecomPluginPermission[];
remark?: string | null;
enabled?: boolean;
sortOrder?: number;
};
export type WecomApiPluginSecretDto = WecomApiPluginDto & {
apiKey: string;
};
export type WecomApiPluginListDto = {
items: WecomApiPluginDto[];
total: number;
page: number;
pageSize: number;
};
/** 按 HQ 当前域名推断插件公网 Base URL(不含密钥) */
export function resolveWecomPluginPublicUrl(hostname: string): string {
const host = String(hostname || '').toLowerCase();