发送邮件接口增加分布式锁, 默认55秒锁定
This commit is contained in:
parent
5001ca558f
commit
8405836b69
@ -60,3 +60,5 @@ OKX_SECRET_KEY='AF7F4CEE2A10715F9709D38452CE0BFD'
|
|||||||
DISCORD_CLIENT_ID='1199290913155981345'
|
DISCORD_CLIENT_ID='1199290913155981345'
|
||||||
DISCORD_CLIENT_SECRET='0-iIPG1waeQ7GpFV3e_dGH6kfjv1SVNS'
|
DISCORD_CLIENT_SECRET='0-iIPG1waeQ7GpFV3e_dGH6kfjv1SVNS'
|
||||||
DISCORD_REDIRECT_URI='https://oauth-svr.cebggame.com/oauth/redirect'
|
DISCORD_REDIRECT_URI='https://oauth-svr.cebggame.com/oauth/redirect'
|
||||||
|
|
||||||
|
REDIS=redis://192.168.100.22:6379/13
|
@ -29,10 +29,12 @@
|
|||||||
"fastify": "^4.8.1",
|
"fastify": "^4.8.1",
|
||||||
"fastify-plugin": "^4.2.1",
|
"fastify-plugin": "^4.2.1",
|
||||||
"google-auth-library": "^8.5.2",
|
"google-auth-library": "^8.5.2",
|
||||||
|
"ioredis": "^5.4.1",
|
||||||
"mongoose": "^6.6.5",
|
"mongoose": "^6.6.5",
|
||||||
"mongoose-findorcreate": "^3.0.0",
|
"mongoose-findorcreate": "^3.0.0",
|
||||||
"nanoid": "^3.1.23",
|
"nanoid": "^3.1.23",
|
||||||
"node-schedule": "^2.1.1",
|
"node-schedule": "^2.1.1",
|
||||||
|
"redlock": "^5.0.0-beta.2",
|
||||||
"rustwallet": "file:./rustwallet",
|
"rustwallet": "file:./rustwallet",
|
||||||
"siwe": "^2.1.4",
|
"siwe": "^2.1.4",
|
||||||
"tracer": "^1.1.6",
|
"tracer": "^1.1.6",
|
||||||
|
4
publish.sh
Executable file
4
publish.sh
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
cd ..
|
||||||
|
tar zcvf wallet-svr.tar.gz ./wallet-svr
|
||||||
|
scp -P27256 ./wallet-svr.tar.gz root@45.78.31.162:./upload
|
||||||
|
cd wallet-svr
|
@ -136,7 +136,13 @@ export class ApiServer {
|
|||||||
})
|
})
|
||||||
this.server.setErrorHandler(function (error: FastifyError, request: FastifyRequest, reply: FastifyReply) {
|
this.server.setErrorHandler(function (error: FastifyError, request: FastifyRequest, reply: FastifyReply) {
|
||||||
let statusCode = (error && error.statusCode) || 100
|
let statusCode = (error && error.statusCode) || 100
|
||||||
logger.info(error)
|
if (statusCode >= 500) {
|
||||||
|
logger.error(error)
|
||||||
|
} else if (statusCode >= 400) {
|
||||||
|
logger.error(error)
|
||||||
|
} else {
|
||||||
|
logger.info(error?.message || error || 'unknown error')
|
||||||
|
}
|
||||||
reply.code(200).send({
|
reply.code(200).send({
|
||||||
errcode: statusCode,
|
errcode: statusCode,
|
||||||
errmsg: error ? error.message : 'unknown error',
|
errmsg: error ? error.message : 'unknown error',
|
||||||
|
66
src/common/SyncLocker.ts
Normal file
66
src/common/SyncLocker.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import { singleton } from 'zutils'
|
||||||
|
import Client from 'ioredis'
|
||||||
|
import Redlock, { Lock, ResourceLockedError } from 'redlock'
|
||||||
|
import logger from 'logger/logger'
|
||||||
|
|
||||||
|
interface IRequest {
|
||||||
|
method: string
|
||||||
|
url: string
|
||||||
|
user?: {
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
lock?: Lock
|
||||||
|
}
|
||||||
|
|
||||||
|
const redisA = new Client(process.env.REDIS)
|
||||||
|
const redlock = new Redlock(
|
||||||
|
// You should have one client for each independent redis node
|
||||||
|
// or cluster.
|
||||||
|
[redisA],
|
||||||
|
{
|
||||||
|
// The expected clock drift; for more details see:
|
||||||
|
// http://redis.io/topics/distlock
|
||||||
|
driftFactor: 0.01, // multiplied by lock ttl to determine drift time
|
||||||
|
|
||||||
|
// The max number of times Redlock will attempt to lock a resource
|
||||||
|
// before erroring.
|
||||||
|
retryCount: 0,
|
||||||
|
|
||||||
|
// the time in ms between attempts
|
||||||
|
retryDelay: 200, // time in ms
|
||||||
|
|
||||||
|
// the max time in ms randomly added to retries
|
||||||
|
// to improve performance under high contention
|
||||||
|
// see https://www.awsarchitectureblog.com/2015/03/backoff.html
|
||||||
|
retryJitter: 200, // time in ms
|
||||||
|
|
||||||
|
// The minimum remaining time on a lock before an extension is automatically
|
||||||
|
// attempted with the `using` API.
|
||||||
|
automaticExtensionThreshold: 500, // time in ms
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
redlock.on('error', error => {
|
||||||
|
// Ignore cases where a resource is explicitly marked as locked on a client.
|
||||||
|
if (error instanceof ResourceLockedError) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Log all other errors.
|
||||||
|
logger.error(error)
|
||||||
|
})
|
||||||
|
|
||||||
|
@singleton
|
||||||
|
export class SyncLocker {
|
||||||
|
public async unlock(req: IRequest) {
|
||||||
|
if (req.lock) {
|
||||||
|
await req.lock.release()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async checkLock(req: IRequest, key?: string, lockTime: number = 60000) {
|
||||||
|
key = key || `${req.method}:${req.url}:${req.user?.id || ''}`
|
||||||
|
let lock = await redlock.acquire([key], lockTime)
|
||||||
|
req.lock = lock
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
@ -13,8 +13,14 @@ import {
|
|||||||
DEFAULT_VERIFY_MAIL_SUBJECT,
|
DEFAULT_VERIFY_MAIL_SUBJECT,
|
||||||
EmailSvr,
|
EmailSvr,
|
||||||
} from 'service/email.svr'
|
} from 'service/email.svr'
|
||||||
import { uuid } from 'zutils/utils/security.util'
|
import { sha1, uuid } from 'zutils/utils/security.util'
|
||||||
import { BaseController, role, ROLE_ANON, router, ZError } from 'zutils'
|
import { BaseController, role, ROLE_ANON, router, ZError } from 'zutils'
|
||||||
|
import { SyncLocker } from 'common/SyncLocker'
|
||||||
|
|
||||||
|
export const isEmail = (email: string) => {
|
||||||
|
const reg = /^(\w-*\.*)+@(\w-?)+(\.\w{2,})+$/
|
||||||
|
return reg.test(email)
|
||||||
|
}
|
||||||
|
|
||||||
class MailController extends BaseController {
|
class MailController extends BaseController {
|
||||||
/**
|
/**
|
||||||
@ -131,7 +137,15 @@ class MailController extends BaseController {
|
|||||||
if (!email || !type) {
|
if (!email || !type) {
|
||||||
throw new ZError(10, 'params mismatch')
|
throw new ZError(10, 'params mismatch')
|
||||||
}
|
}
|
||||||
|
if (!isEmail(email)) {
|
||||||
|
throw new ZError(12, 'email error')
|
||||||
|
}
|
||||||
type = parseInt(type)
|
type = parseInt(type)
|
||||||
|
if (type !== CodeType.REGIST && type !== CodeType.RESET && type !== CodeType.VERIFY && type !== CodeType.LOGIN) {
|
||||||
|
throw new ZError(13, 'type error')
|
||||||
|
}
|
||||||
|
const lockKey = sha1(`${email}_${type}`)
|
||||||
|
await new SyncLocker().checkLock(req, lockKey, 55000)
|
||||||
if (type === CodeType.REGIST) {
|
if (type === CodeType.REGIST) {
|
||||||
let account = await Account.findByEmail(email)
|
let account = await Account.findByEmail(email)
|
||||||
if (account) {
|
if (account) {
|
||||||
|
@ -29,7 +29,7 @@ export class NetClient {
|
|||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
}
|
}
|
||||||
Object.assign(defaultCfg, data)
|
Object.assign(defaultCfg, data)
|
||||||
console.log(defaultCfg)
|
// console.log(defaultCfg)
|
||||||
const res = await axios(defaultCfg)
|
const res = await axios(defaultCfg)
|
||||||
return res.data
|
return res.data
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ export interface IMailData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_MSG_DATA: IMailData = {
|
const DEFAULT_MSG_DATA: IMailData = {
|
||||||
from: 'CEBG <noreply@cebg.games>',
|
from: 'Counter Fire <noreply@cebg.games>',
|
||||||
to: '',
|
to: '',
|
||||||
subject: 'Please verify your email address',
|
subject: 'Please verify your email address',
|
||||||
}
|
}
|
||||||
|
52
yarn.lock
52
yarn.lock
@ -493,6 +493,11 @@
|
|||||||
resolved "https://registry.npmmirror.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz"
|
resolved "https://registry.npmmirror.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz"
|
||||||
integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==
|
integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==
|
||||||
|
|
||||||
|
"@ioredis/commands@^1.1.1":
|
||||||
|
version "1.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@ioredis/commands/-/commands-1.2.0.tgz#6d61b3097470af1fdbbe622795b8921d42018e11"
|
||||||
|
integrity sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==
|
||||||
|
|
||||||
"@jridgewell/resolve-uri@^3.0.3":
|
"@jridgewell/resolve-uri@^3.0.3":
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz"
|
resolved "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz"
|
||||||
@ -1467,6 +1472,11 @@ clone-response@^1.0.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
mimic-response "^1.0.0"
|
mimic-response "^1.0.0"
|
||||||
|
|
||||||
|
cluster-key-slot@^1.1.0:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz#88ddaa46906e303b5de30d3153b7d9fe0a0c19ac"
|
||||||
|
integrity sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==
|
||||||
|
|
||||||
color-convert@^2.0.1:
|
color-convert@^2.0.1:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz"
|
resolved "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz"
|
||||||
@ -2866,6 +2876,21 @@ inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4:
|
|||||||
resolved "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz"
|
resolved "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz"
|
||||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||||
|
|
||||||
|
ioredis@^5.4.1:
|
||||||
|
version "5.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-5.4.1.tgz#1c56b70b759f01465913887375ed809134296f40"
|
||||||
|
integrity sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA==
|
||||||
|
dependencies:
|
||||||
|
"@ioredis/commands" "^1.1.1"
|
||||||
|
cluster-key-slot "^1.1.0"
|
||||||
|
debug "^4.3.4"
|
||||||
|
denque "^2.1.0"
|
||||||
|
lodash.defaults "^4.2.0"
|
||||||
|
lodash.isarguments "^3.1.0"
|
||||||
|
redis-errors "^1.2.0"
|
||||||
|
redis-parser "^3.0.0"
|
||||||
|
standard-as-callback "^2.1.0"
|
||||||
|
|
||||||
ip@^2.0.0:
|
ip@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.npmmirror.com/ip/-/ip-2.0.0.tgz"
|
resolved "https://registry.npmmirror.com/ip/-/ip-2.0.0.tgz"
|
||||||
@ -3184,11 +3209,21 @@ lodash.clonedeep@^4.5.0:
|
|||||||
resolved "https://registry.npmmirror.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz"
|
resolved "https://registry.npmmirror.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz"
|
||||||
integrity sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==
|
integrity sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==
|
||||||
|
|
||||||
|
lodash.defaults@^4.2.0:
|
||||||
|
version "4.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c"
|
||||||
|
integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==
|
||||||
|
|
||||||
lodash.includes@^4.3.0:
|
lodash.includes@^4.3.0:
|
||||||
version "4.3.0"
|
version "4.3.0"
|
||||||
resolved "https://registry.npmmirror.com/lodash.includes/-/lodash.includes-4.3.0.tgz"
|
resolved "https://registry.npmmirror.com/lodash.includes/-/lodash.includes-4.3.0.tgz"
|
||||||
integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==
|
integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==
|
||||||
|
|
||||||
|
lodash.isarguments@^3.1.0:
|
||||||
|
version "3.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a"
|
||||||
|
integrity sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==
|
||||||
|
|
||||||
lodash.isboolean@^3.0.3:
|
lodash.isboolean@^3.0.3:
|
||||||
version "3.0.3"
|
version "3.0.3"
|
||||||
resolved "https://registry.npmmirror.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz"
|
resolved "https://registry.npmmirror.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz"
|
||||||
@ -3580,6 +3615,11 @@ next-tick@^1.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb"
|
resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb"
|
||||||
integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==
|
integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==
|
||||||
|
|
||||||
|
node-abort-controller@^3.0.1:
|
||||||
|
version "3.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.1.1.tgz#a94377e964a9a37ac3976d848cb5c765833b8548"
|
||||||
|
integrity sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==
|
||||||
|
|
||||||
node-addon-api@^2.0.0:
|
node-addon-api@^2.0.0:
|
||||||
version "2.0.2"
|
version "2.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32"
|
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32"
|
||||||
@ -4015,6 +4055,13 @@ redis@^3.1.2:
|
|||||||
redis-errors "^1.2.0"
|
redis-errors "^1.2.0"
|
||||||
redis-parser "^3.0.0"
|
redis-parser "^3.0.0"
|
||||||
|
|
||||||
|
redlock@^5.0.0-beta.2:
|
||||||
|
version "5.0.0-beta.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/redlock/-/redlock-5.0.0-beta.2.tgz#a629c07e07d001c0fdd9f2efa614144c4416fe44"
|
||||||
|
integrity sha512-2RDWXg5jgRptDrB1w9O/JgSZC0j7y4SlaXnor93H/UJm/QyDiFgBKNtrh0TI6oCXqYSaSoXxFh6Sd3VtYfhRXw==
|
||||||
|
dependencies:
|
||||||
|
node-abort-controller "^3.0.1"
|
||||||
|
|
||||||
reflect-metadata@^0.1.13:
|
reflect-metadata@^0.1.13:
|
||||||
version "0.1.13"
|
version "0.1.13"
|
||||||
resolved "https://registry.npmmirror.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz"
|
resolved "https://registry.npmmirror.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz"
|
||||||
@ -4395,6 +4442,11 @@ sshpk@^1.7.0:
|
|||||||
safer-buffer "^2.0.2"
|
safer-buffer "^2.0.2"
|
||||||
tweetnacl "~0.14.0"
|
tweetnacl "~0.14.0"
|
||||||
|
|
||||||
|
standard-as-callback@^2.1.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45"
|
||||||
|
integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==
|
||||||
|
|
||||||
statuses@2.0.1:
|
statuses@2.0.1:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"
|
resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user