This article will introduce to you the method of implementing short link service based on the Node framework Nest. It has certain reference value. Friends in need can refer to it. I hope it will be helpful to everyone.
Recommended study: "nodejs tutorial"
We can see all kinds of strange short links in our daily life. Every time I click to jump, I feel magical. How does this short link guide users to the correct page?
The principle of short chain is to make a short blog longer, so how can this short string be turned into a long string of links? Is it relying on some magical encryption algorithm? No, we only need to rely on the key/value mapping relationship to easily achieve this seemingly magical making short work long.
With a picture, everyone can clearly see the entire process of accessing the short link.
First, we will have a long link. Through the processing of the short link service, a URL with only one layer of directories will usually be output. Then we can process the obtained URL. distribution.
Then we get to the user side. After the user clicks on the short link, the first thing they reach is not the target page, but the short link service.
The short link service will intercept the pathname on the link and use it as a key to find the corresponding value in the mapping relationship.
If the corresponding value cannot be found, it means that the short link does not exist or has expired; if the query is successful, the short link service will directly 302 to the target link in the value to complete a short link access. .
Materials: Fast-NestScaffolding, Redis
The entire implementation is split into 3 parts:
@Post('/createUrl') async createUrl( @Body('url') url: string, @Body('type') type: string, ) { const shortUrl = await this.shorturlService.createUrl(url, type); return { shortUrl, }; }
Create a createUrl
interface in the service, receive the url
and type
fields, and add It is passed into shorturlService
, waits for the short link to be generated and then outputs it.
async createUrl(url: string, type: string = 'normal') { const urlKey = await this.handleUrlKey(); const dataStr = JSON.stringify({ url, type }); await this.client.set(urlKey, dataStr, type === 'permanent' ? -1 : 300); return `${Config.defaultHost}/${urlKey}`; } private async handleUrlKey(count?: number): Promise<string> { const _count = count || 1; const maxCount = Config.maxRetryTimes; if (_count >= maxCount) throw new HttpException('超过重试次数,请重新生成链接', HttpStatus.INTERNAL_SERVER_ERROR); const urlKey: string = Math.random().toString(36).slice(-4); const _url = await this.client.get(urlKey); if (_url) { return await this.handleUrlKey(_count + 1); } return urlKey; }
First get a 4-digit random string through Math.random().toString(36).slice(-4)
, this will Pathname as a short link.
Before mapping, we need to judge its uniqueness. Although it is unlikely, we still need to prevent problems such as short chain coverage. The solution of this service is to retry generation. If the short chain value is unfortunately repeated, it will enter the retry branch. The service will have a built-in number of retries. If the number of retries exceeds the configured number of words, this conversion will return failure. .
In addition to url
, the createUrl
method also accepts a type
field, which involves the characteristics of special short links. Our short links have three modes:
After generating urlKey
, it will be the same as type
is converted into a string and stored in redis, and the spliced short link is output.
@Get('/:key') @Redirect(Config.defaultIndex, 302) async getUrl( @Param('key') key: string, ) { if (key) { const url = await this.shorturlService.getUrl(key); return { url } } } // this.shorturlService.getUrl async getUrl(k: string) { const dataStr = await this.client.get(k); if (!dataStr) return; const { url, type } = JSON.parse(dataStr); if (type === 'once') { await this.client.del(k); } return url; }
The user side will get a link similar to http://localhost:8000/s/ku6a
, after clicking It is equivalent to sending a GET request to the short link service.
After receiving the request, the service obtains the value of the key field in the link, which is the string ku6a
, and uses it to find the mapping relationship in Redis.
There are two branches here. One is that the relevant value cannot be queried in Redis. The service thinks that the short link has expired and will return directly because getUrl
returns a null value and redirects. The decorator will redirect this request to the default target link.
If the relevant value is successfully found in Redis, the url
and type
fields will be read. If type is once, it means that this is a one-time use. The link will actively trigger the deletion method and eventually return the target link.
When using short links, there is a high probability that relevant data statistics will be needed. How to collect data without using a database? What about statistics?
In this service, we can complete the report of short-link access on the same day by scanning the landing log file.
When generating short links, add the urlID field for statistical differentiation and actively output logs, as follows:
async createUrl(url: string, type: string = 'normal') { const urlKey = await this.handleUrlKey(); const urlID = UUID.genV4().toString(); const dataStr = JSON.stringify({ urlID, url, type }); this.myLogger.log(`createUrl**${urlID}`, 'createUrl', false); await this.client.set(urlKey, dataStr, type === 'permanent' ? -1 : 300); return `${Config.defaultHost}/${urlKey}`; }
然后在用户点击短链接时获取该短链接的urlID字段,并主动输出日志,如下:
async getUrl(k: string) { const dataStr = await this.client.get(k); if (!dataStr) return; const { url, type, urlID } = JSON.parse(dataStr); if (type === 'once') { await this.client.del(k); } this.myLogger.log(`getUrl**${urlID}`, 'getUrl', false); return url; }
这么一来我们将能够在服务的logs目录中获得类似这样的日志:
2021-04-25 22:31:03.306 INFO [11999] [-] createUrl**3f577625-474a-4e30-9933-e469ce3b0dcf 2021-04-25 22:31:38.323 INFO [11999] [-] getUrl**3f577625-474a-4e30-9933-e469ce3b0dcf 2021-04-25 22:31:39.399 INFO [11999] [-] getUrl**3f577625-474a-4e30-9933-e469ce3b0dcf 2021-04-25 22:31:40.281 INFO [11999] [-] getUrl**3f577625-474a-4e30-9933-e469ce3b0dcf 2021-04-25 22:31:40.997 INFO [11999] [-] getUrl**3f577625-474a-4e30-9933-e469ce3b0dcf 2021-04-25 22:31:41.977 INFO [11999] [-] getUrl**3f577625-474a-4e30-9933-e469ce3b0dcf 2021-04-25 22:31:42.870 INFO [11999] [-] getUrl**3f577625-474a-4e30-9933-e469ce3b0dcf 2021-04-25 22:31:43.716 INFO [11999] [-] getUrl**3f577625-474a-4e30-9933-e469ce3b0dcf 2021-04-25 22:31:44.614 INFO [11999] [-] getUrl**3f577625-474a-4e30-9933-e469ce3b0dcf
之后我们只需要以createUrl
的日志为索引,对getUrl
类型的日志进行计数,即可完成链接与点击数的报表,如果还需要其他维度的报表只需要在输出日志的时候带上即可,或者修改日志中间件中的日志范式。
根据上述的流程,笔者写了一个比较简易的短链服务,大家可以开箱即用。
具体启动方式
首先请确保有可用的redis,否则无法顺利启动服务。
git clone https://github.com/mykurisu/shorturl.git cd shorturl npm install npm start
可用配置修改
与短链相关的配置收束在根目录的config.ts
中。
serverConfig: { port: 8000, }, redis: { port: 6379, host: '0.0.0.0', db: 0, }, cacheType: 'redis', defaultHost: 'http://localhost:8000/s', defaultIndex: 'http://localhost:8000/defaultIndex',
配置 | 默认值 | 配置用途 |
---|---|---|
serverConfig.port | 8000 | 服务启动端口 |
redis.port | 6379 | redis端口 |
redis.host | 0.0.0.0 | redis服务地址 |
redis.db | 0 | redis具体储存库表 |
cacheType | redis | 短链储存模式,接受memory/redis |
maxRetryTimes | 5 | 生成短链接最大重试次数 |
defaultHost | http://localhost:8000/s | 短链接前缀 |
defaultIndex | http://localhost:8000/defaultIndex | 短链接失效后重定向地址 |
内置接口
接口路由 | 请求方式 | 接口参数 | 接口用途 |
---|---|---|---|
/s/createUrl | POST | url: string, type?: string | 短链接生成接口 |
/s/deleteUrl | POST | k: string | 删除短链接接口 |
/s/:key | GET | none | 目标链接获取 |
shorturl是有本地储存方案的,也就是说我们是可以监听Redis的状态,如果断开连接时就临时将数据储存到内存中,以达到服务降级的目的。当然我们也可以直接使用内存来储存短链内容,在config.ts
配置中可以进行更改。
让我们脱离短链接这个束缚,其实shorturl本身已经是一个微型存储服务了,我们完全可以进行二次开发,输出更多的模块以支撑更多样的业务。
整个短链接服务其实非常简单,麻烦的是服务的搭建,也就是迈出的第一步。笔者也是在无数次最初一步中挣扎,最终积累了fast-nest这么一个脚手架,希望能帮助到有同样境遇的同学。
另外,附上本文的服务源码 -- shorturl(欢迎大家Star)
更多编程相关知识,请访问:编程教学!!
The above is the detailed content of Detailed explanation of the short link service implemented by Nodejs+Nest. For more information, please follow other related articles on the PHP Chinese website!