feat(ops): 总部可按百分比限制接口放行并开关企微通知

线上需要按账户、用户和功能控制登录与加载成功率,同时单独停发订单、核销和账单通知。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-26 12:04:16 +08:00
parent 674b6ac31b
commit cb63d382ad
29 changed files with 1888 additions and 18 deletions
+145
View File
@@ -0,0 +1,145 @@
/** HQ 接口访问:成功百分比功能(v4.0.21) */
export const API_ACCESS_PERCENT_FEATURES = [
'login',
'product_load',
'store_load',
'store_submit',
'audit_notify',
] as const;
export type ApiAccessPercentFeature = (typeof API_ACCESS_PERCENT_FEATURES)[number];
export const API_ACCESS_PERCENT_FEATURE_LABELS: Record<ApiAccessPercentFeature, string> = {
login: '登录',
product_load: '商品加载',
store_load: '门店加载',
store_submit: '门店提交',
audit_notify: '审核通知',
};
/** 只控制企微是否发送,不拦截业务 */
export const API_ACCESS_NOTIFY_KEYS = [
'order_push',
'redeem_push',
'bill_push_winery',
'bill_push_partner',
'bill_push_store',
'store_audit',
'package_change',
] as const;
export type ApiAccessNotifyKey = (typeof API_ACCESS_NOTIFY_KEYS)[number];
export const API_ACCESS_NOTIFY_LABELS: Record<ApiAccessNotifyKey, string> = {
order_push: '订单推送',
redeem_push: '核销推送',
bill_push_winery: '酒厂账单推送',
bill_push_partner: '城市合伙人账单推送',
bill_push_store: '门店账单推送',
store_audit: '门店审核',
package_change: '套餐修改',
};
/** 企微 eventKey → 通知总开关。未列出的事件不受开关影响 */
export const API_ACCESS_NOTIFY_BY_EVENT: Record<string, ApiAccessNotifyKey> = {
'order.paid': 'order_push',
'redeem.success': 'redeem_push',
'finance.winery_bill': 'bill_push_winery',
'finance.partner_bill': 'bill_push_partner',
'finance.store_bill': 'bill_push_store',
'store.audit_pending': 'store_audit',
'store.package_audit_pending': 'package_change',
};
/** 审核通知百分比只作用在这一条企微事件上 */
export const API_ACCESS_AUDIT_NOTIFY_EVENT = 'store.audit_pending';
export const API_ACCESS_ERROR_KINDS = [
'network_load_failed',
'request_error',
'illegal_access',
'wechat_service_error',
] as const;
export type ApiAccessErrorKind = (typeof API_ACCESS_ERROR_KINDS)[number];
export const API_ACCESS_DEFAULT_ERROR_KIND: ApiAccessErrorKind = 'request_error';
export const API_ACCESS_ERROR_LABELS: Record<ApiAccessErrorKind, string> = {
network_load_failed: '网络加载失败',
request_error: '请求异常',
illegal_access: '非法访问',
wechat_service_error: '微信服务异常',
};
export const API_ACCESS_ERROR_STATUS: Record<ApiAccessErrorKind, number> = {
network_load_failed: 503,
request_error: 503,
illegal_access: 403,
wechat_service_error: 503,
};
export const API_ACCESS_ACTOR_TYPES = ['USER', 'STORE', 'PARTNER', 'HQ'] as const;
export type ApiAccessActorType = (typeof API_ACCESS_ACTOR_TYPES)[number];
export const API_ACCESS_ACCOUNT_ACTOR_TYPES = ['STORE', 'PARTNER', 'HQ'] as const;
export type ApiAccessAccountActorType = (typeof API_ACCESS_ACCOUNT_ACTOR_TYPES)[number];
export interface ApiAccessPolicyDto {
id: string;
featureKey: string | null;
featureLabel: string;
scopeType: 'global' | 'account' | 'user';
actorType: ApiAccessActorType | null;
actorId: string | null;
actorLabel: string | null;
successPercent: number | null;
errorKind: ApiAccessErrorKind | null;
enabled: boolean;
}
export interface ApiAccessFormResponse {
percents: ApiAccessPolicyDto[];
overrides: ApiAccessPolicyDto[];
notifies: ApiAccessPolicyDto[];
}
export interface ApiAccessGlobalItem {
featureKey: ApiAccessPercentFeature;
successPercent: number;
errorKind: ApiAccessErrorKind;
}
export interface ApiAccessNotifyItem {
featureKey: ApiAccessNotifyKey;
enabled: boolean;
}
export interface ApiAccessOverrideCreate {
scopeType: 'account' | 'user';
actorType: ApiAccessActorType;
actorId: string;
/** null = 该对象的全部百分比功能 */
featureKey: ApiAccessPercentFeature | null;
successPercent: number;
errorKind: ApiAccessErrorKind;
}
export interface ApiAccessActorHit {
actorType: ApiAccessActorType;
actorId: string;
label: string;
phone: string | null;
}
export function isApiAccessPercentFeature(value: string): value is ApiAccessPercentFeature {
return (API_ACCESS_PERCENT_FEATURES as readonly string[]).includes(value);
}
export function isApiAccessNotifyKey(value: string): value is ApiAccessNotifyKey {
return (API_ACCESS_NOTIFY_KEYS as readonly string[]).includes(value);
}
export function isApiAccessErrorKind(value: string): value is ApiAccessErrorKind {
return (API_ACCESS_ERROR_KINDS as readonly string[]).includes(value);
}
+3 -1
View File
@@ -46,6 +46,7 @@ export const HQ_PERMISSION_CATALOG = [
{ key: 'system_settings_deploy', label: '发布部署', group: '系统设置' },
{ key: 'system_settings_winery_bank', label: '酒厂银行账户', group: '系统设置' },
{ key: 'system_settings_finance', label: '财务结算', group: '系统设置' },
{ key: 'api_access', label: '接口访问', group: '系统设置' },
] as const;
export type HqPermissionKey = (typeof HQ_PERMISSION_CATALOG)[number]['key'];
@@ -76,6 +77,7 @@ export const HQ_DANGEROUS_PERMISSION_KEYS = [
'orders_delete',
'cities_delete',
'store_categories_delete',
'api_access',
] as const satisfies readonly HqPermissionKey[];
export function isHqDangerousPermission(key: string): boolean {
@@ -175,7 +177,7 @@ const OPS_STORE_KEYS: HqPermissionKey[] = [
/** 开发者默认:除权限分配、HQ 账户外全部权限 */
function developerDefaultPermissions(): HqPermissionKey[] {
return HQ_PERMISSION_CATALOG.map((p) => p.key).filter(
(k) => k !== 'hq_permissions' && k !== 'hq_accounts',
(k) => k !== 'hq_permissions' && k !== 'hq_accounts' && k !== 'api_access',
);
}
+1
View File
@@ -36,3 +36,4 @@ export * from './llm-config';
export * from './knowledge-base';
export * from './legal';
export * from './dev-plan';
export * from './api-access';