add some code

This commit is contained in:
cebgcontract 2022-10-18 18:55:25 +08:00
parent 25637cac59
commit ed9977cab8
5 changed files with 527 additions and 1 deletions

201
src/modules/Base.ts Normal file
View File

@ -0,0 +1,201 @@
import { FindOrCreate } from '@typegoose/typegoose/lib/defaultClasses'
import { checkJson } from '../decorators/nojson'
import { plugin, ReturnModelType } from '@typegoose/typegoose'
// @ts-ignore
import findOrCreate from 'mongoose-findorcreate'
import { Connection } from 'mongoose'
import { ObjectId } from 'bson'
import { isTrue } from '../utils/string.util'
import { AnyParamConstructor } from '@typegoose/typegoose/lib/types'
const jsonExcludeKeys = ['updatedAt', '__v']
const saveExcludeKeys = ['createdAt', 'updatedAt', '__v', '_id']
@plugin(findOrCreate)
export abstract class BaseModule extends FindOrCreate {
static db: Connection
public updateFromReq(data: any) {
for (let key in data) {
if (saveExcludeKeys.indexOf(key) == -1) {
this[key] = data[key]
}
}
}
/**
*
* @param condition
* @param data
*/
public static insertOrUpdate<T extends BaseModule>(
this: ReturnModelType<AnyParamConstructor<T>>,
condition: any,
data: any,
) {
return this.findOneAndUpdate(condition, data, { upsert: true, new: true, setDefaultsOnInsert: true })
}
/**
*
* @param {string[]} ids
*/
public static deleteVirtual<T extends BaseModule>(this: ReturnModelType<AnyParamConstructor<T>>, ids: string[]) {
return this.updateMany(
// @ts-ignore
{
_id: { $in: ids },
},
{
$set: {
deleted: true,
deleteTime: new Date(),
},
},
)
}
/**
*
* @param data
* @param {boolean} json
*/
public static async pageQuery<T extends BaseModule>(
this: ReturnModelType<AnyParamConstructor<T>>,
data: any,
json: boolean = false,
) {
let { start, limit, page } = data
limit = +limit || 10
start = +start || (+page - 1) * limit || 0
// @ts-ignore
let { opt, sort } = this.parseQueryParam(data)
let records = await this.find(opt).sort(sort).skip(start).limit(limit)
let total = await this.countDocuments(opt)
if (json) {
records.map((o: T) => o.toJson())
}
return { records, total, start, limit }
}
public toJson() {
let result: any = {}
// @ts-ignore
for (let key in this._doc) {
if (checkJson(this, key + '') && jsonExcludeKeys.indexOf(key) == -1) {
result[key] = this[key]
}
}
return result
}
/**
*
* @param {{}} params req.params
* @param options
* sort: 排序 : {createdAt: 1} {_id: 1}
* opt: 设置一些特殊的过滤条件, {deleted: 0}
* timeKey: 如果需要查询创建时间, createdAt,
* matchKey: 指定关键字查询的匹配字段, string或[string]
*
* @return {{opt: any, sort: {_id: number}}}
*/
public static parseQueryParam(params: {}, options?: any) {
const opt: any = { deleted: false }
// @ts-ignore
let obj = this.schema.paths
for (let key in params) {
if (key !== 'sort' && obj.hasOwnProperty(key)) {
switch (obj[key].instance) {
case 'String':
opt[key] = { $regex: params[key], $options: 'i' }
break
case 'Number':
opt[key] = params[key]
break
case 'Array':
if (Array.isArray(params[key])) {
opt[key] = { $in: params[key] }
} else {
opt[key] = params[key]
}
break
case 'Date':
// TODO:
break
case 'Boolean':
opt[key] = isTrue(params[key])
break
case 'ObjectID':
if (/^[0-9a-fA-F]{24}$/.test(params[key])) {
opt[key] = new ObjectId(params[key])
}
break
}
}
}
if (params.hasOwnProperty('key') && params['key']) {
let orArr = []
if (options?.matchKey) {
if (Array.isArray(options?.matchKey)) {
for (let key in options?.matchKey) {
let _tmp = {}
_tmp[key] = { $regex: params['key'], $options: 'i' }
orArr.push(_tmp)
}
} else {
let _tmp = {}
_tmp[options.matchKey] = { $regex: params['key'], $options: 'i' }
orArr.push(_tmp)
}
} else {
for (let key in obj) {
if (obj[key].instance === 'String') {
let _tmp = {}
_tmp[key] = { $regex: params['key'], $options: 'i' }
orArr.push(_tmp)
}
}
}
Object.assign(opt, { $or: orArr })
}
let timeKey = options?.timeKey ? options.timeKey : 'createdAt'
if (params.hasOwnProperty('timeBegin') && !params.hasOwnProperty('timeEnd')) {
let timeBegin = params['timeBegin']
if (!(timeBegin instanceof Date)) {
timeBegin = new Date(parseInt(timeBegin))
}
opt[timeKey] = { $gte: timeBegin }
} else if (params.hasOwnProperty('timeBegin') && params.hasOwnProperty('timeEnd')) {
let timeBegin = params['timeBegin']
if (!(timeBegin instanceof Date)) {
timeBegin = new Date(parseInt(timeBegin))
}
let timeEnd = params['timeEnd']
if (!(timeEnd instanceof Date)) {
timeEnd = new Date(parseInt(timeEnd))
}
let tmpB = {}
tmpB[timeKey] = { $gte: timeBegin }
let tmpE = {}
tmpE[timeKey] = { $lte: timeEnd }
opt['$and'] = [tmpB, tmpE]
} else if (!params.hasOwnProperty('timeBegin') && params.hasOwnProperty('timeEnd')) {
let timeEnd = params['timeEnd']
if (!(timeEnd instanceof Date)) {
timeEnd = new Date(parseInt(timeEnd))
}
opt[timeKey] = { $lte: timeEnd }
}
if (options?.opt) {
Object.assign(opt, options.opt)
}
let sort = { _id: 1 }
if (params.hasOwnProperty('sort')) {
sort = params['sort']
}
return { opt, sort }
}
}

