2021SC@SDUSC
目录
引言
在SDU信息门户系统中的一个重要模块是任课老师模块,任课老师的需求包括显示名下课程,配置课程功能,发布公告,发布题目这些功能,要实现这些功能有使用到gRPC技术,该技术用于远程调用函数。该模块使用的技术依然是nestjs框架与TypeScript。
代码分析
gRPC的简介与安装
简介
gRPC是一个现代的、高性能RPC框架,可以运行在任何环境下。它可以有效在数据中心之间连接服务。gRPC基于可以定义远程调用的函数的概念。针对每个方法,定义一个参数并返回类型。服务、参数和返回类型在.proto
文件中定义,使用谷歌的开源语言——中性协议缓存机制。
安装
需要安装gRPC的软件包
$ npm i --save @grpc/grpc-js @grpc/proto-loader
创建DTO对象
在教务系统的任课老师功能中操作包括配置课程,显示某个老师名下的课程,发布课程。我们需要创建这些操作相关的dto对象来完成执行操作过程中的数据的传输和管理。
create-title.dto
这个DTO对象是用来做显示教师名下课程操作的数据传输的,其中定义了Title类用于管理课程的相关信息,如titName(课程名),guider(指导者),titOutline(课程大纲)等等。在CreateTitleDto对象中则是定义了两个属性,一个是教师编号,用于唯一标识某一教师,另一个是titles,即该教师名下的课程。
import { ApiProperty } from '@nestjs/swagger';
class Title {
titName: string;
guider: string;
titOutline: string;
minNumber: number;
maxNumber: number;
}
// 创建课题,显示名下课程
export class CreateTitleDto {
// @ApiProperty()
readonly cid: string;
// @ApiProperty({
// type: [Title],
// description: "题目列表"
// })
readonly titles: [Title];
}
config-course.dto
这个DTO对象则是负责配置课程操作的数据传输的,其中定义了Plugin对象用于管理配置的插件,Plugin对象中包括pname(插件名称)与pstatus(插件状态)。而在ConfigCourseDto对象中则定义了配置课程的相关信息,包括cid(课程编号)以及plugins(插件集合)。
import { ApiProperty } from '@nestjs/swagger';
class Plugin {
readonly pname: string;
readonly pstatus: boolean;
}
// 配置课程
export class ConfigCourseDto {
// @ApiProperty()
readonly cid: string;
// @ApiProperty({
// type: [Plugin],
// description: "插件列表"
// })
readonly plugins: [Plugin];
}
配置constants文件
在该文件中导入了管理教师模块中定义的CourseInfoSchema用于课程信息数据类型的管理,在这里还定义了常量TCMODELINFO来将CourseInfoSchema与collection绑定起来。
import { CourseInfoSchema } from '../../admin/schemas/couser-info.schema';
export const TCMODELINFO = {
name: 'tCourseInfo',
schema: CourseInfoSchema,
collection: 'courses',
};
配置Service服务类
系统配置了teacherService任课老师服务类,在该服务类中定义并实现了完成任课老师相关操作的具体方法与具体的实现。在该类中导入了前面定义的dto对象,用于在执行操作时进行数据的传输。以下是导入的各个模块与装饰器:
import { Model } from 'mongoose';
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { ConfigCourseDto } from './dto/config-course.dto';
import { CreateTitleDto } from './dto/create-title.dto';
import { GrpcException } from '@sdu-turing/microservices';
import { status } from 'grpc';
具体的,在TeacherService类中的构造器中定义并注入了数据成员courseModel,并注入了toCourseInfo模块。
constructor(@InjectModel('tCourseInfo') private courseModel: Model<any>) {}
显示名下所有课程
async getAllCourse(tid: string) {
let query = this.courseModel.find();
query.setOptions({ lean: true });
query.where({ tid: tid });
query.select({ _id: 0, cname: 1, sequence: 1, cid: 1, tname: 1 });
const result = await query.find().exec();
let info = {
allInfo: result,
};
// console.log(result);
return info;
该方法中传入了tid(教师编号)的参数,用于查询教师编号是tid的老师的所有课程,首先调用了courseModel的find()方法遍历查询出所有的课程信息,再通过对查询到的课程信息变量query调用where()函数来查询tid教师编号相对应的课程信息,query.select()方法则是从课程信息中有选择地提取出部分属性。之后再调用异步函数query.find().exec()来查询结果,最后将结果赋值给info变量并返回。
根据cid判断课程是否课程是否存在
async assertCourseExists(cid: string) {
const isExists = await this.courseModel.exists({ cid: cid });
if (isExists == false) {
throw new GrpcException(status.NOT_FOUND, 'CANNOT FIND COURSE');
}
}
该方法传入参数cid用于判断某个课程是否存在。通过调用异步方法courseModel.exists(...)方法来判断课程编号为cid的课程是否存在,若存在返回true,否则为false。将结果赋值给isExists,再通过判断isExists是否为false,若不存在则抛出GrpcException异常。
根据cid显示详细课程信息
该方法传入参数cid(课程编号)来显示相应的课程信息。首先调用之前定义的异步方法assertCourseExists(...)方法来判断该课程是否存在,若存在则执行courseModel的find()方法来查询所有的课程信息,再通过调用query的where()与select()方法来筛选和过滤该课程相关的信息,最后调用query.findOne().exec()来返回结果。
async showCourseByCid(cid: string) {
await this.assertCourseExists(cid);
let query = this.courseModel.find();
// 查询所有课程
query.setOptions({ lean: true });
query.where({ cid: cid });
// 搜索到指定地课程
query.select({ _id: 0 });
return query.findOne().exec();
}
配置课程功能
该方法中传入Dto对象作为参数来进行数据的传输。首先将dto对象的cid属性赋值给本地参数cid,再调用异步函数assertCourseExists(...)来判断课程号为cid的课程是否存在,若存在则调用数据成员courseModel的find()方法来查询所有课程的所有信息,再调用query的where方法来筛选出课程号为cid的课程的所有信息,再通过query.updateOne(...)方法将该课程的配置插件加入到该课程信息中来完成课程的配置,最后返回完成操作的信息。
//配置课程功能
async configCourse(configCourseDto: ConfigCourseDto) {
const cid = configCourseDto.cid;
await this.assertCourseExists(cid);//判断配置的课程号是否存在
let query = this.courseModel.find();
query.setOptions({ lean: true });
// ...验证plugins有效性
query.where({ cid: cid });
await query.updateOne({ plugins: configCourseDto.plugins });
return {
status: 200,
message: '修改成功',
};
}
发布题目
该方法传入发布题目的Dto对象来完成课题发布过程中的数据传输。首先查询到需要发布课程cid对应的课程信息,再通过query.updateOne(...)方法将需要创建的课程的题目titles插入到该课程的信息中,再对result进行判断来确定插入操作是否成功,若不成功则抛出异常,cid错误或无效,匹配失败,否则返回修改成功的信息。
async createTitle(createTitleDto: CreateTitleDto) {
let query = this.courseModel.find();
query.setOptions({ lean: true });
// ...验证plugins有效性
query.where({ cid: createTitleDto.cid });
const result = await query.updateOne({ titles: createTitleDto.titles });
if (result.n === 0) {
//抛出异常,cid错误或无效,匹配失败
return {
status: 201,
message: '插入失败',
err: 'CAN NOT FIND COURSE BY CID',
};
}
return {
status: 200,
message: '修改成功',
};
}
配置.proto文件
我们若是想要使用gRPC来完成远程调用函数,那么要在.proto文件中进行配置。.proto
文件是使用协议缓冲区语言构建的。
syntax = "proto3";
package sdu.ep;
//任课老师服务
service TeacherService{
//显示名下所有课程
rpc GetAllCourse(TID) returns (CoursesList);
//根据cid显示课程详情
rpc ShowCourseByCid(CID) returns (CourseInfo);
//配置课程功能
rpc ConfigCourse(ConfigCourseReq) returns (CommonRsp);
}
message CoursesList {
message BriefCourseInfo{
//课程名
string cname = 1;
//课序号
string sequence = 2;
//课编号
string cid = 3;
//任课老师姓名
string tname = 4;
}
repeated BriefCourseInfo allInfo = 1;
}
message CourseInfo {
string cid = 1;
string sequence = 2;
string cname = 3;
//任课老师教工号
string tid = 4;
//任课老师姓名
string tname = 5;
//插件功能表
repeated Plugin plugins = 6;
//学生列表
repeated Stu stuInfos = 7;
}
message ConfigCourseReq{
//课编号
string cid = 1;
//插件列表
Plugin plugins = 2;
}
message CID {
string cid = 1;
}
message TID {
string tid = 1;
}
在上述的文件中,我们定义了一个 TeacherService
,它暴露了一个 GetAllCourse()
gRPC处理程序,该处理程序期望 TID
作为输入并返回一个CoursesList消息(协议缓冲语言使用message
元素定义参数类型和返回类型)。而在message元素则定义了一系列的属性类型。
配置controller处理器类
在controller处理器类中导入了teacherService服务类,通过调用该服务类的方法来完成具体的操作。此外处理器类中还导入了Config CourseDto和CreateTitleDto对象来完成数据传输,同时还引入了GrpcMethod装饰器来在控制器中定义一个远程函数调用满足要求的处理程序。
import { Body, Controller, Post, Request, UseGuards } from '@nestjs/common';
import { ConfigCourseDto } from './dto/config-course.dto';
import { TeacherService } from './teacher.service';
import { CreateTitleDto } from './dto/create-title.dto';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { Roles } from '../auth/decorators/roles.decorator';
import { ApiResponse, ApiTags } from '@nestjs/swagger';
import { GrpcMethod } from '@nestjs/microservices';
@ApiTags('任课老师')
@Controller('api/teacher')
//@UseGuards(JwtAuthGuard)
export class TeacherController {
constructor(private teacherService: TeacherService) {}
//显示名下所有课程
@GrpcMethod('TeacherService', 'GetAllCourse')
async getAllCourse(TID: { tid: string }) {
return this.teacherService.getAllCourse(TID.tid);
}
//根据cid显示课程
@GrpcMethod('TeacherService', 'ShowCourseByCid')
async showCourseByCid(CID: { cid: string }) {
return this.teacherService.showCourseByCid(CID.cid);
}
//配置课程功能
@GrpcMethod('TeacherService', 'ConfigCourse')
async configCourse(configCourseDto: ConfigCourseDto) {
return this.teacherService.configCourse(configCourseDto);
}
}
在TeacherController类中的构造函数中定义并注入了teacherService类型的数据成员,并且定义了三个方法,分别用于处理显示老师名下所有课程,根据cid显示课程,配置课程功能这些操作,具体实现都是调用teacherService中定义的相关方法。此外在每个方法上面都加上了@GrpcMethod装饰器来指定可以进行远程调用的函数,该装饰器有两个参数,一个是服务名称(teacherService),另一个是对应eq.proto
文件中TeacherService
内定义方法。
配置Teacher.Module文件
在模块文件中主要做的处理就是完成该系统中各个模块间的管理,包括模块的导入和导出,处理器的绑定,服务类的导出等等。
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { TCMODELINFO } from './constants/db.constants';
import { TeacherController } from './teacher.controller';
import { TeacherService } from './teacher.service';
@Module({
imports: [MongooseModule.forFeature([TCMODELINFO])],
controllers: [TeacherController],
providers: [TeacherService],
exports: [TeacherService],
})
export class TeacherModule {}
在这个文件中我们导入了前面定义的TCMODELINFO,绑定的处理器是TeacherController,导出了TeacherService服务类,以便在其他模块中导入我们TeacherModule模块后可以直接使用该服务类,最终我们导出了TeacherModule。
总结
教务系统的任课老师功能包括显示老师名下的所有课程信息,发布课程题目和配置课程。其中nestjs框架中的gRPC来实现远程调用函数(方法),同时还用到了Mongoose的Schema来定义课程信息的数据模型,还用到了DTO对象来实现网络数据的传输。这次核心代码分析让我了解到了远程调用函数工具gRPC的基本概念和使用方式,并进一步了解了nestjs框架的使用过程。