feat:初始化 -融骅

This commit is contained in:
2023-10-17 09:15:30 +08:00
parent c9ff84e6a2
commit 405e152b38
1190 changed files with 138344 additions and 455 deletions

View File

@@ -0,0 +1,73 @@
import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
Query,
} from '@nestjs/common';
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
import { TokenData } from 'src/common/decorator/token-data.decorator';
import { CreateLiveTeachingDto } from '../dto/live-create.dto';
import { UpdateLiveTeachingDto } from '../dto/live-update.dto';
import { LiveClassService } from '../service/live-class.service';
import { PaginationDTO } from '../../online-course/common/pagination.dto';
import { CreateCourseclassifyDto } from 'src/online-course/dto/course-manage/course-classify.dto';
import { updateCourseclassifyDto } from 'src/online-course/dto/course-manage/update-course-classify.dto';
import { DeleteDto } from 'src/online-course/common/delete-dto';
import { get } from 'http';
import { NoAuthToken } from 'src/common/decorator/no-token.decorator';
import {
SearchLsitDto,
SearchNoClassLsitDto,
} from 'src/online-course/dto/course-manage/get-course-list-dto';
import { LiveAnsweringDto } from '../dto/live-question-answer.dto';
@ApiTags('在线教学-学员端')
@ApiBearerAuth()
// @NoAuthToken()
@Controller('live-class')
export class LiveClassController {
constructor(private readonly LiveClassService: LiveClassService) {}
// 获取需要参加的直播列表
@Get()
@ApiOperation({ summary: '获取我的直播列表' })
findLiveForMy(@Query() pageInfo: PaginationDTO, @TokenData() tokenInfo: any) {
return this.LiveClassService.findMyLiveList(pageInfo, tokenInfo);
}
@Get('search')
@ApiOperation({ summary: '搜索我的直播列表' })
searchMyLiveList(
@Query() searchInfo: SearchNoClassLsitDto,
@TokenData() tokenInfo: any,
) {
return this.LiveClassService.searchMyLiveList(searchInfo, tokenInfo);
}
@Get('question/:liveId')
@ApiOperation({ summary: '获取所有试题列表' })
getLiveQuestionList(@Param('liveId') liveId: number) {
return this.LiveClassService.getLiveQuestionList(liveId);
}
@Get('liveStudent/:id')
@ApiOperation({ summary: '参加直播时获取详情' })
getStudentList(@Param('id') id: number, @TokenData() tokenInfo: any) {
return this.LiveClassService.getStudentList(id, tokenInfo);
}
@Get(':id')
@ApiOperation({ summary: '参加直播时获取详情' })
getLiveInfo(@Param('id') id: number, @TokenData() tokenInfo: any) {
return this.LiveClassService.getLiveInfo(id, tokenInfo);
}
@Post('answering')
@ApiOperation({ summary: '回答直播的练习题' })
answeringQuestions(
@Body() answerData: LiveAnsweringDto,
@TokenData() tokenData,
) {
return this.LiveClassService.answeringQuestions(answerData, tokenData);
}
}

View File

@@ -0,0 +1,116 @@
import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
Query,
} from '@nestjs/common';
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
import { TokenData } from 'src/common/decorator/token-data.decorator';
import { CreateLiveTeachingDto } from '../dto/live-create.dto';
import { UpdateLiveTeachingDto } from '../dto/live-update.dto';
import { LiveTeachingService } from '../service/live-teaching.service';
import { PaginationDTO } from '../../online-course/common/pagination.dto';
import { CreateCourseclassifyDto } from 'src/online-course/dto/course-manage/course-classify.dto';
import { updateCourseclassifyDto } from 'src/online-course/dto/course-manage/update-course-classify.dto';
import { DeleteDto } from 'src/online-course/common/delete-dto';
import { get } from 'http';
import { SearchLsitDto } from 'src/online-course/dto/course-manage/get-course-list-dto';
import { NoAuthToken } from 'src/common/decorator/no-token.decorator';
@ApiTags('在线教学')
@ApiBearerAuth()
// @NoAuthToken()
@Controller('live-teaching')
export class LiveTeachingController {
constructor(private readonly LiveTeachingService: LiveTeachingService) {}
// 获取直播列表
@Get()
@ApiOperation({ summary: '获取直播列表' })
findLiveList(@Query() pageInfo: PaginationDTO, @TokenData() tokenInfo: any) {
return this.LiveTeachingService.findLiveList(pageInfo, tokenInfo);
}
@Get('live-publish/:id')
@ApiOperation({ summary: '教员端直播时获取直播详情' })
getLiveInfo(@Param('id') id: number, @TokenData() tokenInfo: any) {
return this.LiveTeachingService.getLiveInfo(id, tokenInfo);
}
// 编辑时获取某个直播的详细信息
@Get(':id')
@NoAuthToken()
@ApiOperation({ summary: '获取某个直播的详细信息' })
findOneInfo(@Param('id') id: number, @TokenData() tokenInfo: any) {
return this.LiveTeachingService.findOneInfo(id, tokenInfo);
}
// 搜索直播名称
@NoAuthToken()
@Post('search')
@ApiOperation({ summary: '搜索直播' })
searchSomeLive(@Body() searchInfo: SearchLsitDto) {
return this.LiveTeachingService.searchSomeLive(searchInfo);
}
// 更新课件的学习信息
@Post()
@ApiOperation({ summary: '新建直播' })
createLive(
@Body() LiveInfo: CreateLiveTeachingDto,
@TokenData() tokenInfo: any,
) {
return this.LiveTeachingService.createLive(LiveInfo, tokenInfo);
}
@Patch()
@ApiOperation({ summary: '更新直播信息' })
updateLive(
@Body() LiveInfo: UpdateLiveTeachingDto,
@TokenData() tokenInfo: any,
) {
return this.LiveTeachingService.updateLive(LiveInfo, tokenInfo);
}
@Delete(':id')
@ApiOperation({ summary: '删除直播' })
deleteLive(@Param('id') id: [string], @TokenData() tokenInfo: any) {
let sId: any = id;
sId = sId.split(',');
return this.LiveTeachingService.deleteLive(sId, tokenInfo);
}
@Post('update')
updateLiveInfo(@Body() data: object) {
return this.LiveTeachingService.updateLiveInfo(data);
}
// -------------------分类模块------------------ //
// 分类查询
@Get('classify')
@ApiOperation({ summary: '查询所有分类' })
getclassifyList() {
return this.LiveTeachingService.getclassifyList();
}
// 新建分类
@Post('classify')
@ApiOperation({ summary: '新增分类' })
createclassify(@Body() createclassify: CreateCourseclassifyDto) {
return this.LiveTeachingService.createclassify(createclassify);
}
// 编辑分类
@Patch('classify/:id')
@ApiOperation({ summary: '编辑分类' })
updateCourseclassify(
@Param('id') id: number,
@Body() updateclassify: updateCourseclassifyDto,
) {
return this.LiveTeachingService.updateclassify(id, updateclassify);
}
// 删除分类
@Post('classify/delete')
@ApiOperation({ summary: '删除分类' })
deleteCourseclassify(@Body() id: DeleteDto) {
return this.LiveTeachingService.deleteclassify(id);
}
}

