loginByMobile中的部分逻辑

This commit is contained in:
Xu Chang 2022-07-09 22:36:22 +08:00
parent 17a2024233
commit 32f3d7d9f8
30 changed files with 690 additions and 127 deletions

View File

@ -1,5 +1,6 @@
import { composeFileUrl } from '../../../../src/utils/extraFile';
const SEND_KEY = 'captcha:sendAt';
export default OakPage({
path: 'mobile:me',
entity: 'mobile',
@ -12,24 +13,52 @@ export default OakPage({
mobile: '',
password: '',
captcha: '',
counter: 0,
},
async formData({ features }) {
const lastSendAt = features.localStorage.load(SEND_KEY);
const now = Date.now();
let counter = 0;
if (typeof lastSendAt === 'number') {
counter = Math.max(60 - Math.ceil((now - lastSendAt) / 1000), 0);
if (counter > 0) {
this.counterHandler = setTimeout(() => this.reRender(), 1000);
}
else if (this.counterHandler) {
clearTimeout(this.couuterHandler);
this.counterHandler = undefined;
}
}
return {
counter,
};
},
methods: {
onInput(e) {
onInput(e: any) {
const { dataset, value } = this.resolveInput(e);
const{ attr } = dataset;
this.setState({
[attr]: value,
});
},
async sendCaptcha(type: 'web') {
async sendCaptcha() {
const { mobile } = this.state;
const result = await this.features.token.sendCaptcha(mobile, type);
const result = await this.features.token.sendCaptcha(mobile);
// 显示返回消息
this.setState({
oakError: {
type: 'success',
msg: result,
}
});
this.save(SEND_KEY, Date.now());
this.reRender();
},
async loginByMobile() {
const { eventLoggedIn } = this.props;
const { mobile, password, captcha } = this.state;
await this.features.token.loginByMobile(mobile, password, captcha);
this.pub(eventLoggedIn);
}
},
});

View File

@ -7,10 +7,7 @@ import { isMobile, isPassword, isCaptcha } from 'oak-domain/lib/utils/validator'
const { TabPane } = Tabs;
export default function render() {
const { mobile, captcha, password } = this.state;
const onFinish = (values: any) => {
console.log('Received values of form: ', values);
};
const { mobile, captcha, password, counter} = this.state;
const validMobile = isMobile(mobile);
const validCaptcha = isCaptcha(captcha);
const validPassword = isPassword(password);
@ -28,11 +25,9 @@ export default function render() {
name="normal_login"
className="login-form"
initialValues={{ remember: true }}
onFinish={onFinish}
>
<Form.Item
name="mobile"
rules={[{ required: true, message: 'Please input your Mobile!' }]}
>
<Input
allowClear
@ -47,7 +42,6 @@ export default function render() {
</Form.Item>
<Form.Item
name="password"
rules={[{ required: true, message: 'Please input your Password!' }]}
>
<Input
allowClear
@ -71,6 +65,7 @@ export default function render() {
htmlType="submit"
className="login-form-button"
disabled={!allowSubmit}
onClick={() => this.loginByMobile()}
>
Log in
</Button>
@ -82,11 +77,9 @@ export default function render() {
name="normal_login"
className="login-form"
initialValues={{ remember: true }}
onFinish={onFinish}
>
<Form.Item
name="mobile"
rules={[{ required: true, message: 'Please input your Mobile!' }]}
>
<Input.Group compact>
<Input
@ -102,16 +95,18 @@ export default function render() {
/>
<Button
type="primary"
disabled={!validMobile}
onClick={() => this.sendCaptcha('web')}
style={{
width:65,
}}
disabled={!validMobile || counter > 0}
onClick={() => this.sendCaptcha()}
>
Send
{counter > 0 ? counter : 'Send'}
</Button>
</Input.Group>
</Form.Item>
<Form.Item
name="captcha"
rules={[{ required: true, message: 'Please input the captcha received!' }]}
>
<Input
allowClear
@ -136,6 +131,7 @@ export default function render() {
htmlType="submit"
className="login-form-button"
disabled={!allowSubmit}
onClick={() => this.loginByMobile()}
>
Log in
</Button>

View File

@ -118,8 +118,13 @@ export default OakPage({
break;
}
case 'web': {
const eventLoggedIn = `token:me:login:${Date.now()}`;
this.sub(eventLoggedIn, () => {
this.navigateBack();
})
this.navigateTo({
url: '/mobile/login'
url: '/mobile/login',
eventLoggedIn,
});
break;
}

View File

@ -27,7 +27,7 @@ export declare abstract class GeneralRuntimeContext<ED extends EntityDict> exten
}> | undefined>;
getTokenValue(): string | undefined;
toString(): Promise<string>;
protected static destructString(strCxt: string): {
protected static fromString(strCxt: string): {
applicationId: any;
scene: any;
token: any;

View File

@ -78,7 +78,7 @@ class GeneralRuntimeContext extends UniversalContext_1.UniversalContext {
}
return JSON.stringify(data);
}
static destructString(strCxt) {
static fromString(strCxt) {
const { applicationId, scene, token, } = JSON.parse(strCxt);
return {
applicationId,

View File

@ -3,9 +3,11 @@ import { EntityDict } from "general-app-domain";
import { QiniuUploadInfo } from "oak-frontend-base/src/types/Upload";
import { GeneralRuntimeContext } from "..";
declare type GeneralAspectDict<ED extends EntityDict, Cxt extends GeneralRuntimeContext<ED>> = {
loginByPassword: (params: {
password: string;
loginByMobile: (params: {
captcha?: string;
password?: string;
mobile: string;
env: WebEnv | WechatMpEnv;
}, context: Cxt) => Promise<string>;
loginMp: (params: {
code: string;

View File

@ -1,7 +1,7 @@
import { loginByPassword, loginMp, loginWechatMp, syncUserInfoWechatMp, sendCaptcha } from './token';
import { loginByMobile, loginMp, loginWechatMp, syncUserInfoWechatMp, sendCaptcha } from './token';
import { getUploadInfo } from './extraFile';
export declare const aspectDict: {
loginByPassword: typeof loginByPassword;
loginByMobile: typeof loginByMobile;
loginMp: typeof loginMp;
loginWechatMp: typeof loginWechatMp;
syncUserInfoWechatMp: typeof syncUserInfoWechatMp;

View File

@ -6,7 +6,7 @@ const extraFile_1 = require("./extraFile");
// import commonAspectDict from 'oak-common-aspect';
const lodash_1 = require("lodash");
exports.aspectDict = (0, lodash_1.assign)({
loginByPassword: token_1.loginByPassword,
loginByMobile: token_1.loginByMobile,
loginMp: token_1.loginMp,
loginWechatMp: token_1.loginWechatMp,
syncUserInfoWechatMp: token_1.syncUserInfoWechatMp,

View File

@ -5,9 +5,11 @@ import { WebEnv, WechatMpEnv } from 'general-app-domain/Token/Schema';
export declare function loginMp<ED extends EntityDict, Cxt extends GeneralRuntimeContext<ED>>(params: {
code: string;
}, context: Cxt): Promise<string>;
export declare function loginByPassword<ED extends EntityDict, Cxt extends GeneralRuntimeContext<ED>>(params: {
password: string;
export declare function loginByMobile<ED extends EntityDict, Cxt extends GeneralRuntimeContext<ED>>(params: {
captcha?: string;
password?: string;
mobile: string;
env: WebEnv | WechatMpEnv;
}, context: Cxt): Promise<string>;
export declare function loginWechatMp<ED extends EntityDict, Cxt extends GeneralRuntimeContext<ED>>({ code, env }: {
code: string;

View File

@ -3,29 +3,188 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.sendCaptcha = exports.syncUserInfoWechatMp = exports.loginWechatMp = exports.loginByPassword = exports.loginMp = void 0;
exports.sendCaptcha = exports.syncUserInfoWechatMp = exports.loginWechatMp = exports.loginByMobile = exports.loginMp = void 0;
const oak_external_sdk_1 = require("oak-external-sdk");
const assert_1 = __importDefault(require("assert"));
const lodash_1 = require("lodash");
const types_1 = require("oak-domain/lib/types");
const extraFile_1 = require("../utils/extraFile");
const Exceptions_1 = require("../types/Exceptions");
async function loginMp(params, context) {
const { rowStore } = context;
throw new Error('method not implemented!');
}
exports.loginMp = loginMp;
async function loginByPassword(params, context) {
async function setupMobile(mobile, env, context) {
const { rowStore } = context;
const { result: [mobile] } = await rowStore.select('mobile', {
const currentToken = await context.getToken();
const applicationId = context.getApplicationId();
const { result: result2 } = await rowStore.select('mobile', {
data: {
id: 1,
mobile: 1,
userId: 1,
ableState: 1,
user: {
id: 1,
userState: 1,
wechatUser$user: {
$entity: 'wechatUser',
data: {
id: 1,
},
},
},
},
}, context);
throw new Error('method not implemented!');
filter: {
mobile,
ableState: 'enabled',
}
}, context, { notCollect: true });
if (result2.length > 0) {
// 此手机号已经存在
(0, assert_1.default)(result2.length === 1);
const [mobileRow] = result2;
if (currentToken) {
if (currentToken.userId === mobileRow.userId) {
return currentToken.id;
}
else {
// 此时可能要合并用户如果用户有wechatUser信息则抛出OakDistinguishUserByWechatUser异常否则抛出
const { user } = mobileRow;
const { wechatUser$user } = user;
if (wechatUser$user.length > 0) {
throw new Exceptions_1.OakDistinguishUserByWechatUserException(mobileRow.userId);
}
else {
throw new Exceptions_1.OakDistinguishUserByBusinessException(mobileRow.userId);
}
}
}
else {
// 此时以该手机号登录 todo根据环境来判断用户也有可能是新获得此手机号未来再进一步处理
const tokenData = {
id: await generateNewId(),
applicationId,
playerId: mobileRow.userId,
env,
};
const { user } = mobileRow;
const { userState } = user;
switch (userState) {
case 'disabled': {
throw new Exceptions_1.OakUserDisabledException();
}
case 'shadow': {
(0, lodash_1.assign)(tokenData, {
userId: mobileRow.userId,
user: {
action: 'activate',
}
});
break;
}
default: {
(0, assert_1.default)(userState === 'normal');
(0, lodash_1.assign)(tokenData, {
userId: mobileRow.id,
});
}
}
await rowStore.operate('token', {
data: tokenData,
action: 'create',
}, context);
return tokenData.id;
}
}
else {
//此手机号不存在
if (currentToken) {
// 创建手机号并与之关联即可
const mobileData = {
id: await generateNewId(),
mobile,
userId: currentToken.userId,
};
await rowStore.operate('mobile', {
action: 'create',
data: mobileData
}, context);
return currentToken.id;
}
else {
// 创建token, mobile, user
const userData = {
id: await generateNewId(),
userState: 'normal',
};
await rowStore.operate('user', {
action: 'create',
data: userData,
}, context);
const tokenData = {
id: await generateNewId(),
userId: userData.id,
playerId: userData.id,
env,
mobile: {
action: 'create',
data: {
id: await generateNewId(),
mobile,
userId: userData.id,
}
}
};
await rowStore.operate('token', {
action: 'create',
data: tokenData,
}, context);
return tokenData.id;
}
}
}
exports.loginByPassword = loginByPassword;
async function loginByMobile(params, context) {
const { mobile, captcha, password, env } = params;
const { rowStore } = context;
if (captcha) {
const { result } = await rowStore.select('captcha', {
data: {
id: 1,
expired: 1,
},
filter: {
mobile,
code: captcha,
},
sorter: [{
$attr: {
$$createAt$$: 1,
},
$direction: 'desc',
}],
indexFrom: 0,
count: 1,
}, context, { notCollect: true });
if (result.length > 0) {
const [captchaRow] = result;
if (captchaRow.expired) {
throw new types_1.OakUserException('验证码已经过期');
}
// 到这里说明验证码已经通过
return await setupMobile(mobile, env, context);
}
else {
throw new types_1.OakUserException('验证码无效');
}
}
else {
(0, assert_1.default)(password);
throw new Error('method not implemented!');
}
}
exports.loginByMobile = loginByMobile;
async function loginWechatMp({ code, env }, context) {
const { rowStore } = context;
const application = (await context.getApplication());
@ -246,6 +405,8 @@ async function syncUserInfoWechatMp({ nickname, avatarUrl, encryptedData, iv, si
data: {
id: 1,
sessionKey: 1,
nickname: 1,
avatar: 1,
user: {
id: 1,
nickname: 1,
@ -322,6 +483,7 @@ async function syncUserInfoWechatMp({ nickname, avatarUrl, encryptedData, iv, si
}
}, context);
}
// todo update nickname/avatar in wechatUser
}
exports.syncUserInfoWechatMp = syncUserInfoWechatMp;
async function sendCaptcha({ mobile, env }, context) {
@ -330,26 +492,28 @@ async function sendCaptcha({ mobile, env }, context) {
let { visitorId } = env;
const { rowStore } = context;
const now = Date.now();
const [count1, count2] = await Promise.all([
rowStore.count('captcha', {
filter: {
visitorId,
$$createAt$$: {
$gt: now - 3600 * 1000,
if (process.env.NODE_ENV !== 'development') {
const [count1, count2] = await Promise.all([
rowStore.count('captcha', {
filter: {
visitorId,
$$createAt$$: {
$gt: now - 3600 * 1000,
},
},
},
}, context),
rowStore.count('captcha', {
filter: {
mobile,
$$createAt$$: {
$gt: now - 3600 * 1000,
},
}
}, context)
]);
if (count1 > 5 || count2 > 5) {
throw new types_1.OakUserException('您已发送很多次短信,请休息会再发吧');
}, context),
rowStore.count('captcha', {
filter: {
mobile,
$$createAt$$: {
$gt: now - 3600 * 1000,
},
}
}, context)
]);
if (count1 > 5 || count2 > 5) {
throw new types_1.OakUserException('您已发送很多次短信,请休息会再发吧');
}
}
const { result: [captcha] } = await rowStore.select('captcha', {
data: {
@ -360,7 +524,7 @@ async function sendCaptcha({ mobile, env }, context) {
filter: {
mobile,
$$createAt$$: {
$gt: now - 600 * 1000,
$gt: now - 60 * 1000,
},
expired: false,
}
@ -389,11 +553,12 @@ async function sendCaptcha({ mobile, env }, context) {
code += '0';
}
}
const { v1 } = require('uuid');
const id = await generateNewId();
console.log('captcha created', id);
await rowStore.operate('captcha', {
action: 'create',
data: {
id: v1(),
id,
mobile,
code,
visitorId,

View File

@ -1,12 +1,25 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const action_1 = require("oak-domain/lib/actions/action");
;
const AbleActionDef = (0, action_1.makeAbleActionDef)('enabled');
const locale = {
zh_CN: {
attr: {
ableState: '是否可用',
mobile: '手机号',
user: '关联用户',
tokens: '相关令牌',
},
action: {
enable: '启用',
disable: '禁用',
},
v: {
ableState: {
enabled: '可用的',
disabled: '禁用的',
}
}
},
};

View File

@ -5,6 +5,7 @@ export interface Schema extends EntityShape {
name?: String<16>;
nickname?: String<64>;
password?: Text;
passwordOrigin?: Text;
birth?: Datetime;
gender?: 'male' | 'female';
avatar?: Image;

View File

@ -51,6 +51,7 @@ const locale = {
nickname: '昵称',
birth: '生日',
password: '密码',
passwordOrigin: '明文密码',
gender: '性别',
avatar: '头像',
idCardType: '证件类型',

View File

@ -1,4 +1,4 @@
import { String, Datetime, Boolean } from 'oak-domain/lib/types/DataType';
import { String, Datetime, Image, Boolean } from 'oak-domain/lib/types/DataType';
import { Schema as User } from './User';
import { Schema as Application } from './Application';
import { Schema as Token } from './Token';
@ -15,4 +15,6 @@ export interface Schema extends EntityShape {
user?: User;
application: Application;
tokens: Array<Token>;
nickname?: String<128>;
avatar?: Image;
}

View File

@ -15,6 +15,8 @@ const locale = {
user: '用户',
tokens: '相关令牌',
application: '应用',
nickname: '昵称',
avatar: '头像',
},
v: {
origin: {

View File

@ -11,12 +11,12 @@ export declare class Token<ED extends EntityDict, Cxt extends GeneralRuntimeCont
private cache;
private context;
constructor(aspectWrapper: AspectWrapper<ED, Cxt, AD & CommonAspectDict<ED, Cxt>>, cache: Cache<ED, Cxt, AD & CommonAspectDict<ED, Cxt>>, context: Cxt);
loginByPassword(mobile: string, password: string): Promise<void>;
loginByMobile(mobile: string, password?: string, captcha?: string): Promise<void>;
loginWechatMp(): Promise<void>;
syncUserInfoWechatMp(): Promise<void>;
logout(): Promise<void>;
getToken(): Promise<string | undefined>;
getUserId(): Promise<string | undefined>;
isRoot(): Promise<boolean>;
sendCaptcha(mobile: string, type: 'web'): Promise<string>;
sendCaptcha(mobile: string): Promise<string>;
}

View File

@ -22,10 +22,11 @@ class Token extends oak_frontend_base_1.Feature {
this.cache = cache;
this.context = context;
}
async loginByPassword(mobile, password) {
async loginByMobile(mobile, password, captcha) {
const env = await (0, env_1.getEnv)();
await this.rwLock.acquire('X');
try {
const { result } = await this.getAspectWrapper().exec('loginByPassword', { password, mobile });
const { result } = await this.getAspectWrapper().exec('loginByMobile', { password, mobile, captcha, env });
this.token = result;
this.rwLock.release();
this.context.setToken(result);
@ -125,7 +126,7 @@ class Token extends oak_frontend_base_1.Feature {
}));
return tokenValue?.player?.userRole$user.length > 0 ? tokenValue?.player?.userRole$user[0]?.roleId === constants_1.ROOT_ROLE_ID : false;
}
async sendCaptcha(mobile, type) {
async sendCaptcha(mobile) {
const env = await (0, env_1.getEnv)();
const { result } = await this.getAspectWrapper().exec('sendCaptcha', {
mobile,
@ -136,7 +137,7 @@ class Token extends oak_frontend_base_1.Feature {
}
__decorate([
oak_frontend_base_1.Action
], Token.prototype, "loginByPassword", null);
], Token.prototype, "loginByMobile", null);
__decorate([
oak_frontend_base_1.Action
], Token.prototype, "loginWechatMp", null);
@ -146,4 +147,7 @@ __decorate([
__decorate([
oak_frontend_base_1.Action
], Token.prototype, "logout", null);
__decorate([
oak_frontend_base_1.Action
], Token.prototype, "sendCaptcha", null);
exports.Token = Token;

View File

@ -5,3 +5,21 @@ export declare class OakUnloggedInException extends OakUserException {
export declare class OakNotEnoughMoneyException extends OakUserException {
constructor(message?: string);
}
export declare class OakDistinguishUserByWechatUserException extends OakUserException {
userId: string;
constructor(userId: string, message?: string);
toString(): string;
}
export declare class OakDistinguishUserByBusinessException extends OakUserException {
userId: string;
constructor(userId: string, message?: string);
toString(): string;
}
export declare class OakUserDisabledException extends OakUserException {
constructor(message?: string);
}
export declare function makeException(data: {
name: string;
message?: string;
[A: string]: any;
}): import("oak-domain/lib/types").OakException | undefined;

View File

@ -1,6 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.OakNotEnoughMoneyException = exports.OakUnloggedInException = void 0;
exports.makeException = exports.OakUserDisabledException = exports.OakDistinguishUserByBusinessException = exports.OakDistinguishUserByWechatUserException = exports.OakNotEnoughMoneyException = exports.OakUnloggedInException = void 0;
const types_1 = require("oak-domain/lib/types");
class OakUnloggedInException extends types_1.OakUserException {
constructor(message) {
@ -16,3 +16,67 @@ class OakNotEnoughMoneyException extends types_1.OakUserException {
}
exports.OakNotEnoughMoneyException = OakNotEnoughMoneyException;
;
class OakDistinguishUserByWechatUserException extends types_1.OakUserException {
userId;
constructor(userId, message) {
super(message || '系统中发现可能属于您的另一帐户');
this.userId = userId;
}
toString() {
return JSON.stringify({
name: this.name,
message: this.message,
userId: this.userId,
});
}
}
exports.OakDistinguishUserByWechatUserException = OakDistinguishUserByWechatUserException;
class OakDistinguishUserByBusinessException extends types_1.OakUserException {
userId;
constructor(userId, message) {
super(message || '系统中发现可能属于您的另一帐户');
this.userId = userId;
}
toString() {
return JSON.stringify({
name: this.name,
message: this.message,
userId: this.userId,
});
}
}
exports.OakDistinguishUserByBusinessException = OakDistinguishUserByBusinessException;
class OakUserDisabledException extends types_1.OakUserException {
constructor(message) {
super(message || '您的帐户已被禁用,请联系系统管理员');
}
}
exports.OakUserDisabledException = OakUserDisabledException;
function makeException(data) {
const exception = (0, types_1.makeException)(data);
if (exception) {
return exception;
}
const { name, message } = data;
switch (name) {
case OakUnloggedInException.name: {
return new OakUnloggedInException(message);
}
case OakNotEnoughMoneyException.name: {
return new OakNotEnoughMoneyException(message);
}
case OakDistinguishUserByWechatUserException.name: {
return new OakDistinguishUserByWechatUserException(data.userId, message);
}
case OakDistinguishUserByBusinessException.name: {
return new OakDistinguishUserByBusinessException(data.userId, message);
}
case OakUserDisabledException.name: {
return new OakUserDisabledException(message);
}
default: {
return;
}
}
}
exports.makeException = makeException;

View File

@ -42,7 +42,8 @@
"build": "tsc",
"get:area": "ts-node ./scripts/getAmapArea.ts",
"clean:dir": "ts-node ./scripts/cleanDtsAndJs",
"postinstall": "npm run prebuild"
"postinstall": "npm run prebuild",
"test": "ts-node ./test/test.ts"
},
"main": "src/index"
}

View File

@ -103,7 +103,7 @@ export abstract class GeneralRuntimeContext<ED extends EntityDict> extends Unive
return JSON.stringify(data);
}
protected static destructString(strCxt: string) {
protected static fromString(strCxt: string) {
const {
applicationId,
scene,

View File

@ -5,7 +5,7 @@ import { QiniuUploadInfo } from "oak-frontend-base/src/types/Upload";
import { GeneralRuntimeContext } from "..";
type GeneralAspectDict<ED extends EntityDict, Cxt extends GeneralRuntimeContext<ED>> = {
loginByPassword: (params: { password: string, mobile: string }, context: Cxt) => Promise<string>,
loginByMobile: (params: { captcha?: string, password?: string, mobile: string, env: WebEnv | WechatMpEnv }, context: Cxt) => Promise<string>,
loginMp: (params: { code: string }, context: Cxt) => Promise<string>,
loginWechatMp: ({ code, env }: {
code: string;

View File

@ -1,9 +1,9 @@
import { loginByPassword, loginMp, loginWechatMp, syncUserInfoWechatMp, sendCaptcha } from './token';
import { loginByMobile, loginMp, loginWechatMp, syncUserInfoWechatMp, sendCaptcha } from './token';
import { getUploadInfo } from './extraFile';
// import commonAspectDict from 'oak-common-aspect';
import { assign } from 'lodash';
export const aspectDict = assign({
loginByPassword,
loginByMobile,
loginMp,
loginWechatMp,
syncUserInfoWechatMp,

View File

@ -10,24 +10,194 @@ import { Operation as ExtraFileOperation } from 'general-app-domain/ExtraFile/Sc
import { assign, isEqual, keys } from 'lodash';
import { OakUserException, SelectRowShape } from 'oak-domain/lib/types';
import { composeFileUrl, decomposeFileUrl } from '../utils/extraFile';
import { OakDistinguishUserByBusinessException, OakDistinguishUserByWechatUserException, OakUserDisabledException } from '../types/Exceptions';
export async function loginMp<ED extends EntityDict, Cxt extends GeneralRuntimeContext<ED>>(params: { code: string }, context: Cxt): Promise<string> {
const { rowStore } = context;
throw new Error('method not implemented!');
}
export async function loginByPassword<ED extends EntityDict, Cxt extends GeneralRuntimeContext<ED>>(params: { password: string, mobile: string }, context: Cxt): Promise<string> {
async function setupMobile<ED extends EntityDict, Cxt extends GeneralRuntimeContext<ED>>(mobile: string, env: WebEnv | WechatMpEnv, context: Cxt) {
const { rowStore } = context;
const currentToken = await context.getToken();
const applicationId = context.getApplicationId();
const { result: [mobile] } = await rowStore.select('mobile', {
const { result: result2 } = await rowStore.select('mobile', {
data: {
id: 1,
mobile: 1,
userId: 1,
ableState: 1,
user: {
id: 1,
userState: 1,
wechatUser$user: {
$entity: 'wechatUser',
data: {
id: 1,
},
},
},
},
}, context);
filter: {
mobile,
ableState: 'enabled',
}
}, context, { notCollect: true });
if (result2.length > 0) {
// 此手机号已经存在
assert(result2.length === 1);
const [ mobileRow ] = result2;
if (currentToken) {
if (currentToken.userId === mobileRow.userId) {
return currentToken.id;
}
else {
// 此时可能要合并用户如果用户有wechatUser信息则抛出OakDistinguishUserByWechatUser异常否则抛出
const { user } = mobileRow;
const { wechatUser$user } = user as {
wechatUser$user: any[];
};
if (wechatUser$user.length > 0) {
throw new OakDistinguishUserByWechatUserException(mobileRow.userId as string);
}
else {
throw new OakDistinguishUserByBusinessException(mobileRow.userId as string);
}
}
}
else {
// 此时以该手机号登录 todo根据环境来判断用户也有可能是新获得此手机号未来再进一步处理
const tokenData: EntityDict['token']['CreateSingle']['data'] = {
id: await generateNewId(),
applicationId,
playerId: mobileRow.userId as string,
env,
};
const { user } = mobileRow;
const { userState } = user as SelectRowShape<EntityDict['user']['Schema'], {
id: 1,
userState: 1,
}>;
switch (userState) {
case 'disabled': {
throw new OakUserDisabledException();
}
case 'shadow': {
assign(tokenData, {
userId: mobileRow.userId,
user: {
action: 'activate',
}
});
break;
}
default: {
assert(userState === 'normal');
assign(tokenData, {
userId: mobileRow.id,
});
}
}
throw new Error('method not implemented!');
await rowStore.operate('token', {
data: tokenData,
action: 'create',
}, context);
return tokenData.id;
}
}
else {
//此手机号不存在
if (currentToken) {
// 创建手机号并与之关联即可
const mobileData: EntityDict['mobile']['CreateSingle']['data'] = {
id: await generateNewId(),
mobile,
userId: currentToken.userId!,
};
await rowStore.operate('mobile', {
action: 'create',
data: mobileData
}, context);
return currentToken.id;
}
else {
// 创建token, mobile, user
const userData: EntityDict['user']['CreateSingle']['data'] = {
id: await generateNewId(),
userState: 'normal',
};
await rowStore.operate('user', {
action: 'create',
data: userData,
}, context);
const tokenData: EntityDict['token']['CreateSingle']['data'] = {
id: await generateNewId(),
userId: userData.id,
playerId: userData.id,
env,
mobile: {
action: 'create',
data: {
id: await generateNewId(),
mobile,
userId: userData.id,
}
}
};
await rowStore.operate('token', {
action: 'create',
data: tokenData,
}, context);
return tokenData.id;
}
}
}
export async function loginByMobile<ED extends EntityDict, Cxt extends GeneralRuntimeContext<ED>>(
params: { captcha?: string, password?: string, mobile: string, env: WebEnv | WechatMpEnv },
context: Cxt): Promise<string> {
const { mobile, captcha, password, env } = params;
const { rowStore } = context;
if (captcha) {
const { result } = await rowStore.select('captcha', {
data: {
id: 1,
expired: 1,
},
filter: {
mobile,
code: captcha,
},
sorter: [{
$attr: {
$$createAt$$: 1,
},
$direction: 'desc',
}],
indexFrom: 0,
count: 1,
}, context, { notCollect: true });
if (result.length > 0) {
const [captchaRow] = result;
if (captchaRow.expired) {
throw new OakUserException('验证码已经过期');
}
// 到这里说明验证码已经通过
return await setupMobile<ED, Cxt>(mobile, env, context);
}
else {
throw new OakUserException('验证码无效');
}
}
else {
assert(password);
throw new Error('method not implemented!');
}
}
export async function loginWechatMp<ED extends EntityDict, Cxt extends GeneralRuntimeContext<ED>>({ code, env }: {
@ -180,7 +350,7 @@ export async function loginWechatMp<ED extends EntityDict, Cxt extends GeneralRu
},
unionId,
}
}, context);
}, context);
const wechatUser2 = wechatUser3 as SelectRowShape<EntityDict['wechatUser']['Schema'], {
id: 1,
userId: 1,
@ -227,7 +397,7 @@ export async function loginWechatMp<ED extends EntityDict, Cxt extends GeneralRu
}
// 到这里都是要同时创建wechatUser和user对象了
const userData : CreateUser = {
const userData: CreateUser = {
id: await generateNewId(),
userState: 'normal',
};
@ -268,14 +438,16 @@ export async function loginWechatMp<ED extends EntityDict, Cxt extends GeneralRu
*/
export async function syncUserInfoWechatMp<ED extends EntityDict, Cxt extends GeneralRuntimeContext<ED>>({
nickname, avatarUrl, encryptedData, iv, signature
}: {nickname: string, avatarUrl: string, encryptedData: string, iv: string, signature: string}, context: Cxt) {
}: { nickname: string, avatarUrl: string, encryptedData: string, iv: string, signature: string }, context: Cxt) {
const { rowStore } = context;
const { userId } = (await context.getToken())!;
const application = (await context.getApplication())!;
const { result: [{ sessionKey, user }]} = await rowStore.select('wechatUser', {
const { result: [{ sessionKey, user }] } = await rowStore.select('wechatUser', {
data: {
id: 1,
sessionKey: 1,
nickname: 1,
avatar:1,
user: {
id: 1,
nickname: 1,
@ -358,6 +530,8 @@ export async function syncUserInfoWechatMp<ED extends EntityDict, Cxt extends Ge
}
}, context);
}
// todo update nickname/avatar in wechatUser
}
@ -372,28 +546,30 @@ export async function sendCaptcha<ED extends EntityDict, Cxt extends GeneralRunt
const { rowStore } = context;
const now = Date.now();
const [count1, count2] = await Promise.all(
[
rowStore.count('captcha', {
filter: {
visitorId,
$$createAt$$: {
$gt: now - 3600 * 1000,
if (process.env.NODE_ENV !== 'development') {
const [count1, count2] = await Promise.all(
[
rowStore.count('captcha', {
filter: {
visitorId,
$$createAt$$: {
$gt: now - 3600 * 1000,
},
},
},
}, context),
rowStore.count('captcha', {
filter: {
mobile,
$$createAt$$: {
$gt: now - 3600 * 1000,
},
}
}, context)
]
);
if (count1 > 5 || count2 > 5) {
throw new OakUserException('您已发送很多次短信,请休息会再发吧');
}, context),
rowStore.count('captcha', {
filter: {
mobile,
$$createAt$$: {
$gt: now - 3600 * 1000,
},
}
}, context)
]
);
if (count1 > 5 || count2 > 5) {
throw new OakUserException('您已发送很多次短信,请休息会再发吧');
}
}
const { result: [captcha] } = await rowStore.select('captcha', {
data: {
@ -404,7 +580,7 @@ export async function sendCaptcha<ED extends EntityDict, Cxt extends GeneralRunt
filter: {
mobile,
$$createAt$$: {
$gt: now - 600 * 1000,
$gt: now - 60 * 1000,
},
expired: false,
}
@ -433,12 +609,13 @@ export async function sendCaptcha<ED extends EntityDict, Cxt extends GeneralRunt
code += '0';
}
}
const { v1 } = require('uuid');
const id = await generateNewId();
console.log('captcha created', id);
await rowStore.operate('captcha', {
action: 'create',
data: {
id: v1(),
id,
mobile,
code,
visitorId,
@ -447,7 +624,7 @@ export async function sendCaptcha<ED extends EntityDict, Cxt extends GeneralRunt
expiresAt: now + 660 * 1000,
}
}, context);
if (process.env.NODE_ENV === 'development') {
return `验证码[${code}]已创建`;
}

View File

@ -3,6 +3,7 @@ import { Schema as User } from './User';
import { Schema as Token } from './Token';
import { EntityShape } from 'oak-domain/lib/types/Entity';
import { LocaleDef } from 'oak-domain/lib/types/Locale';
import { AbleAction, AbleState, makeAbleActionDef } from 'oak-domain/lib/actions/action';
export interface Schema extends EntityShape {
mobile: String<16>;
@ -10,12 +11,28 @@ export interface Schema extends EntityShape {
tokens: Array<Token>;
};
const locale: LocaleDef<Schema, '', '', {}> = {
type Action = AbleAction;
const AbleActionDef = makeAbleActionDef('enabled');
const locale: LocaleDef<Schema, Action, '', {
ableState: AbleState;
}> = {
zh_CN: {
attr: {
ableState: '是否可用',
mobile: '手机号',
user: '关联用户',
tokens: '相关令牌',
},
action: {
enable: '启用',
disable: '禁用',
},
v: {
ableState: {
enabled: '可用的',
disabled: '禁用的',
}
}
},
};

View File

@ -9,6 +9,7 @@ export interface Schema extends EntityShape {
name?: String<16>;
nickname?: String<64>;
password?: Text;
passwordOrigin?: Text;
birth?: Datetime;
gender?: 'male' | 'female';
avatar?: Image;
@ -84,6 +85,7 @@ const locale: LocaleDef<Schema, Action, '', {
nickname: '昵称',
birth: '生日',
password: '密码',
passwordOrigin: '明文密码',
gender: '性别',
avatar: '头像',
idCardType: '证件类型',

View File

@ -17,6 +17,8 @@ export interface Schema extends EntityShape {
user?: User;
application: Application;
tokens: Array<Token>;
nickname?: String<128>;
avatar?: Image;
};
const locale: LocaleDef<Schema, '', '', {
@ -35,6 +37,8 @@ const locale: LocaleDef<Schema, '', '', {
user: '用户',
tokens: '相关令牌',
application: '应用',
nickname: '昵称',
avatar: '头像',
},
v: {
origin: {

View File

@ -24,10 +24,11 @@ export class Token<ED extends EntityDict, Cxt extends GeneralRuntimeContext<ED>,
}
@Action
async loginByPassword(mobile: string, password: string) {
async loginByMobile(mobile: string, password?: string, captcha?: string) {
const env = await getEnv();
await this.rwLock.acquire('X');
try {
const { result } = await this.getAspectWrapper().exec('loginByPassword', { password, mobile });
const { result } = await this.getAspectWrapper().exec('loginByMobile', { password, mobile, captcha, env });
this.token = result;
this.rwLock.release();
this.context.setToken(result);
@ -154,7 +155,8 @@ export class Token<ED extends EntityDict, Cxt extends GeneralRuntimeContext<ED>,
return (tokenValue?.player?.userRole$user as any).length > 0 ? (tokenValue?.player?.userRole$user as any)[0]?.roleId === ROOT_ROLE_ID : false;
}
async sendCaptcha(mobile: string, type: 'web') {
@Action
async sendCaptcha(mobile: string) {
const env = await getEnv();
const { result } = await this.getAspectWrapper().exec('sendCaptcha', {
mobile,

View File

@ -1,4 +1,4 @@
import { OakUserException } from "oak-domain/lib/types";
import { OakUserException, makeException as makeException2 } from "oak-domain/lib/types";
export class OakUnloggedInException extends OakUserException {
constructor(message?: string) {
@ -9,4 +9,75 @@ export class OakNotEnoughMoneyException extends OakUserException {
constructor(message?: string) {
super(message || '您的余额不足');
}
};
};
export class OakDistinguishUserByWechatUserException extends OakUserException {
userId: string;
constructor(userId: string, message?: string) {
super(message || '系统中发现可能属于您的另一帐户');
this.userId = userId;
}
toString() {
return JSON.stringify({
name: this.name,
message: this.message,
userId: this.userId,
});
}
}
export class OakDistinguishUserByBusinessException extends OakUserException {
userId: string;
constructor(userId: string, message?: string) {
super(message || '系统中发现可能属于您的另一帐户');
this.userId = userId;
}
toString() {
return JSON.stringify({
name: this.name,
message: this.message,
userId: this.userId,
});
}
}
export class OakUserDisabledException extends OakUserException {
constructor(message?: string) {
super(message || '您的帐户已被禁用,请联系系统管理员');
}
}
export function makeException(data: {
name: string;
message?: string;
[A: string]: any;
}) {
const exception = makeException2(data);
if (exception) {
return exception;
}
const { name, message } = data;
switch (name) {
case OakUnloggedInException.name: {
return new OakUnloggedInException(message);
}
case OakNotEnoughMoneyException.name: {
return new OakNotEnoughMoneyException(message);
}
case OakDistinguishUserByWechatUserException.name: {
return new OakDistinguishUserByWechatUserException(data.userId, message);
}
case OakDistinguishUserByBusinessException.name: {
return new OakDistinguishUserByBusinessException(data.userId, message);
}
case OakUserDisabledException.name: {
return new OakUserDisabledException(message);
}
default: {
return;
}
}
}

View File

@ -1,22 +1,7 @@
import { EntityDict } from 'oak-app-domain';
import { SelectRowShape } from 'oak-domain/lib/types/Entity';
import { v1 } from 'uuid';
function select<T extends keyof EntityDict, P extends EntityDict[T]['Selection']['data']>(entity: T, proj: P): SelectRowShape<EntityDict[T]['Schema'], P> {
throw new Error('method not implemented');
}
const r = select('address', {
id: 1,
name: 1,
detail: 1,
area: {
id: 1,
name: 1,
},
$expr10: {
$abs: 10,
},
});
r.area.name
let iter = 0;
while( iter ++ < 20) {
console.log(v1());
}