342 lines
10 KiB
TypeScript
Executable File
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);
|
|
}
|
|
}
|