116
src/utils/net.util.ts Normal file
View File

@ -0,0 +1,116 @@
const TIMEOUT_ERROR = new Error('timeout')
const hexRe = /^[0-9A-Fa-f]+$/gu
/**
* Execute fetch and verify that the response was successful.
*
* @param request - Request information.
* @param options - Fetch options.
* @returns The fetch response.
*/
export async function successfulFetch(request: string, options?: RequestInit) {
const response = await fetch(request, options)
if (!response.ok) {
throw new Error(`Fetch failed with status '${response.status}' for request '${request}'`)
}
return response
}
/**
* Execute fetch and return object response.
*
* @param request - The request information.
* @param options - The fetch options.
* @returns The fetch response JSON data.
*/
export async function handleFetch(request: string, options?: RequestInit) {
const response = await successfulFetch(request, options)
const object = await response.json()
return object
}
/**
* Execute fetch and return object response, log if known error thrown, otherwise rethrow error.
*
* @param request - the request options object
* @param request.url - The request url to query.
* @param request.options - The fetch options.
* @param request.timeout - Timeout to fail request
* @param request.errorCodesToCatch - array of error codes for errors we want to catch in a particular context
* @returns The fetch response JSON data or undefined (if error occurs).
*/
export async function fetchWithErrorHandling({
url,
options,
timeout,
errorCodesToCatch,
}: {
url: string
options?: RequestInit
timeout?: number
errorCodesToCatch?: number[]
}) {
let result
try {
if (timeout) {
result = Promise.race([
await handleFetch(url, options),
new Promise<Response>((_, reject) =>
setTimeout(() => {
reject(TIMEOUT_ERROR)
}, timeout),
),
])
} else {
result = await handleFetch(url, options)
}
} catch (e) {
logOrRethrowError(e, errorCodesToCatch)
}
return result
}
/**
* Fetch that fails after timeout.
*
* @param url - Url to fetch.
* @param options - Options to send with the request.
* @param timeout - Timeout to fail request.
* @returns Promise resolving the request.
*/
export async function timeoutFetch(url: string, options?: RequestInit, timeout = 500): Promise<Response> {
return Promise.race([
successfulFetch(url, options),
new Promise<Response>((_, reject) =>
setTimeout(() => {
reject(TIMEOUT_ERROR)
}, timeout),
),
])
}
/**
* Utility method to log if error is a common fetch error and otherwise rethrow it.
*
* @param error - Caught error that we should either rethrow or log to console
* @param codesToCatch - array of error codes for errors we want to catch and log in a particular context
*/
function logOrRethrowError(error: any, codesToCatch: number[] = []) {
if (!error) {
return
}
const includesErrorCodeToCatch = codesToCatch.some(code =>
error.message.includes(`Fetch failed with status '${code}'`),
)
if (
error instanceof Error &&
(includesErrorCodeToCatch || error.message.includes('Failed to fetch') || error === TIMEOUT_ERROR)
) {
console.error(error)
} else {
throw error
}
}

