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, } 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'; @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[] = []; deptGeneratedCount = 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(); } 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.logger.log( `Generated ${this.deptGeneratedCount}/${total} departments`, ); } } } } private async generateStaffs(countPerDept: number = 3) { 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 }, // 改为高级更合理 ]; await this.termService.createMany({ data: termsToCreate, }); 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); } }