微信公众号授权网页登录修改

This commit is contained in:
lxy 2024-09-04 11:45:56 +08:00
parent e6b59eb2a1
commit c7305421d0
33 changed files with 405 additions and 98 deletions

View File

@ -1127,7 +1127,22 @@ async function loginFromWechatEnv(code, env, context, wechatLoginId) {
// wechatUser存在直接登录
if (wechatUser) {
const tokenValue = await setUpTokenAndUser(env, context, 'wechatUser', wechatUser.id, undefined, wechatUser.user);
await updateWechatLogin({ userId: wechatUser.userId, wechatUserId: wechatUser.id, successed: true });
const wechatUserUpdateData = wechatUserData;
if (unionId !== wechatUser.unionId) {
Object.assign(wechatUserUpdateData, {
unionId,
...wechatUserData,
});
}
await context.operate('wechatUser', {
id: await generateNewIdAsync(),
action: 'update',
data: wechatUserUpdateData,
filter: {
id: wechatUser.id,
},
}, { dontCollect: true });
await updateWechatLogin({ userId: wechatUser.userId, successed: true });
return tokenValue;
}
else {

View File

@ -84,7 +84,7 @@ function QrCode(props) {
</Button>)}
{onRefresh && (<Button type="text" onClick={() => {
onRefresh();
}}>
}} size="small">
<ReloadOutlined className={`${prefixCls}-qrCodeBox_actions_refreshIcon`}/>
</Button>)}
</Space>}

View File

@ -28,7 +28,7 @@
&_refreshIcon {
color: var(--oak-color-primary);
font-size: 24px;
font-size: 16px;
}
}

View File

