diff --git a/docs/uaw.md b/docs/uaw.md index 68e2999..1540345 100644 --- a/docs/uaw.md +++ b/docs/uaw.md @@ -68,6 +68,11 @@ 1. 用户状态(10) 增加返回emailId, email, gameId, gameMail 2. 增加接口: 发送邮件验证码(33), 验证邮件地址(34) +#### 20240515 +1. 增加接口: 验证客户端分享码(35) + + + ### 1. 钱包预登录 #### Request @@ -1060,6 +1065,38 @@ export const isValiedCode = (code) => { #### Response +```js +{ +} +``` + +### 35.\* 验证客户端分享码 + +#### Request + +- URL:`/api/user/verify_client` +- 方法:POST +- 头部: + - Authorization: Bearer JWT_token + + +body: + +```js +{ + "code": "12322100" +} + +``` +> 验证code的正则 +```js +export const isValiedCode = (code) => { + return /^[23456789abcdefghjkmnpqrstuvwxy]{8}$/.test(code) +} +``` + +#### Response + ```js { } diff --git a/src/controllers/ingame.controller.ts b/src/controllers/ingame.controller.ts index 68652d0..84f3db7 100644 --- a/src/controllers/ingame.controller.ts +++ b/src/controllers/ingame.controller.ts @@ -76,6 +76,9 @@ class InGameController extends BaseController { } else if (user.emailId) { let res = await queryInGameInfo(user.emailId, '6') Object.assign(gameData, res) + } else if (user.clientId) { + let res = await queryInGameInfo(user.clientId, user.clientPlat) + Object.assign(gameData, res) } } catch (e) { logger.info('queryInGameInfo with err: ', e.message || e) diff --git a/src/controllers/mail.controller.ts b/src/controllers/mail.controller.ts index c770b0b..8edb5af 100644 --- a/src/controllers/mail.controller.ts +++ b/src/controllers/mail.controller.ts @@ -13,12 +13,16 @@ import { DEFAULT_LOGIN_MAIL_HTML, DEFAULT_LOGIN_MAIL_SUBJECT, EmailSvr } from 's import { BaseController, router, ZError } from 'zutils' import { sha1 } from 'zutils/utils/security.util' import { SyncLocker } from 'common/SyncLocker' +import { ShareCodeRecord, ShareCodeStatus, ShareCodeType } from 'models/wallet/ShareCodeRecord' +export const isValiedShareCode = (code: string) => { + return /^[23456789abcdefghjkmnpqrstuvwxy]{8}$/.test(code) +} class MailController extends BaseController { /** * 通过邮件验证码形式的登录 */ - @router('post /api/user/verify_email') + // @router('post /api/user/verify_email') async loginWithEmail(req, res) { await new SyncLocker().checkLock(req) logger.db('verify_email', req) @@ -56,10 +60,47 @@ class MailController extends BaseController { return {} } + @router('post /api/user/verify_client') + async loginWithGameClient(req, res) { + await new SyncLocker().checkLock(req) + logger.db('verify_client', req) + let user = req.user + const { code } = req.params + if (!code) { + throw new ZError(10, 'params mismatch') + } + if (!isValiedShareCode(code)) { + throw new ZError(11, 'code error') + } + if (user.gameAccountBinded()) { + throw new ZError(12, 'already bind game account') + } + + let recordCode = await ShareCodeRecord.findByCode(code, ShareCodeType.BIND_UAW) + if (!recordCode) { + throw new ZError(14, 'code expired') + } + const openId = recordCode.openId + let userCheck = await ActivityUser.findOne({ clientId: openId, clientPlat: recordCode.plat }) + if (userCheck && userCheck.id !== user.id) { + throw new ZError(13, 'Email already binded to another account') + } + user.clientId = openId + user.clientMail = recordCode.email + user.clientPlat = recordCode.plat + recordCode.status = ShareCodeStatus.SUCCESS + await ShareCodeRecord.updateOne( + { code, type: ShareCodeType.BIND_UAW, status: ShareCodeStatus.PENDING }, + { status: ShareCodeStatus.SUCCESS }, + ) + await user.save() + return {} + } + /** * 发送验证码 */ - @router('post /api/email/send_code') + // @router('post /api/email/send_code') async sendVerifyCode(req, res) { await new SyncLocker().checkLock(req) logger.db('send_mail_code', req) diff --git a/src/controllers/sign.controller.ts b/src/controllers/sign.controller.ts index 460ffe6..bbf18c6 100644 --- a/src/controllers/sign.controller.ts +++ b/src/controllers/sign.controller.ts @@ -191,7 +191,7 @@ class SignController extends BaseController { @router('post /api/user/verify_google') async verifyGoogleToken(req) { const user = req.user - if (user.googleId) { + if (user.gameAccountBinded()) { throw new ZError(11, 'already bind google') } const { openId, data } = await verifyToken(req) diff --git a/src/models/ActivityUser.ts b/src/models/ActivityUser.ts index 920d600..56ffd3a 100644 --- a/src/models/ActivityUser.ts +++ b/src/models/ActivityUser.ts @@ -51,6 +51,7 @@ export interface ActivityUserClass extends Base, TimeStamps {} @index({ twitterId: 1 }, { unique: true, partialFilterExpression: { twitterId: { $exists: true } } }) @index({ googleId: 1 }, { unique: true, partialFilterExpression: { googleId: { $exists: true } } }) @index({ emailId: 1 }, { unique: true, partialFilterExpression: { emailId: { $exists: true } } }) +@index({ clientId: 1, clientPlat: 1 }, { unique: true, partialFilterExpression: { clientId: { $exists: true } } }) @index({ discordId: 1 }, { unique: true, partialFilterExpression: { discordId: { $exists: true } } }) @modelOptions({ schemaOptions: { collection: 'activity_user', timestamps: true }, @@ -117,6 +118,13 @@ export class ActivityUserClass extends BaseModule { @prop() public email?: string + @prop() + public clientId?: string + @prop() + public clientMail?: string + @prop() + public clientPlat?: string + @prop({ default: false }) public inWhiteList: boolean @@ -150,14 +158,14 @@ export class ActivityUserClass extends BaseModule { } public gameAccountBinded() { - return this.googleId || this.emailId + return this.googleId || this.emailId || this.clientId } public gameId() { - return this.googleId || this.emailId + return this.googleId || this.emailId || this.clientId } public gameMail() { - return this.googleEmail || this.email + return this.googleEmail || this.email || this.clientMail } } diff --git a/src/models/wallet/ShareCodeRecord.ts b/src/models/wallet/ShareCodeRecord.ts new file mode 100644 index 0000000..f7c6ecb --- /dev/null +++ b/src/models/wallet/ShareCodeRecord.ts @@ -0,0 +1,67 @@ +import { getModelForClass, index, modelOptions, pre, prop, ReturnModelType } from '@typegoose/typegoose' +import { dbconn } from 'decorators/dbconn' +import { BaseModule } from '../Base' + +import { customAlphabet } from 'nanoid' + +const nanoid = customAlphabet('1234567890', 8) + +export const DEFAULT_SHARE_CODE = '00000000' +export const DEFAULT_EXPIRE_TIME = 5 * 60 * 1000 + +export enum ShareCodeType { + BIND_UAW = 1, // uaw 绑定 +} + +export enum ShareCodeStatus { + PENDING = 1, + SUCCESS = 2, + FAIL = 3, + EXPIRED = 4, +} + +/** + * 分享码记录 + */ +@dbconn('wallet') +@index({ type: 1, code: 1 }, { unique: true, partialFilterExpression: { status: 1 } }) +@index({ account: 1, type: 1, status: 1 }, { unique: true, partialFilterExpression: { status: 1 } }) +@index({ expiredAt: 1, status: 1 }, { unique: false }) +@modelOptions({ + schemaOptions: { collection: 'share_code_record', timestamps: true }, +}) +class ShareCodeRecordClass extends BaseModule { + @prop({ required: true }) + public account: string + + @prop({ required: true }) + public openId: string + + @prop() + public email?: string + + @prop({ required: true }) + public plat: string + + @prop({ required: true }) + public code!: string + + @prop({ default: Date.now() + DEFAULT_EXPIRE_TIME }) + public expiredAt?: number + + @prop({ required: true, default: ShareCodeType.BIND_UAW }) + public type: ShareCodeType + + @prop({ required: true, default: ShareCodeStatus.PENDING }) + public status: ShareCodeStatus + + public static async findByCode( + this: ReturnModelType, + code: string, + type: ShareCodeType, + ) { + return this.findOne({ code, type, status: ShareCodeStatus.PENDING }).exec() + } +} + +export const ShareCodeRecord = getModelForClass(ShareCodeRecordClass, { existingConnection: ShareCodeRecordClass.db }) diff --git a/src/taskingame/base/ITask.ts b/src/taskingame/base/ITask.ts index 4f5e3dc..68e52dd 100644 --- a/src/taskingame/base/ITask.ts +++ b/src/taskingame/base/ITask.ts @@ -33,7 +33,14 @@ export abstract class ITask { public async claimReward(cfg: any) { const user = this.user - let channel = user.googleId ? '0' : '6' + let channel = '0' + if (user.googleId) { + channel = '0' + } else if (user.emailId) { + channel = '6' + } else if (user.clientId) { + channel = user.clientPlat + } let gameId = user.gameId() let gameData = await queryInGameInfo(gameId, channel) if (!(await this.check(cfg, gameData))) {