NestJS实战之开发短链服务(二)

前言

接上文,NestJS实战之开发短链服务(一),在上一篇文章中我们介绍了短链服务的理论基础,以及完成了数据库的设计,本文开始我们将会带领大家进入详细设计的步骤。

数据库配置

在上文中,我们已经把表结构向大家展示了,大家可以先建好数据库,再建好表,前置准备工作就完成了。

所谓数据库的配置就是连接名称,数据库的端口,用户名密码等内容,这些内容不适合在代码里面写死,为什么呢?因为我们的短链服务开发完成之后,一旦进行部署的话,生成环境的配置跟开发环境是不一致的,所以最后能够使用配置文件或者配置服务来解决这个问题,我将向大家展示两种方式的配置。

环境变量配置

import { Module } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
// 导入上文中,我们已经建好的实体类
import { Rule } from './entities/rule';
import { Strategy } from './entities/strategy';

@Module({
  imports: [
    // ConfigModule加载.env文件有先后顺序,这样才能够读取的到.env文件的配置
    TypeOrmModule.forRootAsync({
      inject: [ConfigService],
      useFactory: (config: ConfigService) => {
        const host = config.get<string>('HOST');
        const port = config.get<number>('PORT');
        const username = config.get<string>('USERNAME');
        const password = config.get<string>('PASSWORD');
        const database = config.get<string>('DB');
        return {
          type: 'mysql',
          host,
          port,
          username,
          password,
          database,
          entities: [Rule, Strategy],
        };
      },
    }),
  ],
})
export class PersistenceModule {}

在上述代码中,申明ConfigModule提供的ConfigService作为依赖,这样我们就可以读取到配置文件中的信息了。

然后,在程序的入口需要加载配置文件。

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { PersistenceModule } from './persistence/persistence.module';
import { getEnvFiles } from './common/config';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: getEnvFiles(),
    }),
    PersistenceModule,
  ]
})
export class AppModule {}

需要将ConfigModule申明为全局Module,因为在NestJS的原理部分我们讲过(原文在这里👉记录我的NestJS探究历程(十二)——NestJS启动流程的详细分析),所有的全局模块都是某个非全局模块的依赖,在NestJS初始化时,会优先加载全局模块,然后再处理别的模块。

最后,给我大家看看我的getEnvFiles都加载了哪些配置文件:

export const MODE = process.env.NODE_ENV;
/**
 * 配置可以扫码的配置文件类型
 * @returns
 */
export function getEnvFiles() {
  return [`.env.${MODE}.local`, `.env.${MODE}`, '.env'];
}

环境服务配置

所谓环境服务配置,就是我们的配置从远端获取,常见的配置系统比如Nacos,Apollo等,我们在项目中一直使用的是Nacos,因此环境服务配置我就以Nacos为例向大家展示。

使用环境服务配置相对要麻烦一些,不过,我们仍然需要环境变量的一个配置,只不过,这个环境变量配置的内容就仅仅是远端服务的地址(也可以在启动脚本里面使用cross-env进行设置,看个人的喜好)。

接下来我们就向大家展示使用配置服务管理数据库的关键信息。

在去年,我在向大家展示NestJS的一些核心原理的时候,有做了一些封装,当时做了一个基于Nacos的封装,在本文我们就直接使用。

首先,我们需要一个常量注入的内容,我们把这个常量管理到一个模块中,这个常量在程序启动的过程中一定会有的,如果没有的话,则认为服务启动失败。

 
import { NACOS_GLOBAL_APP_CONFIG } from '@/modules/common/constants';
import { TinyURLAppConfig } from '@/modules/common/types';
import { DynamicModule } from '@nestjs/common';
import { NacosConfigService } from 'nestjs-nacos';

export class AppConfigModule {
  static forRoot({ dataId }): DynamicModule {
    /**
     * 用这个动态模块来托管nacos的配置
     */
    return {
      module: AppConfigModule,
      global: true,
      providers: [
        {
          provide: NACOS_GLOBAL_APP_CONFIG,
          useFactory: async (nacosService: NacosConfigService) => {
            const config = await nacosService
              .getKeyItemConfig({
                dataId,
              })
              .then((configStr: string) => {
                return JSON.parse(configStr) as TinyURLAppConfig;
              });
            return config;
          },
          inject: [NacosConfigService],
        },
      ],
      exports: [NACOS_GLOBAL_APP_CONFIG],
    };
  }
}

