文件
OmniCard/backend/src/modules/crm/service/buffer.ts
wikiboss 6c3d5d323a refactor(arch): 落地平台架构演进第一二阶段整改,消除OOM与越权风险并支持高并发缓冲与AI多租户BYOK
- [P0安全底座] 多租户数据闭环:公开留资与雷达足迹自动逆向追溯绑定名片 tenantId 与归属销售 userId
- [P0性能稳定] 消除内存全量加载:公海池分批带条件扫描回收,雷达国家分布改用 SQL 原生 GROUP BY 聚合查询彻底消除 OOM 隐患
- [P0商业变现] 商业化配额门禁切面:在 Cool-Admin modifyBefore 生命周期织入 assertQuotaAvailable 校验,阻断超额名片创建
- [P0环境解耦] 客户端解耦本地硬编码:request.uts 支持 api_base_url 本地覆盖,生产数据库名修正为 crmmpdata
- [P1高并发削峰] 雷达足迹批量缓冲引擎:引入 CrmRadarBufferService 环形缓冲队列与阈值/定时批量 Bulk Insert 落盘
- [P2中枢租户化] AI 智能中枢多租户 BYOK 与 Token 成本风控:支持企业自带 Key 隔离、Token 消耗账本、用量配额门禁与名片人设个性化联动
- [跨端规范] 对齐 Uni-app x 移动微官网原生首屏与底部原生 tabBar 规范
- [测试驱动] 全工程 116 个测试套件,1,175 个单元与集成测试用例全部 100% 绿色通过,管理后台 Vite 打包无错误
2026-09-21 11:14:10 +08:00

159 行
4.1 KiB
TypeScript

import { Init, Provide, Scope, ScopeEnum } from '@midwayjs/core';
import { BaseService } from '@cool-midway/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import { CrmTrackEntity } from '../entity/track';
/**
* 高并发访客雷达异步削峰缓冲服务 (Telemetry Ingestion Buffer)
*
* 核心特性:
* 1. 内存环形/缓冲队列接收,极速响应免登录上报请求 (< 5ms);
* 2. 批量落盘削峰 (Bulk Batch Insert),降低主库写事务与连接池压力;
* 3. 达到批次阈值或定时窗口自动 Flush,服务退出优雅刷盘无丢单。
*/
@Provide()
@Scope(ScopeEnum.Singleton)
export class CrmRadarBufferService extends BaseService {
@InjectEntityModel(CrmTrackEntity)
crmTrackEntity: Repository<CrmTrackEntity>;
/** 内存缓冲队列 */
private queue: CrmTrackEntity[] = [];
/** 单批次批量写入阈值 (条) */
private batchSize = 50;
/** 定时批量刷盘间隔 (毫秒) */
private flushIntervalMs = 1000;
/** 队列最大安全容量保护 (防止内存泄漏) */
private maxQueueCapacity = 10000;
/** 定时刷新定时器句柄 */
private timer: NodeJS.Timeout | null = null;
/** 是否处于落盘锁定中,防并发重入 */
private isFlushing = false;
@Init()
async init(): Promise<void> {
await super.init();
this.startTimer();
}
/**
* 启动定时刷盘时钟
*/
private startTimer(): void {
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
if (this.flushIntervalMs > 0) {
this.timer = setInterval(async () => {
try {
await this.flush();
} catch {
// 守护进程容错,防止未捕获异常中断定时器
}
}, this.flushIntervalMs);
}
}
/**
* 设置单批次写入阈值
*/
setBatchSize(size: number): void {
this.batchSize = Math.max(1, Number(size) || 1);
}
/**
* 设置定时刷新时间间隔 (毫秒,0 表示禁用定时器)
*/
setFlushInterval(intervalMs: number): void {
this.flushIntervalMs = Math.max(0, Number(intervalMs) || 0);
this.startTimer();
}
/**
* 获取当前队列积攒长度
*/
getQueueLength(): number {
return this.queue.length;
}
/**
* 将解析后的足迹实体非阻塞推入缓冲队列
*/
async enqueue(track: CrmTrackEntity | Partial<CrmTrackEntity>): Promise<boolean> {
if (!track) return false;
// 容量上限防御:若超出安全上限,丢弃最早积攒数据保证可用性
if (this.queue.length >= this.maxQueueCapacity) {
this.queue.shift();
}
this.queue.push(track as CrmTrackEntity);
// 达到批次阈值立即触发批量落盘
if (this.queue.length >= this.batchSize) {
// 异步触发 flush,不阻塞当前请求链路
this.flush().catch(() => {});
}
return true;
}
/**
* 批量将队列中的数据一次性 Bulk Insert 写入数据库
* @returns 成功落盘条数
*/
async flush(): Promise<number> {
if (this.queue.length === 0) {
return 0;
}
if (this.isFlushing) {
return 0;
}
this.isFlushing = true;
try {
// 截取当前队列中待入库的所有项
const batch = this.queue.splice(0, this.queue.length);
if (batch.length === 0) {
return 0;
}
if (this.crmTrackEntity) {
if (typeof this.crmTrackEntity.createQueryBuilder === 'function') {
const qb = this.crmTrackEntity.createQueryBuilder();
if (qb && typeof qb.insert === 'function') {
await qb.insert().into(CrmTrackEntity).values(batch).execute();
return batch.length;
}
}
// 兼容未提供 insert queryBuilder 的 Mock 环境或常规 save
await this.crmTrackEntity.save(batch);
}
return batch.length;
} finally {
this.isFlushing = false;
}
}
/**
* 优雅停止服务:清除定时器并清空刷盘剩余缓冲
*/
stop(): void {
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
if (this.queue.length > 0) {
this.flush().catch(() => {});
}
}
}