SDU信息门户(6)图灵认证授权子系统:user

2021SC@SDUSC

目录

一.引言

二.代码分析

1.用户环境相关配置

2.用户登录

3.用户登出

4.更新用户命令类结构

5.用户存储库

6.通过Id查询用户

7.学生中查询用户

8.确定用户身份

三.总结


一.引言

       认证授权系统的一个重要的部分就是用户,用户部分需要完成的功能包括用户安全登入登出,用户创建,用户更新,用户upsert操作,从学生队列中寻找用户,根据Id寻找用户。接下来让我们分析相关代码。

二.代码分析

1.用户环境相关配置

        导出类 UserConfigSchema ,类中使用@IsEnum装饰器来判断属性NODE_ENV是否为Enum,判断MONGO_URI是否为String类型等等。在 UserConfig类的构造器中注入UserConfigSchema类型参数,使用@ENV装饰器,将变量env中相关属性赋值给类的私有属性。

import { IsEnum, IsString, IsUrl } from 'class-validator';
import { NodeEnvironment } from './node-environment.enum';
import { Env } from '@sdu-turing/config';

export class UserConfigSchema {
  @IsEnum(NodeEnvironment)
  NODE_ENV: NodeEnvironment;

  @IsString()
  MONGO_URI: string;

  @IsString()
  TURING_APP_ID: string;

  @IsUrl()
  TURING_LOGIN_URL: string;
}

export class UserConfig {
  mongoUri: string;

  turingAppId: string;

  turingLoginUrl: string;

  nodeEnv: NodeEnvironment;

  constructor(@Env() env: UserConfigSchema) {
    this.mongoUri = env.MONGO_URI;
    this.turingAppId = env.TURING_APP_ID;
    this.turingLoginUrl = env.TURING_LOGIN_URL;
  }
}

2.用户登录

 用户登录命令类结构

export class SignInCommand {
  constructor(
    public readonly userId: UserId,
    public readonly password: string,
  ) {}
}

      在构造器中注入用户存储库repository,异步方法execute中将SignInCommand注入,然后通过用户存储库repository的findById方法来根据command中userId来查找user,如果用户Id不存在于用户存储库中,抛出无法找到用户异常,找到用户的话,再调用得到的用户user的validatePassword方法,密码错误抛出异常,正确则调用用户的SignIn方法,然后在用户存储库中保存用户repository.save(user),最后提交用户。

@CommandHandler(SignInCommand)
export class SignInHandler implements ICommandHandler<SignInCommand> {
  constructor(
    @UserRepositoryImplement()
    private readonly repository: UserRepository,
  ) {}

  async execute(command: SignInCommand): Promise<void> {
    const { userId, password } = command;
    const user = await this.repository.findById(userId);
    if (!user) {
      throw new UserNotFoundException();
    }
    if (!user.validatePassword(password)) {
      throw new GrpcException(status.INVALID_ARGUMENT, '用户密码错误');
    }
    user.signIn();
    await this.repository.save(user);
    user.commit();
  }
}

3.用户登出

用户登出只需用户Id即可

export class SignOutCommand {
  constructor(public readonly userId: UserId) {}}

在构造器中注入用户存储库repository,异步方法execute中将SignOutCommand注入,然后通过用户存储库repository的findById方法来根据command中userId来查找user,如果用户Id不存在于用户存储库中,抛出无法找到用户异常,找到用户的话,将用户登出,然后保存并提交用户即可。

@CommandHandler(SignOutCommand)
export class SignOutHandler implements ICommandHandler<SignOutCommand> {
  constructor(
    @UserRepositoryImplement()
    private readonly repository: UserRepository,
  ) {}

  async execute(command: SignOutCommand): Promise<void> {
    const user = await this.repository.findById(command.userId);
    if (!user) {
      throw new UserNotFoundException();
    }
    user.signOut();
    await this.repository.save(user);
    user.commit();
  }
}

4.更新用户命令类结构

更新用户的命令类需要用户Id和需要更新的用户参数。

export class UpsertUserCommand {
  constructor(
    public readonly userId: UserId,
    public readonly changes: UpdateUserParams,
  ) {}
}

        在构造器中注入用户存储库repository,异步方法execute中将SignOutCommand注入UpsertUserCommand,然后通过用户存储库repository的findById方法来根据command中userId来查找user,如果用户不存在,用更新的数据创建用户并且返回,如果用户存在,用需要更新的数据更新用户然后save函数保存用户并提交用户。

@CommandHandler(UpsertUserCommand)
export class UpsertUserHandler implements ICommandHandler<UpsertUserCommand> {
  constructor(
    @UserRepositoryImplement()
    private readonly repository: UserRepository,
  ) {}