47
src/utils/promise.util.ts Normal file
View File

@ -0,0 +1,47 @@
/**
*
* @param {Function} cb
* @param {number} maxRetries
* @param {any[]} errorWhiteList
* @param {number} retries
* @return {Promise<T>}
*/
export function retry<T = any>(cb: Function, maxRetries: number = 3, errorWhiteList: any[] = [], retries: number = 0) {
return new Promise<T>((resolve, reject) => {
cb()
.then(resolve)
.catch(e => {
if (errorWhiteList.indexOf(e.constructor) !== -1 && retries++ < maxRetries) {
setTimeout(() => {
retry<T>(cb, maxRetries, errorWhiteList, retries)
.then(resolve)
.catch(e2 => reject(e2))
}, Math.floor(Math.random() * Math.pow(2, retries) * 400))
} else {
reject(e)
}
})
})
}
export class Deferred<T = any> {
public promise: Promise<T>
public resolve: Function
public reject: Function
constructor() {
this.promise = new Promise<T>((resolve, reject) => {
this.resolve = resolve
this.reject = reject
})
}
public then(func: (value: T) => any) {
return this.promise.then.apply(this.promise, arguments)
}
public catch(func: (value: any) => any) {
return this.promise.catch(func)
}
}

106
src/utils/string.util.ts Normal file
View File

@ -0,0 +1,106 @@
/**
* key升序生成 key1=val1&key2=val2的字符串
* @param {object} data
* @param {boolean} ignoreNull (null值不参与拼接)
* @param splitChar , &
* @param equalChar =
*/
export function generateKeyValStr(data: {}, ignoreNull = true, splitChar: string = '&', equalChar = '=') {
const keys = Object.keys(data)
keys.sort()
let result = ''
let i = 0
for (let key of keys) {
if (ignoreNull && !data[key]) {
return
}
if (i++ > 0) result += splitChar
result += `${key}${equalChar}${data[key]}`
}
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 = {}
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
}
/**
* true
* @param {Object} obj 'true','TRUE',1,'1','on','ON','YES','yes',true,false
* @return {boolean}
*/
export function isTrue(obj) {
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
}

View File

