重构代码, 优化登录逻辑, 增加帐号绑定流程

This commit is contained in:
CounterFire2023 2023-09-06 15:47:14 +08:00
parent 15bb27eefa
commit b6d58c4f5b
16 changed files with 252 additions and 82 deletions

View File

@ -45,4 +45,7 @@ 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?c=Shop&a=buyGoodsDirect
# client登录时,验证用户数据的private key # client登录时,验证用户数据的private key
WALLET_CLIENT_SK='38d9baa24aaea6f87a1caa51f588b0c9578368a1cb00b1639eb9f450b6cada00' WALLET_CLIENT_SK='38d9baa24aaea6f87a1caa51f588b0c9578368a1cb00b1639eb9f450b6cada00'
# 检查guest能否绑定平台账号
GAME_CHECK_RELATION_URL='https://game2006api-test.kingsome.cn/webapp/index.php?c=AccountVerify&a=canBind'

View File

@ -1,9 +1,10 @@
import BaseController, { ROLE_ANON } from 'common/base.controller' import BaseController, { ROLE_ANON } from 'common/base.controller'
import { role, router } from 'decorators/router' import { role, router } from 'decorators/router'
import { Account, PlatEnum } from 'modules/Account' import { Account } from 'modules/Account'
import logger from 'logger/logger' import logger from 'logger/logger'
import { PlatApple } from 'plats/PlatApple' import { PlatApple } from 'plats/PlatApple'
import { IPlat } from 'plats/IPlat' import { IPlat } from 'plats/IPlat'
import { PlatEnum } from 'enums/PlatEnum'
const plat: IPlat = new PlatApple() const plat: IPlat = new PlatApple()
class AppleController extends BaseController { class AppleController extends BaseController {
@ -16,9 +17,13 @@ class AppleController extends BaseController {
@role(ROLE_ANON) @role(ROLE_ANON)
@router('post /wallet/login/apple') @router('post /wallet/login/apple')
async checkGoogleJwt(req, res) { async checkAppleJwt(req, res) {
logger.db('login', req) logger.db('login', req)
const { openId, data } = await plat.verifyToken(req) const { openId, data } = await plat.verifyToken(req)
const { api_platform } = req.headers
if (api_platform) {
data.platform = api_platform
}
let user = await Account.insertOrUpdate({ plat: PlatEnum.APPLE, openId }, data) let user = await Account.insertOrUpdate({ plat: PlatEnum.APPLE, openId }, data)
const ztoken = await res.jwtSign({ const ztoken = await res.jwtSign({
id: user.id, id: user.id,

View File

@ -1,41 +1,29 @@
import BaseController, { ROLE_ANON } from 'common/base.controller' import BaseController, { ROLE_ANON } from 'common/base.controller'
import { ZError } from 'common/ZError' import { ZError } from 'common/ZError'
import { role, router } from 'decorators/router' import { role, router } from 'decorators/router'
import { PlatEnum } from 'enums/PlatEnum'
import logger from 'logger/logger' import logger from 'logger/logger'
import { Account, PlatEnum } from 'modules/Account' import { Account } from 'modules/Account'
import * as wasm from 'rustwallet' import { IPlat } from 'plats/IPlat'
import { isUUID } from 'utils/string.util' import { PlatClient } from 'plats/PlatClient'
const CLIENT_SUFFIX = '_clientid' const plat: IPlat = new PlatClient()
function checkClientId(clientId: string) {
if (!clientId) {
return false
}
if (!clientId.endsWith(CLIENT_SUFFIX)) {
return false
}
const id = clientId.slice(0, clientId.length - CLIENT_SUFFIX.length)
return isUUID(id)
}
class ClientController extends BaseController { class ClientController extends BaseController {
@role(ROLE_ANON) @role(ROLE_ANON)
@router('post /wallet/login/client') @router('post /wallet/login/client')
async clientLogin(req, res) { async clientLogin(req, res) {
const { code } = req.params const { code } = req.params
const { api_platform } = req.headers
logger.db('login', req) logger.db('login', req)
if (!code) { if (!code) {
throw new ZError(11, 'param missing') throw new ZError(11, 'param missing')
} }
const sk = process.env.WALLET_CLIENT_SK const { openId, data } = await plat.verifyToken(req)
let codeDecrypto = wasm.rdecrypt(sk, code) const { api_platform } = req.headers
if (!checkClientId(codeDecrypto)) { if (api_platform) {
throw new ZError(12, 'param invalid') data.platform = api_platform
} }
const openId = codeDecrypto.slice(0, codeDecrypto.length - CLIENT_SUFFIX.length)
logger.info('clientLogin', openId) logger.info('clientLogin', openId)
let user = await Account.insertOrUpdate({ plat: PlatEnum.CLIENT, openId }, { platform: api_platform }) let user = await Account.insertOrUpdate({ plat: PlatEnum.CLIENT, openId }, data)
const ztoken = await res.jwtSign({ const ztoken = await res.jwtSign({
id: user.id, id: user.id,
openid: user.openId, openid: user.openId,

View File

@ -1,8 +1,9 @@
import BaseController, { ROLE_ANON } from 'common/base.controller' import BaseController, { ROLE_ANON } from 'common/base.controller'
import { ZError } from 'common/ZError' import { ZError } from 'common/ZError'
import { role, router } from 'decorators/router' import { role, router } from 'decorators/router'
import { PlatEnum } from 'enums/PlatEnum'
import logger from 'logger/logger' import logger from 'logger/logger'
import { Account, PlatEnum } from 'modules/Account' import { Account } from 'modules/Account'
import { IPlat } from 'plats/IPlat' import { IPlat } from 'plats/IPlat'
import { PlatFacebook } from 'plats/PlatFacebook' import { PlatFacebook } from 'plats/PlatFacebook'

View File

@ -1,8 +1,9 @@
import BaseController, { ROLE_ANON } from 'common/base.controller' import BaseController, { ROLE_ANON } from 'common/base.controller'
import { role, router } from 'decorators/router' import { role, router } from 'decorators/router'
import { PlatEnum } from 'enums/PlatEnum'
import logger from 'logger/logger' import logger from 'logger/logger'
import { Account, PlatEnum } from 'modules/Account' import { Account } from 'modules/Account'
import { IPlat } from 'plats/IPlat' import { IPlat } from 'plats/IPlat'
import { PlatGoogle } from 'plats/PlatGoogle' import { PlatGoogle } from 'plats/PlatGoogle'

View File

@ -1,42 +1,120 @@
import { ZError } from 'common/ZError' import { ZError } from 'common/ZError'
import BaseController, { ROLE_ANON } from 'common/base.controller' import BaseController, { ROLE_ANON } from 'common/base.controller'
import { role, router } from 'decorators/router' import { role, router } from 'decorators/router'
import { PlatEnum } from 'enums/PlatEnum'
import logger from 'logger/logger' import logger from 'logger/logger'
import { Account, PlatEnum } from 'modules/Account' import { Account } from 'modules/Account'
import { UnionAccount } from 'modules/UnionAccount'
import { Wallet } from 'modules/Wallet'
import { IPlat } from 'plats/IPlat' import { IPlat } from 'plats/IPlat'
import { PlatApple } from 'plats/PlatApple' import { PlatApple } from 'plats/PlatApple'
import { PlatClient } from 'plats/PlatClient'
import { PlatFacebook } from 'plats/PlatFacebook' import { PlatFacebook } from 'plats/PlatFacebook'
import { PlatGoogle } from 'plats/PlatGoogle' import { PlatGoogle } from 'plats/PlatGoogle'
import { PlatTikTok } from 'plats/PlatTikTok' import { PlatTikTok } from 'plats/PlatTikTok'
import { checkReleation } from 'service/game.svr'
const plats: Map<PlatEnum, IPlat> = new Map([ const plats: Map<PlatEnum, IPlat> = new Map([
[PlatEnum.GOOGLE, new PlatGoogle()], [PlatEnum.GOOGLE, new PlatGoogle()],
[PlatEnum.APPLE, new PlatApple()], [PlatEnum.APPLE, new PlatApple()],
[PlatEnum.FACEBOOK, new PlatFacebook()], [PlatEnum.FACEBOOK, new PlatFacebook()],
[PlatEnum.TIKTOK, new PlatTikTok()], [PlatEnum.TIKTOK, new PlatTikTok()],
[PlatEnum.CLIENT, new PlatClient()],
]) ])
// 如果客户端有传入account, 则说明该次登录是绑定账号
// 首先查找该账号是否已经绑定了其他账号
const parseBindAccount = async (account: string, channel: PlatEnum, user: any) => {
const uid = user.id
let unionAccount
const filterData: any = {}
filterData[`plats.${channel}`] = uid
if (account) {
// TODO:: check from game svr, verify account and check if plat account could bind
let checkResult: any = await checkReleation(account, channel, user.openId)
console.log(checkResult)
if (checkResult.errcode) {
throw new ZError(30, checkResult.errmsg)
}
unionAccount = await UnionAccount.findOne({ gameAccount: account })
if (unionAccount) {
let platInfo = unionAccount.plats.get(channel + '')
// 如果已经绑定, 且绑定的相同平台下不同的账号, 则抛出异常
// 如果未绑定, 那么查找平台账号是否已经绑定了其他账号
if (platInfo && platInfo !== uid) {
throw new ZError(21, 'account already bind')
} else if (!platInfo) {
// 检查pid是否已经绑定了其他账号
let unionAccount2 = await UnionAccount.findOne(filterData)
// 如果记录不存在, 那么将平台账号绑定至当前unionAccount
if (!unionAccount2) {
// 如果当前unionAccount已经设置了钱包账号, 且钱包账号不是当前账号, 那么检查当前账号是否开启了钱包
// 如果开启了钱包, 那么就不允许绑定
if (unionAccount.walletAccount && unionAccount.walletAccount !== uid) {
const wallet = await Wallet.findByAccount(uid)
if (wallet && wallet.address) {
throw new ZError(23, 'plat account already had wallet')
}
}
unionAccount.plats.set(channel + '', uid)
unionAccount.markModified('plats')
} else if (unionAccount2.gameAccount && unionAccount2.gameAccount === account) {
// 这种情况不用处理, 理论上是不可能出现的
} else {
throw new ZError(22, 'plat account already bind')
}
}
} else {
unionAccount = await UnionAccount.insertOrUpdate(filterData, {})
if (unionAccount.gameAccount && unionAccount.gameAccount !== account) {
throw new ZError(22, 'plat account already bind')
}
unionAccount.gameAccount = account
}
await unionAccount.save()
} else {
unionAccount = await UnionAccount.findOne(filterData)
}
let walletUser
// 如果统一账号存在, 但钱包账号不存在,那么就把当前绑定账号的信息写入钱包账号
if (unionAccount && !unionAccount.walletAccount) {
unionAccount.walletAccount = uid
walletUser = user
await unionAccount.save()
} else if (unionAccount && unionAccount.walletAccount) {
walletUser = await Account.findById(unionAccount.walletAccount)
} else {
walletUser = user
}
return { unionAccount, walletUser }
}
class LoginController extends BaseController { class LoginController extends BaseController {
@role(ROLE_ANON) @role(ROLE_ANON)
@router('post /wallet/login/general') @router('post /wallet/login/general')
async generalLogin(req, res) { async generalLogin(req, res) {
const { channel, account } = req.params const { code, channel, account } = req.params
logger.db('login', req) logger.db('login', req)
if (!code) {
throw new ZError(10, 'code not found')
}
const plat = plats.get(channel) const plat = plats.get(channel)
if (!plat) { if (!plat) {
throw new ZError(10, 'plat not found') throw new ZError(11, 'plat not support')
} }
const { openId, data } = await plat.verifyToken(req) const { openId, data } = await plat.verifyToken(req)
const { api_platform } = req.headers const { api_platform } = req.headers
if (api_platform) { if (api_platform) {
data.platform = api_platform data.platform = api_platform
} }
let user = await Account.insertOrUpdate({ plat: channel, openId }, data) const user = await Account.insertOrUpdate({ plat: channel, openId }, data)
const { unionAccount, walletUser } = await parseBindAccount(account, channel, user)
const ztoken = await res.jwtSign({ const ztoken = await res.jwtSign({
id: user.id, id: walletUser.id,
openid: user.openId, uid: unionAccount?.id || '',
version: user.accountVersion || 0, gid: unionAccount?.gameAccount || '',
plat: user.plat, openid: walletUser.openId,
version: walletUser.accountVersion || 0,
plat: walletUser.plat,
}) })
return { token: ztoken } return { token: ztoken }
} }

View File

@ -1,8 +1,9 @@
import BaseController, { ROLE_ANON } from 'common/base.controller' import BaseController, { ROLE_ANON } from 'common/base.controller'
import { ZError } from 'common/ZError' import { ZError } from 'common/ZError'
import { role, router } from 'decorators/router' import { role, router } from 'decorators/router'
import { PlatEnum } from 'enums/PlatEnum'
import logger from 'logger/logger' import logger from 'logger/logger'
import { Account, PlatEnum } from 'modules/Account' import { Account } from 'modules/Account'
import { CodeRecord, CodeStatus, CodeType, DEFAULT_CODE, DEFAULT_EXPIRE_TIME } from 'modules/CodeRecord' import { CodeRecord, CodeStatus, CodeType, DEFAULT_CODE, DEFAULT_EXPIRE_TIME } from 'modules/CodeRecord'
import { import {
DEFAULT_REGIST_HTML, DEFAULT_REGIST_HTML,

View File

@ -1,8 +1,9 @@
import BaseController, { ROLE_ANON } from 'common/base.controller' import BaseController, { ROLE_ANON } from 'common/base.controller'
import { ZError } from 'common/ZError' import { ZError } from 'common/ZError'
import { role, router } from 'decorators/router' import { role, router } from 'decorators/router'
import { PlatEnum } from 'enums/PlatEnum'
import logger from 'logger/logger' import logger from 'logger/logger'
import { Account, PlatEnum } from 'modules/Account' import { Account } from 'modules/Account'
import { IPlat } from 'plats/IPlat' import { IPlat } from 'plats/IPlat'
import { PlatTikTok } from 'plats/PlatTikTok' import { PlatTikTok } from 'plats/PlatTikTok'
import { fetchAccessToken, refreshAccessToken } from 'service/tiktok.svr' import { fetchAccessToken, refreshAccessToken } from 'service/tiktok.svr'

11
src/enums/PlatEnum.ts Normal file
View File

@ -0,0 +1,11 @@
export enum PlatEnum {
GOOGLE = 0,
APPLE = 1,
TIKTOK = 2,
FACEBOOK = 3,
TWITTER = 4,
TELEGRAM = 5,
EMAIL = 6,
DISCORD = 7,
CLIENT = 10,
}

View File

@ -3,18 +3,7 @@ import { dbconn } from 'decorators/dbconn'
import { Base, TimeStamps } from '@typegoose/typegoose/lib/defaultClasses' import { Base, TimeStamps } from '@typegoose/typegoose/lib/defaultClasses'
import { BaseModule } from './Base' import { BaseModule } from './Base'
import { genRandomString, sha512 } from 'utils/security.util' import { genRandomString, sha512 } from 'utils/security.util'
import { PlatEnum } from 'enums/PlatEnum'
export enum PlatEnum {
GOOGLE = 0,
APPLE = 1,
TIKTOK = 2,
FACEBOOK = 3,
TWITTER = 4,
TELEGRAM = 5,
EMAIL = 6,
DISCORD = 7,
CLIENT = 10,
}
/** /**
* salt和hash * salt和hash

View File

@ -0,0 +1,35 @@
import { getModelForClass, index, modelOptions, mongoose, prop, ReturnModelType, Severity } from '@typegoose/typegoose'
import { dbconn } from 'decorators/dbconn'
import { Base, TimeStamps } from '@typegoose/typegoose/lib/defaultClasses'
import { BaseModule } from './Base'
import { PlatEnum } from 'enums/PlatEnum'
/**
*
*/
interface UnionAccountClass extends Base, TimeStamps {}
@dbconn()
@index({ gameAccount: 1 }, { unique: true, partialFilterExpression: { gameAccount: { $exists: true } } })
@index({ walletAccount: 1 }, { unique: true, partialFilterExpression: { walletAccount: { $exists: true } } })
@modelOptions({
schemaOptions: { collection: 'union_account', timestamps: true },
options: { allowMixed: Severity.ALLOW },
})
class UnionAccountClass extends BaseModule {
// 生成钱包所用账号
@prop()
public walletAccount?: string
// 绑定的guest账号
@prop()
public gameAccount?: string
@prop({ type: String })
public plats: Map<PlatEnum, string>
public static async findByPlat(this: ReturnModelType<typeof UnionAccountClass>, channel: PlatEnum, uid: string) {
const filterData: any = {}
filterData[`plats.${channel}`] = uid
return this.findOne(filterData).exec()
}
}
export const UnionAccount = getModelForClass(UnionAccountClass, { existingConnection: UnionAccountClass.db })

View File

@ -1,4 +1,4 @@
import { getModelForClass, index, modelOptions, mongoose, prop, Severity } from '@typegoose/typegoose' import { getModelForClass, index, modelOptions, mongoose, prop, ReturnModelType, Severity } from '@typegoose/typegoose'
import { dbconn } from 'decorators/dbconn' import { dbconn } from 'decorators/dbconn'
import { BaseModule } from './Base' import { BaseModule } from './Base'
@ -28,6 +28,10 @@ class WalletClass extends BaseModule {
@prop({ required: true, default: true }) @prop({ required: true, default: true })
public nweRecord: boolean public nweRecord: boolean
public static async findByAccount(this: ReturnModelType<typeof WalletClass>, account: string) {
return this.findOne({ account }).exec()
}
public toJson() { public toJson() {
return { return {
key: this.key, key: this.key,

View File

@ -1,7 +1,5 @@
import { OAuth2Client } from 'google-auth-library'
import { IPlat } from './IPlat' import { IPlat } from './IPlat'
import verifyAppleToken from 'verify-apple-id-token' import verifyAppleToken from 'verify-apple-id-token'
import { ZError } from 'common/ZError'
const CLIENT_ID_DEBUG = 'com.jc.tebg' const CLIENT_ID_DEBUG = 'com.jc.tebg'
const CLIENT_ID_RELEASE = 'com.cege.games.release' const CLIENT_ID_RELEASE = 'com.cege.games.release'

31
src/plats/PlatClient.ts Normal file
View File

@ -0,0 +1,31 @@
import { IPlat } from './IPlat'
import verifyAppleToken from 'verify-apple-id-token'
import { ZError } from 'common/ZError'
import { isUUID } from 'utils/string.util'
import * as wasm from 'rustwallet'
const CLIENT_SUFFIX = '_clientid'
function checkClientId(clientId: string) {
if (!clientId) {
return false
}
if (!clientId.endsWith(CLIENT_SUFFIX)) {
return false
}
const id = clientId.slice(0, clientId.length - CLIENT_SUFFIX.length)
return isUUID(id)
}
export class PlatClient implements IPlat {
async verifyToken(req: any): Promise<any> {
let { code, token } = req.params
code = code || token
const sk = process.env.WALLET_CLIENT_SK
let codeDecrypto = wasm.rdecrypt(sk, code)
if (!checkClientId(codeDecrypto)) {
throw new ZError(12, 'param invalid')
}
const openId = codeDecrypto.slice(0, codeDecrypto.length - CLIENT_SUFFIX.length)
return { openId, data: {} }
}
}

View File

@ -1,6 +1,7 @@
import { OAuth2Client } from 'google-auth-library' import { OAuth2Client } from 'google-auth-library'
import { IPlat } from './IPlat' import { IPlat } from './IPlat'
import { ZError } from 'common/ZError' import { ZError } from 'common/ZError'
import logger from 'logger/logger'
const GOOGLE_OAUTH_ISS = 'https://accounts.google.com' const GOOGLE_OAUTH_ISS = 'https://accounts.google.com'
const GOOGLE_OAUTH_ISS1 = 'accounts.google.com' const GOOGLE_OAUTH_ISS1 = 'accounts.google.com'
@ -13,34 +14,55 @@ export class PlatGoogle implements IPlat {
async verifyToken(req: any): Promise<any> { async verifyToken(req: any): Promise<any> {
let { code, token } = req.params let { code, token } = req.params
code = code || token code = code || token
const client = new OAuth2Client(CLIENT_ID)
const ticket = await client.verifyIdToken({
idToken: code,
audience: [CLIENT_ID, CLIENT_ID2, CLIENT_ID_IOS, IOS_TEST], // Specify the CLIENT_ID of the app that accesses the backend
// Or, if multiple clients access the backend:
//[CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3]
})
const payload = ticket.getPayload()
if (!(payload.iss === GOOGLE_OAUTH_ISS || payload.iss === GOOGLE_OAUTH_ISS1)) {
throw new ZError(10, 'id token error')
}
if (
payload.aud !== CLIENT_ID &&
payload.aud !== CLIENT_ID2 &&
payload.aud !== CLIENT_ID_IOS &&
payload.aud !== IOS_TEST
) {
throw new ZError(11, 'client id mismatch')
}
let data: any = {} let data: any = {}
if (payload.email) data.email = payload.email let openId
if (process.env.NODE_ENV !== 'development') { const client = new OAuth2Client(CLIENT_ID)
if (payload.email_verified !== undefined) data.emailVerified = payload.email_verified try {
const ticket = await client.verifyIdToken({
idToken: code,
audience: [CLIENT_ID, CLIENT_ID2, CLIENT_ID_IOS, IOS_TEST], // Specify the CLIENT_ID of the app that accesses the backend
// Or, if multiple clients access the backend:
//[CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3]
})
const payload = ticket.getPayload()
if (!(payload.iss === GOOGLE_OAUTH_ISS || payload.iss === GOOGLE_OAUTH_ISS1)) {
throw new ZError(10, 'id token error')
}
if (
payload.aud !== CLIENT_ID &&
payload.aud !== CLIENT_ID2 &&
payload.aud !== CLIENT_ID_IOS &&
payload.aud !== IOS_TEST
) {
throw new ZError(11, 'client id mismatch')
}
if (payload.email) data.email = payload.email
if (process.env.NODE_ENV !== 'development') {
if (payload.email_verified !== undefined) data.emailVerified = payload.email_verified
}
if (payload.locale) data.locale = payload.locale
if (payload.name) data.nickname = payload.name
if (payload.picture) data.avatar = payload.picture
openId = payload.sub
} catch (err) {
logger.log('error parse google id token', err)
try {
let info: any = await client.getTokenInfo(code)
console.log(info)
if (info.email) data.email = info.email
if (info.aud !== CLIENT_ID2) {
throw new ZError(11, 'client id mismatch')
}
if (process.env.NODE_ENV !== 'development') {
if (info.email_verified !== undefined) data.emailVerified = info.email_verified
}
if (info.name) data.nickname = info.name
openId = info.sub
} catch (e2) {
logger.log('error parse google access token', e2)
}
} }
if (payload.locale) data.locale = payload.locale
if (payload.name) data.nickname = payload.name
if (payload.picture) data.avatar = payload.picture
const openId = payload.sub
return { openId, data } return { openId, data }
} }
} }

View File

@ -2,6 +2,7 @@ import axios from 'axios'
import { PayRecordClass } from 'modules/PayRecord' import { PayRecordClass } from 'modules/PayRecord'
import { DocumentType } from '@typegoose/typegoose' import { DocumentType } from '@typegoose/typegoose'
import { hmacsha256 } from 'utils/security.util' import { hmacsha256 } from 'utils/security.util'
import { PlatEnum } from 'enums/PlatEnum'
export async function reportPayResult(data: DocumentType<PayRecordClass>) { export async function reportPayResult(data: DocumentType<PayRecordClass>) {
let repData = { let repData = {
@ -27,10 +28,10 @@ export async function reportPayResult(data: DocumentType<PayRecordClass>) {
return axios(reqConfig) return axios(reqConfig)
} }
/** /**
* TODO::guest账号和平台账号能否绑定 * guest账号和平台账号能否绑定
*/ */
export async function checkReleation() { export async function checkReleation(gid: string, plat: PlatEnum, openId: string) {
let url = `${process.env.GAME_CHECK_RELATION_URL}` let url = `${process.env.GAME_CHECK_RELATION_URL}&guest_account=${gid}&target_plat=${plat}&target_account=${openId}`
let reqConfig: any = { let reqConfig: any = {
method: 'get', method: 'get',
url, url,
@ -38,5 +39,6 @@ export async function checkReleation() {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
} }
return axios(reqConfig) let res = await axios(reqConfig)
return res.data
} }