project init
This commit is contained in:
commit
c504274ca9
4
.env.development
Normal file
4
.env.development
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
API_PORT=3087
|
||||||
|
API_HOST=0.0.0.0
|
||||||
|
API_TOKEN_SECRET=sdf(**&*&xx2214
|
||||||
|
API_TOKEN_EXPIRESIN=1d
|
4
.env.production
Normal file
4
.env.production
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
API_PORT=3087
|
||||||
|
API_HOST=0.0.0.0
|
||||||
|
API_TOKEN_SECRET=sdf(**&*&xx2214
|
||||||
|
API_TOKEN_EXPIRESIN=1d
|
1
.eslintignore
Normal file
1
.eslintignore
Normal file
@ -0,0 +1 @@
|
|||||||
|
dist/*.js
|
14
.eslintrc.js
Normal file
14
.eslintrc.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/** @format */
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
parser: '@typescript-eslint/parser', //定义ESLint的解析器
|
||||||
|
extends: [
|
||||||
|
'plugin:prettier/recommended', // 使用prettier中的样式规范,且如果使得ESLint会检测prettier的格式问题,同样将格式问题以error的形式抛出
|
||||||
|
],
|
||||||
|
parserOptions: {ecmaVersion: 2019, sourceType: 'module'},
|
||||||
|
env: {
|
||||||
|
//指定代码的运行环境
|
||||||
|
browser: true,
|
||||||
|
node: true,
|
||||||
|
},
|
||||||
|
}
|
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
.idea
|
||||||
|
node_modules
|
||||||
|
build
|
||||||
|
dist
|
||||||
|
.DS_Store
|
||||||
|
tmp
|
||||||
|
target
|
||||||
|
boundle.log
|
13
.prettierrc.js
Normal file
13
.prettierrc.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
module.exports = {
|
||||||
|
"printWidth": 120,
|
||||||
|
"semi": false, // 在语句末尾添加分号
|
||||||
|
"singleQuote": true, // 使用单引号而非双引号
|
||||||
|
"trailingComma": "all", // 在任何可能的多行中输入尾逗号
|
||||||
|
"bracketSpacing": true, // 在对象字面量声明所使用的的花括号前后({})输出空格
|
||||||
|
"jsxBracketSameLine": true, // 在多行JSX元素最后一行的末尾添加 > 而使 > 单独一行(不适用于自闭和元素)
|
||||||
|
"arrowParens": "avoid", // 为单行箭头函数的参数添加圆括号。
|
||||||
|
"requirePragma": false, // Prettier可以严格按照按照文件顶部的一些特殊的注释格式化代码
|
||||||
|
"insertPragma": false, // 顶部插入一个 @format
|
||||||
|
"tabWidth": 2,
|
||||||
|
"useTabs": false,
|
||||||
|
};
|
21
.vscode/launch.json
vendored
Normal file
21
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Debug Api",
|
||||||
|
"request": "launch",
|
||||||
|
"runtimeArgs": [
|
||||||
|
"run-script",
|
||||||
|
"dev:api"
|
||||||
|
],
|
||||||
|
"runtimeExecutable": "npm",
|
||||||
|
"skipFiles": [
|
||||||
|
"<node_internals>/**"
|
||||||
|
],
|
||||||
|
"type": "pwa-node"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
39
package.json
Normal file
39
package.json
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"name": "mail-svr",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"dev:api": "ts-node -r tsconfig-paths/register src/api.ts",
|
||||||
|
"prod:api": "NODE_ENV=production NODE_PATH=./dist node dist/api.js",
|
||||||
|
"build": "tsc",
|
||||||
|
"lint": "eslint --ext .ts src/**",
|
||||||
|
"format": "eslint --ext .ts src/** --fix"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@fastify/cors": "^8.2.0",
|
||||||
|
"@fastify/formbody": "^7.4.0",
|
||||||
|
"@fastify/helmet": "^10.1.0",
|
||||||
|
"@fastify/jwt": "^6.7.0",
|
||||||
|
"dotenv": "^16.0.3",
|
||||||
|
"fastify": "^4.14.1",
|
||||||
|
"fastify-plugin": "^4.5.0",
|
||||||
|
"nodemailer": "^6.9.1",
|
||||||
|
"tracer": "^1.1.6"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^18.15.3",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.55.0",
|
||||||
|
"@typescript-eslint/parser": "^5.55.0",
|
||||||
|
"eslint": "^8.36.0",
|
||||||
|
"eslint-config-prettier": "^8.7.0",
|
||||||
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
|
"prettier": "^2.8.4",
|
||||||
|
"ts-node": "^10.9.1",
|
||||||
|
"ts-node-dev": "^2.0.0",
|
||||||
|
"tsconfig-paths": "^4.1.2",
|
||||||
|
"typescript": "^4.9.5"
|
||||||
|
}
|
||||||
|
}
|
155
src/api.server.ts
Normal file
155
src/api.server.ts
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
import { fastify, FastifyError, FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'
|
||||||
|
import { IncomingMessage, Server, ServerResponse } from 'http'
|
||||||
|
import { RouterMap } from 'decorators/router'
|
||||||
|
import helmet from '@fastify/helmet'
|
||||||
|
import logger from 'logger/logger'
|
||||||
|
import * as dotenv from 'dotenv'
|
||||||
|
const NODE_ENV = process.env.NODE_ENV || 'development'
|
||||||
|
|
||||||
|
const zReqParserPlugin = require('plugins/zReqParser')
|
||||||
|
const zTokenParserPlugin = require('plugins/zTokenParser')
|
||||||
|
|
||||||
|
const fs = require('fs')
|
||||||
|
const join = require('path').join
|
||||||
|
const env = process.env
|
||||||
|
|
||||||
|
export class ApiServer {
|
||||||
|
server: FastifyInstance<Server, IncomingMessage, ServerResponse>
|
||||||
|
|
||||||
|
public constructor() {
|
||||||
|
this.server = fastify({ logger: true, trustProxy: true })
|
||||||
|
this.registerPlugins()
|
||||||
|
}
|
||||||
|
|
||||||
|
private registerPlugins() {
|
||||||
|
this.server.register(require('@fastify/formbody'))
|
||||||
|
this.server.register(zReqParserPlugin)
|
||||||
|
this.server.register(helmet, { hidePoweredBy: false })
|
||||||
|
this.server.register(zTokenParserPlugin)
|
||||||
|
|
||||||
|
// this.server.register(apiAuthPlugin, {
|
||||||
|
// secret: config.api.token_secret,
|
||||||
|
// expiresIn: config.api.token_expiresIn,
|
||||||
|
// })
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
this.server.register(require('@fastify/cors'), {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private registerRouter() {
|
||||||
|
logger.log('register api routers')
|
||||||
|
let self = this
|
||||||
|
for (let [controller, config] of RouterMap.decoratedRouters) {
|
||||||
|
for (let data of config.data) {
|
||||||
|
logger.info(
|
||||||
|
'add router',
|
||||||
|
data.method || 'all',
|
||||||
|
data.path,
|
||||||
|
`${data.target.constructor.name}.${controller.name}()`,
|
||||||
|
)
|
||||||
|
// @ts-ignore
|
||||||
|
self.server[data.method || 'all'](
|
||||||
|
data.path,
|
||||||
|
{
|
||||||
|
preValidation: async function (request: FastifyRequest, reply: FastifyReply) {
|
||||||
|
request.roles = config.roles
|
||||||
|
await this.apiAuth(request, reply)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
controller,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载所有的controller
|
||||||
|
*/
|
||||||
|
initControllers() {
|
||||||
|
logger.info('Bootstrap controllers...')
|
||||||
|
const controllers = join(__dirname, './controllers')
|
||||||
|
fs.readdirSync(controllers)
|
||||||
|
.filter((file: string) => ~file.search(/^[^.].*\.(ts|js)$/))
|
||||||
|
.forEach((file: any) => {
|
||||||
|
// logger.log(file);
|
||||||
|
return require(join(controllers, file))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private setErrHandler() {
|
||||||
|
this.server.setNotFoundHandler(function (
|
||||||
|
request: any,
|
||||||
|
reply: { send: (arg0: { errcode: number; errmsg: string }) => void },
|
||||||
|
) {
|
||||||
|
reply.send({ errcode: 404, errmsg: 'page not found' })
|
||||||
|
})
|
||||||
|
this.server.setErrorHandler(function (error: FastifyError, request: FastifyRequest, reply: FastifyReply) {
|
||||||
|
let statusCode = (error && error.statusCode) || 100
|
||||||
|
if (statusCode >= 500) {
|
||||||
|
logger.error(error)
|
||||||
|
} else if (statusCode >= 400) {
|
||||||
|
logger.info(error)
|
||||||
|
} else {
|
||||||
|
logger.error(error)
|
||||||
|
}
|
||||||
|
reply.code(200).send({
|
||||||
|
errcode: statusCode,
|
||||||
|
errmsg: error ? error.message : 'unknown error',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 格式化接口返回数据, 统一封装成如下格式
|
||||||
|
* {
|
||||||
|
* code: 0,
|
||||||
|
* msg?: '',
|
||||||
|
* data: any
|
||||||
|
* }
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private setFormatSend() {
|
||||||
|
this.server.addHook('preSerialization', async (request: FastifyRequest, reply: FastifyReply, payload) => {
|
||||||
|
reply.header('X-Powered-By', 'PHP/5.4.16')
|
||||||
|
// @ts-ignore
|
||||||
|
if (!payload.errcode) {
|
||||||
|
payload = {
|
||||||
|
errcode: 0,
|
||||||
|
data: payload,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return payload
|
||||||
|
})
|
||||||
|
}
|
||||||
|
private loadConfig() {
|
||||||
|
let path
|
||||||
|
switch (process.env.NODE_ENV) {
|
||||||
|
case 'test':
|
||||||
|
path = `${__dirname}/../.env.development`
|
||||||
|
break
|
||||||
|
case 'production':
|
||||||
|
path = `${__dirname}/../.env.production`
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
path = `${__dirname}/../.env.development`
|
||||||
|
}
|
||||||
|
dotenv.config({ path: path, debug: NODE_ENV === 'development' })
|
||||||
|
}
|
||||||
|
|
||||||
|
public async start() {
|
||||||
|
let self = this
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
self.loadConfig()
|
||||||
|
self.initControllers()
|
||||||
|
self.registerRouter()
|
||||||
|
self.setErrHandler()
|
||||||
|
self.setFormatSend()
|
||||||
|
this.server.listen({ port: +env.API_PORT, host: env.API_HOST }, (err: any, address: any) => {
|
||||||
|
if (err) {
|
||||||
|
logger.log(err)
|
||||||
|
process.exit(0)
|
||||||
|
}
|
||||||
|
resolve && resolve(address)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
12
src/api.ts
Normal file
12
src/api.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { ApiServer } from './api.server'
|
||||||
|
import logger from './logger/logger'
|
||||||
|
|
||||||
|
class Server extends ApiServer {
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let server = new Server()
|
||||||
|
server.start().then(address => {
|
||||||
|
logger.log(`Api Server listening at ${address}`)
|
||||||
|
})
|
107
src/common/AsyncQueue.ts
Normal file
107
src/common/AsyncQueue.ts
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
type Callback<T> = () => Promise<T>
|
||||||
|
|
||||||
|
export type AsyncQueue<T = void> = {
|
||||||
|
push: (task: Callback<T>) => Promise<T>
|
||||||
|
flush: () => Promise<void>
|
||||||
|
size: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures that each callback pushed onto the queue is executed in series.
|
||||||
|
* Such a quetie 😻
|
||||||
|
* @param opts.dedupeConcurrent If dedupeConcurrent is `true` it ensures that if multiple
|
||||||
|
* tasks are pushed onto the queue while there is an active task, only the
|
||||||
|
* last one will be executed, once the active task has completed.
|
||||||
|
* e.g. in the below example, only 0 and 3 will be executed.
|
||||||
|
* ```
|
||||||
|
* const queue = createAsyncQueue({ dedupeConcurrent: true })
|
||||||
|
* queue.push(async () => console.log(0)) // returns 0
|
||||||
|
* queue.push(async () => console.log(1)) // returns 3
|
||||||
|
* queue.push(async () => console.log(2)) // returns 3
|
||||||
|
* queue.push(async () => console.log(3)) // returns 3
|
||||||
|
* ```
|
||||||
|
* */
|
||||||
|
export function createAsyncQueue<T = void>(opts = { dedupeConcurrent: false }): AsyncQueue<T> {
|
||||||
|
const { dedupeConcurrent } = opts
|
||||||
|
let queue: Callback<T>[] = []
|
||||||
|
let running: Promise<void> | undefined
|
||||||
|
let nextPromise = new DeferredPromise<T>()
|
||||||
|
const push = (task: Callback<T>) => {
|
||||||
|
let taskPromise = new DeferredPromise<T>()
|
||||||
|
if (dedupeConcurrent) {
|
||||||
|
queue = []
|
||||||
|
if (nextPromise.started) nextPromise = new DeferredPromise<T>()
|
||||||
|
taskPromise = nextPromise
|
||||||
|
}
|
||||||
|
queue.push(() => {
|
||||||
|
taskPromise.started = true
|
||||||
|
task().then(taskPromise.resolve).catch(taskPromise.reject)
|
||||||
|
return taskPromise.promise
|
||||||
|
})
|
||||||
|
if (!running) running = start()
|
||||||
|
return taskPromise.promise
|
||||||
|
}
|
||||||
|
const start = async () => {
|
||||||
|
while (queue.length) {
|
||||||
|
const task = queue.shift()!
|
||||||
|
await task().catch(() => {})
|
||||||
|
}
|
||||||
|
running = undefined
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
push,
|
||||||
|
flush: () => running || Promise.resolve(),
|
||||||
|
get size() {
|
||||||
|
return queue.length
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createAsyncQueues = <T = void>(opts = { dedupeConcurrent: false }) => {
|
||||||
|
const queues: { [queueId: string]: AsyncQueue<T> } = {}
|
||||||
|
const push = (queueId: string, task: Callback<T>) => {
|
||||||
|
if (!queues[queueId]) queues[queueId] = createAsyncQueue<T>(opts)
|
||||||
|
return queues[queueId].push(task)
|
||||||
|
}
|
||||||
|
const flush = (queueId: string) => {
|
||||||
|
if (!queues[queueId]) queues[queueId] = createAsyncQueue<T>(opts)
|
||||||
|
return queues[queueId].flush()
|
||||||
|
}
|
||||||
|
return { push, flush }
|
||||||
|
}
|
||||||
|
|
||||||
|
class DeferredPromise<T = void, E = any> {
|
||||||
|
started = false
|
||||||
|
resolve: (x: T | PromiseLike<T>) => void = () => {}
|
||||||
|
reject: (x: E) => void = () => {}
|
||||||
|
promise: Promise<T>
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.promise = new Promise<T>((res, rej) => {
|
||||||
|
this.resolve = res
|
||||||
|
this.reject = rej
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// function main() {
|
||||||
|
// const queue = createAsyncQueue()
|
||||||
|
// queue.push(async () => {
|
||||||
|
// console.log(0)
|
||||||
|
// }) // returns 0
|
||||||
|
// queue.push(async () => {
|
||||||
|
// console.log(1)
|
||||||
|
|
||||||
|
// return new Promise((resolve, reject) => {
|
||||||
|
// setTimeout(() => {
|
||||||
|
// console.log('12')
|
||||||
|
// resolve()
|
||||||
|
// }, 1000)
|
||||||
|
// })
|
||||||
|
// }) // returns 3
|
||||||
|
// queue.push(async () => console.log(2)) // returns 3
|
||||||
|
// queue.push(async () => console.log(3)) // returns 3
|
||||||
|
// console.log('hi')
|
||||||
|
// }
|
||||||
|
|
||||||
|
// main()
|
6
src/common/Debug.ts
Normal file
6
src/common/Debug.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import debug from 'debug'
|
||||||
|
|
||||||
|
debug.log = console.info.bind(console)
|
||||||
|
|
||||||
|
export const error = debug('chain:error')
|
||||||
|
error.log = console.error.bind(console)
|
966
src/common/Extend.ts
Normal file
966
src/common/Extend.ts
Normal file
@ -0,0 +1,966 @@
|
|||||||
|
/**
|
||||||
|
* 对数字进行补0操作
|
||||||
|
* @param value 要补0的数值
|
||||||
|
* @param length 要补的总长度
|
||||||
|
* @return 补0之后的字符串
|
||||||
|
*/
|
||||||
|
function zeroize(value: number | string, length: number = 2): string {
|
||||||
|
let str = '' + value
|
||||||
|
let zeros = ''
|
||||||
|
for (let i = 0, len = length - str.length; i < len; i++) {
|
||||||
|
zeros += '0'
|
||||||
|
}
|
||||||
|
return zeros + str
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************扩展Object****************************************/
|
||||||
|
interface Object {
|
||||||
|
/**
|
||||||
|
* 返回一个浅副本的对象
|
||||||
|
* 此对象会拷贝key value
|
||||||
|
*
|
||||||
|
* @memberOf Object
|
||||||
|
*/
|
||||||
|
clone?(): Object
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将数据拷贝到 to
|
||||||
|
* @param to 目标
|
||||||
|
*/
|
||||||
|
copyto?(to: Object): void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定属性的描述,会查找当前数据和原型数据
|
||||||
|
* @param property 指定的属性名字
|
||||||
|
*/
|
||||||
|
getPropertyDescriptor?(property: string): PropertyDescriptor
|
||||||
|
|
||||||
|
zssign?(target: any): any
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.defineProperties(Object.prototype, {
|
||||||
|
clone: {
|
||||||
|
value: function () {
|
||||||
|
let o = {}
|
||||||
|
for (let n in this) {
|
||||||
|
// @ts-ignore
|
||||||
|
o[n] = this[n]
|
||||||
|
}
|
||||||
|
return o
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
},
|
||||||
|
getPropertyDescriptor: {
|
||||||
|
value: function (property: string): any {
|
||||||
|
let data = Object.getOwnPropertyDescriptor(this, property)
|
||||||
|
if (data) {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
let prototype = Object.getPrototypeOf(this)
|
||||||
|
if (prototype) {
|
||||||
|
return prototype.getPropertyDescriptor(property)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
},
|
||||||
|
copyto: {
|
||||||
|
value: function (to: Object) {
|
||||||
|
for (let p in this) {
|
||||||
|
if (!(p in to)) {
|
||||||
|
// 本身没有这个属性
|
||||||
|
// @ts-ignore
|
||||||
|
to[p] = this[p]
|
||||||
|
} else {
|
||||||
|
let data: PropertyDescriptor = to.getPropertyDescriptor(p)
|
||||||
|
if (data) {
|
||||||
|
if (data.set || data.writable) {
|
||||||
|
// 可进行赋值
|
||||||
|
// @ts-ignore
|
||||||
|
to[p] = this[p]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
},
|
||||||
|
zssign: {
|
||||||
|
value: function (target: Object) {
|
||||||
|
if (target === undefined || target === null) {
|
||||||
|
throw new TypeError('Cannot convert undefined or null to object')
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = Object(target)
|
||||||
|
for (let nextKey in this) {
|
||||||
|
if (!(nextKey in output)) {
|
||||||
|
// 本身没有这个属性
|
||||||
|
output[nextKey] = this[nextKey]
|
||||||
|
}
|
||||||
|
// else {
|
||||||
|
// let data: PropertyDescriptor = output.getPropertyDescriptor(nextKey);
|
||||||
|
// if (data) {
|
||||||
|
// if (data.set || data.writable) {// 可进行赋值
|
||||||
|
// output[nextKey] = this[nextKey];
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
/****************************************扩展Math****************************************/
|
||||||
|
interface Math {
|
||||||
|
/**
|
||||||
|
* 角度转弧度的乘数
|
||||||
|
* Math.PI / 180
|
||||||
|
* @type {number}
|
||||||
|
* @memberOf Math
|
||||||
|
*/
|
||||||
|
DEG_TO_RAD: number
|
||||||
|
/**
|
||||||
|
* 弧度转角度的乘数
|
||||||
|
* 180 / Math.PI
|
||||||
|
*/
|
||||||
|
RAD_TO_DEG: number
|
||||||
|
/**
|
||||||
|
* 整圆的弧度
|
||||||
|
*/
|
||||||
|
PI2: number
|
||||||
|
/**
|
||||||
|
* 90°的弧度
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @memberOf Math
|
||||||
|
*/
|
||||||
|
PI_1_2: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 让数值处于指定的最大值和最小值之间,低于最小值取最小值,高于最大值取最大值
|
||||||
|
* @param value 要处理的数值
|
||||||
|
* @param min 最小值
|
||||||
|
* @param max 最大值
|
||||||
|
*/
|
||||||
|
clamp?(value: number, min: number, max: number): number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从最小值到最大值之间随机[min,max)
|
||||||
|
*/
|
||||||
|
random2?(min: number, max: number): number
|
||||||
|
}
|
||||||
|
|
||||||
|
Math.DEG_TO_RAD = Math.PI / 180
|
||||||
|
|
||||||
|
Math.RAD_TO_DEG = 180 / Math.PI
|
||||||
|
|
||||||
|
Math.PI2 = 2 * Math.PI
|
||||||
|
|
||||||
|
Math.PI_1_2 = Math.PI * 0.5
|
||||||
|
|
||||||
|
Math.clamp = (value, min, max) => {
|
||||||
|
if (value < min) {
|
||||||
|
value = min
|
||||||
|
}
|
||||||
|
if (value > max) {
|
||||||
|
value = max
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
Math.random2 = (min, max) => {
|
||||||
|
return min + Math.random() * (max - min)
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************扩展Number********************************************/
|
||||||
|
interface Number {
|
||||||
|
/**
|
||||||
|
* 对数字进行补0操作
|
||||||
|
* @param length 要补的总长度
|
||||||
|
* @return 补0之后的字符串
|
||||||
|
*/
|
||||||
|
zeroize?(length: number): string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数值介于,`min` `max`直接,包含min,max
|
||||||
|
* 即:[min,max]
|
||||||
|
*
|
||||||
|
* @param {number} min
|
||||||
|
* @param {number} max
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
between?(min: number, max: number): boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.defineProperties(Number.prototype, {
|
||||||
|
zeroize: {
|
||||||
|
value: function (this: number, length: number) {
|
||||||
|
return zeroize(this, length)
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
},
|
||||||
|
between: {
|
||||||
|
value: function (this: number, min: number, max: number) {
|
||||||
|
return min <= this && max >= this
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
/****************************************扩展String****************************************/
|
||||||
|
interface String {
|
||||||
|
/**
|
||||||
|
* 替换字符串中{0}{1}{2}{a} {b}这样的数据,用obj对应key替换,或者是数组中对应key的数据替换
|
||||||
|
*/
|
||||||
|
substitute?(args: any[]): string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对数字进行补0操作
|
||||||
|
* @param length 要补的总长度
|
||||||
|
* @return 补0之后的字符串
|
||||||
|
*/
|
||||||
|
zeroize?(length: number): string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将一个字符串转换成一个很小几率重复的数值
|
||||||
|
* <font color="#ff0000">此方法hash的字符串并不一定唯一,慎用</font>
|
||||||
|
*/
|
||||||
|
hash?(): number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取字符串长度,中文方块字符算两个长度
|
||||||
|
*/
|
||||||
|
trueLength?(): number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 中文字符个数
|
||||||
|
* */
|
||||||
|
cnLength?(): number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 比较版本号
|
||||||
|
* */
|
||||||
|
versionCompare?(target: string): number
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.defineProperties(String.prototype, {
|
||||||
|
zeroize: {
|
||||||
|
value: function (length: number) {
|
||||||
|
return zeroize(this, length)
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
},
|
||||||
|
substitute: {
|
||||||
|
value: function (this: string) {
|
||||||
|
let len = arguments.length
|
||||||
|
if (len > 0) {
|
||||||
|
let obj: IArguments
|
||||||
|
if (len == 1) {
|
||||||
|
obj = arguments[0]
|
||||||
|
if (typeof obj !== 'object') {
|
||||||
|
obj = arguments
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
obj = arguments
|
||||||
|
}
|
||||||
|
if (obj instanceof Object && !(obj instanceof RegExp)) {
|
||||||
|
return this.replace(/\{(?:%([^{}]+)%)?([^{}]+)\}/g, function (match: string, handler: string, key: string) {
|
||||||
|
//检查key中,是否为%开头,如果是,则尝试按方法处理
|
||||||
|
// @ts-ignore
|
||||||
|
let value = obj[key]
|
||||||
|
if (handler) {
|
||||||
|
//如果有处理器,拆分处理器
|
||||||
|
let func = String.subHandler[handler]
|
||||||
|
if (func) {
|
||||||
|
value = func(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value !== undefined ? '' + value : match
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.toString() //防止生成String对象,ios反射String对象会当成一个NSDictionary处理
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
},
|
||||||
|
hash: {
|
||||||
|
value: function () {
|
||||||
|
let len = this.length
|
||||||
|
let hash = 5381
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
hash += (hash << 5) + this.charCodeAt(i)
|
||||||
|
}
|
||||||
|
return hash & 0x7fffffff
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
},
|
||||||
|
trueLength: {
|
||||||
|
value: function () {
|
||||||
|
let arr: string[] = this.match(/[^x00-xff]/gi)
|
||||||
|
return this.length + (arr ? arr.length : 0)
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
},
|
||||||
|
cnLength: {
|
||||||
|
value: function () {
|
||||||
|
// /[\u2E80-\u9FBF]
|
||||||
|
let arr: string[] = this.match(/[^x00-xff]/gi)
|
||||||
|
return arr ? arr.length : 0
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
},
|
||||||
|
versionCompare: {
|
||||||
|
value: function (target: string): number {
|
||||||
|
const GTR = 1 //大于
|
||||||
|
const LSS = -1 //小于
|
||||||
|
const EQU = 0 //等于
|
||||||
|
if (!target) {
|
||||||
|
return GTR
|
||||||
|
}
|
||||||
|
let v1arr = String(this)
|
||||||
|
.split('.')
|
||||||
|
.map(function (a) {
|
||||||
|
return parseInt(a)
|
||||||
|
})
|
||||||
|
let v2arr = String(target)
|
||||||
|
.split('.')
|
||||||
|
.map(function (a) {
|
||||||
|
return parseInt(a)
|
||||||
|
})
|
||||||
|
let arrLen = Math.max(v1arr.length, v2arr.length)
|
||||||
|
let result
|
||||||
|
|
||||||
|
//排除错误调用
|
||||||
|
if (this == undefined || target == undefined) {
|
||||||
|
throw new Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
//检查空字符串,任何非空字符串都大于空字符串
|
||||||
|
if (this.length == 0 && target.length == 0) {
|
||||||
|
return EQU
|
||||||
|
} else if (this.length == 0) {
|
||||||
|
return LSS
|
||||||
|
} else if (target.length == 0) {
|
||||||
|
return GTR
|
||||||
|
}
|
||||||
|
//循环比较版本号
|
||||||
|
for (let i = 0; i < arrLen; i++) {
|
||||||
|
result = versionComp(v1arr[i], v2arr[i])
|
||||||
|
if (result == EQU) {
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
|
||||||
|
function versionComp(n1: number, n2: number) {
|
||||||
|
if (typeof n1 != 'number') {
|
||||||
|
n1 = 0
|
||||||
|
}
|
||||||
|
if (typeof n2 != 'number') {
|
||||||
|
n2 = 0
|
||||||
|
}
|
||||||
|
if (n1 > n2) {
|
||||||
|
return GTR
|
||||||
|
} else if (n1 < n2) {
|
||||||
|
return LSS
|
||||||
|
} else {
|
||||||
|
return EQU
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
interface StringConstructor {
|
||||||
|
/**
|
||||||
|
* 对数字进行补0操作
|
||||||
|
* @param value 要补0的数值
|
||||||
|
* @param length 要补的总长度
|
||||||
|
* @return 补0之后的字符串
|
||||||
|
*/
|
||||||
|
zeroize?: (value: number, length: number) => string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* substitute的回调函数
|
||||||
|
*
|
||||||
|
* @type {Readonly<{ [index: string]: { (input: any): string } }>}
|
||||||
|
* @memberOf StringConstructor
|
||||||
|
*/
|
||||||
|
subHandler?: Readonly<{ [index: string]: { (input: any): string } }>
|
||||||
|
}
|
||||||
|
|
||||||
|
String.zeroize = zeroize
|
||||||
|
|
||||||
|
/****************************************扩展Date****************************************/
|
||||||
|
|
||||||
|
interface Date {
|
||||||
|
/**
|
||||||
|
* 格式化日期
|
||||||
|
*
|
||||||
|
* @param {string} mask 时间字符串
|
||||||
|
* @param {boolean} [local] 是否基于本地时间显示,目前项目,除了报错信息,其他时间都用UTC时间显示
|
||||||
|
* @returns {string} 格式化后的时间
|
||||||
|
*/
|
||||||
|
format(mask: string, local?: boolean): string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 增加n天
|
||||||
|
* @param {number} days
|
||||||
|
* @return {Date}
|
||||||
|
*/
|
||||||
|
addDays(days: number): Date
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.defineProperties(Date.prototype, {
|
||||||
|
format: {
|
||||||
|
value: function (mask: string, local?: boolean) {
|
||||||
|
let d: Date = this
|
||||||
|
// @ts-ignore
|
||||||
|
return mask.replace(/"[^"]*"|'[^']*'|(?:d{1,2}|m{1,2}|yy(?:yy)?|([hHMs])\1?)/g, function ($0: string) {
|
||||||
|
switch ($0) {
|
||||||
|
case 'd':
|
||||||
|
return gd()
|
||||||
|
case 'dd':
|
||||||
|
return zeroize(gd())
|
||||||
|
case 'M':
|
||||||
|
return gM() + 1
|
||||||
|
case 'MM':
|
||||||
|
return zeroize(gM() + 1)
|
||||||
|
case 'yy':
|
||||||
|
return (gy() + '').substr(2)
|
||||||
|
case 'yyyy':
|
||||||
|
return gy()
|
||||||
|
case 'h':
|
||||||
|
return gH() % 12 || 12
|
||||||
|
case 'hh':
|
||||||
|
return zeroize(gH() % 12 || 12)
|
||||||
|
case 'H':
|
||||||
|
return gH()
|
||||||
|
case 'HH':
|
||||||
|
return zeroize(gH())
|
||||||
|
case 'm':
|
||||||
|
return gm()
|
||||||
|
case 'mm':
|
||||||
|
return zeroize(gm())
|
||||||
|
case 's':
|
||||||
|
return gs()
|
||||||
|
case 'ss':
|
||||||
|
return zeroize(gs())
|
||||||
|
default:
|
||||||
|
return $0.substr(1, $0.length - 2)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function gd() {
|
||||||
|
return local ? d.getDate() : d.getUTCDate()
|
||||||
|
}
|
||||||
|
|
||||||
|
function gM() {
|
||||||
|
return local ? d.getMonth() : d.getUTCMonth()
|
||||||
|
}
|
||||||
|
|
||||||
|
function gy() {
|
||||||
|
return local ? d.getFullYear() : d.getUTCFullYear()
|
||||||
|
}
|
||||||
|
|
||||||
|
function gH() {
|
||||||
|
return local ? d.getHours() : d.getUTCHours()
|
||||||
|
}
|
||||||
|
|
||||||
|
function gm() {
|
||||||
|
return local ? d.getMinutes() : d.getUTCMinutes()
|
||||||
|
}
|
||||||
|
|
||||||
|
function gs() {
|
||||||
|
return local ? d.getSeconds() : d.getUTCSeconds()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
addDays: {
|
||||||
|
value: function (days: number) {
|
||||||
|
this.setDate(this.getDate() + days)
|
||||||
|
return this
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
/****************************************扩展Array****************************************/
|
||||||
|
const enum ArraySort {
|
||||||
|
/**
|
||||||
|
* 升序
|
||||||
|
*/
|
||||||
|
ASC = 0,
|
||||||
|
/**
|
||||||
|
* 降序
|
||||||
|
*/
|
||||||
|
DESC = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ArrayConstructor {
|
||||||
|
// binaryInsert<T>(partArr: T[], item: T, filter: { (tester: T, ...args): boolean }, ...args);
|
||||||
|
SORT_DEFAULT: { number: 0; string: ''; boolean: false }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于对Array排序时,处理undefined
|
||||||
|
*/
|
||||||
|
Array.SORT_DEFAULT = {
|
||||||
|
number: 0,
|
||||||
|
string: '',
|
||||||
|
boolean: false,
|
||||||
|
}
|
||||||
|
Object.freeze(Array.SORT_DEFAULT)
|
||||||
|
|
||||||
|
interface Array<T> {
|
||||||
|
/**
|
||||||
|
* 如果数组中没有要放入的对象,则将对象放入数组
|
||||||
|
*
|
||||||
|
* @param {T} t 要放入的对象
|
||||||
|
* @returns {number} 放入的对象,在数组中的索引
|
||||||
|
*
|
||||||
|
* @member Array
|
||||||
|
*/
|
||||||
|
pushOnce?(t: T): number
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 删除某个数据
|
||||||
|
* @param {T} t
|
||||||
|
* @returns {boolean} true 有这个数据并且删除成功
|
||||||
|
* false 没有这个数据
|
||||||
|
*/
|
||||||
|
zremove?(t: T): boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排序 支持多重排序
|
||||||
|
* 降序, 升序
|
||||||
|
* @param {(keyof T)[]} kArr 参数属性列表
|
||||||
|
* @param {(boolean[] | ArraySort[])} [dArr] 是否降序,默认升序
|
||||||
|
* @returns {this}
|
||||||
|
*
|
||||||
|
* @member Array
|
||||||
|
*/
|
||||||
|
multiSort?(kArr: (keyof T)[], dArr?: boolean[] | ArraySort[]): this
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认排序
|
||||||
|
*
|
||||||
|
* @param {string} [key]
|
||||||
|
* @param {boolean} [descend]
|
||||||
|
*
|
||||||
|
* @member Array
|
||||||
|
*/
|
||||||
|
doSort?(key?: keyof T, descend?: boolean | ArraySort): this
|
||||||
|
|
||||||
|
doSort?(descend?: boolean | ArraySort, key?: keyof T): this
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将数组克隆到to
|
||||||
|
* to的数组长度会和当前数组一致
|
||||||
|
*
|
||||||
|
* @template T
|
||||||
|
* @param {Array<T>} to
|
||||||
|
*/
|
||||||
|
cloneTo?<T>(to: Array<T>): void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将数组附加到to中
|
||||||
|
*
|
||||||
|
* @template T
|
||||||
|
* @param {Array<T>} to
|
||||||
|
*
|
||||||
|
* @member ArrayConstructor
|
||||||
|
*/
|
||||||
|
appendTo?<T>(to: Array<T>): void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除数组index位置的元素, 比slice效率高
|
||||||
|
* @param index
|
||||||
|
*/
|
||||||
|
spliceOne?(index: number): boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 随机排序
|
||||||
|
*/
|
||||||
|
randomSort?(): void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查数组中是否含有另外一个object
|
||||||
|
* @param obj 与数组同类型的obj | 同类型的数组 | 指定child字段的值 | 指定child字段的数组
|
||||||
|
* @param child 比较字段
|
||||||
|
*/
|
||||||
|
contains?<T>(obj: T | T[] | {} | {}[], child?: string): boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将数组随机插入当前数组中
|
||||||
|
* @param arr
|
||||||
|
*/
|
||||||
|
randomInsert?<T>(arr: Array<T>): void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 随机获取n个元素
|
||||||
|
* @param count
|
||||||
|
*/
|
||||||
|
randomGet?<T>(count?: number): T[]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 随机获取1个元素
|
||||||
|
*/
|
||||||
|
randomOne?<T>(): T
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 随机移除n个元素
|
||||||
|
* @param count
|
||||||
|
*/
|
||||||
|
randomRemove?<T>(count?: number): T[]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数组移动n位
|
||||||
|
* @param n n > 0 右移, n<0 左移
|
||||||
|
*/
|
||||||
|
moveElement?<T>(n: number): T[]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 两个数组并集
|
||||||
|
* @param arr
|
||||||
|
*/
|
||||||
|
union?<T>(arr: T[]): T[]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 两个数组交集
|
||||||
|
* @param arr
|
||||||
|
*/
|
||||||
|
intersect?<T>(arr: T[]): T[]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 相对于arr的差集
|
||||||
|
* @param arr
|
||||||
|
*/
|
||||||
|
difference?<T>(arr: T[]): T[]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换成Map
|
||||||
|
* @param {string} key 用于生成map的key字段名
|
||||||
|
* @return {Map<any, T>}
|
||||||
|
*/
|
||||||
|
toMap?<T>(key: string): Map<any, T>
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.defineProperties(Array.prototype, {
|
||||||
|
cloneTo: {
|
||||||
|
value: function <T>(this: T[], b: any[]) {
|
||||||
|
b.length = this.length
|
||||||
|
let len = this.length
|
||||||
|
b.length = len
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
b[i] = this[i]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
},
|
||||||
|
appendTo: {
|
||||||
|
value: function <T>(this: T[], b: any[]) {
|
||||||
|
let len = this.length
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
b.push(this[i])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
},
|
||||||
|
pushOnce: {
|
||||||
|
value: function <T>(this: T[], t: T) {
|
||||||
|
let idx = this.indexOf(t)
|
||||||
|
if (!~idx) {
|
||||||
|
idx = this.length
|
||||||
|
this.push(t)
|
||||||
|
}
|
||||||
|
return idx
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
},
|
||||||
|
zremove: {
|
||||||
|
value: function <T>(this: T[], t: T) {
|
||||||
|
let idx = this.indexOf(t)
|
||||||
|
if (~idx) {
|
||||||
|
this.splice(idx, 1)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
},
|
||||||
|
doSort: {
|
||||||
|
value: function () {
|
||||||
|
let key: string, descend: boolean
|
||||||
|
let len = arguments.length
|
||||||
|
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
let arg = arguments[i]
|
||||||
|
let t = typeof arg
|
||||||
|
if (t === 'string') {
|
||||||
|
key = arg
|
||||||
|
} else {
|
||||||
|
descend = !!arg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (key) {
|
||||||
|
return this.sort((a: any, b: any) => (descend ? b[key] - a[key] : a[key] - b[key]))
|
||||||
|
} else {
|
||||||
|
return this.sort((a: any, b: any) => (descend ? b - a : a - b))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
},
|
||||||
|
multiSort: {
|
||||||
|
value: function (kArr: string[], dArr?: boolean[] | boolean) {
|
||||||
|
let isArr = Array.isArray(dArr)
|
||||||
|
return this.sort((a: any, b: any): number => {
|
||||||
|
const def = Array.SORT_DEFAULT
|
||||||
|
for (let idx = 0, len = kArr.length; idx < len; idx++) {
|
||||||
|
let key = kArr[idx]
|
||||||
|
// @ts-ignore
|
||||||
|
let mode = isArr ? !!dArr[idx] : !!dArr
|
||||||
|
let av = a[key]
|
||||||
|
let bv = b[key]
|
||||||
|
let typea = typeof av
|
||||||
|
let typeb = typeof bv
|
||||||
|
if (typea == 'object' || typeb == 'object') {
|
||||||
|
return 0
|
||||||
|
} else if (typea != typeb) {
|
||||||
|
if (typea == 'undefined') {
|
||||||
|
// @ts-ignore
|
||||||
|
bv = def[typeb]
|
||||||
|
} else if (typeb == 'undefined') {
|
||||||
|
// @ts-ignore
|
||||||
|
av = def[typea]
|
||||||
|
} else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (av < bv) {
|
||||||
|
return mode ? 1 : -1
|
||||||
|
} else if (av > bv) {
|
||||||
|
return mode ? -1 : 1
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
})
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
},
|
||||||
|
spliceOne: {
|
||||||
|
value: function (index: number): boolean {
|
||||||
|
if (index === -1 || index >= this.length) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const len = this.length - 1
|
||||||
|
for (let i = index; i < len; i++) {
|
||||||
|
this[i] = this[i + 1]
|
||||||
|
}
|
||||||
|
this.length = len
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
randomSort: {
|
||||||
|
value: function <T>() {
|
||||||
|
for (let j, x, i = this.length; i; j = (Math.random() * i) | 0, x = this[--i], this[i] = this[j], this[j] = x) {}
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
},
|
||||||
|
contains: {
|
||||||
|
value: function <T>(obj: T | T[] | {} | {}[], child?: string): boolean {
|
||||||
|
let result = false
|
||||||
|
if (child) {
|
||||||
|
const isArr = Array.isArray(obj)
|
||||||
|
if (isArr) {
|
||||||
|
// @ts-ignore
|
||||||
|
if (obj[0].hasOwnProperty(child)) {
|
||||||
|
let set0 = new Set()
|
||||||
|
// @ts-ignore
|
||||||
|
for (let s of obj) {
|
||||||
|
// @ts-ignore
|
||||||
|
set0.add(s[child])
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
let set1 = new Set(this.filter(x => set0.has(x)))
|
||||||
|
return set0.size === set1.size
|
||||||
|
} else {
|
||||||
|
// @ts-ignore
|
||||||
|
let set0 = new Set(obj)
|
||||||
|
let set1 = new Set(this.filter((x: {}) => set0.has(x)))
|
||||||
|
return set1.size === set0.size
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (obj.hasOwnProperty(child)) {
|
||||||
|
for (let sub of this) {
|
||||||
|
if (sub.hasOwnProperty(child)) {
|
||||||
|
// @ts-ignore
|
||||||
|
if (sub[child] === obj[child]) {
|
||||||
|
result = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let sub of this) {
|
||||||
|
if (sub.hasOwnProperty(child)) {
|
||||||
|
// @ts-ignore
|
||||||
|
if (sub[child] === obj) {
|
||||||
|
result = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 不指定 比较字段 的话, 只处理2种情况
|
||||||
|
// 1: obj 为数组
|
||||||
|
// 2: obj 不是数组
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
let set0 = new Set(obj)
|
||||||
|
// @ts-ignore
|
||||||
|
let set1 = new Set(this.filter(x => set0.has(x)))
|
||||||
|
return set1.size === set0.size
|
||||||
|
} else {
|
||||||
|
let idx = this.indexOf(obj)
|
||||||
|
return !!~idx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
},
|
||||||
|
randomInsert: {
|
||||||
|
value: function <T>(arr: Array<T>) {
|
||||||
|
const length = this.length
|
||||||
|
arr.forEach(value => {
|
||||||
|
this.splice(Math.random() * length, 0, value)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
},
|
||||||
|
randomGet: {
|
||||||
|
value: function <T>(count: number = 1): T[] {
|
||||||
|
let shuffled: T[] = this.slice(0),
|
||||||
|
i = this.length,
|
||||||
|
min = i - count,
|
||||||
|
temp,
|
||||||
|
index
|
||||||
|
if (min < 0) {
|
||||||
|
return shuffled
|
||||||
|
}
|
||||||
|
while (i-- > min) {
|
||||||
|
index = Math.floor((i + 1) * Math.random())
|
||||||
|
temp = shuffled[index]
|
||||||
|
shuffled[index] = shuffled[i]
|
||||||
|
shuffled[i] = temp
|
||||||
|
}
|
||||||
|
return shuffled.slice(min)
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
},
|
||||||
|
randomOne: {
|
||||||
|
value: function <T>(): T {
|
||||||
|
let results = this.randomGet(1)
|
||||||
|
if (results.length > 0) {
|
||||||
|
return results[0]
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
},
|
||||||
|
randomRemove: {
|
||||||
|
value: function <T>(count: number = 1): T[] {
|
||||||
|
let result = []
|
||||||
|
while (count-- > 0 && this.length > 0) {
|
||||||
|
let index = (Math.random() * this.length) | 0
|
||||||
|
result.push(...this.splice(index, 1))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
moveElement: {
|
||||||
|
value: function <T>(n: number): T[] {
|
||||||
|
if (Math.abs(n) > this.length) n = n % this.length
|
||||||
|
return this.slice(-n).concat(this.slice(0, -n))
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
union: {
|
||||||
|
value: function <T>(this: T[], b: any[]): T[] {
|
||||||
|
let a = this.concat(b)
|
||||||
|
return [...new Set(a)]
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
intersect: {
|
||||||
|
value: function <T>(this: T[], b: any[]): T[] {
|
||||||
|
let set0 = new Set(b)
|
||||||
|
let set1 = new Set(this.filter(x => set0.has(x)))
|
||||||
|
return [...set1]
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
difference: {
|
||||||
|
value: function <T>(this: T[], b: any[]): T[] {
|
||||||
|
let set0 = new Set(b)
|
||||||
|
let set1 = new Set(this.filter(x => !set0.has(x)))
|
||||||
|
return [...set1]
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
toMap: {
|
||||||
|
value: function <T>(this: T[], key: string) {
|
||||||
|
let result: Map<any, T> = new Map()
|
||||||
|
for (const o of this) {
|
||||||
|
// @ts-ignore
|
||||||
|
result.set(o[key], o)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
interface Map<K, V> {
|
||||||
|
/**
|
||||||
|
* 只针对V为number的Map, 有值的话, 加上V, 没值则直接set
|
||||||
|
* V为其他类型时, 直接set
|
||||||
|
* @param key
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
|
inc?(key: K, value: V): number
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.defineProperties(Map.prototype, {
|
||||||
|
inc: {
|
||||||
|
value: function <K, V>(key: K, value: V) {
|
||||||
|
if (typeof value == 'number') {
|
||||||
|
this.set(key, (this.get(key) || 0) + value)
|
||||||
|
} else {
|
||||||
|
this.set(key, value)
|
||||||
|
}
|
||||||
|
return this.get(key)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
13
src/common/ZError.ts
Normal file
13
src/common/ZError.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { FastifyError } from 'fastify'
|
||||||
|
|
||||||
|
export class ZError implements FastifyError {
|
||||||
|
code: string
|
||||||
|
statusCode?: number
|
||||||
|
message: string
|
||||||
|
name: string
|
||||||
|
|
||||||
|
constructor(statusCode: number, message: string) {
|
||||||
|
this.statusCode = statusCode
|
||||||
|
this.message = message
|
||||||
|
}
|
||||||
|
}
|
6
src/common/base.controller.ts
Normal file
6
src/common/base.controller.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import fastify = require('fastify')
|
||||||
|
|
||||||
|
class BaseController {
|
||||||
|
aotoRoute(req: fastify.FastifyRequest, res) {}
|
||||||
|
}
|
||||||
|
export default BaseController
|
29
src/decorators/nojson.ts
Normal file
29
src/decorators/nojson.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import 'reflect-metadata'
|
||||||
|
import { singleton } from './singleton'
|
||||||
|
|
||||||
|
@singleton
|
||||||
|
export class NoJsonClass {
|
||||||
|
private noJsonPropSet: Set<string> = new Set()
|
||||||
|
|
||||||
|
public addKey(className: string, propertyKey: string) {
|
||||||
|
this.noJsonPropSet.add(className + '_' + propertyKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
public checkExist(className: string, propertyKey: string) {
|
||||||
|
return this.noJsonPropSet.has(className + '_' + propertyKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 在不需要toJson方法输出的字段上加上 @noJson
|
||||||
|
* @return {{(target: Function): void, (target: Object, propertyKey: (string | symbol)): void}}
|
||||||
|
*/
|
||||||
|
export function noJson() {
|
||||||
|
// return Reflect.metadata(noJsonMetadataKey, !0)
|
||||||
|
return function (target: Object, propertyKey: string) {
|
||||||
|
new NoJsonClass().addKey(target.constructor.name, propertyKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkJson(target: any, propertyKey: string) {
|
||||||
|
return !new NoJsonClass().checkExist(target.constructor.modelName, propertyKey)
|
||||||
|
}
|
142
src/decorators/router.ts
Normal file
142
src/decorators/router.ts
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
import BaseController from '../common/base.controller'
|
||||||
|
|
||||||
|
export class RouterData {
|
||||||
|
target?: any
|
||||||
|
method?: string
|
||||||
|
path?: string
|
||||||
|
fun?: Function
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RouterMap {
|
||||||
|
static decoratedRouters: Map<
|
||||||
|
Function,
|
||||||
|
{
|
||||||
|
roles?: string[]
|
||||||
|
permissions?: string[][]
|
||||||
|
data?: RouterData[]
|
||||||
|
depts?: string[]
|
||||||
|
}
|
||||||
|
> = new Map()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function router(route?: string) {
|
||||||
|
return (target: BaseController, name: string, value: PropertyDescriptor) => {
|
||||||
|
if (!route) {
|
||||||
|
const controller = target.constructor.name
|
||||||
|
const controllerName = controller.toLowerCase().replace('.controller', '')
|
||||||
|
route = 'all ' + ['', controllerName, name].join('/')
|
||||||
|
}
|
||||||
|
const split = route.split(' ')
|
||||||
|
if (split.length > 2) {
|
||||||
|
throw new Error('路由中只允许一个空格')
|
||||||
|
}
|
||||||
|
const [method, path] = split
|
||||||
|
// @ts-ignore
|
||||||
|
const key = target[name]
|
||||||
|
let routerData = new RouterData()
|
||||||
|
routerData.target = target
|
||||||
|
routerData.method = method
|
||||||
|
routerData.path = path
|
||||||
|
// @ts-ignore
|
||||||
|
routerData.fun = target[name]
|
||||||
|
|
||||||
|
if (RouterMap.decoratedRouters.has(key)) {
|
||||||
|
let objCurrent = RouterMap.decoratedRouters.get(key)
|
||||||
|
if (!objCurrent.data) {
|
||||||
|
objCurrent.data = [routerData]
|
||||||
|
} else {
|
||||||
|
objCurrent.data.push(routerData)
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
RouterMap.decoratedRouters.set(target[name], objCurrent)
|
||||||
|
} else {
|
||||||
|
let routerObj = {
|
||||||
|
data: [routerData],
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
RouterMap.decoratedRouters.set(target[name], routerObj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function role(roles?: string | string[]) {
|
||||||
|
return (target: BaseController, name: string, value: PropertyDescriptor) => {
|
||||||
|
let roleList: string[] = []
|
||||||
|
if (roles) {
|
||||||
|
if (Array.isArray(roles)) {
|
||||||
|
roleList = roles
|
||||||
|
} else {
|
||||||
|
roleList = [roles]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
const key = target[name]
|
||||||
|
let roleObj = { roles: roleList }
|
||||||
|
if (RouterMap.decoratedRouters.has(key)) {
|
||||||
|
let objCurrent = RouterMap.decoratedRouters.get(key)
|
||||||
|
Object.assign(objCurrent, roleObj)
|
||||||
|
// @ts-ignore
|
||||||
|
RouterMap.decoratedRouters.set(target[name], objCurrent)
|
||||||
|
} else {
|
||||||
|
// @ts-ignore
|
||||||
|
RouterMap.decoratedRouters.set(target[name], roleObj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function permission(permissions?: string | string[]) {
|
||||||
|
return (target: BaseController, name: string, value: PropertyDescriptor) => {
|
||||||
|
let permissionList: string[][] = [[]]
|
||||||
|
if (permissions) {
|
||||||
|
if (Array.isArray(permissions)) {
|
||||||
|
let arr = []
|
||||||
|
for (let sub of permissions) {
|
||||||
|
arr.push(sub.split(':'))
|
||||||
|
}
|
||||||
|
permissionList = arr
|
||||||
|
} else {
|
||||||
|
permissionList = [permissions.split(':')]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
const key = target[name]
|
||||||
|
let permissionObj = { permissions: permissionList }
|
||||||
|
if (RouterMap.decoratedRouters.has(key)) {
|
||||||
|
let objCurrent = RouterMap.decoratedRouters.get(key)
|
||||||
|
Object.assign(objCurrent, permissionObj)
|
||||||
|
// @ts-ignore
|
||||||
|
RouterMap.decoratedRouters.set(target[name], objCurrent)
|
||||||
|
} else {
|
||||||
|
// @ts-ignore
|
||||||
|
RouterMap.decoratedRouters.set(target[name], permissionObj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 有dept修饰器的, 需要验证部门id是否存在
|
||||||
|
*/
|
||||||
|
export function dept(depts?: string | string[]) {
|
||||||
|
return (target: BaseController, name: string, value: PropertyDescriptor) => {
|
||||||
|
let deptList: string[] = []
|
||||||
|
if (depts) {
|
||||||
|
if (Array.isArray(depts)) {
|
||||||
|
deptList = depts
|
||||||
|
} else {
|
||||||
|
deptList = [depts]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
const key = target[name]
|
||||||
|
let deptObj = { depts: deptList }
|
||||||
|
if (RouterMap.decoratedRouters.has(key)) {
|
||||||
|
let objCurrent = RouterMap.decoratedRouters.get(key)
|
||||||
|
Object.assign(objCurrent, deptObj)
|
||||||
|
// @ts-ignore
|
||||||
|
RouterMap.decoratedRouters.set(target[name], objCurrent)
|
||||||
|
} else {
|
||||||
|
// @ts-ignore
|
||||||
|
RouterMap.decoratedRouters.set(target[name], deptObj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
src/decorators/singleton.ts
Normal file
29
src/decorators/singleton.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* 单例化一个class
|
||||||
|
* 使用方法:
|
||||||
|
* @singleton
|
||||||
|
* class Test {}
|
||||||
|
* new Test() === new Test() // returns `true`
|
||||||
|
* 也可以不使用 decorator
|
||||||
|
* const TestSingleton = singleton(Test)
|
||||||
|
* new TestSingleton() === new TestSingleton() //returns 'true'
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const SINGLETON_KEY = Symbol()
|
||||||
|
|
||||||
|
export type Singleton<T extends new (...args: any[]) => any> = T & {
|
||||||
|
[SINGLETON_KEY]: T extends new (...args: any[]) => infer I ? I : never
|
||||||
|
}
|
||||||
|
export const singleton = <T extends new (...args: any[]) => any>(classTarget: T) =>
|
||||||
|
new Proxy(classTarget, {
|
||||||
|
construct(target: Singleton<T>, argumentsList, newTarget) {
|
||||||
|
// Skip proxy for children
|
||||||
|
if (target.prototype !== newTarget.prototype) {
|
||||||
|
return Reflect.construct(target, argumentsList, newTarget)
|
||||||
|
}
|
||||||
|
if (!target[SINGLETON_KEY]) {
|
||||||
|
target[SINGLETON_KEY] = Reflect.construct(target, argumentsList, newTarget)
|
||||||
|
}
|
||||||
|
return target[SINGLETON_KEY]
|
||||||
|
},
|
||||||
|
})
|
2
src/logger/logger.ts
Normal file
2
src/logger/logger.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
const logger = require('tracer').colorConsole({ dateformat: 'yyyy-mm-dd HH:MM:ss.L' })
|
||||||
|
export default logger
|
49
src/plugins/apiauth.ts
Normal file
49
src/plugins/apiauth.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { FastifyPluginAsync, FastifyReply, FastifyRequest } from 'fastify'
|
||||||
|
import fastifyPlugin from 'fastify-plugin'
|
||||||
|
|
||||||
|
declare module 'fastify' {
|
||||||
|
interface FastifyRequest {
|
||||||
|
roles?: string[]
|
||||||
|
user?: any
|
||||||
|
token?: string
|
||||||
|
}
|
||||||
|
interface FastifyInstance {
|
||||||
|
apiAuth: (request: FastifyRequest, reply: FastifyReply) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiAuthOptions {
|
||||||
|
secret: string
|
||||||
|
expiresIn: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiAuthPlugin: FastifyPluginAsync<ApiAuthOptions> = async function (fastify, opts) {
|
||||||
|
fastify.register(require('@fastify/jwt'), {
|
||||||
|
secret: opts.secret,
|
||||||
|
sign: { expiresIn: opts.expiresIn },
|
||||||
|
})
|
||||||
|
// 只有路由配置的role为anon才不需要过滤
|
||||||
|
fastify.decorate('apiAuth', async function (request: FastifyRequest, reply: FastifyReply) {
|
||||||
|
if (!request.roles || request.roles.indexOf('anon') == -1) {
|
||||||
|
try {
|
||||||
|
if (!request.token) {
|
||||||
|
return reply.send({ code: 11, msg: 'need login' })
|
||||||
|
}
|
||||||
|
//@ts-ignore
|
||||||
|
const data = this.jwt.verify(request.token)
|
||||||
|
if (!data || !data.id) {
|
||||||
|
return reply.send({ code: 10, msg: 'need login' })
|
||||||
|
}
|
||||||
|
// let account = await Account.findById(data.id)
|
||||||
|
// if (!account) {
|
||||||
|
// return reply.send({ code: 10, msg: 'need login' })
|
||||||
|
// }
|
||||||
|
// request.user = account
|
||||||
|
} catch (err) {
|
||||||
|
return reply.send({ code: 401, msg: 'need auth' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default fastifyPlugin(apiAuthPlugin, '4.x')
|
26
src/plugins/zReqParser.ts
Normal file
26
src/plugins/zReqParser.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { FastifyInstance, FastifyPluginAsync, FastifyReply, FastifyRequest } from 'fastify'
|
||||||
|
import fastifyPlugin from 'fastify-plugin'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将post 和 get 的参数统一到 req.params
|
||||||
|
*/
|
||||||
|
declare module 'fastify' {
|
||||||
|
interface FastifyInstance {
|
||||||
|
zReqParser: (request: FastifyRequest, reply: FastifyReply) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const zReqParserPlugin: FastifyPluginAsync = async function (fastify: FastifyInstance, options?: any) {
|
||||||
|
fastify.addHook('preValidation', async (request: FastifyRequest, reply: FastifyReply) => {
|
||||||
|
let params = request.params || {}
|
||||||
|
if (request.query) {
|
||||||
|
Object.assign(params, request.query)
|
||||||
|
}
|
||||||
|
if (request.body) {
|
||||||
|
Object.assign(params, request.body)
|
||||||
|
}
|
||||||
|
request.params = params
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
export default fastifyPlugin(zReqParserPlugin, '4.x')
|
62
src/plugins/zTokenParser.ts
Normal file
62
src/plugins/zTokenParser.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { FastifyInstance, FastifyPluginAsync, FastifyReply, FastifyRequest } from 'fastify'
|
||||||
|
import fastifyPlugin from 'fastify-plugin'
|
||||||
|
|
||||||
|
const getTokenFromHeader = function (request) {
|
||||||
|
let token: string | undefined
|
||||||
|
if (request.headers && request.headers.authorization) {
|
||||||
|
const parts = request.headers.authorization.split(' ')
|
||||||
|
if (parts.length === 2) {
|
||||||
|
const scheme = parts[0]
|
||||||
|
if (/^Bearer$/i.test(scheme)) {
|
||||||
|
token = parts[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
const getTokenFromCookie = function (request) {
|
||||||
|
let token: string | undefined
|
||||||
|
if (request.cookies) {
|
||||||
|
if (request.cookies['token']) {
|
||||||
|
token = request.cookies['token']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTokenFromParams = function (request) {
|
||||||
|
let token: string | undefined
|
||||||
|
token = request.params && request.params.token
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTokenFromQuery = function (request) {
|
||||||
|
let token: string | undefined
|
||||||
|
token = request.query && request.query.token
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTokenFromBody = function (request) {
|
||||||
|
let token: string | undefined
|
||||||
|
token = request.body && request.body.token
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
const zTokenParserPlugin: FastifyPluginAsync = async function (fastify: FastifyInstance, options?: any) {
|
||||||
|
fastify.addHook('preValidation', async (request: FastifyRequest, reply: FastifyReply) => {
|
||||||
|
request['token'] =
|
||||||
|
getTokenFromHeader(request) ||
|
||||||
|
getTokenFromCookie(request) ||
|
||||||
|
getTokenFromParams(request) ||
|
||||||
|
getTokenFromQuery(request) ||
|
||||||
|
getTokenFromBody(request)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 依次从request的header, cookie, params, query和body中获取token, 加入到request.token中
|
||||||
|
* header中的字段key为authorization, 格式为 Bearer xxxx
|
||||||
|
* 其他位置的key都为 token
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default fastifyPlugin(zTokenParserPlugin, '4.x')
|
1034
src/utils/Extend.ts
Normal file
1034
src/utils/Extend.ts
Normal file
File diff suppressed because it is too large
Load Diff
72
src/utils/chain.util.ts
Normal file
72
src/utils/chain.util.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
interface Window {
|
||||||
|
ethereum: any;
|
||||||
|
web3: any;
|
||||||
|
celo: any;
|
||||||
|
}
|
||||||
|
declare let window: Window;
|
||||||
|
|
||||||
|
export function verifyInjectedProvider(check: string): boolean {
|
||||||
|
return window.ethereum
|
||||||
|
? window.ethereum[check]
|
||||||
|
: window.web3 &&
|
||||||
|
window.web3.currentProvider &&
|
||||||
|
window.web3.currentProvider[check];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* check if there have metamask
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
export function hasMetamask(): boolean {
|
||||||
|
if (!!window.ethereum || !!window.web3) {
|
||||||
|
return verifyInjectedProvider("isMetaMask");
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* change price with customer decimals to bigNum with 18 decimals
|
||||||
|
* @param {number} price
|
||||||
|
* @param {number} decimals
|
||||||
|
*/
|
||||||
|
export function parsePrice(price: number, decimals: number): string {
|
||||||
|
const n = 19 - decimals;
|
||||||
|
return price + new Array(n).join("0");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* format price with customer decimals to string for display
|
||||||
|
* @param {number | string} price
|
||||||
|
* @param {number} decimals
|
||||||
|
* @param {number} fixed
|
||||||
|
* @return {number | string}
|
||||||
|
*/
|
||||||
|
export function formatPrice(
|
||||||
|
price: number | string,
|
||||||
|
decimals?: number,
|
||||||
|
fixed = 2
|
||||||
|
): number | string {
|
||||||
|
if (!decimals) {
|
||||||
|
return price;
|
||||||
|
}
|
||||||
|
let str = price + "";
|
||||||
|
const length = str.length;
|
||||||
|
str = str.padStart(decimals, "0");
|
||||||
|
if (decimals >= length) {
|
||||||
|
str = "0." + str;
|
||||||
|
} else {
|
||||||
|
const pos = length - decimals;
|
||||||
|
str = str.slice(0, pos) + "." + str.slice(pos);
|
||||||
|
}
|
||||||
|
str = str.slice(0, str.lastIndexOf(".") + fixed + 1);
|
||||||
|
return str.replace(/0+$/, "").replace(/\.+$/, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* number to hex string
|
||||||
|
* @param {number} chainId
|
||||||
|
*/
|
||||||
|
export function toHexChainId(chainId: number): string {
|
||||||
|
return "0x" + chainId.toString(16);
|
||||||
|
}
|
95
src/utils/net.utils.ts
Normal file
95
src/utils/net.utils.ts
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
export const RE_URL_SCHEME = /^(.+?):\/\/.+?$/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取url中的scheme
|
||||||
|
* @param url
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function findUrlScheme(url: string) {
|
||||||
|
let result = url.match(RE_URL_SCHEME);
|
||||||
|
if (!result) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return result[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成 key1=val1&key2=val2的字符串
|
||||||
|
* @param {object} data 需要处理的对象
|
||||||
|
* @param {boolean} sort 是否按key生序重排
|
||||||
|
* @param {boolean} ignoreNull 是否过滤空值(空格或者null值不参与拼接)
|
||||||
|
* @param splitChar 连接的字符, 默认是&
|
||||||
|
* @param equalChar =
|
||||||
|
*/
|
||||||
|
export function generateKVStr({
|
||||||
|
data = {},
|
||||||
|
sort = false,
|
||||||
|
ignoreNull = true,
|
||||||
|
splitChar = "&",
|
||||||
|
equalChar = "=",
|
||||||
|
uri = "",
|
||||||
|
}: {
|
||||||
|
data?: any;
|
||||||
|
sort?: boolean;
|
||||||
|
ignoreNull?: boolean;
|
||||||
|
splitChar?: string;
|
||||||
|
equalChar?: string;
|
||||||
|
uri?: string;
|
||||||
|
}) {
|
||||||
|
const keys = Object.keys(data);
|
||||||
|
sort && keys.sort();
|
||||||
|
let result = "";
|
||||||
|
let i = 0;
|
||||||
|
for (let key of keys) {
|
||||||
|
if (ignoreNull && !data[key]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (i++ > 0) result += splitChar;
|
||||||
|
result += `${key}${equalChar}${data[key]}`;
|
||||||
|
}
|
||||||
|
if (uri) {
|
||||||
|
const joinChar = uri.search(/\?/) === -1 ? "?" : "&";
|
||||||
|
result = uri + joinChar + result;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将key1=val&key2=val的字符串组装成对象
|
||||||
|
* @param str key1=val&key2=val的字符串
|
||||||
|
* @param splitChar 连接的字符, 默认是&
|
||||||
|
* @param equalChar =
|
||||||
|
*/
|
||||||
|
export function keyValToObject(
|
||||||
|
str: string,
|
||||||
|
splitChar: string = "&",
|
||||||
|
equalChar = "="
|
||||||
|
): {} {
|
||||||
|
let result: any = {};
|
||||||
|
if (!str) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
let arrs = str.split(splitChar);
|
||||||
|
for (let sub of arrs) {
|
||||||
|
let subArr = sub.split(equalChar);
|
||||||
|
result[subArr[0]] = subArr[1];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析jwt token中的用户信息, 不校验签名
|
||||||
|
* @param token
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function decodeJWT(token: string) {
|
||||||
|
let strings = token.split(".");
|
||||||
|
var userinfo = JSON.parse(
|
||||||
|
decodeURIComponent(
|
||||||
|
encodeURIComponent(
|
||||||
|
window.atob(strings[1].replace(/-/g, "+").replace(/_/g, "/"))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return userinfo;
|
||||||
|
}
|
34
src/utils/number.util.ts
Normal file
34
src/utils/number.util.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* 随机数
|
||||||
|
* @param {number} max
|
||||||
|
* @param {number} min
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
export function getRandom(max: number, min?: number): number {
|
||||||
|
min = min || 0;
|
||||||
|
return Math.floor(Math.random() * (max - min) + min);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据概率数组获取随机index
|
||||||
|
* @since 1.0.0
|
||||||
|
* @param prob_array 概率数组
|
||||||
|
*/
|
||||||
|
export function random_prob(prob_array: number[]): number {
|
||||||
|
let total = 0;
|
||||||
|
for (let _d of prob_array) {
|
||||||
|
total += _d;
|
||||||
|
}
|
||||||
|
prob_array = prob_array.map((o) => o / total);
|
||||||
|
// 获取随机数
|
||||||
|
let r = Math.random();
|
||||||
|
// 对概率数组的处理
|
||||||
|
let s = prob_array
|
||||||
|
.map((v, index) => {
|
||||||
|
return { index: index, prob: v };
|
||||||
|
})
|
||||||
|
.sort((a, b) => a.prob - b.prob);
|
||||||
|
// 判断随机位置
|
||||||
|
let result = s.find((v) => (r -= v.prob) <= 0);
|
||||||
|
return result ? result.index : s.length - 1;
|
||||||
|
}
|
152
src/utils/promise.util.ts
Normal file
152
src/utils/promise.util.ts
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
type RetryOptions = {
|
||||||
|
maxRetries: number;
|
||||||
|
whitelistErrors: Error[];
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 使用:
|
||||||
|
* retry(() => fetch("https://example.com"), { maxRetries: 3, whitelistErrors: [] })
|
||||||
|
* .then((response) => console.log(response))
|
||||||
|
* .catch((error) => console.error(error));
|
||||||
|
* @param promiseFn
|
||||||
|
* @param options
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function retry<T>(
|
||||||
|
promiseFn: () => Promise<T>,
|
||||||
|
options: RetryOptions
|
||||||
|
): Promise<T> {
|
||||||
|
let retries = 0;
|
||||||
|
let defaultOptions = {
|
||||||
|
maxRetries: 3,
|
||||||
|
whitelistErrors: [],
|
||||||
|
};
|
||||||
|
Object.assign(defaultOptions, options);
|
||||||
|
const { maxRetries, whitelistErrors } = options;
|
||||||
|
|
||||||
|
const retryPromise = async (): Promise<T> => {
|
||||||
|
try {
|
||||||
|
return await promiseFn();
|
||||||
|
} catch (err) {
|
||||||
|
if (
|
||||||
|
retries < maxRetries &&
|
||||||
|
whitelistErrors.some(
|
||||||
|
(whitelistedError) => err instanceof whitelistedError.constructor
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
retries++;
|
||||||
|
return retryPromise();
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return retryPromise();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 构建一个promise, 在
|
||||||
|
* usage:
|
||||||
|
* function delay(ms: number): Promise<void> {
|
||||||
|
const deferred = new Deferred<void>();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
deferred.resolve();
|
||||||
|
}, ms);
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("start");
|
||||||
|
|
||||||
|
delay(1000).then(() => {
|
||||||
|
console.log("after 1 second");
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("end");
|
||||||
|
*/
|
||||||
|
export class Deferred<T = any> {
|
||||||
|
private _resolve!: (value: T | PromiseLike<T>) => void;
|
||||||
|
private _reject!: (reason?: any) => void;
|
||||||
|
|
||||||
|
public readonly promise: Promise<T>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.promise = new Promise<T>((resolve, reject) => {
|
||||||
|
this._resolve = resolve;
|
||||||
|
this._reject = reject;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public resolve(value: T | PromiseLike<T>): void {
|
||||||
|
this._resolve(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public reject(reason?: any): void {
|
||||||
|
this._reject(reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
public then<TResult1 = T, TResult2 = never>(
|
||||||
|
onfulfilled?:
|
||||||
|
| ((value: T) => TResult1 | PromiseLike<TResult1>)
|
||||||
|
| null
|
||||||
|
| undefined,
|
||||||
|
onrejected?:
|
||||||
|
| ((reason: any) => TResult2 | PromiseLike<TResult2>)
|
||||||
|
| null
|
||||||
|
| undefined
|
||||||
|
): Promise<TResult1 | TResult2> {
|
||||||
|
return this.promise.then(onfulfilled, onrejected);
|
||||||
|
}
|
||||||
|
|
||||||
|
public catch<TResult = never>(
|
||||||
|
onrejected?:
|
||||||
|
| ((reason: any) => TResult | PromiseLike<TResult>)
|
||||||
|
| null
|
||||||
|
| undefined
|
||||||
|
): Promise<T | TResult> {
|
||||||
|
return this.promise.catch(onrejected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 简单限流的 Promise 队列
|
||||||
|
* usage:
|
||||||
|
const q = new PromiseQueue();
|
||||||
|
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].forEach((v) => {
|
||||||
|
q.add(
|
||||||
|
() =>
|
||||||
|
new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log(v);
|
||||||
|
resolve();
|
||||||
|
}, 1000);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
export class PromiseQueue {
|
||||||
|
private readonly concurrency: number;
|
||||||
|
private _current: number = 0;
|
||||||
|
private _list: (() => Promise<any>)[] = [];
|
||||||
|
|
||||||
|
constructor({ concurrency = 2 }: { concurrency: number }) {
|
||||||
|
this.concurrency = concurrency;
|
||||||
|
}
|
||||||
|
|
||||||
|
add(promiseFn: () => Promise<any>) {
|
||||||
|
this._list.push(promiseFn);
|
||||||
|
this.loadNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
loadNext() {
|
||||||
|
if (this._list.length === 0 || this.concurrency === this._current) return;
|
||||||
|
this._current++;
|
||||||
|
const fn = this._list.shift()!;
|
||||||
|
const promise = fn.call(this);
|
||||||
|
promise.then(this.onLoaded.bind(this)).catch(this.onLoaded.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoaded() {
|
||||||
|
this._current--;
|
||||||
|
this.loadNext();
|
||||||
|
}
|
||||||
|
}
|
103
src/utils/string.util.ts
Normal file
103
src/utils/string.util.ts
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
/**
|
||||||
|
* 判断传入的值是否为true
|
||||||
|
* @param {Object} obj 传入值为'true','TRUE',1,'1','on','ON','YES','yes'时,返回true,其他值均返回false
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
export function isTrue(obj: any) {
|
||||||
|
return (
|
||||||
|
obj === "true" ||
|
||||||
|
obj === "TRUE" ||
|
||||||
|
obj === "True" ||
|
||||||
|
obj === "on" ||
|
||||||
|
obj === "ON" ||
|
||||||
|
obj === true ||
|
||||||
|
obj === 1 ||
|
||||||
|
obj === "1" ||
|
||||||
|
obj === "YES" ||
|
||||||
|
obj === "yes"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证ObjectId格式是否正确
|
||||||
|
* @param {string} id
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
export function isObjectId(id: string): boolean {
|
||||||
|
//mongoose.Types.ObjectId.isValid(id)
|
||||||
|
return /^[a-fA-F0-9]{24}$/.test(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 10进制 -> 62进制
|
||||||
|
* @param {string | number} number
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
export function string10to62(number: string | number) {
|
||||||
|
const chars =
|
||||||
|
"0123456789abcdefghigklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ".split("");
|
||||||
|
const radix = chars.length;
|
||||||
|
let qutient = +number;
|
||||||
|
const arr = [];
|
||||||
|
do {
|
||||||
|
const mod = qutient % radix;
|
||||||
|
qutient = (qutient - mod) / radix;
|
||||||
|
arr.unshift(chars[mod]);
|
||||||
|
} while (qutient);
|
||||||
|
return arr.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 62进制 -> 10 进制
|
||||||
|
* @param {string} numberCode
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
export function string62to10(numberCode: string) {
|
||||||
|
const chars =
|
||||||
|
"0123456789abcdefghigklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ";
|
||||||
|
const radix = chars.length;
|
||||||
|
numberCode = numberCode + "";
|
||||||
|
const len = numberCode.length;
|
||||||
|
let i = 0;
|
||||||
|
let originNumber = 0;
|
||||||
|
while (i < len) {
|
||||||
|
originNumber +=
|
||||||
|
Math.pow(radix, i++) * (chars.indexOf(numberCode.charAt(len - i)) || 0);
|
||||||
|
}
|
||||||
|
return originNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否是json格式的字符串
|
||||||
|
* @param {string} str
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
export function isJsonString(str: string) {
|
||||||
|
try {
|
||||||
|
if (typeof JSON.parse(str) == "object") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 检查accountId是否符合规则
|
||||||
|
* 4位渠道id_4位游戏id_openid
|
||||||
|
* @param accountId
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function checkAccountId(accountId: string) {
|
||||||
|
return /^\d{4}_\d{4,6}_.+$/.test(accountId);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 将accountId拆分出 渠道id, 游戏id, 和openId
|
||||||
|
* @param accountId
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function parseGameAccountId(accountId: string) {
|
||||||
|
const arr = accountId.split("_");
|
||||||
|
const gameId = arr[1];
|
||||||
|
const channel = arr[0];
|
||||||
|
const openId = arr[2];
|
||||||
|
return { gameId, channel, openId };
|
||||||
|
}
|
68
src/utils/time.util.ts
Normal file
68
src/utils/time.util.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
export const ONE_DAY_MILLISECOND = 1000 * 3600 * 24
|
||||||
|
/**
|
||||||
|
* 获取n天前的time
|
||||||
|
* @param {number} day
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
export function timeBeforeDay(day: number): number {
|
||||||
|
let time = Date.now()
|
||||||
|
return time - day * ONE_DAY_MILLISECOND
|
||||||
|
}
|
||||||
|
|
||||||
|
//间隔天数
|
||||||
|
export function calcBetweenDays(time1: number, time2: number) {
|
||||||
|
let v1 = Math.floor(time1 / ONE_DAY_MILLISECOND)
|
||||||
|
let v2 = Math.floor(time2 / ONE_DAY_MILLISECOND)
|
||||||
|
return Math.abs(v1 - v2)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否是今天
|
||||||
|
* @param {number} time
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
export function isToday(time: number) {
|
||||||
|
return new Date().toDateString() === new Date(time).toDateString()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 今天开始的时间
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
export function todayStart() {
|
||||||
|
return new Date(new Date().toLocaleDateString()).getTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 今天结束的时间
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
export function todayEnd() {
|
||||||
|
return todayStart() + ONE_DAY_MILLISECOND - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取本周第一天和最后一天(周一开始)
|
||||||
|
* @return {{startDay: string, endDay: string}}
|
||||||
|
*/
|
||||||
|
export function getThisWeekData() {
|
||||||
|
return weekData(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取前后n周的周一和周日的日期
|
||||||
|
* @param {number} n 0为当前周, 1为下一周, -1为上周
|
||||||
|
* @return {{startDay: string, endDay: string}}
|
||||||
|
*/
|
||||||
|
export function weekData(n: number) {
|
||||||
|
const weekData = { startDay: '', endDay: '' }
|
||||||
|
const date = new Date()
|
||||||
|
// 上周一的日期
|
||||||
|
date.setDate(date.getDate() + 7 * n - date.getDay() + 1)
|
||||||
|
weekData.startDay = date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate()
|
||||||
|
|
||||||
|
// 上周日的日期
|
||||||
|
date.setDate(date.getDate() + 6)
|
||||||
|
weekData.endDay = date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate()
|
||||||
|
return weekData
|
||||||
|
}
|
22
tsconfig.json
Normal file
22
tsconfig.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"ts-node": {
|
||||||
|
"files": true
|
||||||
|
},
|
||||||
|
"compilerOptions": {
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"module": "commonjs",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"target": "es2018",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"sourceMap": true,
|
||||||
|
"outDir": "./dist",
|
||||||
|
"baseUrl": "./src",
|
||||||
|
"rootDir": "./src"
|
||||||
|
},
|
||||||
|
"lib": ["es2018"],
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts",
|
||||||
|
"typings/extend.d.ts"
|
||||||
|
]
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user