View File

@@ -0,0 +1,18 @@
import { ApiProperty } from '@nestjs/swagger';
import { isNotEmpty, IsNotEmpty, IsNumber, IsString } from 'class-validator';
import { CreateDto } from 'src/common/dto/create.dto';
export class CreateLiveclassifyDto extends CreateDto {
@ApiProperty({ description: '分类父级ID', default: 0 })
@IsNotEmpty({ message: '分类父级ID不可为空' })
@IsNumber()
pid: number;
@ApiProperty({ description: '分类名称', default: '新增的分类' })
@IsNotEmpty({ message: '分类名称不可为空' })
name: string;
@ApiProperty({ description: '分类层级', default: 1 })
@IsNotEmpty({ message: '分类层级不可为空' })
@IsNumber()
level: number;
}

View File

@@ -0,0 +1,102 @@
import { ApiProperty } from '@nestjs/swagger';
import {
IsArray,
IsDate,
IsNotEmpty,
IsNumber,
isString,
IsString,
} from 'class-validator';
import { CreateDto } from 'src/common/dto/create.dto';
/**
* 直播信息
*/
export class CreateLiveTeachingDto extends CreateDto {
@ApiProperty({ description: '直播标题' })
@IsNotEmpty({ message: '直播标题不可为空' })
@IsString()
title: string;
@ApiProperty({ description: '直播开始时间', default: new Date() })
@IsNotEmpty({ message: '直播开始时间不可为空' })
startTime: Date;
@ApiProperty({ description: '直播真实开始时间', default: new Date() })
actualStartTime: Date;
@ApiProperty({ description: '直播真实结束时间', default: new Date() })
actualEndTime: Date;
@ApiProperty({ description: '直播时长' })
duration: number;
@ApiProperty({ description: '直播分类' })
@IsNotEmpty({ message: '直播分类不可为空' })
@IsNumber()
classify: number;
@ApiProperty({ description: '直播分类' })
@IsString()
explain: string;
@ApiProperty({ description: '直播封面地址' })
cover: string;
@ApiProperty({ description: ' 直播状态 0/1/2 未开始/进行中/已结束' })
status: string;
@ApiProperty({ description: '暖场图片' })
warmUpPicture: string;
@ApiProperty({ description: '暖场音乐' })
warmUpMusice: string;
@ApiProperty({ description: '是否支持回放', default: 0 })
@IsNumber()
playback: number;
@ApiProperty({ description: '回放停止观看时间', default: null })
playbackDeadline: Date;
@ApiProperty({ description: '讲师' })
@IsNotEmpty({ message: '直播讲师不可为空' })
@IsString()
lecturer: string;
@ApiProperty({ description: '是否开启讨论', default: 0 })
@IsNumber()
openDiscussion: number;
@ApiProperty({ description: '是否开启评价', default: 0 })
@IsNumber()
openEval: number;
@ApiProperty({ description: '文本评价或维度评价', default: 0 })
@IsNumber()
txtOrGradeEvealute: number;
@ApiProperty({ description: '文本评价标题', default: '' })
@IsString()
textEvaluateTitle: string;
@ApiProperty({ description: '可以观看直播的学生ID', default: [] })
@IsArray()
liveStudent: [number];
@ApiProperty({ description: '直播的课后试题', default: [] })
@IsArray()
liveQuestion: [number];
@ApiProperty({ description: '直播的评价维度设置', default: [] })
@IsArray()
evalDimension: [
{
dimensionality: string;
type: number;
},
];
@ApiProperty({ description: '直播的课件ID', default: [] })
@IsArray()
liveCourseware: [number];
}

