增加web端钱包登录相关接口
This commit is contained in:
parent
81ff8b5778
commit
4258a7fd26
65
src/controllers/wallet.bridge.controller.ts
Normal file
65
src/controllers/wallet.bridge.controller.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import BaseController from "common/base.controller";
|
||||
import {ZError} from "common/ZError";
|
||||
import {role, router} from "decorators/router";
|
||||
import {BridgeSvr} from "service/bridge.svr";
|
||||
|
||||
class WalletBridgeController extends BaseController {
|
||||
@role('anon')
|
||||
@router('post /bridge/regist')
|
||||
async registWebClient(req, res) {
|
||||
let { clientId } = req.params;
|
||||
if (!clientId) {
|
||||
throw new ZError(10, 'params mismatch')
|
||||
}
|
||||
const qrId = new BridgeSvr().registClientReq(clientId)
|
||||
const token = await res.jwtSign({ qrId })
|
||||
return { token }
|
||||
}
|
||||
|
||||
@role('anon')
|
||||
@router('post /bridge/status')
|
||||
async bridgeStatus(req, res) {
|
||||
let { webtoken,clientId } = req.params;
|
||||
if (!webtoken || !clientId) {
|
||||
throw new ZError(10, 'params mismatch')
|
||||
}
|
||||
const reqData = await req.jwtVerify({ extractToken: () => webtoken})
|
||||
if (!reqData || !reqData.qrId) {
|
||||
throw new ZError(14, 'token error')
|
||||
}
|
||||
const { qrId } = reqData
|
||||
const data = new BridgeSvr().fetchData(qrId)
|
||||
if (!data) {
|
||||
throw new ZError(11, 'data not found')
|
||||
}
|
||||
if (data.clientId !== clientId) {
|
||||
throw new ZError(13, 'login info mismatch')
|
||||
}
|
||||
if (data.status === 0 && data.expire < Date.now()) {
|
||||
throw new ZError(12, 'login expired')
|
||||
}
|
||||
return data
|
||||
}
|
||||
/**
|
||||
* 1. 验证游戏客户端的token
|
||||
* 2. 验证扫码获得的web端的token, 获取qrId
|
||||
* 3. 生成一个有时效的token, 保存至cache, 供web端获取
|
||||
*/
|
||||
@router('post /bridge/upload')
|
||||
async uploadKeyInfo(req, res) {
|
||||
let { key, webtoken } = req.params;
|
||||
let user = req.user
|
||||
if (!webtoken || !key ) {
|
||||
throw new ZError(10, 'params mismatch')
|
||||
}
|
||||
const reqData = await req.jwtVerify({ extractToken: () => webtoken})
|
||||
if (!reqData || !reqData.qrId) {
|
||||
throw new ZError(14, 'token error')
|
||||
}
|
||||
const { qrId } = reqData
|
||||
const token = await res.jwtSign({ id: user.id })
|
||||
let result = new BridgeSvr().updateData(qrId, key, token)
|
||||
return {result}
|
||||
}
|
||||
|
||||
}
|
@ -27,12 +27,15 @@ class WalletController extends BaseController {
|
||||
@router('post /wallet/info')
|
||||
async uploadWalletInfo(req, res) {
|
||||
let user = req.user
|
||||
let { key } = req.params
|
||||
let { key, address } = req.params
|
||||
if (!key) {
|
||||
throw new ZError(10, 'no data to save')
|
||||
}
|
||||
let record = await Wallet.insertOrUpdate({ account: user.id }, {})
|
||||
record.key = key
|
||||
if (address) {
|
||||
record.address = address
|
||||
}
|
||||
await record.save()
|
||||
return {}
|
||||
}
|
||||
|
@ -17,6 +17,9 @@ class WalletClass extends BaseModule {
|
||||
@prop()
|
||||
public key: string
|
||||
|
||||
@prop()
|
||||
public address: string
|
||||
|
||||
/**
|
||||
* 钱包客户端存储的密码
|
||||
*/
|
||||
|
92
src/service/bridge.svr.ts
Normal file
92
src/service/bridge.svr.ts
Normal file
@ -0,0 +1,92 @@
|
||||
import { ZError } from 'common/ZError'
|
||||
import { singleton } from 'decorators/singleton'
|
||||
import { shortUuid, uuid } from 'utils/security.util'
|
||||
|
||||
export interface IQrData {
|
||||
clientId: string
|
||||
qrId: string
|
||||
expire: number
|
||||
remove: number
|
||||
status: number
|
||||
userId?: string
|
||||
key?: string
|
||||
token?: string
|
||||
}
|
||||
|
||||
const DEFAULT_EXPIRE_TIME = 5 * 60 * 1000
|
||||
const DEFAULT_DELETE_TIME = 10 * 60 * 1000
|
||||
|
||||
@singleton
|
||||
export class BridgeSvr {
|
||||
private clientMap: Map<string, IQrData> = new Map()
|
||||
private qrMap: Map<string, IQrData> = new Map()
|
||||
|
||||
public registClientReq(clientId: string): string {
|
||||
const qrId = shortUuid()
|
||||
const now = Date.now()
|
||||
if (this.clientMap.has(clientId)) {
|
||||
const data = this.clientMap.get(clientId)
|
||||
if (data.expire > now && data.status == 0) {
|
||||
data.expire = now + DEFAULT_EXPIRE_TIME
|
||||
data.remove = now + DEFAULT_DELETE_TIME
|
||||
return data.qrId
|
||||
} else {
|
||||
if (this.qrMap.has(data.qrId)) {
|
||||
this.qrMap.delete(data.qrId)
|
||||
}
|
||||
this.clientMap.delete(clientId)
|
||||
}
|
||||
}
|
||||
let data = {
|
||||
clientId,
|
||||
qrId,
|
||||
expire: now + DEFAULT_EXPIRE_TIME,
|
||||
remove: now + DEFAULT_DELETE_TIME,
|
||||
status: 0,
|
||||
}
|
||||
this.clientMap.set(clientId, data)
|
||||
this.qrMap.set(qrId, data)
|
||||
let self = this
|
||||
setImmediate(function () {
|
||||
self.cacheGc()
|
||||
})
|
||||
return qrId
|
||||
}
|
||||
|
||||
public cacheGc() {
|
||||
let now = Date.now()
|
||||
for (let [key, data] of this.qrMap.entries()) {
|
||||
if (data.remove <= now) {
|
||||
this.qrMap.delete(key)
|
||||
}
|
||||
}
|
||||
for (let [key, data] of this.clientMap.entries()) {
|
||||
if (data.remove <= now) {
|
||||
this.clientMap.delete(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fetchData(qrId: string) {
|
||||
const data = this.qrMap.get(qrId)
|
||||
return data
|
||||
}
|
||||
|
||||
public updateData(qrId: string, key: string, token: string) {
|
||||
if (!this.qrMap.has(qrId)) {
|
||||
throw new ZError(21, 'login data not found')
|
||||
}
|
||||
let data = this.qrMap.get(qrId)
|
||||
const now = Date.now()
|
||||
if (data.status === 0 && data.expire < now) {
|
||||
throw new ZError(22, 'login data expired')
|
||||
}
|
||||
if (data.status != 0) {
|
||||
throw new ZError(23, 'already logined')
|
||||
}
|
||||
data.status = 1
|
||||
data.key = key
|
||||
data.token = token
|
||||
return true
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
import crypto from 'crypto'
|
||||
import {compressUuid} from './string.util'
|
||||
const ENCODER = 'base64'
|
||||
const REG_KEY = /^[0-9a-fA-F]{63,64}$/
|
||||
|
||||
@ -22,3 +23,12 @@ export function aesDecrypt(encryptedText: string, password: string, iv: string)
|
||||
let decrypted = decipher.update(encryptedText, ENCODER, 'utf8')
|
||||
return decrypted + decipher.final('utf8')
|
||||
}
|
||||
|
||||
export function uuid() {
|
||||
return crypto.randomUUID()
|
||||
}
|
||||
|
||||
export function shortUuid() {
|
||||
let uid = uuid()
|
||||
return compressUuid(uid)
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ export function generateKeyValStr(data: {}, ignoreNull = true, splitChar: string
|
||||
return
|
||||
}
|
||||
if (i++ > 0) result += splitChar
|
||||
result += `${key}${equalChar}${data[key]}`
|
||||
result += `${key}${equalChar}${data[key]}`
|
||||
}
|
||||
return result
|
||||
}
|
||||
@ -47,15 +47,15 @@ export function keyValToObject(str: string, splitChar: string = '&', equalChar =
|
||||
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'
|
||||
obj === 'TRUE' ||
|
||||
obj === 'True' ||
|
||||
obj === 'on' ||
|
||||
obj === 'ON' ||
|
||||
obj === true ||
|
||||
obj === 1 ||
|
||||
obj === '1' ||
|
||||
obj === 'YES' ||
|
||||
obj === 'yes'
|
||||
)
|
||||
}
|
||||
|
||||
@ -84,7 +84,7 @@ export function string10to62(number: string | number) {
|
||||
qutient = (qutient - mod) / radix
|
||||
arr.unshift(chars[mod])
|
||||
} while (qutient)
|
||||
return arr.join('')
|
||||
return arr.join('')
|
||||
}
|
||||
|
||||
/**
|
||||
@ -104,3 +104,33 @@ export function string62to10(numberCode: string) {
|
||||
}
|
||||
return originNumber
|
||||
}
|
||||
|
||||
const reNormalUUID = /^[0-9a-fA-F-]{36}$/;
|
||||
const reLongUUID = /^[0-9a-fA-F]{32}$/;
|
||||
const reShortUUID = /^[0-9a-zA-Z+/]{22,23}$/;
|
||||
const n = /-/g;
|
||||
|
||||
export function compressUuid(e:string, t: boolean = false) {
|
||||
if (reNormalUUID.test(e)) {
|
||||
e = e.replace(n, '');
|
||||
} else if (!reLongUUID.test(e)) {
|
||||
return e;
|
||||
}
|
||||
var r = !0 === t ? 2 : 5;
|
||||
return compressHex(e, r)
|
||||
}
|
||||
|
||||
const CHARS_BASE64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
||||
export function compressHex(e: string, r: number) {
|
||||
var i, n = e.length;
|
||||
i = void 0 !== r ? r : n % 3;
|
||||
for (var s = e.slice(0, i), o = []; i < n;) {
|
||||
var u = parseInt(e[i], 16),
|
||||
a = parseInt(e[i + 1], 16),
|
||||
c = parseInt(e[i + 2], 16);
|
||||
o.push(CHARS_BASE64[u << 2 | a >> 2]);
|
||||
o.push(CHARS_BASE64[(3 & a) << 4 | c]);
|
||||
i += 3;
|
||||
}
|
||||
return s + o.join('')
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user