mobile-login的部分业务逻辑
This commit is contained in:
parent
c20791a4ed
commit
17a2024233
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"navigationBarTitleText": "手机号登录",
|
||||
"usingComponents": {
|
||||
"l-button": "../../../lin-ui/button/index"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
/** index.wxss **/
|
||||
@import "../../../config/styles/_base.less";
|
||||
@import "../../../config/styles/_mixins.less";
|
||||
|
||||
.page-body {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
background-color: @background-color-base;
|
||||
.safe-area-inset-bottom();
|
||||
}
|
||||
|
||||
.card {
|
||||
width: 90%;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.login-form-button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ant-tabs-nav-list {
|
||||
width: 100%;
|
||||
.ant-tabs-tab {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import { composeFileUrl } from '../../../../src/utils/extraFile';
|
||||
|
||||
export default OakPage({
|
||||
path: 'mobile:me',
|
||||
entity: 'mobile',
|
||||
projection: {
|
||||
id: 1,
|
||||
mobile: 1,
|
||||
userId: 1,
|
||||
},
|
||||
data: {
|
||||
mobile: '',
|
||||
password: '',
|
||||
captcha: '',
|
||||
},
|
||||
methods: {
|
||||
onInput(e) {
|
||||
const { dataset, value } = this.resolveInput(e);
|
||||
const{ attr } = dataset;
|
||||
this.setState({
|
||||
[attr]: value,
|
||||
});
|
||||
},
|
||||
async sendCaptcha(type: 'web') {
|
||||
const { mobile } = this.state;
|
||||
const result = await this.features.token.sendCaptcha(mobile, type);
|
||||
this.setState({
|
||||
oakError: {
|
||||
type: 'success',
|
||||
msg: result,
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
import React, { Component } from 'react';
|
||||
import { Card, Form, Input, Checkbox, Button, Tabs } from 'antd';
|
||||
import { LockOutlined, FieldNumberOutlined, MobileOutlined } from '@ant-design/icons';
|
||||
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 validMobile = isMobile(mobile);
|
||||
const validCaptcha = isCaptcha(captcha);
|
||||
const validPassword = isPassword(password);
|
||||
const allowSubmit = validMobile && (validCaptcha|| validPassword);
|
||||
|
||||
return (
|
||||
<div className='page-body'>
|
||||
<div style={{
|
||||
flex: 2,
|
||||
}} />
|
||||
<Card className="card">
|
||||
<Tabs defaultActiveKey="1" size="large" tabBarStyle={{ width: '100%' }}>
|
||||
<TabPane tab="in Password" key="1">
|
||||
<Form
|
||||
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
|
||||
value={mobile}
|
||||
type="tel"
|
||||
data-attr="mobile"
|
||||
maxLength={11}
|
||||
prefix={<MobileOutlined className="site-form-item-icon" />}
|
||||
placeholder="Mobile"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => this.onInput(e)}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="password"
|
||||
rules={[{ required: true, message: 'Please input your Password!' }]}
|
||||
>
|
||||
<Input
|
||||
allowClear
|
||||
value={password}
|
||||
data-attr="password"
|
||||
prefix={<LockOutlined className="site-form-item-icon" />}
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => this.onInput(e)}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Form.Item name="remember" valuePropName="checked" noStyle>
|
||||
<Checkbox>Remember me</Checkbox>
|
||||
</Form.Item>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
className="login-form-button"
|
||||
disabled={!allowSubmit}
|
||||
>
|
||||
Log in
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</TabPane>
|
||||
<TabPane tab="in Captcha" key="2">
|
||||
<Form
|
||||
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
|
||||
allowClear
|
||||
value={mobile}
|
||||
data-attr="mobile"
|
||||
type="tel"
|
||||
maxLength={11}
|
||||
prefix={<MobileOutlined className="site-form-item-icon" />}
|
||||
placeholder="Mobile"
|
||||
style={{ width: 'calc(100% - 65px)' }}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => this.onInput(e)}
|
||||
/>
|
||||
<Button
|
||||
type="primary"
|
||||
disabled={!validMobile}
|
||||
onClick={() => this.sendCaptcha('web')}
|
||||
>
|
||||
Send
|
||||
</Button>
|
||||
</Input.Group>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="captcha"
|
||||
rules={[{ required: true, message: 'Please input the captcha received!' }]}
|
||||
>
|
||||
<Input
|
||||
allowClear
|
||||
value={captcha}
|
||||
data-attr="captcha"
|
||||
prefix={<FieldNumberOutlined className="site-form-item-icon" />}
|
||||
type="number"
|
||||
maxLength={4}
|
||||
placeholder="Captcha"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => this.onInput(e)}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Form.Item name="remember" valuePropName="checked" noStyle>
|
||||
<Checkbox>Remember me</Checkbox>
|
||||
</Form.Item>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
className="login-form-button"
|
||||
disabled={!allowSubmit}
|
||||
>
|
||||
Log in
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</Card>
|
||||
<div style={{
|
||||
flex: 3,
|
||||
}} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<!-- index.wxml -->
|
||||
<view class="page-body">
|
||||
<block wx:if="{{mobiles && mobiles.length > 0}}">
|
||||
<view wx:for="{{mobiles}}" wx:key="index" class="card">
|
||||
<text>{{item.mobile}}</text>
|
||||
</view>
|
||||
</block>
|
||||
<block wx:else>
|
||||
<view style="flex:1; display:flex; align-items:center;justify-content:center">尚未授权手机号</view>
|
||||
</block>
|
||||
<view style="display: flex; flex: 1"></view>
|
||||
<view class="btn-box">
|
||||
<l-button open-type="getPhoneNumber" type="default" size="large" bindgetphonenumber="onRefreshMobile">
|
||||
授权手机号
|
||||
</l-button>
|
||||
</view>
|
||||
</view>
|
||||
|
|
@ -119,7 +119,7 @@ export default OakPage({
|
|||
}
|
||||
case 'web': {
|
||||
this.navigateTo({
|
||||
url: '/mobile/me'
|
||||
url: '/mobile/login'
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { WebEnv, WechatMpEnv } from "general-app-domain/Token/Schema";
|
||||
import { EntityDict } from "general-app-domain";
|
||||
import { WechatMpEnv } from "general-app-domain/Token/Schema";
|
||||
import { QiniuUploadInfo } from "oak-frontend-base/src/types/Upload";
|
||||
import { GeneralRuntimeContext } from "..";
|
||||
declare type GeneralAspectDict<ED extends EntityDict, Cxt extends GeneralRuntimeContext<ED>> = {
|
||||
|
|
@ -25,6 +25,10 @@ declare type GeneralAspectDict<ED extends EntityDict, Cxt extends GeneralRuntime
|
|||
origin: string;
|
||||
fileName: string;
|
||||
}, context: Cxt) => Promise<QiniuUploadInfo>;
|
||||
sendCaptcha: (params: {
|
||||
mobile: string;
|
||||
env: WechatMpEnv | WebEnv;
|
||||
}) => Promise<string>;
|
||||
};
|
||||
export declare type AspectDict<ED extends EntityDict, Cxt extends GeneralRuntimeContext<ED>> = GeneralAspectDict<ED, Cxt>;
|
||||
export {};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { loginByPassword, loginMp, loginWechatMp, syncUserInfoWechatMp } from './token';
|
||||
import { loginByPassword, loginMp, loginWechatMp, syncUserInfoWechatMp, sendCaptcha } from './token';
|
||||
import { getUploadInfo } from './extraFile';
|
||||
export declare const aspectDict: {
|
||||
loginByPassword: typeof loginByPassword;
|
||||
|
|
@ -6,4 +6,5 @@ export declare const aspectDict: {
|
|||
loginWechatMp: typeof loginWechatMp;
|
||||
syncUserInfoWechatMp: typeof syncUserInfoWechatMp;
|
||||
getUploadInfo: typeof getUploadInfo;
|
||||
sendCaptcha: typeof sendCaptcha;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,5 +11,6 @@ exports.aspectDict = (0, lodash_1.assign)({
|
|||
loginWechatMp: token_1.loginWechatMp,
|
||||
syncUserInfoWechatMp: token_1.syncUserInfoWechatMp,
|
||||
getUploadInfo: extraFile_1.getUploadInfo,
|
||||
sendCaptcha: token_1.sendCaptcha,
|
||||
} /* , commonAspectDict */);
|
||||
// export type AspectDict<ED extends EntityDict & BaseEntityDict> = TokenAD<ED> & CrudAD<ED>;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { GeneralRuntimeContext } from '../RuntimeContext';
|
||||
import { EntityDict } from 'general-app-domain';
|
||||
import { WechatMpEnv } from 'general-app-domain/Token/Schema';
|
||||
import { WechatMpConfig } from 'general-app-domain/Application/Schema';
|
||||
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>;
|
||||
|
|
@ -24,3 +25,7 @@ export declare function syncUserInfoWechatMp<ED extends EntityDict, Cxt extends
|
|||
iv: string;
|
||||
signature: string;
|
||||
}, context: Cxt): Promise<void>;
|
||||
export declare function sendCaptcha<ED extends EntityDict, Cxt extends GeneralRuntimeContext<ED>>({ mobile, env }: {
|
||||
mobile: string;
|
||||
env: WechatMpConfig | WebEnv;
|
||||
}, context: Cxt): Promise<string>;
|
||||
|
|
|
|||
|
|
@ -3,10 +3,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.syncUserInfoWechatMp = exports.loginWechatMp = exports.loginByPassword = exports.loginMp = void 0;
|
||||
exports.sendCaptcha = exports.syncUserInfoWechatMp = exports.loginWechatMp = exports.loginByPassword = 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");
|
||||
async function loginMp(params, context) {
|
||||
const { rowStore } = context;
|
||||
|
|
@ -323,8 +324,90 @@ async function syncUserInfoWechatMp({ nickname, avatarUrl, encryptedData, iv, si
|
|||
}
|
||||
}
|
||||
exports.syncUserInfoWechatMp = syncUserInfoWechatMp;
|
||||
/* export type AspectDict<ED extends EntityDict> = {
|
||||
loginMp: (params: { code: string }, context: GeneralRuntimeContext<ED>) => Promise<string>;
|
||||
loginByPassword: (params: { password: string, mobile: string }, context: GeneralRuntimeContext<ED>) => Promise<string>;
|
||||
};
|
||||
*/
|
||||
async function sendCaptcha({ mobile, env }, context) {
|
||||
const { type } = env;
|
||||
(0, assert_1.default)(type === 'web');
|
||||
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,
|
||||
},
|
||||
},
|
||||
}, 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: {
|
||||
id: 1,
|
||||
code: 1,
|
||||
$$createAt$$: 1,
|
||||
},
|
||||
filter: {
|
||||
mobile,
|
||||
$$createAt$$: {
|
||||
$gt: now - 600 * 1000,
|
||||
},
|
||||
expired: false,
|
||||
}
|
||||
}, context);
|
||||
if (captcha) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
const { code } = captcha;
|
||||
return `验证码[${code}]已创建`;
|
||||
}
|
||||
else if (captcha.$$createAt$$ - now < 60000) {
|
||||
throw new types_1.OakUserException('您的操作太迅捷啦,请稍等再点吧');
|
||||
}
|
||||
else {
|
||||
// todo 再次发送
|
||||
return '验证码已发送';
|
||||
}
|
||||
}
|
||||
else {
|
||||
let code;
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
code = mobile.substring(7);
|
||||
}
|
||||
else {
|
||||
code = Math.floor(Math.random() * 10000).toString();
|
||||
while (code.length < 4) {
|
||||
code += '0';
|
||||
}
|
||||
}
|
||||
const { v1 } = require('uuid');
|
||||
await rowStore.operate('captcha', {
|
||||
action: 'create',
|
||||
data: {
|
||||
id: v1(),
|
||||
mobile,
|
||||
code,
|
||||
visitorId,
|
||||
env,
|
||||
expired: false,
|
||||
expiresAt: now + 660 * 1000,
|
||||
}
|
||||
}, context);
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
return `验证码[${code}]已创建`;
|
||||
}
|
||||
else {
|
||||
return '验证码已创建';
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.sendCaptcha = sendCaptcha;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export declare function createWechatQrCode<ED extends EntityDict, T extends keyo
|
|||
lifetimeLength?: number;
|
||||
permanent?: boolean;
|
||||
props: WechatQrCodeProps;
|
||||
}, context: Cxt): Promise<Omit<Omit<import("general-app-domain/WechatQrCode/Schema").OpSchema, "entity" | "entityId" | "applicationId">, import("oak-domain/lib/types").InstinctiveAttributes> & {
|
||||
}, context: Cxt): Promise<Omit<Omit<import("general-app-domain/WechatQrCode/Schema").OpSchema, "applicationId" | "entity" | "entityId">, import("oak-domain/lib/types").InstinctiveAttributes> & {
|
||||
id: string;
|
||||
} & {
|
||||
applicationId: string;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
import { EntityShape } from 'oak-domain/lib/types/Entity';
|
||||
import { String, Text, Boolean, Datetime } from 'oak-domain/lib/types/DataType';
|
||||
export interface Schema extends EntityShape {
|
||||
mobile: String<11>;
|
||||
code: String<4>;
|
||||
visitorId: Text;
|
||||
reason?: Text;
|
||||
env: Object;
|
||||
expired: Boolean;
|
||||
expiresAt: Datetime;
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
;
|
||||
const IActionDef = {
|
||||
stm: {
|
||||
send: ['unsent', 'sending'],
|
||||
success: ['sending', 'sent'],
|
||||
fail: ['sending', 'failure'],
|
||||
},
|
||||
is: 'unsent',
|
||||
};
|
||||
const locale = {
|
||||
zh_CN: {
|
||||
attr: {
|
||||
mobile: '手机号',
|
||||
code: '验证码',
|
||||
visitorId: '用户标识',
|
||||
reason: '失败原因',
|
||||
env: '用户环境',
|
||||
expired: '是否过期',
|
||||
expiresAt: '过期时间',
|
||||
iState: '状态',
|
||||
},
|
||||
action: {
|
||||
send: '发送',
|
||||
fail: '失败',
|
||||
success: '成功',
|
||||
},
|
||||
v: {
|
||||
iState: {
|
||||
unsent: '未发送',
|
||||
sending: '发送中',
|
||||
sent: '已发送',
|
||||
failure: '已失败',
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -21,6 +21,19 @@ export declare type WechatMpEnv = {
|
|||
};
|
||||
export declare type WebEnv = {
|
||||
type: 'web';
|
||||
visitorId: string;
|
||||
platform: {
|
||||
value: string;
|
||||
};
|
||||
timezone: {
|
||||
value: string;
|
||||
};
|
||||
vendor: {
|
||||
value: string;
|
||||
};
|
||||
vendorFlavors: {
|
||||
value: string[];
|
||||
};
|
||||
};
|
||||
export declare type ServerEnv = {
|
||||
type: 'server';
|
||||
|
|
|
|||
|
|
@ -18,4 +18,5 @@ export declare class Token<ED extends EntityDict, Cxt extends GeneralRuntimeCont
|
|||
getToken(): Promise<string | undefined>;
|
||||
getUserId(): Promise<string | undefined>;
|
||||
isRoot(): Promise<boolean>;
|
||||
sendCaptcha(mobile: string, type: 'web'): Promise<string>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.Token = void 0;
|
||||
const lodash_1 = require("lodash");
|
||||
const oak_frontend_base_1 = require("oak-frontend-base");
|
||||
const concurrent_1 = require("oak-domain/lib/utils/concurrent");
|
||||
const env_1 = require("../utils/env");
|
||||
const constants_1 = require("../constants");
|
||||
class Token extends oak_frontend_base_1.Feature {
|
||||
token;
|
||||
|
|
@ -39,26 +39,10 @@ class Token extends oak_frontend_base_1.Feature {
|
|||
await this.rwLock.acquire('X');
|
||||
try {
|
||||
const { code } = await wx.login();
|
||||
const env = await wx.getSystemInfo();
|
||||
const env2 = (0, lodash_1.pick)(env, [
|
||||
'brand',
|
||||
'model',
|
||||
'pixelRatio',
|
||||
'screenWidth',
|
||||
'screenHeight',
|
||||
'windowWidth',
|
||||
'windowHeight',
|
||||
'statusBarHeight',
|
||||
'language',
|
||||
'version',
|
||||
'system',
|
||||
'platform',
|
||||
'fontSizeSetting',
|
||||
'SDKVersion'
|
||||
]);
|
||||
const env = await (0, env_1.getEnv)();
|
||||
const { result } = await this.getAspectWrapper().exec('loginWechatMp', {
|
||||
code,
|
||||
env: Object.assign(env2, { type: 'wechatMp' }),
|
||||
env: env,
|
||||
});
|
||||
this.token = result;
|
||||
this.context.setToken(result);
|
||||
|
|
@ -141,6 +125,14 @@ 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) {
|
||||
const env = await (0, env_1.getEnv)();
|
||||
const { result } = await this.getAspectWrapper().exec('sendCaptcha', {
|
||||
mobile,
|
||||
env: env,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
__decorate([
|
||||
oak_frontend_base_1.Action
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
import { getEnv } from './env.web';
|
||||
export { getEnv, };
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getEnv = void 0;
|
||||
const env_web_1 = require("./env.web");
|
||||
Object.defineProperty(exports, "getEnv", { enumerable: true, get: function () { return env_web_1.getEnv; } });
|
||||
console.log('不应该走到这里');
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* fingerprintJs当中的一些敏感项
|
||||
* @returns
|
||||
*/
|
||||
export declare function getEnv(): Promise<any>;
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getEnv = void 0;
|
||||
const fingerprintjs_1 = __importDefault(require("@fingerprintjs/fingerprintjs"));
|
||||
const lodash_1 = require("lodash");
|
||||
/**
|
||||
* fingerprintJs当中的一些敏感项
|
||||
* @returns
|
||||
*/
|
||||
async function getEnv() {
|
||||
const fp = await fingerprintjs_1.default.load();
|
||||
const result = await fp.get();
|
||||
const { visitorId, components } = result;
|
||||
return (0, lodash_1.assign)((0, lodash_1.pick)(components, [
|
||||
'platform',
|
||||
'timezone',
|
||||
'vendor',
|
||||
'vendorFlavors'
|
||||
]), {
|
||||
type: 'web',
|
||||
visitorId,
|
||||
});
|
||||
}
|
||||
exports.getEnv = getEnv;
|
||||
|
|
@ -0,0 +1 @@
|
|||
export declare function getEnv(): Promise<any>;
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getEnv = void 0;
|
||||
const lodash_1 = require("lodash");
|
||||
async function getEnv() {
|
||||
const env = await wx.getSystemInfo();
|
||||
const env2 = (0, lodash_1.pick)(env, [
|
||||
'brand',
|
||||
'model',
|
||||
'pixelRatio',
|
||||
'screenWidth',
|
||||
'screenHeight',
|
||||
'windowWidth',
|
||||
'windowHeight',
|
||||
'statusBarHeight',
|
||||
'language',
|
||||
'version',
|
||||
'system',
|
||||
'platform',
|
||||
'fontSizeSetting',
|
||||
'SDKVersion'
|
||||
]);
|
||||
return (0, lodash_1.assign)(env2, {
|
||||
type: 'wechatMp',
|
||||
});
|
||||
}
|
||||
exports.getEnv = getEnv;
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
"version": "1.0.0",
|
||||
"description": "oak框架中公共业务逻辑的实现",
|
||||
"dependencies": {
|
||||
"@fingerprintjs/fingerprintjs": "^3.3.3",
|
||||
"lodash": "^4.17.21",
|
||||
"oak-common-aspect": "file:../oak-common-aspect",
|
||||
"oak-domain": "file:../oak-domain",
|
||||
|
|
@ -40,7 +41,8 @@
|
|||
"prebuild": "ts-node ./scripts/make.ts",
|
||||
"build": "tsc",
|
||||
"get:area": "ts-node ./scripts/getAmapArea.ts",
|
||||
"clean:dir": "ts-node ./scripts/cleanDtsAndJs"
|
||||
"clean:dir": "ts-node ./scripts/cleanDtsAndJs",
|
||||
"postinstall": "npm run prebuild"
|
||||
},
|
||||
"main": "src/index"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { WebEnv, WechatMpEnv } from "general-app-domain/Token/Schema";
|
||||
import { EntityDict } from "general-app-domain";
|
||||
import { WechatMpEnv } from "general-app-domain/Token/Schema";
|
||||
import { QiniuUploadInfo } from "oak-frontend-base/src/types/Upload";
|
||||
// import { AspectDict as CommonAspectDict } from 'oak-common-aspect/src/aspectDict';
|
||||
import { GeneralRuntimeContext } from "..";
|
||||
|
|
@ -15,6 +15,10 @@ type GeneralAspectDict<ED extends EntityDict, Cxt extends GeneralRuntimeContext<
|
|||
nickname, avatarUrl, encryptedData, iv, signature
|
||||
}: {nickname: string, avatarUrl: string, encryptedData: string, iv: string, signature: string}, context: Cxt) => Promise<void>,
|
||||
getUploadInfo: (params: { origin: string, fileName: string }, context: Cxt) => Promise<QiniuUploadInfo>,
|
||||
sendCaptcha: (params: {
|
||||
mobile: string;
|
||||
env: WechatMpEnv | WebEnv;
|
||||
}) => Promise<string>,
|
||||
};
|
||||
|
||||
export type AspectDict<ED extends EntityDict, Cxt extends GeneralRuntimeContext<ED>> = GeneralAspectDict<ED, Cxt>;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { loginByPassword, loginMp, loginWechatMp, syncUserInfoWechatMp } from './token';
|
||||
import { loginByPassword, loginMp, loginWechatMp, syncUserInfoWechatMp, sendCaptcha } from './token';
|
||||
import { getUploadInfo } from './extraFile';
|
||||
// import commonAspectDict from 'oak-common-aspect';
|
||||
import { assign } from 'lodash';
|
||||
|
|
@ -8,6 +8,7 @@ export const aspectDict = assign({
|
|||
loginWechatMp,
|
||||
syncUserInfoWechatMp,
|
||||
getUploadInfo,
|
||||
sendCaptcha,
|
||||
}/* , commonAspectDict */);
|
||||
|
||||
// export type AspectDict<ED extends EntityDict & BaseEntityDict> = TokenAD<ED> & CrudAD<ED>;
|
||||
|
|
@ -3,12 +3,12 @@ import { EntityDict } from 'general-app-domain';
|
|||
import { WechatSDK } from 'oak-external-sdk';
|
||||
import assert from 'assert';
|
||||
import { WechatMpConfig } from 'general-app-domain/Application/Schema';
|
||||
import { CreateOperationData as CreateToken, WechatMpEnv } from 'general-app-domain/Token/Schema';
|
||||
import { CreateOperationData as CreateToken, WebEnv, WechatMpEnv } from 'general-app-domain/Token/Schema';
|
||||
import { CreateOperationData as CreateWechatUser } from 'general-app-domain/WechatUser/Schema';
|
||||
import { CreateOperationData as CreateUser, Schema as User } from 'general-app-domain/User/Schema';
|
||||
import { Operation as ExtraFileOperation } from 'general-app-domain/ExtraFile/Schema';
|
||||
import { assign, isEqual, keys } from 'lodash';
|
||||
import { SelectRowShape } from 'oak-domain/lib/types';
|
||||
import { OakUserException, SelectRowShape } from 'oak-domain/lib/types';
|
||||
import { composeFileUrl, decomposeFileUrl } from '../utils/extraFile';
|
||||
|
||||
export async function loginMp<ED extends EntityDict, Cxt extends GeneralRuntimeContext<ED>>(params: { code: string }, context: Cxt): Promise<string> {
|
||||
|
|
@ -360,8 +360,99 @@ export async function syncUserInfoWechatMp<ED extends EntityDict, Cxt extends Ge
|
|||
}
|
||||
}
|
||||
|
||||
/* export type AspectDict<ED extends EntityDict> = {
|
||||
loginMp: (params: { code: string }, context: GeneralRuntimeContext<ED>) => Promise<string>;
|
||||
loginByPassword: (params: { password: string, mobile: string }, context: GeneralRuntimeContext<ED>) => Promise<string>;
|
||||
};
|
||||
*/
|
||||
|
||||
export async function sendCaptcha<ED extends EntityDict, Cxt extends GeneralRuntimeContext<ED>>({ mobile, env }: {
|
||||
mobile: string;
|
||||
env: WechatMpConfig | WebEnv
|
||||
}, context: Cxt): Promise<string> {
|
||||
const { type } = env;
|
||||
|
||||
assert(type === 'web');
|
||||
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,
|
||||
},
|
||||
},
|
||||
}, 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: {
|
||||
id: 1,
|
||||
code: 1,
|
||||
$$createAt$$: 1,
|
||||
},
|
||||
filter: {
|
||||
mobile,
|
||||
$$createAt$$: {
|
||||
$gt: now - 600 * 1000,
|
||||
},
|
||||
expired: false,
|
||||
}
|
||||
}, context);
|
||||
if (captcha) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
const { code } = captcha;
|
||||
return `验证码[${code}]已创建`;
|
||||
}
|
||||
else if (captcha.$$createAt$$! as number - now < 60000) {
|
||||
throw new OakUserException('您的操作太迅捷啦,请稍等再点吧');
|
||||
}
|
||||
else {
|
||||
// todo 再次发送
|
||||
return '验证码已发送';
|
||||
}
|
||||
}
|
||||
else {
|
||||
let code: string;
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
code = mobile.substring(7);
|
||||
}
|
||||
else {
|
||||
code = Math.floor(Math.random() * 10000).toString();
|
||||
while (code.length < 4) {
|
||||
code += '0';
|
||||
}
|
||||
}
|
||||
|
||||
const { v1 } = require('uuid');
|
||||
await rowStore.operate('captcha', {
|
||||
action: 'create',
|
||||
data: {
|
||||
id: v1(),
|
||||
mobile,
|
||||
code,
|
||||
visitorId,
|
||||
env,
|
||||
expired: false,
|
||||
expiresAt: now + 660 * 1000,
|
||||
}
|
||||
}, context);
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
return `验证码[${code}]已创建`;
|
||||
}
|
||||
else {
|
||||
return '验证码已创建';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
import { EntityShape } from 'oak-domain/lib/types/Entity';
|
||||
import { String, Text, Boolean, Datetime } from 'oak-domain/lib/types/DataType';
|
||||
import { LocaleDef } from 'oak-domain/lib/types/Locale';
|
||||
import { ActionDef } from 'oak-domain/lib/types';
|
||||
|
||||
export interface Schema extends EntityShape {
|
||||
mobile: String<11>;
|
||||
code: String<4>;
|
||||
visitorId: Text;
|
||||
reason?: Text;
|
||||
env: Object;
|
||||
expired: Boolean;
|
||||
expiresAt: Datetime;
|
||||
};
|
||||
|
||||
type IState = 'unsent' | 'sending' | 'sent' | 'failure';
|
||||
type IAction = 'send' | 'success' | 'fail';
|
||||
|
||||
const IActionDef: ActionDef<IAction, IState> = {
|
||||
stm: {
|
||||
send: ['unsent', 'sending'],
|
||||
success: ['sending', 'sent'],
|
||||
fail: ['sending', 'failure'],
|
||||
},
|
||||
is: 'unsent',
|
||||
};
|
||||
|
||||
type Action = IAction;
|
||||
|
||||
const locale: LocaleDef<Schema, Action, '', {
|
||||
iState: IState,
|
||||
}> = {
|
||||
zh_CN: {
|
||||
attr: {
|
||||
mobile: '手机号',
|
||||
code: '验证码',
|
||||
visitorId: '用户标识',
|
||||
reason: '失败原因',
|
||||
env: '用户环境',
|
||||
expired: '是否过期',
|
||||
expiresAt: '过期时间',
|
||||
iState: '状态',
|
||||
},
|
||||
action: {
|
||||
send: '发送',
|
||||
fail: '失败',
|
||||
success: '成功',
|
||||
},
|
||||
v: {
|
||||
iState: {
|
||||
unsent: '未发送',
|
||||
sending: '发送中',
|
||||
sent: '已发送',
|
||||
failure: '已失败',
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -26,6 +26,19 @@ export type WechatMpEnv = {
|
|||
|
||||
export type WebEnv = {
|
||||
type: 'web',
|
||||
visitorId: string;
|
||||
platform: {
|
||||
value: string;
|
||||
};
|
||||
timezone: {
|
||||
value: string;
|
||||
};
|
||||
vendor: {
|
||||
value: string;
|
||||
};
|
||||
vendorFlavors: {
|
||||
value: string[];
|
||||
};
|
||||
};
|
||||
|
||||
export type ServerEnv = {
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ const locale: LocaleDef<Schema, Action, '', {
|
|||
ref: '介绍人',
|
||||
files: '相关文件',
|
||||
userState: '用户状态',
|
||||
idState: '身份验证状态',
|
||||
idState: '身份验证状态',
|
||||
},
|
||||
action: {
|
||||
activate: '激活',
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { pick } from 'lodash';
|
||||
import { EntityDict } from 'general-app-domain';
|
||||
import { Action, Feature } from 'oak-frontend-base';
|
||||
import { RWLock } from 'oak-domain/lib/utils/concurrent';
|
||||
import { WechatMpEnv } from 'general-app-domain/Token/Schema';
|
||||
import { WebEnv, WechatMpEnv } from 'general-app-domain/Token/Schema';
|
||||
import { Cache } from 'oak-frontend-base';
|
||||
import { CommonAspectDict } from 'oak-common-aspect';
|
||||
import { getEnv } from '../utils/env';
|
||||
import { AspectDict } from '../aspects/AspectDict';
|
||||
import { GeneralRuntimeContext } from '..';
|
||||
import { AspectWrapper, SelectRowShape } from 'oak-domain/lib/types';
|
||||
|
|
@ -43,26 +43,11 @@ export class Token<ED extends EntityDict, Cxt extends GeneralRuntimeContext<ED>,
|
|||
await this.rwLock.acquire('X');
|
||||
try {
|
||||
const { code } = await wx.login();
|
||||
const env = await wx.getSystemInfo();
|
||||
const env2 = pick(env, [
|
||||
'brand',
|
||||
'model',
|
||||
'pixelRatio',
|
||||
'screenWidth',
|
||||
'screenHeight',
|
||||
'windowWidth',
|
||||
'windowHeight',
|
||||
'statusBarHeight',
|
||||
'language',
|
||||
'version',
|
||||
'system',
|
||||
'platform',
|
||||
'fontSizeSetting',
|
||||
'SDKVersion'
|
||||
]);
|
||||
|
||||
const env = await getEnv();
|
||||
const { result } = await this.getAspectWrapper().exec('loginWechatMp', {
|
||||
code,
|
||||
env: Object.assign(env2, { type: 'wechatMp' }) as WechatMpEnv,
|
||||
env: env as WechatMpEnv,
|
||||
});
|
||||
this.token = result;
|
||||
this.context.setToken(result);
|
||||
|
|
@ -168,4 +153,13 @@ 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') {
|
||||
const env = await getEnv();
|
||||
const { result } = await this.getAspectWrapper().exec('sendCaptcha', {
|
||||
mobile,
|
||||
env: env as WebEnv,
|
||||
});
|
||||
return result as string;
|
||||
}
|
||||
}
|
||||
|
|
@ -16,7 +16,7 @@ async function checkIsRoot<ED extends EntityDict, Cxt extends GeneralRuntimeCont
|
|||
userId: playerId!,
|
||||
roleId: ROOT_ROLE_ID,
|
||||
},
|
||||
} as any, context);
|
||||
}, context);
|
||||
if (count === 0) {
|
||||
// 只有root允许扮演其他用户身份
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
import { getEnv } from './env.web';
|
||||
|
||||
export {
|
||||
getEnv,
|
||||
};
|
||||
|
||||
console.log('不应该走到这里');
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import FingerprintJS from '@fingerprintjs/fingerprintjs';
|
||||
import { WebEnv } from 'general-app-domain/Token/Schema';
|
||||
import { assign, pick } from 'lodash';
|
||||
|
||||
/**
|
||||
* fingerprintJs当中的一些敏感项
|
||||
* @returns
|
||||
*/
|
||||
export async function getEnv() {
|
||||
const fp = await FingerprintJS.load();
|
||||
const result = await fp.get();
|
||||
|
||||
const { visitorId, components } = result;
|
||||
return assign(
|
||||
pick(components, [
|
||||
'platform',
|
||||
'timezone',
|
||||
'vendor',
|
||||
'vendorFlavors'
|
||||
]), {
|
||||
type: 'web',
|
||||
visitorId,
|
||||
}) as any;
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import { WechatMpEnv } from "general-app-domain/Token/Schema";
|
||||
import { pick, assign } from "lodash";
|
||||
|
||||
export async function getEnv() {
|
||||
const env = await wx.getSystemInfo();
|
||||
const env2 = pick(env, [
|
||||
'brand',
|
||||
'model',
|
||||
'pixelRatio',
|
||||
'screenWidth',
|
||||
'screenHeight',
|
||||
'windowWidth',
|
||||
'windowHeight',
|
||||
'statusBarHeight',
|
||||
'language',
|
||||
'version',
|
||||
'system',
|
||||
'platform',
|
||||
'fontSizeSetting',
|
||||
'SDKVersion'
|
||||
]);
|
||||
return assign(env2, {
|
||||
type: 'wechatMp',
|
||||
}) as any;
|
||||
}
|
||||
Loading…
Reference in New Issue