import { Injectable, Logger } from '@nestjs/common'; import { DepartmentService } from '@server/models/department/department.service'; import { AppConfigService } from '@server/models/app-config/app-config.service'; import { TermService } from '@server/models/term/term.service'; import { db, Department, getRandomElement, getRandomElements, PostType, Staff, TaxonomySlug, Term, TrainContent, } from '@nice/common'; import EventBus from '@server/utils/event-bus'; import { capitalizeFirstLetter, DevDataCounts, getCounts } from './utils'; import { StaffService } from '@server/models/staff/staff.service'; import dayjs from 'dayjs'; import { TrainContentType } from '@nice/common'; @Injectable() export class GenDevService { private readonly logger = new Logger(GenDevService.name); counts = {} as DevDataCounts; deptStaffRecord: Record = {}; terms: Record = { [TaxonomySlug.CATEGORY]: [], [TaxonomySlug.TAG]: [], [TaxonomySlug.LEVEL]: [], }; depts: Department[] = []; domains: Department[] = []; domainDepts: Record = {}; staffs: Staff[] = []; trainContents: TrainContent[] = []; deptGeneratedCount = 0; courseGeneratedCount = 1; trainContentGeneratedCount = 0; constructor( private readonly appConfigService: AppConfigService, private readonly departmentService: DepartmentService, private readonly staffService: StaffService, private readonly termService: TermService, ) {} async genDataEvent() { EventBus.emit('genDataEvent', { type: 'start' }); try { await this.calculateCounts(); await this.generateDepartments(3, 6); //await this.generateTerms(2, 6); await this.generateStaffs(4); //await this.generateCourses(8); await this.generateTrainContent(2,6) await this.generateTrainSituations() } catch (err) { this.logger.error(err); } EventBus.emit('genDataEvent', { type: 'end' }); } private async calculateCounts() { this.counts = await getCounts(); Object.entries(this.counts).forEach(([key, value]) => { this.logger.log(`${capitalizeFirstLetter(key)} count: ${value}`); }); } private async generateTerms(depth: number = 2, count: number = 10) { if (this.counts.termCount === 0) { this.logger.log('Generate terms'); await this.createTerms(null, TaxonomySlug.CATEGORY, depth, count); await this.createLevelTerm(); const domains = this.depts.filter((item) => item.isDomain); for (const domain of domains) { await this.createTerms(domain, TaxonomySlug.CATEGORY, depth, count); // await this.createTerms(domain, TaxonomySlug.UNIT, depth, count); } } const termCount = await db.term.count(); this.logger.log(`terms ${termCount} generated`); } private async generateDepartments(depth: number = 3, count: number = 6) { if (this.counts.deptCount !== 0) return; const totalDepts = this.calculateTotalDepartments(depth, count); this.logger.log('Starting department generation...'); await this.generateSubDepartments(null, 1, depth, count, totalDepts); this.depts = await db.department.findMany(); this.domains.forEach((domain) => { this.domainDepts[domain.id] = this.getAllChildDepartments(domain.id); this.logger.log( `Domain: ${domain.name} has ${this.domainDepts[domain.id].length} child departments`, ); }); this.logger.log(`Completed: Generated ${this.depts.length} departments.`); } private async generateSubDepartments( parentId: string | null, currentDepth: number, maxDepth: number, count: number, total: number, ) { if (currentDepth > maxDepth) return; for (let i = 0; i < count; i++) { const deptName = `${parentId?.slice(0, 6) || '根'}公司${currentDepth}-${i}`; const newDept = await this.createDepartment( deptName, parentId, currentDepth, ); if (newDept.isDomain) { this.domains.push(newDept); } this.deptGeneratedCount++; this.logger.log( `Generated ${this.deptGeneratedCount}/${total} departments`, ); await this.generateSubDepartments( newDept.id, currentDepth + 1, maxDepth, count, total, ); } } // Helper function to calculate the total number of departments to be generated private calculateTotalDepartments(depth: number, count: number): number { // The total number of departments is the sum of departments at each level. let total = 0; for (let i = 1; i <= depth; i++) { total += Math.pow(count, i); } return total; } private getAllChildDepartments(domainId: string): Department[] { const children: Department[] = []; const collectChildren = (parentId: string) => { const directChildren = this.depts.filter( (dept) => dept.parentId === parentId, ); children.push(...directChildren); directChildren.forEach((child) => { collectChildren(child.id); }); }; collectChildren(domainId); return children; } private async generateCourses(countPerCate: number = 3) { const titleList = [ '计算机科学导论', '数据结构与算法', '网络安全', '机器学习', '数据库管理系统', 'Web开发', '移动应用开发', '人工智能', '计算机网络', '操作系统', '数字信号处理', '无线通信', '信息论', '密码学', '计算机图形学', ]; if (!this.counts.courseCount) { this.logger.log('Generating courses...'); const depts = await db.department.findMany({ select: { id: true, name: true }, }); const cates = await db.term.findMany({ where: { taxonomy: { slug: TaxonomySlug.CATEGORY }, }, select: { id: true, name: true }, }); const total = cates.length * countPerCate; const levels = await db.term.findMany({ where: { taxonomy: { slug: TaxonomySlug.LEVEL }, }, select: { id: true, name: true }, }); for (const cate of cates) { for (let i = 0; i < countPerCate; i++) { const randomTitle = `${titleList[Math.floor(Math.random() * titleList.length)]} ${Math.random().toString(36).substring(7)}`; const randomLevelId = levels[Math.floor(Math.random() * levels.length)].id; const randomDeptId = depts[Math.floor(Math.random() * depts.length)].id; await this.createCourse( randomTitle, randomDeptId, cate.id, randomLevelId, ); this.courseGeneratedCount++; this.logger.log( `Generated ${this.courseGeneratedCount}/${total} course`, ); } } } } private async generateStaffs(countPerDept: number = 2) { if (this.counts.staffCount === 1) { this.logger.log('Generating staffs...'); // Calculate the total number of staffs to be generated const totalStaffs = this.domains.reduce((sum, domain) => { return sum + (this.domainDepts[domain.id]?.length || 0) * countPerDept; }, 0); let staffsGenerated = 0; for (const domain of this.domains) { for (const dept of this.domainDepts[domain.id]) { if (!this.deptStaffRecord[dept.id]) { this.deptStaffRecord[dept.id] = []; } for (let i = 0; i < countPerDept; i++) { const username = `${dept.name}-S${staffsGenerated.toString().padStart(4, '0')}`; const staff = await this.staffService.create({ data: { showname: username, username: username, deptId: dept.id, domainId: domain.id, }, }); // Update both deptStaffRecord and staffs array this.deptStaffRecord[dept.id].push(staff); staffsGenerated++; // Log the progress after each staff is created this.logger.log( `Generated ${staffsGenerated}/${totalStaffs} staffs`, ); } } } } } private async createLevelTerm() { try { // 1. 获取分类时添加异常处理 const taxLevel = await db.taxonomy.findFirst({ where: { slug: TaxonomySlug.LEVEL }, }); if (!taxLevel) { throw new Error('LEVEL taxonomy not found'); } // 2. 使用数组定义初始化数据 + 名称去重 const termsToCreate = [ { name: '初级', taxonomyId: taxLevel.id }, { name: '中级', taxonomyId: taxLevel.id }, { name: '高级', taxonomyId: taxLevel.id }, // 改为高级更合理 ]; for (const termData of termsToCreate) { await this.termService.create({ data: termData, }); } console.log('created level terms'); } catch (error) { console.error('Failed to create level terms:', error); throw error; // 向上抛出错误供上层处理 } } private async createCourse( title: string, deptId: string, cateId: string, levelId: string, ) { const course = await db.post.create({ data: { type: PostType.COURSE, title: title, updatedAt: dayjs().toDate(), depts: { connect: { id: deptId, }, }, terms: { connect: [cateId, levelId].map((id) => ({ id: id, })), }, }, }); return course; } private async createDepartment( name: string, parentId?: string | null, currentDepth: number = 1, ) { const department = await this.departmentService.create({ data: { name, isDomain: currentDepth === 1 ? true : false, parentId, }, }); return department; } private async createTerms( domain: Department | null, taxonomySlug: TaxonomySlug, depth: number, nodesPerLevel: number, ) { const taxonomy = await db.taxonomy.findFirst({ where: { slug: taxonomySlug }, }); let counter = 1; const createTermTree = async ( parentId: string | null, currentDepth: number, ) => { if (currentDepth > depth) return; for (let i = 0; i < nodesPerLevel; i++) { const name = `${taxonomySlug}-${domain?.name || 'public'}-${currentDepth}-${counter++} `; const newTerm = await this.termService.create({ data: { name, taxonomyId: taxonomy!.id, domainId: domain?.id, parentId, }, }); this.terms[taxonomySlug].push(newTerm); await createTermTree(newTerm.id, currentDepth + 1); } }; // Start creating the tree from root level await createTermTree(null, 1); } // 生成培训内容 private async createTrainContent( type:string, title:string, parentId:string|null ){ const trainContent = await db.trainContent.create({ data: { type, title, parentId }, }); return trainContent; } // 生成培训内容 private async generateTrainContent(depth:number=3,count:number=6){ if(this.counts.trainContentCount !== 0) return; const totalTrainContent = this.calculateTotalTrainContent(depth,count) this.logger.log("Start generating train content...") await this.generateSubTrainContent(null,1,depth,count,totalTrainContent) this.trainContents = await db.trainContent.findMany() this.logger.log(`Completed: Generated ${this.trainContents.length} departments.`); } // 生成培训内容子内容 private async generateSubTrainContent( parentId: string | null, currentDepth:number, maxDepth:number, count:number, total:number, ){ if(currentDepth > maxDepth) return; const contentType = [TrainContentType.SUBJECTS,TrainContentType.COURSE] for(let i = 0 ; i < count ; i++){ const trainContentTitle = `${parentId?.slice(0,6) || '根'}公司${currentDepth}-${i}` const newTrainContent = await this.createTrainContent( contentType[currentDepth-1], trainContentTitle, parentId ) this.trainContentGeneratedCount++; this.logger.log( `Generated ${this.trainContentGeneratedCount}/${total} train contents` ) await this.generateSubTrainContent( newTrainContent.id, currentDepth+1, maxDepth, count, total ) } } private calculateTotalTrainContent(depth:number,count:number):number{ let total = 0; for(let i = 1 ; i<=depth;i++){ total += Math.pow(count,i); } return total; } private async createTrainSituation(staffId:string,trainContentId:string){ const trainSituation = await db.trainSituation.create({ data:{ staffId, trainContentId, mustTrainTime:Math.floor(Math.random()*100), alreadyTrainTime:Math.floor(Math.random()*100), score:Math.floor(Math.random()*100), } }) return trainSituation } private async generateTrainSituations(probability: number = 0.7){ this.logger.log("Start generating train situations...") const allTrainContents = await db.trainContent.findMany(); // 这里相当于两次遍历 找到没有parentID的即是 const leafNodes = allTrainContents.filter((item)=>item.parentId !== null) console.log(leafNodes.length) const staffs = await db.staff.findMany() let situationCount = 0 const totalPossibleSituations = leafNodes.length * staffs.length for (const staff of staffs){ for(const leaf of leafNodes){ if(Math.random() < probability){ await this.createTrainSituation(staff.id,leaf.id) situationCount++ if (situationCount % 100 === 0) { this.logger.log( `Generated ${situationCount} train situations` ); } } } } this.logger.log( `Completed: Generated ${situationCount} train situations out of ${totalPossibleSituations} possible combinations.` ); } }