  async execute(command: UpsertUserCommand): Promise<void> {
    const user = await this.repository.findById(command.userId);
    if (!user) {
      // 用户不存在,创建数据
      return this.createUser({
        ...command.changes,
        casId: command.userId,
        status: UserStatus.SignedOut,
        updatedAt: new Date(),
      });
    }
    // 用户存在,更新数据
    user.updateData(command.changes);
    await this.repository.save(user);
    user.commit();
  }

5.用户存储库

      用@Injectable()装饰器的类MongoUserRepository实现了UserRepository接口,构造器中注入userModel,用户存储库的方法包括findById根据Id寻找用户,根据传入的UserId,通过userModel的findById方法得到userDoc实例,判断是否存在userDoc,不存在返回undefined,存在则返回Model类型的userDoc。保存用户的方法save将传入的user转化为Document类型用uerModel.updateOne方法保存在用户存储器中。

@Injectable()
export class MongoUserRepository implements UserRepository {
  constructor(
    @InjectModel(UserCollection)
    private readonly userModel: Model<UserDocument>,
  ) {}

  async findById(userId: UserId): Promise<User | undefined> {
    const userDoc = await this.userModel.findById(userId.toString());
    if (!userDoc) {
      return undefined;
    }
    return this.documentToModel(userDoc);
  }

  async save(user: User): Promise<void> {
    const userDoc = this.modelToDocument(user);
    await this.userModel.updateOne(
      {
        _id: userDoc._id,
      },
      userDoc,
      {
        upsert: true,
      },
    );
  }

6.通过Id查询用户

      构造器中注入用户查询UserQuery类变量,在异步函数execute中传入参数FindUserByIdQuery类型的变量query,通过userQuery的findById函数,以用户Id作为参数得到用户data返回data。

@QueryHandler(FindUserByIdQuery)
export class FindUserByIdHandler implements IQueryHandler<FindUserByIdQuery> {
  constructor(@UserQueryImplement() private readonly userQuery: UserQuery) {}

  async execute(query: FindUserByIdQuery): Promise<UserData> {
    const data = await this.userQuery.findById(query.casId);
    return data;
  }

7.学生中查询用户

           构造器中注入用户查询UserQuery类变量,在异步函数execute中传入参数FindUserFromSduQuery类型的变量query,通过userQuery的findFromSdu函数,以用户Id和password作为参数得到用户user并返回。

@QueryHandler(FindUserFromSduQuery)
export class FindUserFromSduHandler
  implements IQueryHandler<FindUserFromSduQuery>
{
  constructor(@UserQueryImplement() private readonly userQuery: UserQuery) {}

  async execute(query: FindUserFromSduQuery): Promise<UserData> {
    const user = await this.userQuery.findFromSdu(query.casId, query.password);
    return user;
  }
}

8.确定用户身份

         向异步函数findFromSdu传入Id和password,将userConfig中Url和appId赋值给相关变量,调用this.httpService.get函数,传入loginUrl和参数体,参数体中包括Id,password,设备类型,软件Id,得到对象再调用toPromise(),然后验证得到的data是不是success,如果不是就报错'学工号或密码错误',然后根据data.extraInfo.type判断是不是'xs',role就是学生,不是就是教师,然后返回包括各种属性casId,name,role,grade,workplace,campus, major,type的用户数据。

async findFromSdu(casId: string, password: string): Promise<UserData> {
    const loginUrl = this.userConfig.turingLoginUrl;
    const appId = this.userConfig.turingAppId;
    const data = await this.httpService
      .get(loginUrl, {
        params: {
          userId: casId,
          passWord: password,
          deviceType: 'android',
          appId: appId,
        },
      })
      .toPromise()
      .then(
        (resp) => resp.data,
        () => {
          throw new TuringServiceException();
        },
      );
    if (data.result !== 'success') {
      throw new GrpcException(status.INVALID_ARGUMENT, '学工号或密码错误');
    }
    const userJson = data.extraInfo.info;
    const role =
      data.extraInfo.type === 'xs' ? UserRole.Student : UserRole.Teacher;
    const name = data.user?.userName || '未知';
    return {
      casId,
      name,
      role,
      grade: parseInt(userJson.sznj || '1901', 10),
      workplace: userJson.dwmc || '未知',
      campus: userJson.xqmc || '未知',
      major: userJson.zymc || '未知',
      type: userJson.xslx,
    };
  }

三.总结

        分析了这么久的图灵认证授权,在SDU信息门户的user中终于使用上了,完成了用户安全登入登出,用户创建,用户更新,用户upsert操作,从学生队列中寻找用户,根据Id寻找用户,具体的实现是通过UserCommand中命令类和UserQueries中查询类来实现的。该子功能所使用的框架是nestjs,还涉及到了mongoose的Schema模块以及Grpc的远程函数调用的使用。通过分析,更加深入了解了nestjs和Typescript的相关知识。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值