短链服务不会写?用NestJS实现一个

1,769 阅读4分钟

为什么用短链

有的url会非常长,不利展示也不利于给别人分享,例如以下图片中的url,是不是非常的长 image.png

一个比较常见的应案例就是短信,大家看这个图片: image.png 如果短信中的链接使用的是以上那很长的url的话短信费用也会增加,而且用户看的话也很不友好。

如何实现短链服务

其实实现起来并不复杂,我们点开这种短链的时候最后会重定向到真实的url。

创建项目

创建一个项目

nest new short-url

这里使用TypeORM

npm install --save @nestjs/typeorm typeorm mysql2

在app.module.ts中引入TypeOrmModule动态模块并出入相关的配置

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: '123123',
      database: 'short-url',
      entities: [],
      synchronize: true,
    })
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

我简单说一下里面的配置项目: entities:[实体类0,实体类1, .....], synchronize如果是true则是同步创建表(entities添加实体类并保存后数据库新增对应的表)。

创建实体类

在src下新建entity/code.ts。这里面主要存储的是短链。在code.ts中添加如下代码:

import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class Code {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({
    comment: '短链',
  })
  code: string;

  @Column({
    comment: '状态, 0 未使用、1 已使用',
  })
  status: number;
}

接着在entity里面创建CodeMap.ts,代码如下:

import {
  Column,
  CreateDateColumn,
  Entity,
  PrimaryGeneratedColumn,
} from 'typeorm';

@Entity()
export class CodeMap {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({
    comment: '短链',
  })
  shortUrl: string;

  @Column({
    comment: '真正的url',
  })
  longUrl: string;

  @CreateDateColumn()
  createTime: Date;
}

将这两个实体添加到TypeOrmModule配置里面:

TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: '123123',
      database: 'short-url',
      entities: [Code, CodeMap],
      synchronize: true,
    }),

执行命令

npm run dev:start

查看数据库就会新增两个表:

image.png

生成短链

这里会用到base62这个包

npm install base62

创建一个服务:

nest g s code --no-spec

添加如下代码。这段代码实际上是随机产生len位的字符串。

import * as base62 from 'base62/lib/ascii';

@Injectable()
export class UniqueCodeService {
  constructor() {}

  generateRandomStr(len: number) {
    let str = '';
    for (let i = 0; i < len; i++) {
      const num = Math.floor(Math.random() * 62);
      str += base62.encode(num);
    }
    return str;
  }
}

继续添加向数据插入短链的代码

async generateCode() {
    // 获取到6位字符串
    const code = this.generateRandomStr(6);

    // 向Code表中查找这个生成的code
    const res_code = await this.entityManager.findOne(Code, {
      where: { code: code },
    });

    // 如果没有找到生成的code则将这个code插入并将状态设置为0
    if (!res_code) {
      // 这里返回的是新增的{code, status}
      return await this.entityManager.save(Code, {
        code: code,
        status: 0,
      });
    } else {
      // 如果数据库中有刚刚生产的code那么继续执行这个函数。
      return await this.generateCode();
    }
  }

写到这里又一个问题,数据库里的这些短链是在什么时机存储的呢,其实我们可以在凌晨四点或者其他访问量少的时间执行插入操作,这里就涉及到了定时任务。 首先安装一个包:

npm install --save @nestjs/schedule

添加如下代码。这里的@Cron装饰器里面可以写很多参数,可以是几秒或者小时等,也可以是某个时间点。

// 每天的凌晨四点创建100·00个code
  @Cron(CronExpression.EVERY_DAY_AT_4AM)
  async batchGenerateCode() {
    for (let i = 0; i < 10000; i++) {
      await this.generateCode();
    }
  }

给code(短链)添加对应的url

创建一个服务:

nest g s code-map --no-spec 

添加如下代码:

@Injectable()
export class ShortLongMapService {
  constructor(
    private readonly entityManager: EntityManager,
    private readonly uniqueCodeService: UniqueCodeService,
  ) {}

  // 向数据库里添加code相对应的url
  async generate(longUrl: string) {
    // 首先在Code里面查找status味0的code,也就是这个短链还没对应的url
    let uniqueCode = await this.entityManager.findOneBy(Code, {
      status: 0,
    });

    // 如果没有找到,也就是数据库里的code都被添加有相应的url了
    if (!uniqueCode) {
      // 插入一条code
      uniqueCode = await this.uniqueCodeService.generateCode();
    }

    // 将code和相应的URL插入
    await this.entityManager.save(CodeMap, {
      shortUrl: uniqueCode.code,
      longUrl,
    });

    // 在Code表里把这个code的status的改成1
    await this.entityManager.update(Code, uniqueCode.id, {
      status: 1,
    });

    return uniqueCode.code;
  }
}

继续添加获取code相对应的url的函数的代码

async getLongUrl(shortUrl: string) {
    const shortLongMap = await this.entityManager.findOneBy(CodeMap, {
      shortUrl,
    });

    return shortLongMap?.longUrl;
  }

添加路由,实现重定向

首先添加一个插入url的路由

@Get('short-url')
  getHello(@Query('url') url: string) {
    return this.shortLongMapService.generate(url);
  }

在浏览器输入

image.png 接着看数据库中的Code表:

image.png

再来看code_map表:

image.png

接着添加重定向

  @Get(':code')
  @Redirect()
  async getLongUrl(@Param('code') shortUrl: string) {
    const longUrl = await this.shortLongMapService.getLongUrl(shortUrl);
    if (!longUrl) throw new HttpException('链接不存在', 404);
    return {
      url: longUrl,
      statusCode: 302,
    };
  }

在浏览输入刚刚生成的code,会重定向到百度页面

image.png

至此一个短链服务就写完了,如果有不对的地方希望大家在评论区留言。🙏