View File

@@ -0,0 +1,22 @@
import { ApiProperty } from '@nestjs/swagger';
import { User } from 'src/system/entities/user.entity';
/**
* 课程评价维度
*/
export class LiveMsgDataDto {
@ApiProperty({ description: '消息发送人' })
from: User;
@ApiProperty({ description: '房间ID' })
roomId: string;
@ApiProperty({ description: '消息类型' })
type: string;
@ApiProperty({ description: '消息' })
msg: string;
@ApiProperty({ description: '发送时间' })
timestamp: number;
}

View File

@@ -0,0 +1,36 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsNumber } from 'class-validator';
import { CreateDto } from 'src/common/dto/create.dto';
/**
* 学员课程课后习题
*/
export class LiveQuestionAnswerDto extends CreateDto {
@ApiProperty({ description: '所属的直播ID' })
@IsNotEmpty({ message: '所属的直播ID不能为空' })
@IsNumber()
liveId: number;
@ApiProperty({ description: '练习题ID' })
@IsNotEmpty({ message: '练习题ID不能为空' })
@IsNumber()
exerciseId: number;
@ApiProperty({ description: '答案信息' })
@IsNotEmpty({ message: '答案信息不可为空' })
answer: string;
@ApiProperty({ description: '是否正确' })
@IsNotEmpty({ message: '是否正确不可为空' })
isRight: number;
}
export class LiveAnsweringDto {
@ApiProperty({ description: '练习题答案信息提交' })
@IsNotEmpty({ message: '答题不可为空' })
answerInfo: [{ questionId: number; answer: [string] }];
@ApiProperty({ description: '当前答案所属的直播id' })
@IsNotEmpty({ message: '当前问题所属的直播id不可为空' })
liveId: number;
@ApiProperty({ description: '练习题创建时间' })
createTime: Date;
}

View File

@@ -0,0 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
import { CreateLiveTeachingDto } from './live-create.dto';
/**
* 直播信息
*/
export class UpdateLiveTeachingDto extends CreateLiveTeachingDto {
@ApiProperty({ description: '需要更新的id' })
id: number;
}

View File

@@ -0,0 +1,28 @@
import { IncrementIdEntity } from 'src/common/entities/increment-id.entity';
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
/**
* 在线教学课件
*/
@Entity()
export class LiveClassify {
@PrimaryGeneratedColumn('increment')
id: number;
@Column({ comment: '父级ID' })
pid: number;
@Column({ comment: '分类名称', type: 'text' })
name: string;
@Column({ comment: '层级' })
level: number;
@Column({
default: 0,
select: false,
name: 'del_flag',
comment: '是否删除0否/1是',
})
delFlag: number;
}

View File

@@ -0,0 +1,25 @@
import { IncrementIdEntity } from 'src/common/entities/increment-id.entity';
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
/**
* 在线教学课件
*/
@Entity()
export class LiveCourseware {
@PrimaryGeneratedColumn('increment')
id: number;
@Column({ name: 'file_id', comment: '课件id' })
fileId: number;
@Column({ name: 'live_id', comment: '直播id' })
liveId: number;
@Column({
default: 0,
select: false,
name: 'del_flag',
comment: '是否删除0否/1是',
})
delFlag: number;
}

View File

@@ -0,0 +1,27 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
/**
* 课程评价维度
*/
@Entity()
export class LiveEvaluate {
@PrimaryGeneratedColumn('increment')
id: number;
@Column({ name: 'coruse_id', comment: '课程ID' })
liveId: number;
@Column({ comment: '维度名称', type: 'text' })
dimensionality: string;
@Column({ comment: '0/1 直播评价或学院自评', default: 0 })
type: number;
@Column({
default: 0,
select: false,
name: 'del_flag',
comment: '是否删除0否/1是',
})
delFlag: number;
}

View File

@@ -0,0 +1,45 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class LiveMsgData {
@PrimaryGeneratedColumn('increment')
id: number;
@Column({ name: 'from_id', type: 'text', comment: '发送人ID' })
fromId: string;
@Column({ name: 'room_id', type: 'text', comment: '房间ID' })
roomId: string;
@Column({ type: 'text', comment: '消息类型' })
type: string;
@Column({ type: 'text', nullable: true, comment: '消息' })
msg: string;
@Column({ type: 'bigint', comment: '发送时间' })
timestamp: number;
@Column({
name: 'total_user',
comment: '当前房间总人数',
default: 0,
})
totalUser: number;
@Column({
type: 'text',
nullable: true,
name: 'mag_srt',
comment: 'string 消息',
})
msgStr: string;
@Column({
default: 0,
select: false,
name: 'del_flag',
comment: '是否删除0否/1是',
})
delFlag: number;
}

View File

@@ -0,0 +1,29 @@
import { IncrementIdEntity } from 'src/common/entities/increment-id.entity';
import { Column, Entity } from 'typeorm';
/**
* 学员课程课后习题
*/
@Entity()
export class LiveQuestionAnswer extends IncrementIdEntity {
@Column({ name: 'record_id', comment: '学习记录Id' })
liveId: number;
@Column({ name: 'question_id', comment: '试题ID' })
questionId: number;
@Column({ name: 'question_score', comment: '该题分值' })
questionScore: number;
@Column({ comment: '我的得分' })
score: number;
@Column({ name: 'mistake_score', comment: '错题得分', default: 0 })
mistakeScore: number;
@Column({ name: 'user_answer', type: 'longtext', comment: '我的答案' })
userAnswer: string;
@Column({ comment: '老师的评语', default: null, type: 'longtext' })
comment: string;
}

