发送邮件接口增加分布式锁, 默认55秒锁定

This commit is contained in:
CounterFire2023 2024-05-24 14:37:05 +08:00
parent 5001ca558f
commit 8405836b69
9 changed files with 151 additions and 5 deletions

View File

@ -59,4 +59,6 @@ OKX_SECRET_KEY='AF7F4CEE2A10715F9709D38452CE0BFD'
DISCORD_CLIENT_ID='1199290913155981345'
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

View File

@ -29,10 +29,12 @@
"fastify": "^4.8.1",
"fastify-plugin": "^4.2.1",
"google-auth-library": "^8.5.2",
"ioredis": "^5.4.1",
"mongoose": "^6.6.5",
"mongoose-findorcreate": "^3.0.0",
"nanoid": "^3.1.23",
"node-schedule": "^2.1.1",
"redlock": "^5.0.0-beta.2",
"rustwallet": "file:./rustwallet",
"siwe": "^2.1.4",
"tracer": "^1.1.6",

4
publish.sh Executable file
View 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

View File

@ -136,7 +136,13 @@ export class ApiServer {
})
this.server.setErrorHandler(function (error: FastifyError, request: FastifyRequest, reply: FastifyReply) {
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({
errcode: statusCode,
errmsg: error ? error.message : 'unknown error',

66
src/common/SyncLocker.ts Normal file
View 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
}
}

View File

@ -13,8 +13,14 @@ import {
DEFAULT_VERIFY_MAIL_SUBJECT,
EmailSvr,
} 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 { SyncLocker } from 'common/SyncLocker'
export const isEmail = (email: string) => {
const reg = /^(\w-*\.*)+@(\w-?)+(\.\w{2,})+$/
return reg.test(email)
}
class MailController extends BaseController {
/**
@ -131,7 +137,15 @@ class MailController extends BaseController {
if (!email || !type) {
throw new ZError(10, 'params mismatch')
}
if (!isEmail(email)) {
throw new ZError(12, 'email error')
}
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) {
let account = await Account.findByEmail(email)
if (account) {

View File

@ -29,7 +29,7 @@ export class NetClient {
headers: { 'Content-Type': 'application/json' },
}
Object.assign(defaultCfg, data)
console.log(defaultCfg)
// console.log(defaultCfg)
const res = await axios(defaultCfg)
return res.data
}

View File

@ -52,7 +52,7 @@ export interface IMailData {
}
const DEFAULT_MSG_DATA: IMailData = {
from: 'CEBG <noreply@cebg.games>',
from: 'Counter Fire <noreply@cebg.games>',
to: '',
subject: 'Please verify your email address',
}

View File

@ -493,6 +493,11 @@
resolved "https://registry.npmmirror.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz"
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":
version "3.1.0"
resolved "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz"
@ -1467,6 +1472,11 @@ clone-response@^1.0.2:
dependencies:
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:
version "2.0.1"
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"
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:
version "2.0.0"
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"
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:
version "4.3.0"
resolved "https://registry.npmmirror.com/lodash.includes/-/lodash.includes-4.3.0.tgz"
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:
version "3.0.3"
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"
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:
version "2.0.2"
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-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:
version "0.1.13"
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"
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:
version "2.0.1"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"