From 3e24b97a64a99fbd6502873d469a9ed5b0592c0e Mon Sep 17 00:00:00 2001 From: gray Date: Wed, 10 Aug 2022 16:29:50 +0800 Subject: [PATCH 1/2] Add save user answer --- src/app.module.ts | 19 +++-- src/config/config.ts | 2 +- src/controllers/index.ts | 4 +- ...e.controller.ts => language.controller.ts} | 43 +++++----- src/controllers/questionAnswer.controller.ts | 28 +++++++ src/controllers/questionBank.controller.ts | 81 +++++++++++++++++++ src/dtos/code.ts | 7 +- src/entities/index.ts | 1 + src/entities/questionAnswer.entity.ts | 19 +++++ src/entities/questionBank.entity.ts | 19 +++-- src/models/questionBank.model.ts | 13 +++ src/services/index.ts | 4 +- .../{code.service.ts => language.service.ts} | 57 ++++++++----- src/services/questionAnswer.service.ts | 26 ++++++ src/services/questionBank.service.ts | 46 +++++++++++ 15 files changed, 313 insertions(+), 56 deletions(-) rename src/controllers/{code.controller.ts => language.controller.ts} (64%) create mode 100644 src/controllers/questionAnswer.controller.ts create mode 100644 src/controllers/questionBank.controller.ts create mode 100644 src/entities/questionAnswer.entity.ts create mode 100644 src/models/questionBank.model.ts rename src/services/{code.service.ts => language.service.ts} (55%) create mode 100644 src/services/questionAnswer.service.ts create mode 100644 src/services/questionBank.service.ts diff --git a/src/app.module.ts b/src/app.module.ts index 6973f2c..e830d53 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -12,7 +12,9 @@ import { TimeSheetController, UserController, InformController, - CodeController, + LanguageController, + QuestionBankController, + QuestionAnswerController, } from './controllers'; import { jwtModuleOptions, @@ -29,15 +31,17 @@ import { UserService, InformService, TimeSheetService, - CodeService, + LanguageService, + QuestionBankService, + QuestionAnswerService, } from './services'; import { JwtStrategy, WsGuard } from './strategys'; import { DataResource, DataDepartment, UserTimesheet, - Language, QuestionBank, + QuestionAnswer, } from './entities'; @Module({ @@ -52,6 +56,7 @@ import { DataDepartment, UserTimesheet, QuestionBank, + QuestionAnswer, ]), ], controllers: [ @@ -62,7 +67,9 @@ import { TimeSheetController, UserController, InformController, - CodeController, + LanguageController, + QuestionAnswerController, + QuestionBankController, ], providers: [ WsGuard, @@ -73,7 +80,9 @@ import { AuthService, UserService, InformService, - CodeService, + LanguageService, + QuestionBankService, + QuestionAnswerService, TimeSheetService, TimeSheetSocket, TimeSheetSchedule, diff --git a/src/config/config.ts b/src/config/config.ts index 0ed3b8f..fdeeb29 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -7,7 +7,7 @@ interface IConfig { environment: ServerEnvironment; }; services: { - codeService: string; + languageService: string; }; dingTalk: { bossId: string; diff --git a/src/controllers/index.ts b/src/controllers/index.ts index 1eb4959..48e0f8c 100644 --- a/src/controllers/index.ts +++ b/src/controllers/index.ts @@ -4,4 +4,6 @@ export * from './auth.controller'; export * from './timesheet.controller'; export * from './user.controller'; export * from './inform.controller'; -export * from './code.controller'; +export * from './language.controller'; +export * from './questionBank.controller'; +export * from './questionAnswer.controller'; diff --git a/src/controllers/code.controller.ts b/src/controllers/language.controller.ts similarity index 64% rename from src/controllers/code.controller.ts rename to src/controllers/language.controller.ts index ceecd9f..02e238e 100644 --- a/src/controllers/code.controller.ts +++ b/src/controllers/language.controller.ts @@ -1,22 +1,34 @@ -import { ICodeRunBody, IQuestionDto } from '@dtos/code'; -import { Controller, UseGuards, Post, Body, Get, Param } from '@nestjs/common'; +import { ICodeRunBody } from '@dtos/code'; +import { + Controller, + UseGuards, + Post, + Body, + Get, + Request, +} from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; -import { CodeService } from '@services/code.service'; +import { LanguageService } from '@services/language.service'; import recorder from '@utils/recorder'; import { sleep } from '@utils/utils'; import { v4 as uuid } from 'uuid'; import { ICodeLanguageDto } from '@dtos/code'; +import { NestRes } from '@interfaces/nestbase'; const MAX_WAITING_TIME = 360; @UseGuards(AuthGuard('jwt')) -@Controller('code') -export class CodeController { - constructor(private readonly codeService: CodeService) {} +@Controller('language') +export class LanguageController { + constructor(private readonly languageService: LanguageService) {} @Post('run/case') - async runByCase(@Body() body: ICodeRunBody): Promise { + async runByCase( + @Body() body: ICodeRunBody, + @Request() req: NestRes, + ): Promise { const id = uuid(); let waitTime = 0; let result = null; + body.userId = req.user.userId; recorder.add({ id: id, @@ -42,7 +54,7 @@ export class CodeController { return ''; } - return await this.codeService.runCodeByCase(body); + return await this.languageService.runCodeByCase(body); } @Post('run') @@ -71,12 +83,12 @@ export class CodeController { async runImplement(body: ICodeRunBody) { const { code, languageId } = body; - return await this.codeService.run(languageId, code); + return await this.languageService.run(languageId, code); } @Get('languages') async getLanguages() { - const data = await this.codeService.getLanguages(); + const data = await this.languageService.getLanguageList(); return data.map((x) => { return { id: x.id, @@ -86,15 +98,4 @@ export class CodeController { } as ICodeLanguageDto; }); } - - @Get('question/:questionId') - async getQuestion(@Param('questionId') questionId: string) { - const data = await this.codeService.getQuestion(questionId); - return { - id: data.id, - name: data.name, - desribe: data.describe, - code: data.code, - } as IQuestionDto; - } } diff --git a/src/controllers/questionAnswer.controller.ts b/src/controllers/questionAnswer.controller.ts new file mode 100644 index 0000000..8847a7d --- /dev/null +++ b/src/controllers/questionAnswer.controller.ts @@ -0,0 +1,28 @@ +import { Controller, UseGuards, Get, Param, Request } from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; +import { NestRes } from '@interfaces/nestbase'; +import { QuestionAnswerService } from '@services/questionAnswer.service'; + +@UseGuards(AuthGuard('jwt')) +@Controller('answer') +export class QuestionAnswerController { + constructor(private readonly questionAnswerService: QuestionAnswerService) {} + + @Get('/last/:questionId/:languageId') + async getUserLastQuestionAnswer( + @Param('questionId') questionId: string, + @Param('languageId') languageId: number, + @Request() req: NestRes, + ) { + const data = await this.questionAnswerService.getUserLastQuestionAnswer( + questionId, + languageId, + req.user.userId, + ); + + if (data.length > 0) { + return data[0].code; + } + return null; + } +} diff --git a/src/controllers/questionBank.controller.ts b/src/controllers/questionBank.controller.ts new file mode 100644 index 0000000..073f800 --- /dev/null +++ b/src/controllers/questionBank.controller.ts @@ -0,0 +1,81 @@ +import { IQuestionDto } from '@dtos/code'; +import { + Controller, + UseGuards, + Post, + Body, + Get, + Param, + Put, + Request, + HttpException, + HttpStatus, +} from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; +import { + ICreateQuestionBankBody, + IUpdateQuestionBankBody, +} from '@models/questionBank.model'; +import { QuestionBankService } from '@services/questionBank.service'; +import { NestRes } from '@interfaces/nestbase'; +import { QuestionAnswerService } from '@services/questionAnswer.service'; + +@UseGuards(AuthGuard('jwt')) +@Controller('question') +export class QuestionBankController { + constructor( + private readonly questionAnswerService: QuestionAnswerService, + private readonly questionBankService: QuestionBankService, + ) {} + + @Get('/list') + async getQuestionList() { + const data = await this.questionBankService.getQuestionList(); + return data.map((x) => { + return { + id: x.id, + name: x.name, + createTime: x.createTime, + level: x.level, + } as IQuestionDto; + }); + } + + @Get('/:questionId') + async getQuestion(@Param('questionId') questionId: string) { + const data = await this.questionBankService.getQuestion(questionId); + return { + id: data.id, + name: data.name, + desribe: data.describe, + entrys: data.entryCodes, + } as IQuestionDto; + } + + verifyQuestion(question: ICreateQuestionBankBody) { + try { + JSON.parse(JSON.stringify(question.cases)); + JSON.parse(JSON.stringify(question.entryCodes)); + } catch (error) { + throw new HttpException(JSON.stringify(error), HttpStatus.BAD_REQUEST); + } + } + + @Post('/add') + async createQuestion( + @Body() body: ICreateQuestionBankBody, + @Request() req: NestRes, + ) { + this.verifyQuestion(body); + return await this.questionBankService.createQuestion(body); + } + + @Put('update') + async updateQuestion( + @Body() body: IUpdateQuestionBankBody, + @Request() req: NestRes, + ) { + this.verifyQuestion(body); + return await this.questionBankService.updateQuestion(body); + } +} diff --git a/src/dtos/code.ts b/src/dtos/code.ts index 3707b98..b3d7215 100644 --- a/src/dtos/code.ts +++ b/src/dtos/code.ts @@ -1,8 +1,11 @@ +import { IEntryCode } from '@entities/questionBank.entity'; + export interface ICodeRunBody { once?: boolean; code: string; questionId: string; languageId: number; + userId: string; } export interface ICodeLanguageDto { @@ -16,7 +19,9 @@ export interface IQuestionDto { id: string; name: string; desribe: string; - code: string; + createTime: string; + level: number; + entrys: IEntryCode[]; } export interface ICodeRunResult { diff --git a/src/entities/index.ts b/src/entities/index.ts index d9a35e0..6b48edf 100644 --- a/src/entities/index.ts +++ b/src/entities/index.ts @@ -3,3 +3,4 @@ export * from './data.permission.entity'; export * from './timesheet.enetity'; export * from './language.entity'; export * from './questionBank.entity'; +export * from './questionAnswer.entity'; diff --git a/src/entities/questionAnswer.entity.ts b/src/entities/questionAnswer.entity.ts new file mode 100644 index 0000000..5739e76 --- /dev/null +++ b/src/entities/questionAnswer.entity.ts @@ -0,0 +1,19 @@ +import { Column, Entity } from 'typeorm'; + +@Entity({ name: 'question_answer' }) +export class QuestionAnswer { + @Column({ primary: true, generated: 'uuid' }) + id: string; + @Column('varchar', { length: 50, nullable: true }) + questionId: string; + @Column('int2', { nullable: true }) + languageId: number; + @Column('varchar', { length: 50, nullable: true }) + userId: string; + @Column('text', { nullable: true }) + code: string; + @Column('text', { nullable: true }) + result: string; + @Column('timestamp') + createTime: string; +} diff --git a/src/entities/questionBank.entity.ts b/src/entities/questionBank.entity.ts index aee786c..273daac 100644 --- a/src/entities/questionBank.entity.ts +++ b/src/entities/questionBank.entity.ts @@ -6,22 +6,27 @@ export class QuestionBank { id: string; @Column('varchar', { length: 100 }) name: string; // 题目名称 - @Column('varchar', { length: 50 }) - entry: string; // 入口方法 - @Column('text') - code: string; // 默认填充代码 - @Column('text') + @Column('smallint', { nullable: true }) + level: number; // 难度级别 + @Column('jsonb', { array: false, default: () => "'[]'", nullable: true }) + entryCodes: IEntryCode[]; // 默认填充代码 + @Column('text', { nullable: true }) describe?: string; // 题目描述 @Column('jsonb', { array: false, default: () => "'[]'", nullable: true }) cases?: IQuestionCase[]; // 测试cases - @Column('jsonb', { array: false, default: () => "'[]'", nullable: true }) - contributor?: string[]; // 贡献者 可以多个 @Column('date') createTime: string; } export class IQuestionCase { + languageId: number; comments: string; input: any; output: any; } + +export class IEntryCode { + languageId: number; + function: string; // 入口方法 + code: string; +} diff --git a/src/models/questionBank.model.ts b/src/models/questionBank.model.ts new file mode 100644 index 0000000..c7f1ccc --- /dev/null +++ b/src/models/questionBank.model.ts @@ -0,0 +1,13 @@ +import { IEntryCode, IQuestionCase } from '@entities/questionBank.entity'; + +export interface ICreateQuestionBankBody { + name: string; + level?: number; + describe?: string; + entryCodes?: IEntryCode[]; + cases?: IQuestionCase[]; +} + +export interface IUpdateQuestionBankBody extends ICreateQuestionBankBody { + id: string; +} diff --git a/src/services/index.ts b/src/services/index.ts index faae119..66c31fc 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -5,4 +5,6 @@ export * from './auth.service'; export * from './user.service'; export * from './inform.service'; export * from './timesheet.service'; -export * from './code.service'; +export * from './language.service'; +export * from './questionBank.service'; +export * from './questionAnswer.service'; diff --git a/src/services/code.service.ts b/src/services/language.service.ts similarity index 55% rename from src/services/code.service.ts rename to src/services/language.service.ts index 546a555..2aeb9f0 100644 --- a/src/services/code.service.ts +++ b/src/services/language.service.ts @@ -1,34 +1,31 @@ import config from '@config/config'; import { ICodeRunResult, ICodeRunBody, IRunCaseResult } from '@dtos/code'; -import { IQuestionCase, QuestionBank } from '@entities/questionBank.entity'; +import { QuestionAnswer } from '@entities/questionAnswer.entity'; +import { IQuestionCase } from '@entities/questionBank.entity'; import { LanguageModel } from '@models/language.model'; import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { httpGet, httpPost } from '@utils/httpRequest'; -import { matchCaseResult } from '@utils/utils'; +import { matchCaseResult, now } from '@utils/utils'; import { Repository } from 'typeorm'; +import { QuestionBankService } from './questionBank.service'; @Injectable() -export class CodeService { +export class LanguageService { constructor( - @InjectRepository(QuestionBank) - private readonly questionRepository: Repository, + @InjectRepository(QuestionAnswer) + private readonly answerRepository: Repository, + private readonly questionBankService: QuestionBankService, ) {} - async getQuestion(questionId: string) { - return await this.questionRepository.findOneBy({ - id: questionId, - }); - } - async getLanguage(languageId: number) { - const languages = await this.getLanguages(); + const languages = await this.getLanguageList(); return languages.find((x) => x.id == languageId); } - async getLanguages() { + async getLanguageList() { return await httpGet( - `${config.services.codeService}/languages`, + `${config.services.languageService}/languages`, ); } @@ -42,6 +39,7 @@ export class CodeService { .replace('${entry}', entry) .replace('${input}', cases.input); code += testCaseCode; + return code; } @@ -54,37 +52,58 @@ export class CodeService { } async runCodeByCase(params: ICodeRunBody) { - const { code, questionId, languageId, once } = params; + const { code, questionId, languageId, once, userId } = params; - const question = await this.getQuestion(questionId); + const question = await this.questionBankService.getQuestion(questionId); const language = await this.getLanguage(languageId); + const entry = question.entryCodes.find((x) => x.languageId === languageId); + const cases = question.cases.filter((x) => x.languageId === languageId); const result = [] as IRunCaseResult[]; - for (const testcase of question.cases) { + const recordResults = []; + for (const testcase of cases) { const codeCommand = await this.prepareCodeCase( language, code, - question.entry, + entry.function, testcase, ); const _result = await this.run(languageId, codeCommand); + result.push({ ...testcase, ...this.extractCaseResult(_result.data), logs: _result.data, }); + recordResults.push({ + ...testcase, + ...this.extractCaseResult(_result.data), + }); + if (once || !_result.isSuccess) { break; } } + + await this.answerRepository.save([ + { + userId: userId, + questionId: questionId, + code: code, + languageId: languageId, + createTime: now(), + result: JSON.stringify(recordResults), + }, + ]); + return result; } async run(languageId: number, code: string): Promise { return await httpPost( - `${config.services.codeService}/run`, + `${config.services.languageService}/run`, { body: JSON.stringify({ languageId: languageId, diff --git a/src/services/questionAnswer.service.ts b/src/services/questionAnswer.service.ts new file mode 100644 index 0000000..6e070f5 --- /dev/null +++ b/src/services/questionAnswer.service.ts @@ -0,0 +1,26 @@ +import { QuestionAnswer } from '@entities/questionAnswer.entity'; +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +@Injectable() +export class QuestionAnswerService { + constructor( + @InjectRepository(QuestionAnswer) + private readonly answerRepository: Repository, + ) {} + async getUserLastQuestionAnswer( + questionId: string, + languageId: number, + userId: string, + ) { + return await this.answerRepository.find({ + where: { + questionId: questionId, + languageId: languageId, + userId: userId, + }, + order: { createTime: 'DESC' }, + }); + } +} diff --git a/src/services/questionBank.service.ts b/src/services/questionBank.service.ts new file mode 100644 index 0000000..6d35a23 --- /dev/null +++ b/src/services/questionBank.service.ts @@ -0,0 +1,46 @@ +import { QuestionBank } from '@entities/questionBank.entity'; +import { + ICreateQuestionBankBody, + IUpdateQuestionBankBody, +} from '@models/questionBank.model'; +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +@Injectable() +export class QuestionBankService { + constructor( + @InjectRepository(QuestionBank) + private readonly questionRepository: Repository, + ) {} + + async getQuestion(questionId: string) { + return await this.questionRepository.findOneBy({ + id: questionId, + }); + } + + async getQuestionList() { + return await this.questionRepository.find(); + } + + async updateQuestion(body: IUpdateQuestionBankBody) { + const question = await this.questionRepository.findOneBy({ + id: body.id, + }); + return await this.questionRepository.update(question.id, question); + } + + async createQuestion(body: ICreateQuestionBankBody) { + const data = await this.questionRepository.save([ + { + name: body.name, + level: body.level, + cases: body.cases, + describe: body.describe, + entryCodes: body.entryCodes, + }, + ]); + return data; + } +} From d2fef889696a77cce32fb7bc15576984efca9a5f Mon Sep 17 00:00:00 2001 From: gray Date: Fri, 12 Aug 2022 09:44:20 +0800 Subject: [PATCH 2/2] Add create/update question apis --- src/app.module.ts | 2 + src/constants/resources.ts | 9 ++ src/controllers/questionAnswer.controller.ts | 7 +- src/controllers/questionBank.controller.ts | 37 ++++----- src/entities/index.ts | 1 + src/entities/questionAnswer.entity.ts | 4 + src/entities/questionBank.entity.ts | 4 +- src/entities/questionContributor.entity.ts | 15 ++++ src/models/questionBank.model.ts | 16 ++++ src/services/language.service.ts | 41 ++++++--- src/services/questionAnswer.service.ts | 20 +++-- src/services/questionBank.service.ts | 87 +++++++++++++++++--- src/utils/httpRequest.ts | 1 + 13 files changed, 189 insertions(+), 55 deletions(-) create mode 100644 src/constants/resources.ts create mode 100644 src/entities/questionContributor.entity.ts diff --git a/src/app.module.ts b/src/app.module.ts index e830d53..4d81902 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -42,6 +42,7 @@ import { UserTimesheet, QuestionBank, QuestionAnswer, + QuestionContributor, } from './entities'; @Module({ @@ -57,6 +58,7 @@ import { UserTimesheet, QuestionBank, QuestionAnswer, + QuestionContributor, ]), ], controllers: [ diff --git a/src/constants/resources.ts b/src/constants/resources.ts new file mode 100644 index 0000000..3853d08 --- /dev/null +++ b/src/constants/resources.ts @@ -0,0 +1,9 @@ +export enum Resources { + 'dashboard' = '1', + 'timesheet' = '2', + 'attendance' = '3', + 'questionBank' = '4', + 'codeOnline' = '5', + 'createQuestion' = '6', + 'updateQuestion' = '7', +} diff --git a/src/controllers/questionAnswer.controller.ts b/src/controllers/questionAnswer.controller.ts index 8847a7d..03f94eb 100644 --- a/src/controllers/questionAnswer.controller.ts +++ b/src/controllers/questionAnswer.controller.ts @@ -16,13 +16,10 @@ export class QuestionAnswerController { ) { const data = await this.questionAnswerService.getUserLastQuestionAnswer( questionId, - languageId, req.user.userId, + languageId, ); - if (data.length > 0) { - return data[0].code; - } - return null; + return data?.code || null; } } diff --git a/src/controllers/questionBank.controller.ts b/src/controllers/questionBank.controller.ts index 073f800..e210646 100644 --- a/src/controllers/questionBank.controller.ts +++ b/src/controllers/questionBank.controller.ts @@ -1,4 +1,3 @@ -import { IQuestionDto } from '@dtos/code'; import { Controller, UseGuards, @@ -19,6 +18,7 @@ import { import { QuestionBankService } from '@services/questionBank.service'; import { NestRes } from '@interfaces/nestbase'; import { QuestionAnswerService } from '@services/questionAnswer.service'; +import { Resources } from '@constants/resources'; @UseGuards(AuthGuard('jwt')) @Controller('question') @@ -29,30 +29,23 @@ export class QuestionBankController { ) {} @Get('/list') - async getQuestionList() { - const data = await this.questionBankService.getQuestionList(); - return data.map((x) => { - return { - id: x.id, - name: x.name, - createTime: x.createTime, - level: x.level, - } as IQuestionDto; - }); + async getQuestionList(@Request() req: NestRes) { + const authorized = req.user.resources.includes(Resources.updateQuestion); + return await this.questionBankService.getQuestionList( + req.user.userId, + authorized, + ); } @Get('/:questionId') async getQuestion(@Param('questionId') questionId: string) { - const data = await this.questionBankService.getQuestion(questionId); - return { - id: data.id, - name: data.name, - desribe: data.describe, - entrys: data.entryCodes, - } as IQuestionDto; + return await this.questionBankService.getQuestion(questionId); } verifyQuestion(question: ICreateQuestionBankBody) { + if (!question.name) { + throw new HttpException('题目名称不能为空', HttpStatus.BAD_REQUEST); + } try { JSON.parse(JSON.stringify(question.cases)); JSON.parse(JSON.stringify(question.entryCodes)); @@ -61,21 +54,25 @@ export class QuestionBankController { } } - @Post('/add') + @Post() async createQuestion( @Body() body: ICreateQuestionBankBody, @Request() req: NestRes, ) { this.verifyQuestion(body); + body.userId = req.user.userId; + body.userName = req.user.username; return await this.questionBankService.createQuestion(body); } - @Put('update') + @Put() async updateQuestion( @Body() body: IUpdateQuestionBankBody, @Request() req: NestRes, ) { this.verifyQuestion(body); + body.userId = req.user.userId; + body.userName = req.user.username; return await this.questionBankService.updateQuestion(body); } } diff --git a/src/entities/index.ts b/src/entities/index.ts index 6b48edf..afe4552 100644 --- a/src/entities/index.ts +++ b/src/entities/index.ts @@ -4,3 +4,4 @@ export * from './timesheet.enetity'; export * from './language.entity'; export * from './questionBank.entity'; export * from './questionAnswer.entity'; +export * from './questionContributor.entity'; diff --git a/src/entities/questionAnswer.entity.ts b/src/entities/questionAnswer.entity.ts index 5739e76..c4a21ef 100644 --- a/src/entities/questionAnswer.entity.ts +++ b/src/entities/questionAnswer.entity.ts @@ -14,6 +14,10 @@ export class QuestionAnswer { code: string; @Column('text', { nullable: true }) result: string; + @Column('boolean', { nullable: true }) + isPassed: boolean; + @Column('double precision', { nullable: true }) + elapsedTime: number; @Column('timestamp') createTime: string; } diff --git a/src/entities/questionBank.entity.ts b/src/entities/questionBank.entity.ts index 273daac..1581acf 100644 --- a/src/entities/questionBank.entity.ts +++ b/src/entities/questionBank.entity.ts @@ -14,7 +14,9 @@ export class QuestionBank { describe?: string; // 题目描述 @Column('jsonb', { array: false, default: () => "'[]'", nullable: true }) cases?: IQuestionCase[]; // 测试cases - @Column('date') + @Column('boolean', { nullable: true }) + enabled: boolean; // 是否展示 + @Column('timestamp') createTime: string; } diff --git a/src/entities/questionContributor.entity.ts b/src/entities/questionContributor.entity.ts new file mode 100644 index 0000000..6f37950 --- /dev/null +++ b/src/entities/questionContributor.entity.ts @@ -0,0 +1,15 @@ +import { Column, Entity } from 'typeorm'; + +@Entity({ name: 'question_contributor' }) +export class QuestionContributor { + @Column({ primary: true, generated: 'uuid' }) + id: string; + @Column('varchar', { length: 50, nullable: true }) + questionId: string; + @Column('varchar', { length: 50, nullable: true }) + userId: string; + @Column('varchar', { length: 50, nullable: true }) + userName: string; + @Column('timestamp') + createTime: string; +} diff --git a/src/models/questionBank.model.ts b/src/models/questionBank.model.ts index c7f1ccc..a40d916 100644 --- a/src/models/questionBank.model.ts +++ b/src/models/questionBank.model.ts @@ -1,6 +1,8 @@ import { IEntryCode, IQuestionCase } from '@entities/questionBank.entity'; export interface ICreateQuestionBankBody { + userId?: string; + userName?: string; name: string; level?: number; describe?: string; @@ -10,4 +12,18 @@ export interface ICreateQuestionBankBody { export interface IUpdateQuestionBankBody extends ICreateQuestionBankBody { id: string; + enabled: boolean; +} + +export interface IQuestionListModel { + id: string; + name: string; + createTime: string; + level: number; + describe?: string; + isPassed: boolean; + elapsedTime: number; + cases?: IQuestionCase[]; + entryCodes?: IEntryCode[]; + enabled: boolean; } diff --git a/src/services/language.service.ts b/src/services/language.service.ts index 2aeb9f0..0505e2c 100644 --- a/src/services/language.service.ts +++ b/src/services/language.service.ts @@ -55,12 +55,20 @@ export class LanguageService { const { code, questionId, languageId, once, userId } = params; const question = await this.questionBankService.getQuestion(questionId); + + if (!question.enabled) { + return { isSuccess: false, message: '题目暂时不能作答,请稍后再试' }; + } + const language = await this.getLanguage(languageId); const entry = question.entryCodes.find((x) => x.languageId === languageId); const cases = question.cases.filter((x) => x.languageId === languageId); const result = [] as IRunCaseResult[]; const recordResults = []; + let isPassed = false, + elapsedTime = 0; + for (const testcase of cases) { const codeCommand = await this.prepareCodeCase( language, @@ -86,17 +94,30 @@ export class LanguageService { break; } } + if (!once) { + const passedResult = result.filter((x) => { + elapsedTime += parseFloat(x.elapsedTime); + return ( + JSON.stringify(`${x.output}`.replace(/\s/g, '')) === + JSON.stringify(`${x.codeOutput}`.replace(/\s/g, '')) + ); + }); - await this.answerRepository.save([ - { - userId: userId, - questionId: questionId, - code: code, - languageId: languageId, - createTime: now(), - result: JSON.stringify(recordResults), - }, - ]); + isPassed = passedResult.length === result.length; + + await this.answerRepository.save([ + { + userId: userId, + questionId: questionId, + code: code, + languageId: languageId, + createTime: now(), + result: JSON.stringify(recordResults), + elapsedTime, + isPassed, + }, + ]); + } return result; } diff --git a/src/services/questionAnswer.service.ts b/src/services/questionAnswer.service.ts index 6e070f5..6b2174a 100644 --- a/src/services/questionAnswer.service.ts +++ b/src/services/questionAnswer.service.ts @@ -11,15 +11,21 @@ export class QuestionAnswerService { ) {} async getUserLastQuestionAnswer( questionId: string, - languageId: number, userId: string, + languageId?: number, ) { - return await this.answerRepository.find({ - where: { - questionId: questionId, - languageId: languageId, - userId: userId, - }, + const where = { + questionId, + languageId, + userId, + }; + + if (!languageId) { + delete where.languageId; + } + + return await this.answerRepository.findOne({ + where: where, order: { createTime: 'DESC' }, }); } diff --git a/src/services/questionBank.service.ts b/src/services/questionBank.service.ts index 6d35a23..e0aca02 100644 --- a/src/services/questionBank.service.ts +++ b/src/services/questionBank.service.ts @@ -1,17 +1,24 @@ import { QuestionBank } from '@entities/questionBank.entity'; +import { QuestionContributor } from '@entities/questionContributor.entity'; import { ICreateQuestionBankBody, + IQuestionListModel, IUpdateQuestionBankBody, } from '@models/questionBank.model'; import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; +import { now } from '@utils/utils'; import { Repository } from 'typeorm'; +import { QuestionAnswerService } from './questionAnswer.service'; @Injectable() export class QuestionBankService { constructor( @InjectRepository(QuestionBank) private readonly questionRepository: Repository, + @InjectRepository(QuestionContributor) + private readonly contributorRepository: Repository, + private readonly snswerService: QuestionAnswerService, ) {} async getQuestion(questionId: string) { @@ -20,27 +27,83 @@ export class QuestionBankService { }); } - async getQuestionList() { - return await this.questionRepository.find(); + async getQuestionList(userId: string, showAll: boolean) { + const where = { enabled: true }; + if (showAll) { + delete where.enabled; + } + + const questionList = await this.questionRepository.find({ + where: where, + order: { createTime: 'ASC' }, + }); + + const list = [] as IQuestionListModel[]; + for (const question of questionList) { + const questionAnswer = await this.snswerService.getUserLastQuestionAnswer( + question.id, + userId, + ); + + list.push({ + id: question.id, + name: question.name, + level: question.level, + enabled: question.enabled, + isPassed: questionAnswer?.isPassed, + elapsedTime: questionAnswer?.elapsedTime, + createTime: question.createTime, + }); + } + return list; } async updateQuestion(body: IUpdateQuestionBankBody) { const question = await this.questionRepository.findOneBy({ id: body.id, }); - return await this.questionRepository.update(question.id, question); + + question.cases = body.cases; + question.describe = body.describe; + question.enabled = body.enabled; + question.entryCodes = body.entryCodes; + question.level = body.level; + question.name = body.name; + + const data = await this.questionRepository.update(question.id, question); + await this.contributorRepository.save({ + questionId: question.id, + userId: body.userId, + userName: body.userName, + createTime: now(), + }); + + if (data.affected > 0) { + return question; + } + return null; } async createQuestion(body: ICreateQuestionBankBody) { - const data = await this.questionRepository.save([ - { - name: body.name, - level: body.level, - cases: body.cases, - describe: body.describe, - entryCodes: body.entryCodes, - }, - ]); + const data = await this.questionRepository.save({ + name: body.name, + level: body.level, + cases: body.cases, + describe: body.describe, + entryCodes: body.entryCodes, + createTime: now(), + status: false, + }); + + if (data.id) { + await this.contributorRepository.save({ + questionId: data.id, + userId: body.userId, + userName: body.userName, + createTime: now(), + }); + } + return data; } } diff --git a/src/utils/httpRequest.ts b/src/utils/httpRequest.ts index 1036405..a144af0 100644 --- a/src/utils/httpRequest.ts +++ b/src/utils/httpRequest.ts @@ -1,4 +1,5 @@ import fetch from 'node-fetch'; + export async function httpRequest( input: RequestInfo | URL, init?: RequestInit,