View File

@@ -0,0 +1,34 @@
import { IncrementIdEntity } from 'src/common/entities/increment-id.entity';
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
/**
* 在线教学答题
*/
@Entity()
export class LiveQuestion {
@PrimaryGeneratedColumn('increment')
id: number;
@Column({ name: 'live_id', comment: '直播id' })
liveId: number;
@Column({ name: 'question_id', comment: '试题ID对应Question表' })
questionId: number;
@Column({ comment: '该题分值' })
score: number;
@Column({ comment: '题类型' })
type: number;
@Column({ name: 'is_fixed', comment: '固定选题、随机选题' })
isFixed: number;
@Column({
default: 0,
select: false,
name: 'del_flag',
comment: '是否删除0否/1是',
})
delFlag: number;
}

View File

@@ -0,0 +1,25 @@
import { IncrementIdEntity } from 'src/common/entities/increment-id.entity';
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
/**
* 在线教学观众
*/
@Entity()
export class LiveStudent {
@PrimaryGeneratedColumn('increment')
id: number;
@Column({ name: 'student_id', type: 'longtext', comment: '学员id' })
studentId: string;
@Column({ name: 'live_id', comment: '直播id' })
liveId: number;
@Column({
default: 0,
select: false,
name: 'del_flag',
comment: '是否删除0否/1是',
})
delFlag: number;
}

View File

@@ -0,0 +1,95 @@
import { IncrementIdEntity } from 'src/common/entities/increment-id.entity';
import { Column, Entity } from 'typeorm';
/**
* 在线教学
*/
@Entity()
export class LiveTeaching extends IncrementIdEntity {
@Column({ type: 'longtext', comment: '直播名称' })
title: string;
@Column({ name: 'start_time', comment: '开始时间' })
startTime: Date;
@Column({
name: 'actual_start_time',
comment: '真实开始时间',
nullable: true,
default: null,
})
actualStartTime: Date;
@Column({
name: 'actual_end_time',
comment: '真实结束时间',
nullable: true,
default: null,
})
actualEndTime: Date;
@Column({ comment: '直播时长', default: 0 })
duration: number;
@Column({ comment: '直播分类' })
classify: number;
@Column({ comment: '直播简介', type: 'longtext' })
explain: string;
@Column({ comment: '直播封面', type: 'longtext' })
cover: string;
@Column({ comment: '直播状态 0/1/2 未开始/进行中/已结束', default: 0 })
status: number;
@Column({ name: 'warm_up_picture', comment: '暖场图片', type: 'longtext' })
warmUpPicture: string;
@Column({ name: 'warm_up_musice', comment: '暖场音乐', type: 'longtext' })
warmUpMusice: string;
@Column({ comment: '是否回放0/1' })
playback: number;
@Column({
name: 'playback_deadline',
comment: '回放期限',
nullable: true,
default: null,
})
playbackDeadline: Date;
@Column({ name: 'open_quiz', comment: '0/1开启课程问答' })
openQuiz: number;
@Column({ type: 'longtext', comment: '讲师' })
lecturer: string;
@Column({ name: 'open_discussion', comment: '是否开启讨论', default: 0 })
openDiscussion: number;
@Column({
name: 'open_self_evaluation',
default: 0,
comment: '0/1开启学员自评',
})
openSelfEvaluation: number;
@Column({ name: 'open_eval', comment: '是否开启评价', default: 0 })
openEval: number;
@Column({
name: 'txt_or_grade_evealute',
comment: '文本或维度评价',
default: 0,
})
txtOrGradeEvealute: number;
@Column({
type: 'longtext',
name: 'text_evaluate_title',
comment: '文本评价标题',
})
textEvaluateTitle: string;
}

View File

