前言
接上文,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