origin/apps/server/src/tasks/init/gendev.service.ts

342 lines
10 KiB
TypeScript
Executable File

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<string, Staff[]> = {};
terms: Record<TaxonomySlug, Term[]> = {
[TaxonomySlug.CATEGORY]: [],
[TaxonomySlug.TAG]: [],
[TaxonomySlug.LEVEL]: [],
};
depts: Department[] = [];
domains: Department[] = [];
domainDepts: Record<string, Department[]> = {};
staffs: Staff[] = [];
deptGeneratedCount = 0;
courseGeneratedCount = 1;
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);
} 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 = 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 }, // 改为高级更合理
];
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);
}
}