@@ -0,0 +1,109 @@
import { InjectRepository } from '@nestjs/typeorm';
import {
WebSocketGateway,
SubscribeMessage,
OnGatewayDisconnect,
} from '@nestjs/websockets';
import { Socket } from 'socket.io';
import { Repository } from 'typeorm';
import { LiveMsgDataDto } from '../dto/live-msg-data.dto';
import { LiveMsgData } from '../entities/live-msg-data.entity';
import { JwtService } from '@nestjs/jwt';
@WebSocketGateway()
export class OnlineTeachingGateway implements OnGatewayDisconnect {
constructor(
private jwtService: JwtService,
@InjectRepository(LiveMsgData)
private lmdRepository: Repository<LiveMsgData>,
) {}
rooms = {};
handleDisconnect(socket: Socket) {
const authToken: string = socket.handshake?.auth?.token;
const user = this.jwtService.decode(authToken.replace('Bearer ', ''));
console.log(user, this.rooms);
let roomId = null;
Object.keys(this.rooms).forEach((item) => {
if (Object.keys(this.rooms[item].users).includes(user.sub)) {
roomId = item;
}
});
if (user?.sub && roomId) {
const data = {
fromId: user.sub,
roomId: roomId,
type: 'leave',
msg: 'leave',
timestamp: new Date().getTime(),
msgStr: null,
};
// socket.leave(roomId);
// delete this.rooms[roomId]?.users[user.sub];
// this.sendRoomInfo(socket, roomId);
return this.lmdRepository.insert(data);
} else {
return null;
}
}
@SubscribeMessage('join_live_room')
joinLiveRoom(socket: Socket, data: LiveMsgDataDto) {
socket.join(data.roomId);
this.rooms[data.roomId] ??= {};
this.rooms[data.roomId].users ??= {};
this.rooms[data.roomId].users[data.from.id] = data.from;
this.sendRoomInfo(socket, data.roomId);
return this.sendLiveMsg(socket, data);
}
@SubscribeMessage('send_live_msg')
sendLiveMsg(socket: Socket, data: LiveMsgDataDto) {
data.timestamp = new Date().getTime();
socket.to(data.roomId).emit('received_live_msg', data);
this.saveMsg(data);
return data;
}
@SubscribeMessage('leave_live_room')
leaveRoom(socket: Socket, data: LiveMsgDataDto) {
if (Array.isArray(data)) {
data = data[0];
}
socket.leave(data.roomId);
delete this.rooms[data.roomId]?.users[data.from.id];
this.sendRoomInfo(socket, data.roomId);
if (!Object.keys(this.rooms[data.roomId]?.users ?? {})) {
delete this.rooms[data.roomId];
}
this.saveMsg(data);
return this.sendLiveMsg(socket, data);
}
@SubscribeMessage('find_room_info')
findRoomInfo(socket: Socket, roomId: string) {
return {
users: Object.values(this.rooms[roomId]?.users ?? {}),
};
}
async saveMsg(data: LiveMsgDataDto) {
const { from, roomId, type, msg, timestamp, ...other } = data;
console.log(this.rooms);
if (!from?.id) return null;
return this.lmdRepository.insert({
fromId: from.id,
roomId,
type,
msg,
timestamp: timestamp ? timestamp : new Date().getTime(),
totalUser: this.rooms?.[roomId]
? Object.keys(this.rooms[roomId].users).length
: 0,
msgStr: Object.keys(other).length ? JSON.stringify(other) : null,
});
}
sendRoomInfo(socket: Socket, roomId: string) {
socket.to(roomId).emit('room_info', this.findRoomInfo(socket, roomId));
}
}

View File

@@ -0,0 +1,51 @@
import { Module } from '@nestjs/common';
import { LiveTeachingService } from './service/live-teaching.service';
import { LiveTeachingController } from './controller/live-teaching.controller';
import { LiveTeaching } from './entities/live-teaching.entity';
import { TypeOrmModule } from '@nestjs/typeorm';
import { LiveCourseware } from './entities/live-courseware.entity';
import { LiveQuestion } from './entities/live-question.entity';
import { LiveEvaluate } from './entities/live-evaluate.entity';
import { LiveStudent } from './entities/live-student.entity';
import { LiveClassify } from './entities/live-classify.entity';
import { LiveClassController } from './controller/live-class.controller';
import { LiveClassService } from './service/live-class.service';
import { OnlineTeachingGateway } from './gateway/online-teaching.gateway';
import { LiveMsgData } from './entities/live-msg-data.entity';
import { LiveQuestionAnswer } from './entities/live-question-answer.entity';
import { Question } from 'src/assessment-evaluation/entities/entities-question-manager/question.entity';
import { JwtAuthGuard } from 'src/system/jwt/jwt-auth.guard';
import { JwtModule } from '@nestjs/jwt';
import { APP_GUARD } from '@nestjs/core';
@Module({
imports: [
TypeOrmModule.forFeature([
LiveTeaching,
LiveCourseware,
LiveQuestion,
LiveEvaluate,
LiveStudent,
LiveClassify,
LiveMsgData,
LiveQuestion,
LiveQuestionAnswer,
Question,
]),
JwtModule.register({
secret: 'jwtConstants.secret',
signOptions: { expiresIn: '7d' },
}),
],
controllers: [LiveTeachingController, LiveClassController],
providers: [
LiveTeachingService,
LiveClassService,
OnlineTeachingGateway,
{
provide: APP_GUARD,
useClass: JwtAuthGuard,
},
],
})
export class OnlineTeachingModule {}

View File

