Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TypeScript Backend -- 孙雷 #1971

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions backend/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
MONGO_CONNECTION_STRING=
PORT=5000
LOG_PATH=log
HOST_HTTPS=false
HOST_DOMAIN=localhost:5000

RATE_LIMIT_URL_GET=10
RATE_LIMIT_URL_GET_WINDOW_SEC=60
RATE_LIMIT_URL_POST=5
RATE_LIMIT_URL_POST_WINDOW_SEC=60
20 changes: 20 additions & 0 deletions backend/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"env": {
"node": true,
"commonjs": true,
"es2021": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest"
},
"plugins": [
"@typescript-eslint"
],
"rules": {
}
}
133 changes: 133 additions & 0 deletions backend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Snowpack dependency directory (https://snowpack.dev/)
web_modules/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional stylelint cache
.stylelintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variable files
# .env
.env.development.local
.env.test.local
.env.production.local
.env.local

# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache

# Next.js build output
.next
out

# Nuxt.js build / generate output
.nuxt
dist

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public

# vuepress build output
.vuepress/dist

# vuepress v2.x temp and cache directory
.temp
.cache

# Docusaurus cache and generated files
.docusaurus

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TernJS port file
.tern-port

# Stores VSCode versions used for testing VSCode extensions
.vscode-test

# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

# log
log/
137 changes: 137 additions & 0 deletions backend/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,141 @@

[toc]

# TypeScript Backend Engineer Assignment
## 设计
### 假设与估算
#### 假设
用户量: 100,000
单用户每天创建链接数:3
单用户每天获取链接数:30

长链接平均长度:30
#### 估算
QPS峰值:`100,000 * 33 / 24 / 3600 * 4 = 152 query/sec`
> 4为峰值估算的系数,平均值为38

存储量:`100,000 * 3 * (30 + 8 + 32) * 365 = 7665MB/year`
服务器数量:2,分布在不同可用区

### Database Schema
#### urls
链接记录表
| Field | Type | Index | Description | Validate |
| --- | --- | --- | --- | --- |
| shortenUrl | string | unique | 短链 | 最长8位字符 |
| originUrl | string | - | 原始链 | 最长512位字符 |
| description | string | - | 描述信息 | 最长256位字符 |
| creator | string | - | 原始链 | 最长32位字符 |
| expires | Date | - | 过期时间 | - |
| status | string | - | "o": open,可用 "c": close,不可用 | - |
| createdAt | Date | - | 创建时间 | - |
| updatedAt | Date | - | 更新时间 | - |
#### systems
系统数据项表,name="id"的记录,为短链id生成器
| Field | Type | Index | Description | Validate |
| --- | --- | --- | --- | --- |
| id | BigInt | unique | 短链生成器 | - |
| name | string | unique | 系统数据项name | 取值只有"id" |
### 架构图
#### 生成短链:
Request --> Load Balancer --> RateLimiter --> Sequencer --> Database(Mongodb)
> RateLimiter: [ratelimiter.ts](./src/middleware/ratelimiter.ts)为避免大量无效流量影响系统性能,对流量采用了基于IP的时间窗口限流策略

> Sequencer: 短链生成算法原理[url.ts/newUrl](src/data/url.ts)是,基于mongodb的findOneAndUpdate的$inc原子操作[id.ts](./src/data/id.ts),采用base58编码[base58.ts](./src/util/base58.ts),生成链接
#####
#### 获取原始链接:
Request --> Load Balancer --> RateLimiter --> Cache(Memory) --missed,read through--> Database(Mongodb)
> 选择的缓存策略是read through模式[url.ts/getOriginUrlFromCache](src/data/url.ts),辅助策略是miss保护位(分别对应db miss和数据失效的情况,避免缓存穿透)
### API
#### 生成链接
`POST /url`
##### Request
Request Json Body
```
interface NewUrlRequest {
originUrl: string,
description: string,
creator: string,
expires?: Date,
}
```
##### Response
```
{
"bizErrorCode": "0",
"message": "",
"data": {
"url": "http://localhost:5000/url/0000000p",
"originUrl": "https://bing.com",
"description": "bing search engine",
"creator": "Simon",
"expires": null,
"createdAt": "2023-09-12T02:59:05.309Z"
}
}
```
| bizErrorCode | message |
| --- | --- |
| 0 | ok |
| 20000 | id system config is not created yet |
| 20001 | max id exceed |
| 30000 | short url should be provided |
| 30001 | origin url not found |
| 30012 | creator should be provided |
| 30013 | originUrl should be provided |
| 30014 | description should be provided |
| 30015 | creator too long |
| 30016 | originUrl too long |
| 30017 | description too long |

#### 获取原始链接
`GET /url/:shortUrl`
##### Response
1. HTTP STATUS CODE 302, HTTP HEADER Location: originUrl
2. HTTP STATUS CODE 404, Response Json Body
```
{
"bizErrorCode": "0",
"message": "no such url"
}
```

### 后续扩展完善
1. 添加监控报警功能
2. 观察数据量以及缓存命中率,可扩展缓存为redis,可扩展服务器数量
3. 添加数据埋点,挖掘用户行为的数据价值

## 单元测试代码以及单元测试覆盖率
#### 单元测试代码
使用jest框架进行单元测试,100%覆盖率
对于复杂逻辑的测试,利用jest的mock功能实现每个代码分支的测试,

例如:
[./src/data/url.test.ts](./src/data/url.test.ts) 中,url数据的读取采用了read-through的缓存策略,测试代码分别用mock功能模拟相应接口,实现了以下分支覆盖:
1. 缓存miss;数据库exists
2. 缓存miss;数据库miss
3. 缓存exists
#### 单元测试覆盖率
![单元测试覆盖率](./images/test_coverage.png)
## 集成测试
#### 集成测试代码
> [postman数据](./test/api_integration/hongshan.api.integration.test.json)

采用postman编写集成测试代码:
1. 调用api POST /url 创建短链
2. 调用api GET /url/:shortUrl 访问上一步创建的短链

异常情况:
1. 输入值不合法
2. 读取不存在的短链
#### 集成测试结果
![API 集成测试案例](./images/api_integration.png)


---
---



### Typescript 实现短域名服务(细节可以百度/谷歌)

Expand Down
Binary file added backend/images/api_integration.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added backend/images/test_coverage.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions backend/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
setupFiles: ['dotenv/config'],
testEnvironment: 'node',
};
Loading