我们向大家解释一下上面代码的含义,它是一个动态模块,并且是异步的,对外导出一个叫做NACOS_GLOBAL_APP_CONFIG的字面量,这个模块依赖NacosConfigService,这个Service是在我的Nacos插件中提供的,一会儿在程序的主入口,一定是要先于这个模块注册的。

在这个模块中,我们读取到远端的配置内容,将其存到NestJS的Ioc容器中,供其它服务使用。

然后,我们再看看程序的入口是怎样配置的。

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { PersistenceModule } from './persistence/persistence.module';
import {
  AppBootstrapConfigId,
  NacosNamespace,
  getEnvFiles,
} from './common/config';
import { AppConfigModule } from './app-config/app-config.module';
import { NacosConfigModule } from 'nestjs-nacos';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: getEnvFiles(),
    }),
    NacosConfigModule.register(
      {
        url: process.env.NACOS_ADDRESS,
        namespace: NacosNamespace,
        timeout: 30000,
      },
      true,
    ),
    AppConfigModule.forRoot({
      dataId: AppBootstrapConfigId,
    }),
    PersistenceModule,
  ],
})
export class AppModule {}

我以全局模块的形式注册Nacos配置模块,待会儿NestJS启动的时候将会先于我们的配置模块加载,在配置模块注册的时候,将我们要取用的数据Id传入,就可以正确读取配置服务了。

最后,我们看看PersistenceModule怎么使用配置的内容:

 
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Rule } from './entities/rule';
import { Strategy } from './entities/strategy';
import { NACOS_GLOBAL_APP_CONFIG } from '@/modules/common/constants';
import { TinyURLAppConfig } from '@/modules/common/types';

@Module({
  imports: [
    TypeOrmModule.forRootAsync({
      inject: [NACOS_GLOBAL_APP_CONFIG],
      useFactory: (config: TinyURLAppConfig) => {
        const host = config.HOST;
        const port = config.DB_PORT;
        const username = config.USERNAME;
        const password = config.PASSWORD;
        const database = config.DB;
        return {
          type: 'mysql',
          host,
          port,
          username,
          password,
          database,
          entities: [Rule, Strategy],
        };
      },
    }),
  ],
})
export class PersistenceModule {}

因为在这之前,我们已经把启动的配置托管给了NestJS,现在可以直接从NestJS取用,因此,直接注入NACOS_GLOBAL_APP_CONFIG这个字面量就可以了。

到这儿,我们的启动配置基本上就完成了。

短链码生成方案

短链的生成方式我在之前的文章有讲过,由一道简单的手写题引出的方案设计——短链雪花算法,本文就不再详细赘述。

以下是我的生成因子:

/**
 * 生成短链的base62因子
 */
export const INDEX_CHAR_MAP = 'EyS2Y4NTem76tW0AV8BDLRohJPzkwsglICUv5K9iqQfnXdMpHxruG1ObZcaFj3';

 我的哈希函数比较简单,直接使用简单的Y=k*X+b的模型,各位可以根据自己的需求处理哈希函数,这个哈希函数就是使用我们数据库的自增ID得到一个比较大的数字,然后把这个数字转化成base62的结果,得到的就是那个最终的短链码了。

/**
 * 膨胀系数
 * @param val
 */
export function exposeInt(val: number) {
  // k * x + b,k为1000000,b为10亿
  return val * 10e6 + 10e9;
}

大家可以根据自己的需求调整系数,我目前的这个模型在JS的最大安全数字的限制下,大概能够得到30W个不同的短链。

 
/**
 * 根据整数获取短链码
 * @param num
 * @returns
 */
export function getShortLinkCode(num: number): string {
  let result = '';
  // 我的字符映射码没有乱序,实际项目中一定要乱序,并且一定要存下来
  const charMap = INDEX_CHAR_MAP;
  // 仅仅只需要将原来的26进制转化为62进制即可
  while (num >= 62) {
    const digit = (num - 1) % 62;
    const alphabet = charMap[digit];
    result = alphabet + result;
    num = Math.floor((num - 1) / 62);
  }
  // 处理最后一位数
  result = charMap[num - 1] + result;
  return result;
}

结语

在这篇文章中,我们主要阐述的是配置相关的内容,配置的好坏关系到后续开发的代码的质量和维护的难易程度,并且在这个过程中,我们已经确定好了短链生成的方案。

在下一篇文章中,我们将开始对短链进行增删改查的操作了,在这个过程中,我们会向大家阐述如何编写可以维护的代码,敬请期待.....

原文:https://juejin.cn/post/7380241307116486666
 

  • 21
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值