@ -1,5 +1,5 @@
import React from 'react';
import { Row, Col, Card, Divider, Input, Form, Space, message, } from 'antd';
import { Row, Col, Card, Divider, Input, Form, Space, Select, message, } from 'antd';
import Styles from './web.module.less';
export default function Web(props) {
const { config, setValue } = props;
@ -52,6 +52,40 @@ export default function Web(props) {
</Form>
</Col>
<Col flex="auto">
<Divider orientation="left" className={Styles.title}>
location
</Divider>
<Form colon={true} labelAlign="left" layout="vertical" style={{ marginTop: 10 }}>
<Form.Item label="协议">
<>
<Select style={{ width: '100%' }} placeholder="请选择协议" value={config?.location?.protocol} onChange={(value) => {
setValue(`location.protocol`, value);
}} options={[
{
label: 'http',
value: 'http',
},
{
label: 'https',
value: 'https',
},
]}/>
</>
</Form.Item>
<Form.Item label="主机">
<>
<Input placeholder="请输入主机" type="text" value={config?.location?.host} onChange={(e) => setValue(`location.host`, e.target.value)}/>
</>
</Form.Item>
<Form.Item label="端口">
<>
<Input placeholder="请输入端口" type="text" value={config?.location?.port} onChange={(e) => setValue(`location.port`, e.target.value || '')}/>
</>
</Form.Item>
</Form>
</Col>
{/* <Col flex="auto">
<Divider orientation="left" className={Styles.title}>
网站-授权方式

View File

@ -39,6 +39,41 @@ export default function WechatPublic(props) {
</Form.Item>)}
</Form>
</Col>
<Col flex="auto">
<Divider orientation="left" className={Styles.title}>
location
</Divider>
<Form colon={true} labelAlign="left" layout="vertical" style={{ marginTop: 10 }}>
<Form.Item label="协议">
<>
<Select style={{ width: '100%' }} placeholder="请选择协议" value={config?.location?.protocol} onChange={(value) => {
setValue(`location.protocol`, value);
}} options={[
{
label: 'http',
value: 'http',
},
{
label: 'https',
value: 'https',
},
]}/>
</>
</Form.Item>
<Form.Item label="主机">
<>
<Input placeholder="请输入主机" type="text" value={config?.location?.host} onChange={(e) => setValue(`location.host`, e.target.value)}/>
</>
</Form.Item>
<Form.Item label="端口">
<>
<Input placeholder="请输入端口" type="text" value={config?.location?.port} onChange={(e) => setValue(`location.port`, e.target.value || '')}/>
</>
</Form.Item>
</Form>
</Col>
{/* <Col flex="auto">
<Divider orientation="left" className={Styles.title}>
网站-授权方式

View File

@ -120,7 +120,7 @@ export default function Render(props) {
{Tip}
</div>}
{loginMode === 'wechatPublicForWeb' && <div className={Style['loginbox-qrcode']}>
<WechatLoginQrCodeForPublic type="login" oakPath="$login-wechatLogin/qrCode" oakAutoUnmount={true} url={state} size={200}/>
<WechatLoginQrCodeForPublic type="login" oakPath="$login-wechatLogin/qrCode" oakAutoUnmount={true} url={state} size={180}/>
{Tip}
</div>}
{InputMethods}

View File

@ -16,8 +16,7 @@ export default OakComponent({
const userId = wechatLogin?.userId;
const type = wechatLogin?.type;
const application = features.application.getApplication();
const appId = application?.config?.wechat
?.appId;
const appId = application?.config?.appId;
return {
type,
userId,
@ -42,8 +41,8 @@ export default OakComponent({
}
else {
const { appId } = this.state;
const redirectUrl = `${window.location.host}/wechatUser/login?wechatLoginId=${this.props.oakId}`;
window.location.href = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}&state=${state}&redirect_uri=${redirectUrl}&response_type=code&scope=SCOPE#wechat_redirect`;
const redirectUrl = `https%3A%2F%2F${window.location.host}%2FwechatUser%2Flogin%3FwechatLoginId%3D${this.props.oakId}`; //网址转义 `https://${window.location.host}/wechatUser/login?wechatLoginId=${this.props.oakId}`
window.location.href = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}&redirect_uri=${redirectUrl}&response_type=code&scope=snsapi_userinfo&state=${state}#wechat_redirect`;
}
}
},

View File

@ -1,6 +1,6 @@
import { EntityDict } from '../../../oak-app-domain';
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<EntityDict, keyof EntityDict, false, {
type: "login" | "bind";
type: "bind" | "login";
url: string;
size: undefined;
}>) => React.ReactElement;

View File

@ -1,4 +1,4 @@
const Interval = 2 * 60 * 1000;
const Interval = 5 * 60 * 1000;
export default OakComponent({
isList: false,
lifetimes: {
@ -64,7 +64,7 @@ export default OakComponent({
id: 1,
entity: 1,
entityId: 1,
type: 1, //类型
type: 1,
ticket: 1,
url: 1,
buffer: 1,
@ -150,5 +150,14 @@ export default OakComponent({
}
});
},
refreshQrCode() {
if (this.createTimer) {
clearInterval(this.createTimer);
}
this.createWechatLogin();
this.createTimer = setInterval(() => {
this.createWechatLogin();
}, Interval);
}
},
});

View File

@ -8,4 +8,6 @@ export default function Render(props: WebComponentProps<EntityDict, 'wechatLogin
successful: boolean;
type: EntityDict['wechatLogin']['Schema']['type'];
size: number;
}, {}>): React.JSX.Element;
}, {
refreshQrCode: () => void;
}>): React.JSX.Element;

View File

@ -2,7 +2,8 @@ import React from 'react';
import QrCode from '../../../components/common/qrCode';
export default function Render(props) {
const { oakFullpath, qrCodeUrl, loading, successful, type, size } = props.data;
const { refreshQrCode } = props.methods;
return (<div>
<QrCode loading={loading} url={qrCodeUrl} disableDownload={true} tips={<div>微信扫一扫</div>} successful={successful} type={type} size={size}/>
<QrCode loading={loading} url={qrCodeUrl} disableDownload={true} tips={<div>微信扫一扫</div>} successful={successful} type={type} size={size} onRefresh={() => refreshQrCode()}/>
</div>);
}

View File

@ -5,7 +5,7 @@ export default OakComponent({
id: 1,
entity: 1,
entityId: 1,
type: 1, //类型
type: 1,
ticket: 1,
url: 1,
expired: 1,

View File

@ -4,7 +4,7 @@ export default OakComponent({
id: 1,
entity: 1,
entityId: 1,
type: 1, //类型
type: 1,
ticket: 1,
url: 1,
buffer: 1,

View File

@ -8,6 +8,7 @@ import { composeDomainUrl } from '../utils/domain';
import { composeUrl } from 'oak-domain/lib/utils/domain';
import { createSession } from '../aspects/session';
import { getMaterial } from '../aspects/application';
import { composeLocationUrl } from '../utils/application';
const X2Js = new x2js();
function assertFromWeChat(query, config) {
const { signature, nonce, timestamp } = query;
@ -170,7 +171,7 @@ async function setUserSubscribed(openId, eventKey, context) {
const application = context.getApplication();
const { type, config, systemId } = application;
assert(type === 'wechatPublic');
const { appId, appSecret } = config;
const { appId, appSecret, location } = config;
const wechatInstance = WechatSDK.getInstance(appId, 'wechatPublic', appSecret);
const { expired } = wechatQrCode;
if (expired) {
@ -309,24 +310,40 @@ async function setUserSubscribed(openId, eventKey, context) {
// todo 公众号跳小程序 绑定login 后面再实现
}
else {
const [domain] = await context.select('domain', {
data: {
id: 1,
url: 1,
apiPath: 1,
protocol: 1,
port: 1,
},
filter: {
system: {
application$system: {
id: applicationId,
},
},
},
}, { dontCollect: true });
assert(domain, `处理wechatLogin时找不到对应的domainapplicationId是「${applicationId}`);
const url = composeDomainUrl(domain, 'wechatQrCode/scan', {
// const [domain] = await context.select(
// 'domain',
// {
// data: {
// id: 1,
// url: 1,
// apiPath: 1,
// protocol: 1,
// port: 1,
// },
// filter: {
// system: {
// application$system: {
// id: applicationId,
// },
// },
// },
// },
// { dontCollect: true }
// );
// assert(
// domain,
// `处理wechatLogin时找不到对应的domainapplicationId是「${applicationId}」`
// );
// const url = composeDomainUrl(
// domain as EntityDict['domain']['Schema'],
// 'wechatQrCode/scan',
// {
// scene: sceneStr,
// time: `${Date.now()}`,
// }
// );
//从application的config中获取前端访问地址
const url = composeLocationUrl(location, 'wechatQrCode/scan', {
scene: sceneStr,
time: `${Date.now()}`,
});

View File

@ -30,6 +30,11 @@ export type WebConfig = {
enable?: boolean;
};
passport?: Passport[];
location: {
protocol: 'http' | 'https';
host: string;
port: string;
};
};
export type WechatPublicTemplateMsgsConfig = Record<string, string>;
export type WechatPublicConfig = {
@ -51,6 +56,11 @@ export type WechatPublicConfig = {
originalId: string;
};
passport?: Passport[];
location: {
protocol: 'http' | 'https';
host: string;
port: string;
};
};
export type NativeConfig = {
type: 'native';

View File

@ -45,6 +45,11 @@ export type WebConfig = {
enable?: boolean;
};
passport?: Passport[];
location: {
protocol: "http" | "https";
host: string;
port: string;
};
};
export type WechatPublicTemplateMsgsConfig = Record<string, string>;
export type WechatPublicConfig = {
@ -66,6 +71,11 @@ export type WechatPublicConfig = {
originalId: string;
};
passport?: Passport[];
location: {
protocol: "http" | "https";
host: string;
port: string;
};
};
export type NativeConfig = {
type: "native";

6
es/utils/application.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
export type Location = {
protocol: 'http' | 'https';
host: string;
port: string;
};
export declare function composeLocationUrl(location: Location, url?: string, props?: Record<string, string>): string;

20
es/utils/application.js Normal file
View File

@ -0,0 +1,20 @@
export function composeLocationUrl(location, url, props) {
const { port, protocol, host } = location;
let Url = `${protocol}://${host}`;
if (port) {
Url += `:${port}`;
}
if (url) {
Url += url.startsWith('/') ? url : `/${url}`;
if (props) {
const k = Object.keys(props);
if (k.length > 0) {
for (const k2 of k) {
Url += Url.includes('?') ? '&' : '?';
Url += `${k2}=${encodeURI(props[k2])}`;
}
}
}
}
return Url;
}

View File

@ -1136,7 +1136,22 @@ async function loginFromWechatEnv(code, env, context, wechatLoginId) {
// wechatUser存在直接登录
if (wechatUser) {
const tokenValue = await setUpTokenAndUser(env, context, 'wechatUser', wechatUser.id, undefined, wechatUser.user);
await updateWechatLogin({ userId: wechatUser.userId, wechatUserId: wechatUser.id, successed: true });
const wechatUserUpdateData = wechatUserData;
if (unionId !== wechatUser.unionId) {
Object.assign(wechatUserUpdateData, {
unionId,
...wechatUserData,
});
}
await context.operate('wechatUser', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'update',
data: wechatUserUpdateData,
filter: {
id: wechatUser.id,
},
}, { dontCollect: true });
await updateWechatLogin({ userId: wechatUser.userId, successed: true });
return tokenValue;
}
else {

View File

@ -12,6 +12,7 @@ const domain_1 = require("../utils/domain");
const domain_2 = require("oak-domain/lib/utils/domain");
const session_1 = require("../aspects/session");
const application_1 = require("../aspects/application");
const application_2 = require("../utils/application");
const X2Js = new x2js_1.default();
function assertFromWeChat(query, config) {
const { signature, nonce, timestamp } = query;
@ -175,7 +176,7 @@ async function setUserSubscribed(openId, eventKey, context) {
const application = context.getApplication();
const { type, config, systemId } = application;
(0, assert_1.assert)(type === 'wechatPublic');
const { appId, appSecret } = config;
const { appId, appSecret, location } = config;
const wechatInstance = WechatSDK_1.default.getInstance(appId, 'wechatPublic', appSecret);
const { expired } = wechatQrCode;
if (expired) {
@ -314,24 +315,40 @@ async function setUserSubscribed(openId, eventKey, context) {
// todo 公众号跳小程序 绑定login 后面再实现
}
else {
const [domain] = await context.select('domain', {
data: {
id: 1,
url: 1,
apiPath: 1,
protocol: 1,
port: 1,
},
filter: {
system: {
application$system: {
id: applicationId,
},
},
},
}, { dontCollect: true });
(0, assert_1.assert)(domain, `处理wechatLogin时找不到对应的domainapplicationId是「${applicationId}`);
const url = (0, domain_1.composeDomainUrl)(domain, 'wechatQrCode/scan', {
// const [domain] = await context.select(
// 'domain',
// {
// data: {
// id: 1,
// url: 1,
// apiPath: 1,
// protocol: 1,
// port: 1,
// },
// filter: {
// system: {
// application$system: {
// id: applicationId,
// },
// },
// },
// },
// { dontCollect: true }
// );
// assert(
// domain,
// `处理wechatLogin时找不到对应的domainapplicationId是「${applicationId}」`
// );
// const url = composeDomainUrl(
// domain as EntityDict['domain']['Schema'],
// 'wechatQrCode/scan',
// {
// scene: sceneStr,
// time: `${Date.now()}`,
// }
// );
//从application的config中获取前端访问地址
const url = (0, application_2.composeLocationUrl)(location, 'wechatQrCode/scan', {
scene: sceneStr,
time: `${Date.now()}`,
});

View File

@ -30,6 +30,11 @@ export type WebConfig = {
enable?: boolean;
};
passport?: Passport[];
location: {
protocol: 'http' | 'https';
host: string;
port: string;
};
};
export type WechatPublicTemplateMsgsConfig = Record<string, string>;
export type WechatPublicConfig = {
@ -51,6 +56,11 @@ export type WechatPublicConfig = {
originalId: string;
};
passport?: Passport[];
location: {
protocol: 'http' | 'https';
host: string;
port: string;
};
};
export type NativeConfig = {
type: 'native';

View File

@ -45,6 +45,11 @@ export type WebConfig = {
enable?: boolean;
};
passport?: Passport[];
location: {
protocol: "http" | "https";
host: string;
port: string;
};
};
export type WechatPublicTemplateMsgsConfig = Record<string, string>;
export type WechatPublicConfig = {
@ -66,6 +71,11 @@ export type WechatPublicConfig = {
originalId: string;
};
passport?: Passport[];
location: {
protocol: "http" | "https";
host: string;
port: string;
};
};
export type NativeConfig = {
type: "native";

6
lib/utils/application.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
export type Location = {
protocol: 'http' | 'https';
host: string;
port: string;
};
export declare function composeLocationUrl(location: Location, url?: string, props?: Record<string, string>): string;

24
lib/utils/application.js Normal file
View File

@ -0,0 +1,24 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.composeLocationUrl = void 0;
function composeLocationUrl(location, url, props) {
const { port, protocol, host } = location;
let Url = `${protocol}://${host}`;
if (port) {
Url += `:${port}`;
}
if (url) {
Url += url.startsWith('/') ? url : `/${url}`;
if (props) {
const k = Object.keys(props);
if (k.length > 0) {
for (const k2 of k) {
Url += Url.includes('?') ? '&' : '?';
Url += `${k2}=${encodeURI(props[k2])}`;
}
}
}
}
return Url;
}
exports.composeLocationUrl = composeLocationUrl;

View File

@ -1546,7 +1546,22 @@ async function loginFromWechatEnv<ED extends EntityDict>(
undefined,
wechatUser.user!
);
await updateWechatLogin({ userId: wechatUser.userId, wechatUserId: wechatUser.id, successed: true });
const wechatUserUpdateData = wechatUserData;
if (unionId !== wechatUser.unionId) {
Object.assign(wechatUserUpdateData, {
unionId,
...wechatUserData,
});
}
await context.operate('wechatUser', {
id: await generateNewIdAsync(),
action: 'update',
data: wechatUserUpdateData,
filter: {
id: wechatUser.id,
},
}, { dontCollect: true });
await updateWechatLogin({ userId: wechatUser.userId, successed: true });
return tokenValue;
} else {
// 创建user和wechatUser(绑定并登录)

View File

@ -28,7 +28,7 @@
&_refreshIcon {
color: var(--oak-color-primary);
font-size: 24px;
font-size: 16px;
}
}

View File

@ -87,11 +87,11 @@ function QrCode(props: IQrCodeProps) {
title={
type === 'bind'
? features.locales.t(
'weChat-account-successfully-bound'
)
'weChat-account-successfully-bound'
)
: features.locales.t(
'weChat-authorization-login-successful'
)
'weChat-authorization-login-successful'
)
}
/>
</div>
@ -162,6 +162,7 @@ function QrCode(props: IQrCodeProps) {
onClick={() => {
onRefresh();
}}
size="small"
>
<ReloadOutlined
className={`${prefixCls}-qrCodeBox_actions_refreshIcon`}

View File

@ -271,7 +271,7 @@ export default function Render(
oakPath="$login-wechatLogin/qrCode"
oakAutoUnmount={true}
url={state}
size={200}
size={180}
/>
{Tip}
</div>}

View File

@ -1,5 +1,4 @@
import { WebConfig } from "../../../oak-app-domain/Application/Schema";
import { WechatPublicConfig } from "../../../oak-app-domain/Application/Schema";
export default OakComponent({
entity: 'wechatLogin',
@ -19,8 +18,8 @@ export default OakComponent({
const userId = wechatLogin?.userId;
const type = wechatLogin?.type;
const application = features.application.getApplication();
const appId = (application?.config as WebConfig)?.wechat
?.appId;
const appId = (application?.config as WechatPublicConfig)?.appId;
return {
type,
userId,
@ -47,8 +46,8 @@ export default OakComponent({
}
else {
const { appId } = this.state;
const redirectUrl = `${window.location.host}/wechatUser/login?wechatLoginId=${this.props.oakId}`;
window.location.href = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}&state=${state}&redirect_uri=${redirectUrl}&response_type=code&scope=SCOPE#wechat_redirect`
const redirectUrl = `https%3A%2F%2F${window.location.host}%2FwechatUser%2Flogin%3FwechatLoginId%3D${this.props.oakId}`; //网址转义 `https://${window.location.host}/wechatUser/login?wechatLoginId=${this.props.oakId}`
window.location.href = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}&redirect_uri=${redirectUrl}&response_type=code&scope=snsapi_userinfo&state=${state}#wechat_redirect`
}
}
},

View File

@ -1,6 +1,6 @@
import { EntityDict } from '../../../oak-app-domain';
const Interval = 2 * 60 * 1000;
const Interval = 5 * 60 * 1000;
export default OakComponent({
isList: false,
@ -170,5 +170,14 @@ export default OakComponent({
}
);
},
refreshQrCode() {
if ((this as any).createTimer) {
clearInterval((this as any).createTimer);
}
this.createWechatLogin();
(this as any).createTimer = setInterval(() => {
this.createWechatLogin();
}, Interval);
}
},
});

View File

@ -18,10 +18,13 @@ export default function Render(
type: EntityDict['wechatLogin']['Schema']['type'];
size: number;
},
{}
{
refreshQrCode: () => void;
}
>
) {
const { oakFullpath, qrCodeUrl, loading, successful, type, size } = props.data;
const { refreshQrCode } = props.methods;
return (
<div>
@ -33,6 +36,7 @@ export default function Render(
successful={successful}
type={type}
size={size}
onRefresh={() => refreshQrCode()}
/>
</div>
);

View File

@ -16,6 +16,7 @@ import { composeDomainUrl } from '../utils/domain';
import { composeUrl } from 'oak-domain/lib/utils/domain';
import { createSession } from '../aspects/session';
import { getMaterial } from '../aspects/application';
import { composeLocationUrl, Location } from '../utils/application';
type VerifyQuery = {
signature: string;
@ -238,7 +239,7 @@ async function setUserSubscribed(
const application = context.getApplication();
const { type, config, systemId } = application!;
assert(type === 'wechatPublic');
const { appId, appSecret } = config as WechatPublicConfig;
const { appId, appSecret, location } = config as WechatPublicConfig;
const wechatInstance = WechatSDK.getInstance(
appId,
@ -419,38 +420,49 @@ async function setUserSubscribed(
if (qrCodeType === 'wechatPublicForMp') {
// todo 公众号跳小程序 绑定login 后面再实现
} else {
const [domain] = await context.select(
'domain',
{
data: {
id: 1,
url: 1,
apiPath: 1,
protocol: 1,
port: 1,
},
filter: {
system: {
application$system: {
id: applicationId,
},
},
},
},
{ dontCollect: true }
);
assert(
domain,
`处理wechatLogin时找不到对应的domainapplicationId是「${applicationId}`
);
const url = composeDomainUrl(
domain as EntityDict['domain']['Schema'],
// const [domain] = await context.select(
// 'domain',
// {
// data: {
// id: 1,
// url: 1,
// apiPath: 1,
// protocol: 1,
// port: 1,
// },
// filter: {
// system: {
// application$system: {
// id: applicationId,
// },
// },
// },
// },
// { dontCollect: true }
// );
// assert(
// domain,
// `处理wechatLogin时找不到对应的domainapplicationId是「${applicationId}」`
// );
// const url = composeDomainUrl(
// domain as EntityDict['domain']['Schema'],
// 'wechatQrCode/scan',
// {
// scene: sceneStr,
// time: `${Date.now()}`,
// }
// );
//从application的config中获取前端访问地址
const url = composeLocationUrl(
location as Location,
'wechatQrCode/scan',
{
scene: sceneStr,
time: `${Date.now()}`,
}
);
)
const title = type === 'bind' ? '扫码绑定' : '扫码登录';
const description =
type === 'bind' ? '去绑定' : '去登录';

27
src/utils/application.ts Normal file
View File

@ -0,0 +1,27 @@
export type Location = {
protocol: 'http' | 'https';
host: string;
port: string;
}
export function composeLocationUrl(location: Location, url?: string, props?: Record<string, string>) {
const { port, protocol, host } = location;
let Url = `${protocol}://${host}`;
if (port) {
Url += `:${port}`;
}
if (url) {
Url += url.startsWith('/') ? url : `/${url}`;
if (props) {
const k = Object.keys(props);
if (k.length > 0) {
for (const k2 of k) {
Url += Url.includes('?') ? '&' : '?';
Url += `${k2}=${encodeURI(props[k2])}`;
}
}
}
}
return Url;
}