@@ -0,0 +1,242 @@
import { InjectRepository } from '@nestjs/typeorm';
import { checkAnswer } from 'src/assessment-evaluation/controller/sim-test/mistake-again.controller';
import { QuestionType } from 'src/assessment-evaluation/entities/entities-question-manager/question-type.entity';
import { Question } from 'src/assessment-evaluation/entities/entities-question-manager/question.entity';
import { SearchNoClassLsitDto } from 'src/online-course/dto/course-manage/get-course-list-dto';
import { Resource } from 'src/resource/entities/resource.entity';
import { User } from 'src/system/entities/user.entity';
import { DataSource, Repository } from 'typeorm';
import { LiveAnsweringDto } from '../dto/live-question-answer.dto';
import { LiveClassify } from '../entities/live-classify.entity';
import { LiveCourseware } from '../entities/live-courseware.entity';
import { LiveEvaluate } from '../entities/live-evaluate.entity';
import { LiveQuestionAnswer } from '../entities/live-question-answer.entity';
import { LiveQuestion } from '../entities/live-question.entity';
import { LiveStudent } from '../entities/live-student.entity';
import { LiveTeaching } from '../entities/live-teaching.entity';
export class LiveClassService {
constructor(
// 课程学习记录
private dataSource: DataSource,
@InjectRepository(LiveTeaching)
private Live_Sql: Repository<LiveTeaching>,
@InjectRepository(LiveCourseware)
private LiveCourseware_Sql: Repository<LiveCourseware>,
@InjectRepository(LiveEvaluate)
private LiveEvaluate_Sql: Repository<LiveEvaluate>,
@InjectRepository(LiveQuestion)
private LiveQuestion_Sql: Repository<LiveQuestion>,
@InjectRepository(LiveStudent)
private LiveStudent_Sql: Repository<LiveStudent>,
@InjectRepository(LiveClassify)
private LiveClassify_Sql: Repository<LiveClassify>,
@InjectRepository(LiveQuestionAnswer)
private LiveQuesstionAnswer_Sql: Repository<LiveQuestionAnswer>,
// 试题表
@InjectRepository(Question)
private Questions_sql: Repository<Question>,
) {}
// 查询课程列表
async findMyLiveList(pageInfo, tokenInfo) {
const page = pageInfo.page - 1;
const pageSize = pageInfo.pageSize;
const sql = `
SELECT
huai_li.live_teaching.*,
huai_li.live_teaching.start_time AS startTime,
huai_li.live_teaching.create_time AS createTime,
huai_li.\`user\`.id AS userId,
huai_li.\`user\`.name AS userName
FROM
huai_li.live_student
INNER JOIN
huai_li.live_teaching
ON
huai_li.live_student.live_id = huai_li.live_teaching.id
LEFT JOIN
huai_li.\`user\`
ON
huai_li.live_teaching.lecturer = huai_li.\`user\`.id
WHERE
huai_li.live_student.student_id = ? AND
huai_li.live_student.del_flag = 0 AND
huai_li.live_teaching.del_flag = 0
ORDER BY
huai_li.live_teaching.update_time DESC
LIMIT ?,?
`;
let res = await this.LiveStudent_Sql.query(sql, [
tokenInfo.userId,
+page,
+pageSize,
]);
const total = await this.LiveStudent_Sql.query(
`SELECT FOUND_ROWS() as total;`,
);
// console.log(total);
// res.total = total[0];
res = { data: res, total: +total[0].total };
return res;
}
// 搜索直播
async searchMyLiveList(searchInfo: SearchNoClassLsitDto, tokenInfo) {
const page = searchInfo.page - 1;
const pageSize = searchInfo.pageSize;
const sql = `
SELECT
huai_li.live_teaching.*,
huai_li.live_teaching.start_time AS startTime,
huai_li.live_teaching.create_time AS createTime,
huai_li.\`user\`.id AS userId,
huai_li.\`user\`.name AS userName
FROM
huai_li.live_student
INNER JOIN
huai_li.live_teaching
ON
huai_li.live_student.live_id = huai_li.live_teaching.id
LEFT JOIN
huai_li.\`user\`
ON
huai_li.live_teaching.lecturer = huai_li.\`user\`.id
WHERE
huai_li.live_student.student_id = ? AND
huai_li.live_teaching.title LIKE ? AND
huai_li.live_teaching.del_flag = 0 AND
huai_li.live_student.del_flag = 0
ORDER BY
huai_li.live_teaching.update_time DESC
LIMIT ?, ?
`;
let res = await this.LiveStudent_Sql.query(sql, [
tokenInfo.userId,
`%${searchInfo.value}%`,
+page,
+pageSize,
]);
const total = await this.LiveStudent_Sql.query(
`SELECT FOUND_ROWS() as total;`,
);
// console.log(total);
// res.total = total[0];
res = { data: res, total: +total[0].total };
return res;
}
// 查询直播详情
async getLiveInfo(id, tokenInfo) {
const judgeIsviewerSql = `ls.del_flag = 0 AND ls.liveId = l.id AND ls.studentId = '${tokenInfo.userId}'`;
const res = this.Live_Sql.createQueryBuilder('l')
.innerJoin(LiveStudent, 'ls', judgeIsviewerSql)
.leftJoinAndMapMany(
'l.courseware',
LiveCourseware,
'lc',
'lc.delFlag = 0 AND lc.liveId = l.id',
)
.leftJoinAndMapMany(
'l.evealuate',
LiveEvaluate,
'le',
'le.delFlag = 0 AND le.liveId = l.id',
)
.leftJoinAndMapMany(
'l.question',
LiveQuestion,
'lq',
'lq.delFlag = 0 AND lq.liveId = l.id',
)
.leftJoinAndMapOne('l.lecturerInfo', User, 'u', 'u.id = l.lecturer')
.leftJoinAndMapOne('u.photo', Resource, 'photo', 'photo.id = u.photo')
.where({ id })
.getOne();
return res;
}
// 获取直播学员列表
async getStudentList(id, tokenInfo) {
return await this.dataSource.query(
`select u.id id,u.name name,rr.diskname photo,0 status from (select ls.student_id uid from live_student ls where ls.live_id = ?
union all (select lt.lecturer uid from live_teaching lt where lt.id = ?)) tdd left join user u on u.id = tdd.uid left join resource rr on rr.id = u.photo`,
[id, id],
);
}
/**
* 获取当前直播下所有试题
*/
async getLiveQuestionList(liveId) {
const res = await this.LiveQuestion_Sql.createQueryBuilder('lqs')
.leftJoinAndMapOne('lqs.info', Question, 'q', 'q.id = lqs.question_id')
.leftJoinAndMapOne('q.typeInfo', QuestionType, 'qt', 'qt.id = q.type')
.leftJoinAndMapOne(
'lqs.answer',
LiveQuestionAnswer,
'lqa',
'lqa.question_id = lqs.question_id AND lqa.del_flag = 0 AND lqa.liveId = ' +
liveId,
)
.where({ liveId })
.getMany();
return res;
}
// 回答练习题
async answeringQuestions(
{ answerInfo, liveId, createTime }: LiveAnsweringDto,
TokenData,
) {
const ids = answerInfo.map((_) => _.questionId);
console.log(ids, '====================>');
const questions = await this.Questions_sql.query(
`
SELECT
q.id as id,
q.type as typeId,
q.difficulty_level as difficultyLevelId,
q.classify_id as classifyId,
q.knowledge_id as knowledgeId,
q.title as title,
q.answer as answer ,
q.analysis as analysis,
q.options as options,
qs.score as score
FROM question q LEFT JOIN live_question qs ON q.id = qs.question_id
where qs.live_id = ? AND qs.del_flag =0 AND
q.id IN (?)`,
[liveId, ids],
);
console.log(questions);
const res = { quesInfo: [...questions], score: 0, errorNum: 0 };
questions.forEach((quesItem) => {
const answerItem = answerInfo.find((an) => an.questionId === quesItem.id);
quesItem.questionScore = quesItem.score;
if (answerItem) {
const result = checkAnswer(quesItem, answerItem.answer);
res.score += result.score;
quesItem.isPass = result.pass;
if (result.isPass) res.errorNum++;
quesItem.userAnswer = JSON.stringify(answerItem.answer);
quesItem.score = result.score;
quesItem.createTime = createTime;
quesItem.mistakeScor = 0;
} else {
quesItem.score = 0;
quesItem.createTime = new Date();
quesItem.userAnswer = '';
}
quesItem.comment = '';
quesItem.liveId = liveId;
quesItem.questionId = quesItem.id;
quesItem.creator = TokenData.userId;
quesItem.updater = TokenData.userId;
delete quesItem.id;
});
await this.LiveQuesstionAnswer_Sql.update(
{ liveId, creator: TokenData.userId },
{ delFlag: 1 },
);
console.log(questions);
await this.LiveQuesstionAnswer_Sql.insert(questions);
return res;
}
}

