增加google退款相关功能
This commit is contained in:
parent
e2ab4da847
commit
e5dc33970c
@ -44,4 +44,5 @@ PAY_TRANSFER_CB_URL='http://127.0.0.1:3007/api/internal/update_task'
|
||||
HASH_SALT='iG4Rpsa)6U31$H#^T85$^^3'
|
||||
|
||||
# 游戏服, 支付上报地址
|
||||
GAME_PAY_CB_URL=https://game2006api-test.kingsome.cn/webapp/index.php?c=Shop&a=buyGoodsDirect
|
||||
GAME_PAY_CB_URL=https://game2006api-test.kingsome.cn/webapp/index.php
|
||||
REDIS=redis://127.0.0.1:6379/14
|
13
.vscode/launch.json
vendored
13
.vscode/launch.json
vendored
@ -17,5 +17,18 @@
|
||||
],
|
||||
"type": "pwa-node"
|
||||
},
|
||||
{
|
||||
"name": "Debug Monitor",
|
||||
"request": "launch",
|
||||
"runtimeArgs": [
|
||||
"run-script",
|
||||
"dev:monitor"
|
||||
],
|
||||
"runtimeExecutable": "npm",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"type": "pwa-node"
|
||||
},
|
||||
]
|
||||
}
|
@ -5,6 +5,7 @@
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev:api": "ts-node -r tsconfig-paths/register src/api.ts",
|
||||
"dev:monitor": "ts-node -r tsconfig-paths/register src/google.monitor.ts",
|
||||
"build": "tsc",
|
||||
"prod:api": "NODE_ENV=production NODE_PATH=./dist node dist/api.js",
|
||||
"lint": "eslint --ext .ts src/**",
|
||||
@ -31,6 +32,7 @@
|
||||
"mongoose-findorcreate": "^3.0.0",
|
||||
"nanoid": "^3.1.23",
|
||||
"node-schedule": "^2.1.1",
|
||||
"redis": "^3.1.2",
|
||||
"tracer": "^1.1.6",
|
||||
"verify-apple-id-token": "^3.0.0"
|
||||
},
|
||||
@ -38,6 +40,7 @@
|
||||
"@typegoose/typegoose": "^9.12.1",
|
||||
"@types/dotenv": "^8.2.0",
|
||||
"@types/node-schedule": "^2.1.0",
|
||||
"@types/redis": "^2.8.28",
|
||||
"@typescript-eslint/eslint-plugin": "^5.40.1",
|
||||
"@typescript-eslint/parser": "^5.40.1",
|
||||
"eslint": "^8.25.0",
|
||||
|
@ -171,8 +171,8 @@ export class ApiServer {
|
||||
self.registerRouter()
|
||||
self.setErrHandler()
|
||||
self.setFormatSend()
|
||||
self.initSchedules()
|
||||
await self.initOtherServices()
|
||||
self.initSchedules()
|
||||
this.server.listen({ port: config.api.port, host: config.api.host }, (err: any, address: any) => {
|
||||
if (err) {
|
||||
logger.log(err)
|
||||
|
@ -3,6 +3,7 @@ import { ZError } from 'common/ZError'
|
||||
import { router } from 'decorators/router'
|
||||
import logger from 'logger/logger'
|
||||
import { GoogleInApp, GooglePayStatus } from 'modules/GoogleInApp'
|
||||
import { IPayResult, reportGooglePurchaseResult } from 'service/game.svr'
|
||||
import { GooglePaySvr } from 'service/googlepay.svr'
|
||||
|
||||
class GooglePayController extends BaseController {
|
||||
@ -14,7 +15,7 @@ class GooglePayController extends BaseController {
|
||||
throw new ZError(10, 'purchase data is empty')
|
||||
}
|
||||
logger.info(`verify google purchase::list=${list}`)
|
||||
let results = []
|
||||
let results: IPayResult[] = []
|
||||
for (let sub of list) {
|
||||
let infoRes = await new GooglePaySvr().fetchPurchaseData(sub.id, sub.token)
|
||||
// logger.log(JSON.stringify(infoRes))
|
||||
@ -58,6 +59,16 @@ class GooglePayController extends BaseController {
|
||||
}
|
||||
}
|
||||
//TODO:: 通知游戏服
|
||||
if (results.length) {
|
||||
setImmediate(async () => {
|
||||
try {
|
||||
await reportGooglePurchaseResult(results)
|
||||
} catch (err) {
|
||||
logger.error('report google voided purchases failed', err.message || err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
}
|
||||
|
23
src/google.monitor.ts
Normal file
23
src/google.monitor.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import * as dotenv from 'dotenv'
|
||||
import logger from 'logger/logger'
|
||||
import { RedisClient } from 'redis/RedisClient'
|
||||
const envFile = process.env.NODE_ENV && process.env.NODE_ENV === 'production' ? `.env.production` : '.env.development'
|
||||
dotenv.config({ path: envFile })
|
||||
|
||||
import 'common/Extend'
|
||||
import { GooglePaySvr } from 'service/googlepay.svr'
|
||||
import GooglePurchaseSchedule from 'schedule/googlepurchase.schedule'
|
||||
|
||||
async function main() {
|
||||
let opts = { url: process.env.REDIS }
|
||||
new RedisClient(opts)
|
||||
logger.info('REDIS Connected')
|
||||
await new GooglePaySvr().init()
|
||||
logger.info('GooglePaySvr Connected')
|
||||
setInterval(function () {
|
||||
new GooglePurchaseSchedule().parseAllRecord()
|
||||
}, 60 * 60 * 1000)
|
||||
new GooglePurchaseSchedule().parseAllRecord()
|
||||
}
|
||||
|
||||
main()
|
@ -1,6 +1,8 @@
|
||||
import { getModelForClass, index, modelOptions, mongoose, prop, ReturnModelType, Severity } from '@typegoose/typegoose'
|
||||
import { dbconn } from 'decorators/dbconn'
|
||||
import { BaseModule } from './Base'
|
||||
import logger from 'logger/logger'
|
||||
import { IPayResult, reportGooglePurchaseResult } from 'service/game.svr'
|
||||
|
||||
export enum GooglePayStatus {
|
||||
PENDING = 0, // 默认状态, 未支付
|
||||
@ -80,6 +82,38 @@ export class GoogleInAppClass extends BaseModule {
|
||||
public static async findByRecordId(this: ReturnModelType<typeof GoogleInAppClass>, outOrderId: string) {
|
||||
return this.findOne({ outOrderId }).exec()
|
||||
}
|
||||
|
||||
public static async parseVoidedRecords(this: ReturnModelType<typeof GoogleInAppClass>, voidedPurchases: any) {
|
||||
if (!voidedPurchases || !voidedPurchases.length) {
|
||||
return
|
||||
}
|
||||
let results: IPayResult[] = []
|
||||
for (let sub of voidedPurchases) {
|
||||
let { orderId, voidedTimeMillis, voidedSource, voidedReason } = sub
|
||||
// orderId = 'GPA.3355-1172-9416-16839'
|
||||
const record = await GoogleInApp.findOneAndUpdate(
|
||||
{ outOrderId: orderId, status: GooglePayStatus.SUCCESS },
|
||||
{ $set: { voidedTime: voidedTimeMillis, voidedSource, voidedReason, status: GooglePayStatus.VOIDED } },
|
||||
{ returnDocument: 'after' },
|
||||
)
|
||||
if (record) {
|
||||
results.push({
|
||||
productId: record.productId,
|
||||
gameOrderId: record.gameOrderId,
|
||||
orderId: record.outOrderId,
|
||||
status: record.status,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (results.length) {
|
||||
try {
|
||||
await reportGooglePurchaseResult(results)
|
||||
} catch (err) {
|
||||
logger.error('report google voided purchases failed', err.message || err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const GoogleInApp = getModelForClass(GoogleInAppClass, { existingConnection: GoogleInAppClass.db })
|
||||
|
306
src/redis/RedisClient.ts
Normal file
306
src/redis/RedisClient.ts
Normal file
@ -0,0 +1,306 @@
|
||||
import { resolveCname } from 'dns'
|
||||
import redis from 'redis'
|
||||
import { promisify } from 'util'
|
||||
import { singleton } from '../decorators/singleton'
|
||||
|
||||
type Callback = (...args: any[]) => void
|
||||
|
||||
@singleton
|
||||
export class RedisClient {
|
||||
public pub: redis.RedisClient
|
||||
public sub: redis.RedisClient
|
||||
|
||||
protected subscribeAsync: any
|
||||
protected unsubscribeAsync: any
|
||||
protected publishAsync: any
|
||||
|
||||
protected subscriptions: { [channel: string]: Callback[] } = {}
|
||||
|
||||
protected smembersAsync: any
|
||||
protected sismemberAsync: any
|
||||
protected hgetAsync: any
|
||||
protected hlenAsync: any
|
||||
protected pubsubAsync: any
|
||||
protected incrAsync: any
|
||||
protected decrAsync: any
|
||||
|
||||
constructor(opts?: redis.ClientOpts) {
|
||||
this.sub = redis.createClient(opts)
|
||||
this.pub = redis.createClient(opts)
|
||||
|
||||
// no listener limit
|
||||
this.sub.setMaxListeners(0)
|
||||
|
||||
// create promisified pub/sub methods.
|
||||
this.subscribeAsync = promisify(this.sub.subscribe).bind(this.sub)
|
||||
this.unsubscribeAsync = promisify(this.sub.unsubscribe).bind(this.sub)
|
||||
|
||||
this.publishAsync = promisify(this.pub.publish).bind(this.pub)
|
||||
|
||||
// create promisified redis methods.
|
||||
this.smembersAsync = promisify(this.pub.smembers).bind(this.pub)
|
||||
this.sismemberAsync = promisify(this.pub.sismember).bind(this.pub)
|
||||
this.hlenAsync = promisify(this.pub.hlen).bind(this.pub)
|
||||
this.hgetAsync = promisify(this.pub.hget).bind(this.pub)
|
||||
this.pubsubAsync = promisify(this.pub.pubsub).bind(this.pub)
|
||||
this.decrAsync = promisify(this.pub.decr).bind(this.pub)
|
||||
this.incrAsync = promisify(this.pub.incr).bind(this.pub)
|
||||
}
|
||||
|
||||
public async subscribe(topic: string, callback: Callback) {
|
||||
if (!this.subscriptions[topic]) {
|
||||
this.subscriptions[topic] = []
|
||||
}
|
||||
|
||||
this.subscriptions[topic].push(callback)
|
||||
|
||||
if (this.sub.listeners('message').length === 0) {
|
||||
this.sub.addListener('message', this.handleSubscription)
|
||||
}
|
||||
|
||||
await this.subscribeAsync(topic)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
public async unsubscribe(topic: string, callback?: Callback) {
|
||||
if (callback) {
|
||||
const index = this.subscriptions[topic].indexOf(callback)
|
||||
this.subscriptions[topic].splice(index, 1)
|
||||
} else {
|
||||
this.subscriptions[topic] = []
|
||||
}
|
||||
|
||||
if (this.subscriptions[topic].length === 0) {
|
||||
await this.unsubscribeAsync(topic)
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
public async publish(topic: string, data: any) {
|
||||
if (data === undefined) {
|
||||
data = false
|
||||
}
|
||||
|
||||
await this.publishAsync(topic, JSON.stringify(data))
|
||||
}
|
||||
|
||||
public async exists(roomId: string): Promise<boolean> {
|
||||
return (await this.pubsubAsync('channels', roomId)).length > 0
|
||||
}
|
||||
|
||||
public async setex(key: string, value: string, seconds: number) {
|
||||
return new Promise(resolve => this.pub.setex(key, seconds, value, resolve))
|
||||
}
|
||||
|
||||
public async expire(key: string, seconds: number) {
|
||||
return new Promise(resolve => this.pub.expire(key, seconds, resolve))
|
||||
}
|
||||
|
||||
public async get(key: string): Promise<string | null> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.pub.get(key, (err, data: string | null) => {
|
||||
if (err) {
|
||||
return reject(err)
|
||||
}
|
||||
resolve(data)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
public async set(key: string, val: string) {
|
||||
return new Promise(resolve => {
|
||||
this.pub.set(key, val, () => {
|
||||
resolve && resolve('')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
public async del(roomId: string) {
|
||||
return new Promise(resolve => {
|
||||
this.pub.del(roomId, resolve)
|
||||
})
|
||||
}
|
||||
|
||||
public async sadd(key: string, value: any) {
|
||||
return new Promise(resolve => {
|
||||
this.pub.sadd(key, value, resolve)
|
||||
})
|
||||
}
|
||||
|
||||
public async smembers(key: string): Promise<string[]> {
|
||||
return await this.smembersAsync(key)
|
||||
}
|
||||
|
||||
public async sismember(key: string, field: string): Promise<number> {
|
||||
return await this.sismemberAsync(key, field)
|
||||
}
|
||||
|
||||
public async srem(key: string, value: any) {
|
||||
return new Promise(resolve => {
|
||||
this.pub.srem(key, value, resolve)
|
||||
})
|
||||
}
|
||||
|
||||
public async scard(key: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.pub.scard(key, (err, data) => {
|
||||
if (err) {
|
||||
return reject(err)
|
||||
}
|
||||
resolve(data)
|
||||
})
|
||||
})
|
||||
}
|
||||
public async srandmember(key: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.pub.srandmember(key, (err, data) => {
|
||||
if (err) {
|
||||
return reject(err)
|
||||
}
|
||||
resolve(data)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
public async sinter(...keys: string[]) {
|
||||
return new Promise<string[]>((resolve, reject) => {
|
||||
this.pub.sinter(...keys, (err, data) => {
|
||||
if (err) {
|
||||
return reject(err)
|
||||
}
|
||||
resolve(data)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
public async zadd(key: string, value: any, member: string) {
|
||||
return new Promise(resolve => {
|
||||
this.pub.zadd(key, value, member, resolve)
|
||||
})
|
||||
}
|
||||
public async zrangebyscore(key: string, min: number, max: number) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.pub.zrangebyscore(key, min, max, 'withscores', (err, data) => {
|
||||
if (err) {
|
||||
return reject(err)
|
||||
}
|
||||
resolve(data)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
public async zcard(key: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.pub.zcard(key, (err, data) => {
|
||||
if (err) {
|
||||
return reject(err)
|
||||
}
|
||||
resolve(data)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
public async zcount(key: string, min: number, max: number) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.pub.zcount(key, min, max, (err, data) => {
|
||||
if (err) {
|
||||
return reject(err)
|
||||
}
|
||||
resolve(data)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
public async zrevrank(key: string, member: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.pub.zrevrank(key, member, (err, data) => {
|
||||
if (err) {
|
||||
return reject(err)
|
||||
}
|
||||
resolve(data)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
public async zscore(key: string, member: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.pub.zscore(key, member, (err, data) => {
|
||||
if (err) {
|
||||
return reject(err)
|
||||
}
|
||||
resolve(data)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
public async zrevrange(key: string, start: number, end: number) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.pub.zrevrange(key, start, end, 'withscores', (err, data) => {
|
||||
if (err) {
|
||||
return reject(err)
|
||||
}
|
||||
resolve(data)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
public async hset(key: string, field: string, value: string) {
|
||||
return new Promise(resolve => {
|
||||
this.pub.hset(key, field, value, resolve)
|
||||
})
|
||||
}
|
||||
|
||||
public async hincrby(key: string, field: string, value: number) {
|
||||
return new Promise(resolve => {
|
||||
this.pub.hincrby(key, field, value, resolve)
|
||||
})
|
||||
}
|
||||
|
||||
public async hget(key: string, field: string) {
|
||||
return await this.hgetAsync(key, field)
|
||||
}
|
||||
|
||||
public async hgetall(key: string) {
|
||||
return new Promise<{ [key: string]: string }>((resolve, reject) => {
|
||||
this.pub.hgetall(key, (err, values) => {
|
||||
if (err) {
|
||||
return reject(err)
|
||||
}
|
||||
resolve(values)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
public async hdel(key: string, field: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.pub.hdel(key, field, (err, ok) => {
|
||||
if (err) {
|
||||
return reject(err)
|
||||
}
|
||||
resolve(ok)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
public async hlen(key: string): Promise<number> {
|
||||
return await this.hlenAsync(key)
|
||||
}
|
||||
|
||||
public async incr(key: string): Promise<number> {
|
||||
return await this.incrAsync(key)
|
||||
}
|
||||
|
||||
public async decr(key: string): Promise<number> {
|
||||
return await this.decrAsync(key)
|
||||
}
|
||||
|
||||
protected handleSubscription = (channel: string, message: string) => {
|
||||
if (this.subscriptions[channel]) {
|
||||
for (let i = 0, l = this.subscriptions[channel].length; i < l; i++) {
|
||||
this.subscriptions[channel][i](JSON.parse(message))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
48
src/schedule/googlepurchase.schedule.ts
Normal file
48
src/schedule/googlepurchase.schedule.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { singleton } from 'decorators/singleton'
|
||||
import logger from 'logger/logger'
|
||||
import { GoogleInApp } from 'modules/GoogleInApp'
|
||||
import * as schedule from 'node-schedule'
|
||||
import { RedisClient } from 'redis/RedisClient'
|
||||
import { GooglePaySvr } from 'service/googlepay.svr'
|
||||
|
||||
/**
|
||||
* 定时更新发送邮件验证码的过期状态
|
||||
*
|
||||
* * * * * *
|
||||
┬ ┬ ┬ ┬ ┬ ┬
|
||||
│ │ │ │ │ │
|
||||
│ │ │ │ │ └ day of week (0 - 7) (0 or 7 is Sun)
|
||||
│ │ │ │ └───── month (1 - 12)
|
||||
│ │ │ └────────── day of month (1 - 31)
|
||||
│ │ └─────────────── hour (0 - 23)
|
||||
│ └──────────────────── minute (0 - 59)
|
||||
└───────────────────────── second (0 - 59, OPTIONAL)
|
||||
*/
|
||||
@singleton
|
||||
export default class GooglePurchaseSchedule {
|
||||
async parseAllRecord() {
|
||||
const timeKey = 'google_inapp_voided_check_time'
|
||||
let timeStr = await new RedisClient().get(timeKey)
|
||||
let startTime = timeStr ? parseInt(timeStr) : Date.now() - 24 * 60 * 60 * 1000
|
||||
let endTime = Date.now()
|
||||
try {
|
||||
let res = await new GooglePaySvr().queryVoidedPurchases(startTime, endTime)
|
||||
if (res.status !== 200) {
|
||||
logger.info('error check google voided purchase', res.status, res.statusText)
|
||||
return
|
||||
}
|
||||
const { data } = res
|
||||
await GoogleInApp.parseVoidedRecords(data.voidedPurchases)
|
||||
await new RedisClient().set(timeKey, endTime + '')
|
||||
logger.info(`success check google voided purchase:: voidedPurchases: ${data.voidedPurchases?.length || 0}`)
|
||||
} catch (err) {
|
||||
logger.info('error check google voided purchase', err.message || err)
|
||||
}
|
||||
}
|
||||
scheduleAll() {
|
||||
const job = schedule.scheduleJob('1 * * * *', async () => {
|
||||
await this.parseAllRecord()
|
||||
})
|
||||
this.parseAllRecord()
|
||||
}
|
||||
}
|
@ -2,22 +2,31 @@ import axios from 'axios'
|
||||
import { PayRecordClass } from 'modules/PayRecord'
|
||||
import { DocumentType } from '@typegoose/typegoose'
|
||||
import { hmacsha256 } from 'utils/security.util'
|
||||
import { NetClient } from 'net/NetClient'
|
||||
import logger from 'logger/logger'
|
||||
|
||||
export interface IPayResult {
|
||||
productId: string
|
||||
gameOrderId: string
|
||||
orderId: string
|
||||
status: number
|
||||
}
|
||||
|
||||
export async function reportPayResult(data: DocumentType<PayRecordClass>) {
|
||||
let repData = {
|
||||
const repData = {
|
||||
account_id: data.gameAccountId,
|
||||
order_id: data.gameOrderId,
|
||||
status: data.status,
|
||||
id: data.id,
|
||||
txhash: data.txHash,
|
||||
}
|
||||
let signStr = Object.keys(repData)
|
||||
const signStr = Object.keys(repData)
|
||||
.sort()
|
||||
.map(key => `${key}=${encodeURIComponent(repData[key])}`)
|
||||
.join('&')
|
||||
const sign = hmacsha256(signStr, process.env.HASH_SALT)
|
||||
let url = `${process.env.GAME_PAY_CB_URL}&${signStr}&sign=${sign}`
|
||||
let reqConfig: any = {
|
||||
const url = `${process.env.GAME_PAY_CB_URL}?c=Shop&a=buyGoodsDirect&${signStr}&sign=${sign}`
|
||||
const reqConfig: any = {
|
||||
method: 'get',
|
||||
url,
|
||||
headers: {
|
||||
@ -26,3 +35,27 @@ export async function reportPayResult(data: DocumentType<PayRecordClass>) {
|
||||
}
|
||||
return axios(reqConfig)
|
||||
}
|
||||
// 上报google支付结果
|
||||
export async function reportGooglePurchaseResult(records: IPayResult[]) {
|
||||
const url = `${process.env.GAME_PAY_CB_URL}?c=Shop&a=inappPurchaseDiamonds`
|
||||
let reportData: any = {
|
||||
channel: 'google',
|
||||
records,
|
||||
}
|
||||
let signStr = 'channel=google&'
|
||||
signStr += records
|
||||
.map(record =>
|
||||
Object.keys(record)
|
||||
.sort()
|
||||
.map(key => `${key}=${record[key]}`)
|
||||
.join('&'),
|
||||
)
|
||||
.join('&')
|
||||
const sign = hmacsha256(signStr, process.env.HASH_SALT)
|
||||
reportData.sign = sign
|
||||
const reqData = {
|
||||
url,
|
||||
data: JSON.stringify(reportData),
|
||||
}
|
||||
return new NetClient().httpPost(reqData)
|
||||
}
|
||||
|
@ -43,12 +43,12 @@ export class GooglePaySvr {
|
||||
* @param startTime
|
||||
* @param endTime
|
||||
*/
|
||||
public async queryVoidedPurchases(startTime: string, endTime: string) {
|
||||
public async queryVoidedPurchases(startTime: number, endTime: number) {
|
||||
return this.androidpublisher.purchases.voidedpurchases.list({
|
||||
packageName: PAGEAGE_NAME,
|
||||
type: 0,
|
||||
startTime,
|
||||
endTime,
|
||||
startTime: startTime + '',
|
||||
endTime: endTime + '',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
39
yarn.lock
39
yarn.lock
@ -272,6 +272,13 @@
|
||||
resolved "https://registry.npmmirror.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc"
|
||||
integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==
|
||||
|
||||
"@types/redis@^2.8.28":
|
||||
version "2.8.32"
|
||||
resolved "https://registry.yarnpkg.com/@types/redis/-/redis-2.8.32.tgz#1d3430219afbee10f8cfa389dad2571a05ecfb11"
|
||||
integrity sha512-7jkMKxcGq9p242exlbsVzuJb57KqHRhNl4dHoQu2Y5v9bCAbtIXXH0R3HleSQW4CTOqpHIYUW3t6tpUj4BVQ+w==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/semver@^7.3.12":
|
||||
version "7.3.12"
|
||||
resolved "https://registry.npmmirror.com/@types/semver/-/semver-7.3.12.tgz#920447fdd78d76b19de0438b7f60df3c4a80bf1c"
|
||||
@ -737,6 +744,11 @@ delayed-stream@~1.0.0:
|
||||
resolved "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
||||
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
|
||||
|
||||
denque@^1.5.0:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.1.tgz#07f670e29c9a78f8faecb2566a1e2c11929c5cbf"
|
||||
integrity sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==
|
||||
|
||||
denque@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmmirror.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1"
|
||||
@ -2055,6 +2067,33 @@ 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-commands@^1.7.0:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.7.0.tgz#15a6fea2d58281e27b1cd1acfb4b293e278c3a89"
|
||||
integrity sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ==
|
||||
|
||||
redis-errors@^1.0.0, redis-errors@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad"
|
||||
integrity sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==
|
||||
|
||||
redis-parser@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4"
|
||||
integrity sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==
|
||||
dependencies:
|
||||
redis-errors "^1.0.0"
|
||||
|
||||
redis@^3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/redis/-/redis-3.1.2.tgz#766851117e80653d23e0ed536254677ab647638c"
|
||||
integrity sha512-grn5KoZLr/qrRQVwoSkmzdbw6pwF+/rwODtrOr6vuBRiR/f3rjSTGupbF90Zpqm2oenix8Do6RV7pYEkGwlKkw==
|
||||
dependencies:
|
||||
denque "^1.5.0"
|
||||
redis-commands "^1.7.0"
|
||||
redis-errors "^1.2.0"
|
||||
redis-parser "^3.0.0"
|
||||
|
||||
reflect-metadata@^0.1.13:
|
||||
version "0.1.13"
|
||||
resolved "https://registry.npmmirror.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08"
|
||||
|
Loading…
x
Reference in New Issue
Block a user