chore: commit remaining integration files, PM2 single instance, PRD doc

This commit is contained in:
2026-07-06 16:04:53 +08:00
parent 8bd59b8ac6
commit c81b0a0b5c
10 changed files with 2175 additions and 37 deletions

View File

@@ -36,6 +36,7 @@
"bcryptjs": "^3.0.3",
"class-transformer": "^0.5.1",
"class-validator": "^0.15.1",
"echarts": "^6.1.0",
"exceljs": "^4.4.0",
"multer": "^2.1.1",
"mysql2": "^3.22.2",

View File

@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Department, User } from '../entities';
import { DingTalkService } from './dingtalk.service';
import { WeComService } from './wecom.service';
@Module({
imports: [TypeOrmModule.forFeature([Department, User])],
providers: [DingTalkService, WeComService],
exports: [DingTalkService, WeComService],
})
export class IntegrationModule {}

View File

@@ -1,6 +1,7 @@
import { Module } from '@nestjs/common';
import { ScheduleModule } from '@nestjs/schedule';
import { TypeOrmModule } from '@nestjs/typeorm';
import { IntegrationModule } from '../integration/integration.module';
import { SyncLog, SyncState } from '../entities';
import { SyncService } from './sync.service';
import { SyncController } from './sync.controller';
@@ -9,6 +10,7 @@ import { SyncController } from './sync.controller';
imports: [
ScheduleModule.forRoot(),
TypeOrmModule.forFeature([SyncLog, SyncState]),
IntegrationModule,
],
controllers: [SyncController],
providers: [SyncService],

View File

@@ -4,6 +4,8 @@ import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { SyncLog, SyncState } from '../entities';
import type { SyncPlatform, SyncType, SyncStatus } from '../entities/sync-log.entity';
import { DingTalkService } from '../integration/dingtalk.service';
import { WeComService } from '../integration/wecom.service';
@Injectable()
export class SyncService {
@@ -14,6 +16,8 @@ export class SyncService {
private readonly syncLogRepo: Repository<SyncLog>,
@InjectRepository(SyncState)
private readonly syncStateRepo: Repository<SyncState>,
private readonly dingTalkService: DingTalkService,
private readonly weComService: WeComService,
) {}
// ── Scheduled cron: daily at 2 AM ──
@@ -43,9 +47,10 @@ export class SyncService {
await this.updateLastSyncAt(platform);
await this.finishSyncLog(log, 'success', recordsCount);
this.logger.log(`DingTalk sync complete: ${recordsCount} records`);
} catch (error: any) {
await this.finishSyncLog(log, 'failed', 0, error.message);
this.logger.error(`DingTalk sync failed: ${error.message}`, error.stack);
} catch (error: unknown) {
const message = error instanceof Error ? error.message : String(error);
await this.finishSyncLog(log, 'failed', 0, message);
this.logger.error(`DingTalk sync failed: ${message}`, error instanceof Error ? error.stack : undefined);
}
return log;
@@ -69,9 +74,10 @@ export class SyncService {
await this.updateLastSyncAt(platform);
await this.finishSyncLog(log, 'success', recordsCount);
this.logger.log(`WeCom sync complete: ${recordsCount} records`);
} catch (error: any) {
await this.finishSyncLog(log, 'failed', 0, error.message);
this.logger.error(`WeCom sync failed: ${error.message}`, error.stack);
} catch (error: unknown) {
const message = error instanceof Error ? error.message : String(error);
await this.finishSyncLog(log, 'failed', 0, message);
this.logger.error(`WeCom sync failed: ${message}`, error instanceof Error ? error.stack : undefined);
}
return log;
@@ -86,7 +92,7 @@ export class SyncService {
// ── Sync log queries ──
async getLogs(platform?: SyncPlatform, limit: number = 50): Promise<SyncLog[]> {
const where: any = {};
const where: Record<string, SyncPlatform> = {};
if (platform) where.platform = platform;
return this.syncLogRepo.find({ where, order: { createdAt: 'DESC' }, take: limit });
}
@@ -145,36 +151,13 @@ export class SyncService {
await this.syncLogRepo.save(log);
}
// ── Integration stubs — replace with real API calls ──
/**
* Perform the actual DingTalk data pull.
* Pass `lastSyncAt` to the DingTalk API for incremental sync.
*/
private async performDingTalkSync(_lastSyncAt: Date | null): Promise<number> {
const appKey = process.env.DINGTALK_APP_KEY;
if (!appKey) {
this.logger.warn('DingTalk not configured (DINGTALK_APP_KEY missing), skipping sync');
return 0;
}
// 接入指引:
// 1. 创建 apps/server/src/integration/dingtalk.service.ts实现 fetchDepartments()/fetchUsers()
// 2. 在 SyncModule 中注入 DingTalkService
// 3. 取消以下注释并调用 this.dingTalkService.fetchDepartments()
this.logger.warn('DingTalk sync stub: create DingTalkService in src/integration/ to enable real sync');
return 0;
const result = await this.dingTalkService.syncAll();
return result.deptCount + result.userCount;
}
private async performWeComSync(_lastSyncAt: Date | null): Promise<number> {
const corpId = process.env.WECOM_CORP_ID;
if (!corpId) {
this.logger.warn('WeCom not configured (WECOM_CORP_ID missing), skipping sync');
return 0;
}
this.logger.warn(
'WeCom integration service not yet implemented — add WeComService to SyncModule to enable real sync',
);
return 0;
const result = await this.weComService.syncAll();
return result.deptCount + result.userCount;
}
}