View File

@@ -0,0 +1,240 @@
import { InjectRepository } from '@nestjs/typeorm';
import { Question } from 'src/assessment-evaluation/entities/entities-question-manager/question.entity';
import { CreateCourseclassifyDto } from 'src/online-course/dto/course-manage/course-classify.dto';
import { SearchLsitDto } from 'src/online-course/dto/course-manage/get-course-list-dto';
import { Resource } from 'src/resource/entities/resource.entity';
import { User } from 'src/system/entities/user.entity';
import { Like, Repository } from 'typeorm';
import { UpdateLiveTeachingDto } from '../dto/live-update.dto';
import { LiveClassify } from '../entities/live-classify.entity';
import { LiveCourseware } from '../entities/live-courseware.entity';
import { LiveEvaluate } from '../entities/live-evaluate.entity';
import { LiveQuestion } from '../entities/live-question.entity';
import { LiveStudent } from '../entities/live-student.entity';
import { LiveTeaching } from '../entities/live-teaching.entity';
export class LiveTeachingService {
constructor(
// 课程学习记录
@InjectRepository(LiveTeaching)
private Live_Sql: Repository<LiveTeaching>,
@InjectRepository(LiveCourseware)
private LiveCourseware_Sql: Repository<LiveCourseware>,
@InjectRepository(LiveEvaluate)
private LiveEvaluate_Sql: Repository<LiveEvaluate>,
@InjectRepository(LiveQuestion)
private LiveQuestion_Sql: Repository<LiveQuestion>,
@InjectRepository(LiveStudent)
private LiveStudent_Sql: Repository<LiveStudent>,
@InjectRepository(LiveClassify)
private LiveClassify_Sql: Repository<LiveClassify>,
) {}
// 查询直播列表
async findLiveList(pageInfo, { userId }) {
const page = pageInfo.page;
const pageSize = pageInfo.pageSize;
const res = await this.Live_Sql.createQueryBuilder('live')
.leftJoinAndMapOne(
'live.lecturerInfo',
User,
'user',
'user.id = live.lecturer',
)
.where({
delFlag: 0,
lecturer: userId,
})
.orderBy('live.updateTime', 'DESC')
.skip((page - 1) * pageSize)
.take(pageSize)
.getManyAndCount();
return {
data: res[0],
total: res[1],
};
}
// 新建直播
async createLive(liveData: any, tokenData) {
liveData.creator = tokenData.userId;
liveData.updater = tokenData.userId;
const res = await this.Live_Sql.save(liveData);
const liveId = res.id;
const courseware = liveData.liveCourseware.map((ware) => {
return { fileId: ware.fileId, liveId };
});
const evealuate = liveData.evalDimension.map((eveal) => {
return {
liveId,
dimensionality: eveal.dimensionality,
type: eveal.type,
};
});
const question = liveData.liveQuestion.map((lq) => {
delete lq.liveId;
return {
liveId,
...lq,
};
});
const students = liveData.liveStudent.map((id) => {
return {
studentId: id,
liveId,
};
});
this.LiveCourseware_Sql.insert(courseware);
this.LiveEvaluate_Sql.insert(evealuate);
this.LiveQuestion_Sql.insert(question);
this.LiveStudent_Sql.insert(students);
return res;
}
// 搜索直播
async searchSomeLive(searchInfo: SearchLsitDto) {
const page = searchInfo.page - 1;
const pageSize = searchInfo.pageSize;
const res: any = await this.Live_Sql.createQueryBuilder('live')
.leftJoinAndMapOne(
'live.lecturerInfo',
User,
'user',
'user.id = live.lecturer',
)
.where({
delFlag: 0,
title: Like(`%${searchInfo.value}%`),
})
.skip(page * pageSize)
.take(pageSize)
.getManyAndCount();
return {
total: res[1],
data: res[0],
};
}
// 编辑直播时某个直播的详细信息
async findOneInfo(id, tokenInfo) {
const res = this.Live_Sql.createQueryBuilder('live')
.leftJoinAndMapMany(
'live.liveCourseware',
LiveCourseware,
'lcs',
`lcs.liveId = live.id AND lcs.del_flag = 0`,
)
.leftJoinAndMapOne(
'live.lecturerInfo',
User,
'user',
'user.id = live.lecturer',
)
.leftJoinAndMapMany(
'live.evalDimension',
LiveEvaluate,
'le',
'le.liveId = live.id AND le.del_flag = 0',
)
.leftJoinAndMapMany(
'live.liveQuestion',
LiveQuestion,
'lq',
'lq.liveId = live.id AND lq.del_flag = 0',
)
.leftJoinAndMapMany(
'live.liveStudent',
LiveStudent,
'ls',
'ls.liveId = live.id AND ls.del_flag = 0',
)
.leftJoinAndMapOne(
'ls.info',
User,
'student',
'student.id = ls.student_id ',
)
.leftJoinAndMapOne(
'lcs.info',
Resource,
'lcsInfo',
'lcs.file_id = lcsInfo.id',
)
.leftJoinAndMapOne(
'lq.info',
Question,
'lqInfo',
'lq.question_id = lqInfo.id',
)
.leftJoinAndMapOne('ls.info', User, 'lsInfo', 'ls.studentId = lsInfo.id')
.where({ id })
.getOne();
return res;
}
// 编辑直播
async updateLive(liveData: UpdateLiveTeachingDto, tokenInfo) {
this.Live_Sql.update(liveData.id, { delFlag: 1 });
delete liveData.id;
return this.createLive(liveData, tokenInfo);
}
// 删除直播
async deleteLive(id: [], tokenInfo) {
const res = await this.Live_Sql.update(id, {
delFlag: 1,
updater: tokenInfo.userId,
});
return res;
}
// 开始直播时查询直播详情
async getLiveInfo(id, tokenInfo) {
const res = this.Live_Sql.createQueryBuilder('l')
.leftJoinAndMapMany(
'l.courseware',
LiveCourseware,
'lc',
'lc.delFlag = 0 AND lc.liveId = l.id',
)
.leftJoinAndMapOne('l.lecturerInfo', User, 'user', 'user.id = l.lecturer')
.leftJoinAndMapOne('user.photo', Resource, 'r', 'r.id = user.photo')
.leftJoinAndMapMany(
'l.evealuate',
LiveEvaluate,
'le',
'le.delFlag = 0 AND le.liveId = l.id',
)
.leftJoinAndMapMany(
'l.question',
LiveQuestion,
'lq',
'lq.delFlag = 0 AND lq.liveId = l.id',
)
.where({ id })
.getOne();
return res;
}
/*
* 更新直播某部分信息 如(开始直播时,直播状态调整)
*/
async updateLiveInfo(data) {
return await this.Live_Sql.update(data.id, data);
}
// -------------------分类模块------------------ //
// 分类查询
// 查询所有分类
async getclassifyList() {
const res = await this.LiveClassify_Sql.findBy({ delFlag: 0 });
return res;
}
// 新建分类
async createclassify(classify: CreateCourseclassifyDto) {
const res = await this.LiveClassify_Sql.save(classify);
return res;
}
// 编辑分类
async updateclassify(id, classify) {
const res = await this.LiveClassify_Sql.update(id, classify);
return res;
}
// 删除分类
async deleteclassify({ id }) {
const res = await this.LiveClassify_Sql.update(id, { delFlag: 1 });
return res;
}
}

View File

@@ -0,0 +1,24 @@
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { WsException } from '@nestjs/websockets';
import { Socket } from 'socket.io';
@Injectable()
export class WsJwtGuard implements CanActivate {
constructor(private jwtService: JwtService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
try {
const client: Socket = context.switchToWs().getClient<Socket>();
const authToken: string = client.handshake?.auth?.token;
client.join(`house_${authToken}`);
const user = this.jwtService.decode(authToken.replace('Bearer ', ''));
context.switchToHttp().getRequest().user = user;
return Boolean(user);
} catch (err) {
throw new WsException(err.message);
}
}
}