@ -148,6 +148,40 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
"@redis/bloom@1.0.2":
version "1.0.2"
resolved "https://registry.npmmirror.com/@redis/bloom/-/bloom-1.0.2.tgz#42b82ec399a92db05e29fffcdfd9235a5fc15cdf"
integrity sha512-EBw7Ag1hPgFzdznK2PBblc1kdlj5B5Cw3XwI9/oG7tSn85/HKy3X9xHy/8tm/eNXJYHLXHJL/pkwBpFMVVefkw==
"@redis/client@1.3.0":
version "1.3.0"
resolved "https://registry.npmmirror.com/@redis/client/-/client-1.3.0.tgz#c62ccd707f16370a2dc2f9e158a28b7da049fa77"
integrity sha512-XCFV60nloXAefDsPnYMjHGtvbtHR8fV5Om8cQ0JYqTNbWcQo/4AryzJ2luRj4blveWazRK/j40gES8M7Cp6cfQ==
dependencies:
cluster-key-slot "1.1.0"
generic-pool "3.8.2"
yallist "4.0.0"
"@redis/graph@1.0.1":
version "1.0.1"
resolved "https://registry.npmmirror.com/@redis/graph/-/graph-1.0.1.tgz#eabc58ba99cd70d0c907169c02b55497e4ec8a99"
integrity sha512-oDE4myMCJOCVKYMygEMWuriBgqlS5FqdWerikMoJxzmmTUErnTRRgmIDa2VcgytACZMFqpAOWDzops4DOlnkfQ==
"@redis/json@1.0.4":
version "1.0.4"
resolved "https://registry.npmmirror.com/@redis/json/-/json-1.0.4.tgz#f372b5f93324e6ffb7f16aadcbcb4e5c3d39bda1"
integrity sha512-LUZE2Gdrhg0Rx7AN+cZkb1e6HjoSKaeeW8rYnt89Tly13GBI5eP4CwDVr+MY8BAYfCg4/N15OUrtLoona9uSgw==
"@redis/search@1.1.0":
version "1.1.0"
resolved "https://registry.npmmirror.com/@redis/search/-/search-1.1.0.tgz#7abb18d431f27ceafe6bcb4dd83a3fa67e9ab4df"
integrity sha512-NyFZEVnxIJEybpy+YskjgOJRNsfTYqaPbK/Buv6W2kmFNaRk85JiqjJZA5QkRmWvGbyQYwoO5QfDi2wHskKrQQ==
"@redis/time-series@1.0.3":
version "1.0.3"
resolved "https://registry.npmmirror.com/@redis/time-series/-/time-series-1.0.3.tgz#4cfca8e564228c0bddcdf4418cba60c20b224ac4"
integrity sha512-OFp0q4SGrTH0Mruf6oFsHGea58u8vS/iI5+NpYdicaM+7BgqBZH8FFvNZ8rYYLrUO/QRqMq72NpXmxLVNcdmjA==
"@tsconfig/node10@^1.0.7":
version "1.0.9"
resolved "https://registry.npmmirror.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2"
@ -529,6 +563,11 @@ chokidar@^3.5.1:
optionalDependencies:
fsevents "~2.3.2"
cluster-key-slot@1.1.0:
version "1.1.0"
resolved "https://registry.npmmirror.com/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz#30474b2a981fb12172695833052bc0d01336d10d"
integrity sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==
color-convert@^2.0.1:
version "2.0.1"
resolved "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
@ -991,6 +1030,11 @@ function-bind@^1.1.1:
resolved "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
generic-pool@3.8.2:
version "3.8.2"
resolved "https://registry.npmmirror.com/generic-pool/-/generic-pool-3.8.2.tgz#aab4f280adb522fdfbdc5e5b64d718d3683f04e9"
integrity sha512-nGToKy6p3PAbYQ7p1UlWl6vSPwfwU6TMSWK7TTu+WUY4ZjyZQGniGGt2oNVvyNSpyZYSB43zMXVLcBm08MTMkg==
glob-parent@^5.1.2, glob-parent@~5.1.2:
version "5.1.2"
resolved "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
@ -1551,6 +1595,18 @@ real-require@^0.2.0:
resolved "https://registry.npmmirror.com/real-require/-/real-require-0.2.0.tgz#209632dea1810be2ae063a6ac084fee7e33fba78"
integrity sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==
redis@^4.3.1:
version "4.3.1"
resolved "https://registry.npmmirror.com/redis/-/redis-4.3.1.tgz#290532a0c22221e05e991162ac4dca1e1b2ff6da"
integrity sha512-cM7yFU5CA6zyCF7N/+SSTcSJQSRMEKN0k0Whhu6J7n9mmXRoXugfWDBo5iOzGwABmsWKSwGPTU5J4Bxbl+0mrA==
dependencies:
"@redis/bloom" "1.0.2"
"@redis/client" "1.3.0"
"@redis/graph" "1.0.1"
"@redis/json" "1.0.4"
"@redis/search" "1.1.0"
"@redis/time-series" "1.0.3"
reflect-metadata@^0.1.13:
version "0.1.13"
resolved "https://registry.npmmirror.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08"
@ -1960,7 +2016,7 @@ xtend@^4.0.0, xtend@^4.0.2:
resolved "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
yallist@^4.0.0:
yallist@4.0.0, yallist@^4.0.0:
version "4.0.0"
resolved "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==