From 606b9e2023dd1b87d684eda2809ca9f1ab5a5b8d Mon Sep 17 00:00:00 2001
From: wkj <278599135@.com>
Date: Tue, 6 May 2025 16:25:33 +0800
Subject: [PATCH] =?UTF-8?q?=E9=82=AE=E7=AE=B1=E6=94=AF=E6=8C=81=E9=85=8D?=
=?UTF-8?q?=E7=BD=AE=E5=90=8E=E7=BC=80=EF=BC=8C=E5=BD=93=E8=AE=BE=E7=BD=AE?=
=?UTF-8?q?=E5=90=8E=E7=BC=80=EF=BC=8C=E4=BC=9A=E6=A3=80=E6=9F=A5=E8=BE=93?=
=?UTF-8?q?=E5=85=A5=E7=9A=84=E9=82=AE=E7=AE=B1=E6=98=AF=E5=90=A6=E7=AC=A6?=
=?UTF-8?q?=E5=90=88=E8=A6=81=E6=B1=82?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
es/aspects/session.js | 6 +-
es/aspects/token.js | 198 +++++++++++++++-
es/aspects/wechatQrCode.js | 2 +-
es/components/article/detail/index.js | 4 +-
es/components/article/preview/index.js | 4 +-
es/components/article/treeList/index.js | 2 +-
es/components/article/upsert/index.js | 10 +-
es/components/article/upsert/web.js | 6 +-
es/components/articleMenu/treeCell/index.js | 2 +-
es/components/articleMenu/treeList/index.js | 2 +-
.../articleMenu/treeManager/index.js | 22 +-
es/components/common/errorPage/index.js | 2 +-
es/components/common/tabBar/index.js | 2 +-
es/components/config/upsert/email/index.js | 7 +-
es/components/extraFile/crop/index.js | 90 ++++----
es/components/extraFile/crop/web.js | 2 +-
es/components/extraFile/forUrl/index.js | 4 +-
es/components/extraFile/gallery/index.js | 8 +-
es/components/extraFile/upload/index.js | 28 +--
es/components/passport/email/index.js | 63 ++++-
es/components/platform/panel/index.js | 34 +++
es/components/platform/panel/web.pc.js | 2 +-
.../platform/panel/web.pc.module.less | 8 +-
es/components/platform/system/index.js | 7 +
es/components/platform/system/web.pc.js | 4 +-
es/components/session/forMessage/index.js | 2 +-
es/components/session/list/index.js | 10 +-
es/components/sessionMessage/list/index.js | 2 +-
es/components/system/panel/index.js | 12 -
es/components/system/panel/web.pc.js | 2 +-
es/components/user/login/email/index.js | 4 +-
es/components/user/login/index.js | 6 +-
es/components/user/login/password/index.js | 8 +-
es/components/user/login/sms/index.js | 8 +-
es/components/userEntityGrant/share/index.js | 2 +-
es/components/wechatLogin/qrCode/index.js | 2 +-
es/components/wechatQrCode/scan/index.js | 2 +-
es/components/wechatQrCode/share/index.js | 2 +-
es/data/i18n.js | 3 +-
es/entities/Livestream.js | 4 +-
es/entities/Passport.d.ts | 1 +
es/locales/error/zh-CN.json | 3 +-
es/oak-app-domain/Passport/_baseSchema.d.ts | 1 +
es/triggers/index.d.ts | 2 +-
es/triggers/notification.js | 2 +-
es/types/Config.d.ts | 18 +-
es/types/Email.d.ts | 11 +-
es/utils/email/node-mailer.d.ts | 5 +
es/utils/email/node-mailer.js | 48 +++-
lib/aspects/session.js | 6 +-
lib/aspects/token.js | 198 +++++++++++++++-
lib/aspects/wechatQrCode.js | 2 +-
lib/data/i18n.js | 3 +-
lib/entities/Livestream.js | 4 +-
lib/entities/Passport.d.ts | 1 +
lib/locales/error/zh-CN.json | 3 +-
lib/oak-app-domain/Passport/_baseSchema.d.ts | 1 +
lib/triggers/index.d.ts | 2 +-
lib/triggers/notification.js | 2 +-
lib/types/Config.d.ts | 18 +-
lib/types/Email.d.ts | 11 +-
lib/utils/email/node-mailer.d.ts | 5 +
lib/utils/email/node-mailer.js | 48 +++-
src/aspects/token.ts | 215 +++++++++++++++++-
src/components/config/upsert/email/index.tsx | 15 ++
src/components/passport/email/index.tsx | 106 ++++++++-
src/components/platform/panel/index.ts | 34 +++
.../platform/panel/web.pc.module.less | 8 +-
src/components/platform/panel/web.pc.tsx | 2 +-
src/components/platform/system/index.ts | 7 +
src/components/platform/system/web.pc.tsx | 2 +
src/components/system/panel/index.ts | 12 -
src/components/system/panel/web.pc.tsx | 2 +-
src/data/i18n.ts | 3 +-
src/entities/Passport.ts | 1 +
src/locales/error/zh-CN.json | 3 +-
src/types/Config.ts | 19 +-
src/types/Email.ts | 11 +-
src/utils/email/node-mailer.ts | 61 ++++-
79 files changed, 1217 insertions(+), 267 deletions(-)
diff --git a/es/aspects/session.js b/es/aspects/session.js
index 022bb1b58..53276de61 100644
--- a/es/aspects/session.js
+++ b/es/aspects/session.js
@@ -103,7 +103,7 @@ export async function createSession(params, context) {
origin: 'wechat',
type: 'image',
tag1: 'image',
- objectId: await generateNewIdAsync(),
+ objectId: await generateNewIdAsync(), // 这个域用来标识唯一性
sort: 1000,
uploadState: 'success',
extra1: data.MediaId,
@@ -128,7 +128,7 @@ export async function createSession(params, context) {
origin: 'wechat',
type: 'video',
tag1: 'video',
- objectId: await generateNewIdAsync(),
+ objectId: await generateNewIdAsync(), // 这个域用来标识唯一性
sort: 1000,
uploadState: 'success',
extra1: data.MediaId,
@@ -150,7 +150,7 @@ export async function createSession(params, context) {
origin: 'wechat',
type: 'audio',
tag1: 'audio',
- objectId: await generateNewIdAsync(),
+ objectId: await generateNewIdAsync(), // 这个域用来标识唯一性
sort: 1000,
uploadState: 'success',
extra1: data.MediaId,
diff --git a/es/aspects/token.js b/es/aspects/token.js
index 6f949a6ba..31b16ce14 100644
--- a/es/aspects/token.js
+++ b/es/aspects/token.js
@@ -476,6 +476,28 @@ export async function loginByMobile(params, context) {
}
};
const closeRootMode = context.openRootMode();
+ const application = context.getApplication();
+ const [applicationPassport] = await context.select('applicationPassport', {
+ data: {
+ id: 1,
+ passportId: 1,
+ passport: {
+ id: 1,
+ config: 1,
+ type: 1,
+ },
+ applicationId: 1,
+ },
+ filter: {
+ applicationId: application?.id,
+ passport: {
+ type: 'sms'
+ },
+ }
+ }, {
+ dontCollect: true,
+ });
+ assert(applicationPassport?.passport);
if (disableRegister) {
const [existMobile] = await context.select('mobile', {
data: {
@@ -538,6 +560,49 @@ export async function loginByAccount(params, context) {
assert(account);
const accountType = isEmail(account) ? 'email' : (isMobile(account) ? 'mobile' : 'loginName');
if (accountType === 'email') {
+ // const application = context.getApplication();
+ // const { system } = application!;
+ // const [applicationPassport] = await context.select('applicationPassport',
+ // {
+ // data: {
+ // id: 1,
+ // passportId: 1,
+ // passport: {
+ // id: 1,
+ // config: 1,
+ // type: 1,
+ // },
+ // applicationId: 1,
+ // },
+ // filter: {
+ // applicationId: application?.id!,
+ // passport: {
+ // type: 'email'
+ // },
+ // }
+ // },
+ // {
+ // dontCollect: true,
+ // }
+ // );
+ // assert(applicationPassport?.passport);
+ // const config = applicationPassport.passport.config as EmailConfig;
+ // const emailConfig = system?.config.Emails?.find((ele) => ele.account === config.account);
+ // assert(emailConfig);
+ // const emailSuffix = config.emailSuffix;
+ // // 检查邮箱后缀是否满足配置
+ // if (emailSuffix?.length! > 0) {
+ // let isValid = false;
+ // for (const suffix of emailSuffix!) {
+ // if (account.endsWith(suffix)) {
+ // isValid = true;
+ // break;
+ // }
+ // }
+ // if (!isValid) {
+ // throw new OakUserException('error::user.emailSuffixIsInvalid');
+ // }
+ // }
const existEmail = await context.select('email', {
data: {
id: 1,
@@ -822,6 +887,28 @@ export async function loginByAccount(params, context) {
}
};
const closeRootMode = context.openRootMode();
+ const application = context.getApplication();
+ const [applicationPassport] = await context.select('applicationPassport', {
+ data: {
+ id: 1,
+ passportId: 1,
+ passport: {
+ id: 1,
+ config: 1,
+ type: 1,
+ },
+ applicationId: 1,
+ },
+ filter: {
+ applicationId: application?.id,
+ passport: {
+ type: 'password'
+ },
+ }
+ }, {
+ dontCollect: true,
+ });
+ assert(applicationPassport?.passport);
const tokenValue = await loginLogic();
const [tokenInfo] = await loadTokenInfo(tokenValue, context);
const { userId } = tokenInfo;
@@ -889,6 +976,46 @@ export async function loginByEmail(params, context) {
}
};
const closeRootMode = context.openRootMode();
+ const application = context.getApplication();
+ const { system } = application;
+ const [applicationPassport] = await context.select('applicationPassport', {
+ data: {
+ id: 1,
+ passportId: 1,
+ passport: {
+ id: 1,
+ config: 1,
+ type: 1,
+ },
+ applicationId: 1,
+ },
+ filter: {
+ applicationId: application?.id,
+ passport: {
+ type: 'email'
+ },
+ }
+ }, {
+ dontCollect: true,
+ });
+ assert(applicationPassport?.passport);
+ const config = applicationPassport.passport.config;
+ const emailConfig = system?.config.Emails?.find((ele) => ele.account === config.account);
+ assert(emailConfig);
+ const emailSuffix = config.emailSuffix;
+ // 检查邮箱后缀是否满足配置
+ if (emailSuffix?.length > 0) {
+ let isValid = false;
+ for (const suffix of emailSuffix) {
+ if (email.endsWith(suffix)) {
+ isValid = true;
+ break;
+ }
+ }
+ if (!isValid) {
+ throw new OakUserException('邮箱后缀不符合要求');
+ }
+ }
if (disableRegister) {
const [existEmail] = await context.select('email', {
data: {
@@ -942,7 +1069,7 @@ export async function bindByMobile(params, context) {
throw new OakUserException('验证码已经过期');
}
// 到这里说明验证码已经通过
- //检查当前user是否已绑定mobile
+ // 检查当前user是否已绑定mobile
const [boundMobile] = await context.select('mobile', {
data: {
id: 1,
@@ -1102,6 +1229,46 @@ export async function bindByEmail(params, context) {
}
};
const closeRootMode = context.openRootMode();
+ const application = context.getApplication();
+ const { system } = application;
+ const [applicationPassport] = await context.select('applicationPassport', {
+ data: {
+ id: 1,
+ passportId: 1,
+ passport: {
+ id: 1,
+ config: 1,
+ type: 1,
+ },
+ applicationId: 1,
+ },
+ filter: {
+ applicationId: application?.id,
+ passport: {
+ type: 'email'
+ },
+ }
+ }, {
+ dontCollect: true,
+ });
+ assert(applicationPassport?.passport);
+ const config = applicationPassport.passport.config;
+ const emailConfig = system?.config.Emails?.find((ele) => ele.account === config.account);
+ assert(emailConfig);
+ const emailSuffix = config.emailSuffix;
+ // 检查邮箱后缀是否满足配置
+ if (emailSuffix?.length > 0) {
+ let isValid = false;
+ for (const suffix of emailSuffix) {
+ if (email.endsWith(suffix)) {
+ isValid = true;
+ break;
+ }
+ }
+ if (!isValid) {
+ throw new OakUserException('邮箱后缀不符合要求');
+ }
+ }
const [otherUserEmail] = await context.select('email', {
data: {
id: 1,
@@ -2026,13 +2193,28 @@ export async function sendCaptchaByEmail({ email, env, type: captchaType, }, con
const duration = config.codeDuration || 5;
const digit = config.digit || 4;
const mockSend = config.mockSend;
+ const emailSuffix = config.emailSuffix;
+ // 检查邮箱后缀是否满足配置
+ if (emailSuffix?.length > 0) {
+ let isValid = false;
+ for (const suffix of emailSuffix) {
+ if (email.endsWith(suffix)) {
+ isValid = true;
+ break;
+ }
+ }
+ if (!isValid) {
+ throw new OakUserException('邮箱后缀不符合要求');
+ }
+ }
let emailOptions = {
- host: emailConfig.host,
- port: emailConfig.port,
- account: emailConfig.account,
- password: emailConfig.password,
- subject: config.subject,
+ // host: emailConfig.host,
+ // port: emailConfig.port,
+ // secure: emailConfig.secure,
+ // account: emailConfig.account,
+ // password: emailConfig.password,
from: emailConfig.name ? `"${emailConfig.name}" <${emailConfig.account}>` : emailConfig.account,
+ subject: config.subject,
to: email,
text: config.text,
html: config.html,
@@ -2341,8 +2523,8 @@ export async function refreshToken(params, context) {
// 只有server模式去刷新token
// 'development' | 'production' | 'staging'
const intervals = {
- development: 7200 * 1000,
- staging: 600 * 1000,
+ development: 7200 * 1000, // 2小时
+ staging: 600 * 1000, // 十分钟
production: 600 * 1000, // 十分钟
};
let applicationId = token.applicationId;
diff --git a/es/aspects/wechatQrCode.js b/es/aspects/wechatQrCode.js
index 6e62e08c7..3da0ebebd 100644
--- a/es/aspects/wechatQrCode.js
+++ b/es/aspects/wechatQrCode.js
@@ -161,7 +161,7 @@ export async function createWechatQrCode(options, context) {
permanent,
url,
expired: false,
- expiresAt: Date.now() + 2592000 * 1000,
+ expiresAt: Date.now() + 2592000 * 1000, // wecharQrCode里的过期时间都放到最大,由上层关联对象来主动过期(by Xc, 20230131)
props,
};
// 直接创建
diff --git a/es/components/article/detail/index.js b/es/components/article/detail/index.js
index 53db304d0..ad432b5ae 100644
--- a/es/components/article/detail/index.js
+++ b/es/components/article/detail/index.js
@@ -23,9 +23,9 @@ export default OakComponent({
tocFixed: true,
tocPosition: 'none',
highlightBgColor: 'none',
- headerTop: 0,
+ headerTop: 0, //页面中吸顶部分高度
className: '',
- scrollId: '',
+ scrollId: '', // 滚动条所在容器id,不传默认body
tocWidth: undefined,
tocHeight: undefined,
},
diff --git a/es/components/article/preview/index.js b/es/components/article/preview/index.js
index f90ebe6ba..f3942cac1 100644
--- a/es/components/article/preview/index.js
+++ b/es/components/article/preview/index.js
@@ -28,9 +28,9 @@ export default OakComponent({
tocFixed: true,
tocPosition: 'none',
highlightBgColor: 'none',
- headerTop: 0,
+ headerTop: 0, //页面中吸顶部分高度
className: '',
- scrollId: '',
+ scrollId: '', // 滚动条所在容器id,不传默认body
tocWidth: undefined,
tocHeight: undefined,
},
diff --git a/es/components/article/treeList/index.js b/es/components/article/treeList/index.js
index 4c04d8285..dc05b4dbd 100644
--- a/es/components/article/treeList/index.js
+++ b/es/components/article/treeList/index.js
@@ -4,7 +4,7 @@ export default OakComponent({
properties: {
articleMenuId: '',
onChildEditArticleChange: (data) => undefined,
- show: 'edit',
+ show: 'edit', // edit为编辑,doc为查看,preview为预览
getBreadcrumbItemsByParent: (breadcrumbItems) => undefined,
breadcrumbItems: [],
drawerOpen: false,
diff --git a/es/components/article/upsert/index.js b/es/components/article/upsert/index.js
index 66be79471..24efb4321 100644
--- a/es/components/article/upsert/index.js
+++ b/es/components/article/upsert/index.js
@@ -25,11 +25,11 @@ export default OakComponent({
properties: {
articleMenuId: '',
changeIsEdit: () => undefined,
- tocPosition: 'none',
- highlightBgColor: 'none',
- onArticlePreview: (content, title) => undefined,
- origin: 'qiniu',
- scrollId: '',
+ tocPosition: 'none', //目录显示位置,none为不显示目录
+ highlightBgColor: 'none', //点击目录时标题高亮背景色,none为不显示高亮背景色
+ onArticlePreview: (content, title) => undefined, //预览文章
+ origin: 'qiniu', // 默认为七牛云
+ scrollId: '', // 滚动条所在容器id,不传默认页面编辑器容器id
height: 600,
},
listeners: {
diff --git a/es/components/article/upsert/web.js b/es/components/article/upsert/web.js
index 15e456e4a..7c9685d03 100644
--- a/es/components/article/upsert/web.js
+++ b/es/components/article/upsert/web.js
@@ -31,7 +31,7 @@ function customCheckImageFn(src, alt, url) {
export default function Render(props) {
const { methods, data } = props;
const { t, setEditor, check, uploadFile, update, setHtml, gotoPreview, clearContentTip, } = methods;
- const { oakFullpath, id, content, editor, origin = 'qiniu', tocPosition = 'none', highlightBgColor = 'none', scrollId, tocWidth, tocHeight, height = 600 } = data;
+ const { oakId, oakFullpath, id, content, editor, origin = 'qiniu', tocPosition = 'none', highlightBgColor = 'none', scrollId, tocWidth, tocHeight, height = 600 } = data;
const [articleId, setArticleId] = useState('');
const [toc, setToc] = useState([]);
const [showToc, setShowToc] = useState(false);
@@ -78,7 +78,7 @@ export default function Render(props) {
const filename = name.substring(0, name.lastIndexOf("."));
const extraFile = {
entity: "article",
- entityId: articleId,
+ entityId: oakId || articleId,
origin: origin,
type: "image",
tag1: "source",
@@ -109,7 +109,7 @@ export default function Render(props) {
const filename = name.substring(0, name.lastIndexOf("."));
const extraFile = {
entity: "article",
- entityId: articleId,
+ entityId: oakId || articleId,
origin: origin,
type: "video",
tag1: "source",
diff --git a/es/components/articleMenu/treeCell/index.js b/es/components/articleMenu/treeCell/index.js
index 044ee9ff6..9e2269f08 100644
--- a/es/components/articleMenu/treeCell/index.js
+++ b/es/components/articleMenu/treeCell/index.js
@@ -47,7 +47,7 @@ export default OakComponent({
onRemove: () => undefined,
onUpdateName: async (name) => undefined,
onChildEditArticleChange: (data) => undefined,
- show: 'edit',
+ show: 'edit', // edit为编辑,doc为查看,preview为预览
getBreadcrumbItemsByParent: (breadcrumbItems) => undefined,
breadItems: [],
drawerOpen: false,
diff --git a/es/components/articleMenu/treeList/index.js b/es/components/articleMenu/treeList/index.js
index 302412fd5..84ab67d6a 100644
--- a/es/components/articleMenu/treeList/index.js
+++ b/es/components/articleMenu/treeList/index.js
@@ -6,7 +6,7 @@ export default OakComponent({
entityId: '',
parentId: '',
onGrandChildEditArticleChange: (data) => undefined,
- show: 'edit',
+ show: 'edit', // edit为编辑,doc为查看,preview为预览
articleMenuId: '',
articleId: '',
getBreadcrumbItems: (breadcrumbItems) => undefined,
diff --git a/es/components/articleMenu/treeManager/index.js b/es/components/articleMenu/treeManager/index.js
index 4a0a41a48..1d64b9e88 100644
--- a/es/components/articleMenu/treeManager/index.js
+++ b/es/components/articleMenu/treeManager/index.js
@@ -14,18 +14,18 @@ export default OakComponent({
properties: {
entity: '',
entityId: '',
- show: 'edit',
- articleMenuId: '',
- articleId: '',
- tocPosition: 'none',
- highlightBgColor: 'none',
- onMenuView: () => undefined,
- onMenuViewById: (articleMenuId) => undefined,
- onArticleView: (articleId) => undefined,
- onArticlePreview: (content, title) => undefined,
- onArticleEdit: (articleId) => undefined,
+ show: 'edit', // edit为编辑,doc为查看,preview为预览
+ articleMenuId: '', // 菜单id
+ articleId: '', //文章id
+ tocPosition: 'none', //文章目录显示位置,none为不显示目录
+ highlightBgColor: 'none', //点击文章目录时标题高亮背景色,none为不显示高亮背景色
+ onMenuView: () => undefined, //查看全部菜单
+ onMenuViewById: (articleMenuId) => undefined, //查看指定id菜单
+ onArticleView: (articleId) => undefined, //查看文章
+ onArticlePreview: (content, title) => undefined, //预览文章
+ onArticleEdit: (articleId) => undefined, //编辑文章
setCopyArticleUrl: (articleId) => '',
- origin: 'qiniu',
+ origin: 'qiniu', // cos origin默认七牛云
scrollId: '', // 滚动条所在容器id,不传默认页面编辑器容器id
},
});
diff --git a/es/components/common/errorPage/index.js b/es/components/common/errorPage/index.js
index 45afa922d..8dfa687a8 100644
--- a/es/components/common/errorPage/index.js
+++ b/es/components/common/errorPage/index.js
@@ -37,7 +37,7 @@ export default OakComponent({
code: '',
title: '',
desc: '',
- icon: '',
+ icon: '', //web独有
imagePath: '', //小程序独有
},
lifetimes: {
diff --git a/es/components/common/tabBar/index.js b/es/components/common/tabBar/index.js
index bb5fb6958..ffb8ea41c 100644
--- a/es/components/common/tabBar/index.js
+++ b/es/components/common/tabBar/index.js
@@ -12,7 +12,7 @@ export default OakComponent({
color: '#666',
selectedColor: '',
border: false,
- selectedIconPath: '',
+ selectedIconPath: '', //一般在list设置
iconPath: '',
},
lifetimes: {
diff --git a/es/components/config/upsert/email/index.js b/es/components/config/upsert/email/index.js
index b1ff27188..59dff6810 100644
--- a/es/components/config/upsert/email/index.js
+++ b/es/components/config/upsert/email/index.js
@@ -1,5 +1,5 @@
import React from 'react';
-import { Tabs, Col, Divider, Input, Form, Space, Modal, DatePicker, } from 'antd';
+import { Tabs, Col, Divider, Input, Form, Space, Modal, Switch, DatePicker, } from 'antd';
import Styles from './web.module.less';
import dayjs from 'dayjs';
const { confirm } = Modal;
@@ -41,6 +41,11 @@ export default function Email(props) {
setValue(`${idx}.port`, Number(e.target.value));
}}/>
+
+ <>
+ setValue(`${idx}.secure`, checked)}/>
+ >
+
{
setValue(`${idx}.account`, e.target.value);
diff --git a/es/components/extraFile/crop/index.js b/es/components/extraFile/crop/index.js
index 125adac90..806ab742a 100644
--- a/es/components/extraFile/crop/index.js
+++ b/es/components/extraFile/crop/index.js
@@ -47,19 +47,19 @@ export default OakComponent({
bucket: '',
autoUpload: false,
maxNumber: 20,
- extension: [],
- selectCount: 1,
- sourceType: ['album', 'camera'],
- mediaType: ['image'],
- mode: 'aspectFit',
- size: 3,
- showUploadList: true,
- showUploadProgress: false,
- accept: 'image/*',
- disablePreview: false,
- disableDelete: false,
- disableAdd: false,
- disableDownload: false,
+ extension: [], //小程序独有 chooseMessageFile
+ selectCount: 1, // 每次打开图片时,可选中的数量 小程序独有
+ sourceType: ['album', 'camera'], // 小程序独有 chooseMedia
+ mediaType: ['image'], // 小程序独有 chooseMedia
+ mode: 'aspectFit', // 图片显示模式
+ size: 3, // 每行可显示的个数 小程序独有
+ showUploadList: true, //web独有
+ showUploadProgress: false, // web独有
+ accept: 'image/*', // web独有
+ disablePreview: false, // 图片是否可预览
+ disableDelete: false, // 图片是否可删除
+ disableAdd: false, // 上传按钮隐藏
+ disableDownload: false, // 下载按钮隐藏
type: 'image',
origin: 'qiniu',
tag1: '',
@@ -67,40 +67,40 @@ export default OakComponent({
entity: '',
entityId: '',
theme: 'image',
- enableCrop: false,
- enableCompross: false,
+ enableCrop: false, //启用裁剪
+ enableCompross: false, //启用压缩
//图片裁剪
- cropQuality: 1,
- showRest: false,
- showGrid: false,
- fillColor: 'white',
- rotationSlider: false,
- aspectSlider: false,
- zoomSlider: true,
- resetText: '重置',
- aspect: 1 / 1,
- minZoom: 1,
- maxZoom: 3,
- cropShape: 'rect',
- cropperProps: {},
- modalTitle: '编辑图片',
- modalWidth: '40vw',
- modalOk: '确定',
- modalCancel: '取消',
+ cropQuality: 1, //图片裁剪质量,范围:0 ~ 1
+ showRest: false, //显示重置按钮,重置缩放及旋转
+ showGrid: false, //显示裁切区域网格(九宫格)
+ fillColor: 'white', //裁切图像填充色
+ rotationSlider: false, //图片旋转控制
+ aspectSlider: false, //裁切比率控制
+ zoomSlider: true, //图片缩放控制
+ resetText: '重置', //重置按钮文字
+ aspect: 1 / 1, //裁切区域宽高比,width / height
+ minZoom: 1, //最小缩放倍数
+ maxZoom: 3, //最大缩放倍数
+ cropShape: 'rect', //裁切区域形状,'rect' 或 'round'
+ cropperProps: {}, //recat-easy-crop的props
+ modalTitle: '编辑图片', //弹窗标题
+ modalWidth: '40vw', //弹窗宽度
+ modalOk: '确定', //确定按钮文字
+ modalCancel: '取消', //取消按钮的文字
//图片压缩
- strict: true,
- checkOrientation: true,
- retainExif: false,
- maxWidth: Infinity,
- maxHeight: Infinity,
- minWidth: 0,
- minHeight: 0,
- compressWidth: undefined,
- compressHeight: undefined,
- resize: 'none',
- compressQuality: 0.8,
- mimeType: 'auto',
- convertTypes: ['image/png'],
+ strict: true, //当压缩后的图片尺寸大于原图尺寸时输出原图
+ checkOrientation: true, //读取图像的Exif方向值并自动旋转或翻转图像(仅限 JPEG 图像)
+ retainExif: false, //压缩后保留图片的Exif信息
+ maxWidth: Infinity, //输出图片的最大宽度,值需大于0
+ maxHeight: Infinity, //输出图片的最大高度,值需大于0
+ minWidth: 0, //输出图片的最小宽度,值需大于0且不应大于maxWidth
+ minHeight: 0, //输出图片的最小高度。值需大于0且不应大于maxHeight
+ compressWidth: undefined, //输出图像的宽度。如果未指定,则将使用原始图像的宽度,若设置了height,则宽度将根据自然纵横比自动计算。
+ compressHeight: undefined, //输出图像的高度。如果未指定,则将使用原始图像的高度,若设置了width,则高度将根据自然纵横比自动计算。
+ resize: 'none', //仅在同时指定了width和height时生效
+ compressQuality: 0.8, //输出图像的质量。范围:0 ~ 1
+ mimeType: 'auto', //输出图片的 MIME 类型。默认情况下,将使用源图片文件的原始 MIME 类型。
+ convertTypes: ['image/png'], //文件类型包含在其中且文件大小超过该convertSize值的文件将被转换为 JPEG。
convertSize: Infinity, //文件类型包含在convertTypes中且文件大小超过此值的文件将转换为 JPEG,Infinity表示禁用该功能
},
features: ['extraFile'],
diff --git a/es/components/extraFile/crop/web.js b/es/components/extraFile/crop/web.js
index 10b4941c9..09f15740c 100644
--- a/es/components/extraFile/crop/web.js
+++ b/es/components/extraFile/crop/web.js
@@ -13,7 +13,7 @@ export default function render(props) {
};
return (<>
{enableCrop ? (
{
diff --git a/es/components/extraFile/forUrl/index.js b/es/components/extraFile/forUrl/index.js
index 9488a37cf..cae1fb5d7 100644
--- a/es/components/extraFile/forUrl/index.js
+++ b/es/components/extraFile/forUrl/index.js
@@ -55,9 +55,9 @@ export default OakComponent({
data: {
isModalOpen: false,
isModalOpen1: false,
- renderImgs: [],
+ renderImgs: [], // 读取的原文图片,在modal使用
methodsType: '',
- bridgeUrl: '',
+ bridgeUrl: '', // 通过桥接方式获得的url
selectedId: -1,
},
properties: {
diff --git a/es/components/extraFile/gallery/index.js b/es/components/extraFile/gallery/index.js
index 7e3d08e92..aaa8f4633 100644
--- a/es/components/extraFile/gallery/index.js
+++ b/es/components/extraFile/gallery/index.js
@@ -67,10 +67,10 @@ export default OakComponent({
},
],
properties: {
- mode: 'aspectFit',
- size: 3,
- disablePreview: false,
- disableDownload: false,
+ mode: 'aspectFit', // 图片显示模式
+ size: 3, // 每行可显示的个数 小程序独有
+ disablePreview: false, // 图片是否可预览
+ disableDownload: false, // 下载按钮隐藏
tag1: '',
tag2: '',
entity: '',
diff --git a/es/components/extraFile/upload/index.js b/es/components/extraFile/upload/index.js
index 9993f92db..c61143357 100644
--- a/es/components/extraFile/upload/index.js
+++ b/es/components/extraFile/upload/index.js
@@ -50,19 +50,19 @@ export default OakComponent({
bucket: '',
autoUpload: false,
maxNumber: 20,
- extension: [],
- selectCount: 1,
- sourceType: ['album', 'camera'],
- mediaType: ['image'],
- mode: 'aspectFit',
- size: 3,
- showUploadList: true,
- showUploadProgress: false,
- accept: 'image/*',
- disablePreview: false,
- disableDelete: false,
- disableAdd: false,
- disableDownload: false,
+ extension: [], //小程序独有 chooseMessageFile
+ selectCount: 1, // 每次打开图片时,可选中的数量 小程序独有
+ sourceType: ['album', 'camera'], // 小程序独有 chooseMedia
+ mediaType: ['image'], // 小程序独有 chooseMedia
+ mode: 'aspectFit', // 图片显示模式
+ size: 3, // 每行可显示的个数 小程序独有
+ showUploadList: true, //web独有
+ showUploadProgress: false, // web独有
+ accept: 'image/*', // web独有
+ disablePreview: false, // 图片是否可预览
+ disableDelete: false, // 图片是否可删除
+ disableAdd: false, // 上传按钮隐藏
+ disableDownload: false, // 下载按钮隐藏
type: 'image',
origin: 'qiniu',
tag1: '',
@@ -147,7 +147,7 @@ export default OakComponent({
type,
tag1,
tag2,
- objectId: generateNewId(),
+ objectId: generateNewId(), // 这个域用来标识唯一性
entity,
filename,
size,
diff --git a/es/components/passport/email/index.js b/es/components/passport/email/index.js
index 1713c038b..b076732d0 100644
--- a/es/components/passport/email/index.js
+++ b/es/components/passport/email/index.js
@@ -1,10 +1,60 @@
-import React, { useEffect, useState } from "react";
-import { Switch, Alert, Typography, Form, Input, Radio, Tag } from 'antd';
+import React, { useEffect, useState, useRef } from "react";
+import { Switch, Alert, Typography, Form, Input, Radio, Tag, Tooltip, Flex } from 'antd';
import Styles from './web.module.less';
import '@wangeditor/editor/dist/css/style.css'; // 引入 css
import { Editor, Toolbar } from "@wangeditor/editor-for-react";
+import { PlusOutlined, CloseOutlined } from '@ant-design/icons';
const { TextArea } = Input;
const { Text } = Typography;
+function RenderEmailSuffix(props) {
+ const { emailSuffix, onChange, t } = props;
+ const [inputVisible, setInputVisible] = useState(false);
+ const [inputValue, setInputValue] = useState('');
+ const inputRef = useRef(null);
+ const tagInputStyle = {
+ width: 100,
+ height: 22,
+ marginInlineEnd: 8,
+ verticalAlign: 'top',
+ };
+ const tagPlusStyle = {
+ height: 22,
+ borderStyle: 'dashed',
+ };
+ const handleInputChange = (e) => {
+ setInputValue(e.target.value);
+ };
+ const handleInputConfirm = () => {
+ if (inputValue && !emailSuffix?.includes(inputValue)) {
+ onChange([...emailSuffix || [], inputValue]);
+ }
+ setInputVisible(false);
+ setInputValue('');
+ };
+ const handleClose = (removedTag) => {
+ const emailSuffix2 = emailSuffix.filter((tag) => tag !== removedTag);
+ onChange(emailSuffix2);
+ };
+ const showInput = () => {
+ setInputVisible(true);
+ };
+ return (
+ {(emailSuffix || []).map((tag, index) => {
+ const isLongTag = tag.length > 20;
+ const tagElem = (} key={tag} onClose={() => handleClose(tag)}>
+
+ {isLongTag ? `${tag.slice(0, 20)}...` : tag}
+
+ );
+ return isLongTag ? (
+ {tagElem}
+ ) : (tagElem);
+ })}
+ {inputVisible ? () : (} onClick={showInput}>
+ {t('common::action.add')}
+ )}
+ );
+}
export default function Email(props) {
const { passport, t, changeEnabled, updateConfig } = props;
const { id, type, enabled, stateColor } = passport;
@@ -16,6 +66,7 @@ export default function Email(props) {
const [html, setHtml] = useState(config?.html || '');
const [emailCodeDuration, setEmailCodeDuration] = useState(config?.codeDuration || '');
const [emailDigit, setEmailDigit] = useState(config?.digit || '');
+ const [emailSuffix, setEmailSuffix] = useState(config?.emailSuffix || []);
// editor 实例
const [editor, setEditor] = useState(null); // TS 语法
// 工具栏配置
@@ -40,6 +91,7 @@ export default function Email(props) {
setHtml(config?.html || '');
setEmailCodeDuration(config?.codeDuration || '');
setEmailDigit(config?.digit || '');
+ setEmailSuffix(config?.emailSuffix || []);
if (config?.html) {
setEContentType('html');
}
@@ -150,6 +202,13 @@ export default function Email(props) {
else {
updateConfig(id, config, 'digit', undefined, 'email');
}
+ }}/>
+
+
+ {
+ if (v !== config?.emailSuffix) {
+ updateConfig(id, config, 'emailSuffix', v, 'email');
+ }
}}/>
diff --git a/es/components/platform/panel/index.js b/es/components/platform/panel/index.js
index c50786c61..532c90565 100644
--- a/es/components/platform/panel/index.js
+++ b/es/components/platform/panel/index.js
@@ -7,6 +7,40 @@ export default OakComponent({
config: 1,
description: 1,
style: 1,
+ // system$platform: {
+ // $entity: 'system',
+ // data: {
+ // id: 1,
+ // name: 1,
+ // config: 1,
+ // description: 1,
+ // super: 1,
+ // folder: 1,
+ // platformId: 1,
+ // style: 1,
+ // domain$system: {
+ // $entity: 'domain',
+ // data: {
+ // id: 1,
+ // systemId: 1,
+ // url: 1,
+ // },
+ // },
+ // application$system: {
+ // $entity: 'application',
+ // data: {
+ // id: 1,
+ // name: 1,
+ // config: 1,
+ // description: 1,
+ // type: 1,
+ // systemId: 1,
+ // domainId: 1,
+ // style: 1,
+ // }
+ // }
+ // }
+ // }
},
formData({ data }) {
return data || {};
diff --git a/es/components/platform/panel/web.pc.js b/es/components/platform/panel/web.pc.js
index 8c925f084..79a99aa87 100644
--- a/es/components/platform/panel/web.pc.js
+++ b/es/components/platform/panel/web.pc.js
@@ -29,7 +29,7 @@ export default function render(props) {
{
label: {t('system-list')}
,
key: 'system',
- children: (),
+ children: (),
},
]}/>
);
diff --git a/es/components/platform/panel/web.pc.module.less b/es/components/platform/panel/web.pc.module.less
index bb24882e6..caa7d4881 100644
--- a/es/components/platform/panel/web.pc.module.less
+++ b/es/components/platform/panel/web.pc.module.less
@@ -1,9 +1,7 @@
.container {
- padding: 8px;
height: 100%;
- .tabLabel {
- // writing-mode: vertical-rl;
- letter-spacing: .2rem;
- }
+ // .tabLabel {
+ // letter-spacing: .2rem;
+ // }
}
\ No newline at end of file
diff --git a/es/components/platform/system/index.js b/es/components/platform/system/index.js
index ac7fc266d..940c0a56c 100644
--- a/es/components/platform/system/index.js
+++ b/es/components/platform/system/index.js
@@ -32,6 +32,13 @@ export default OakComponent({
}
}
},
+ filters: [{
+ filter() {
+ return {
+ platformId: this.props.platformId,
+ };
+ },
+ }],
properties: {
platformId: '',
},
diff --git a/es/components/platform/system/web.pc.js b/es/components/platform/system/web.pc.js
index 9171e4154..fd342e7d3 100644
--- a/es/components/platform/system/web.pc.js
+++ b/es/components/platform/system/web.pc.js
@@ -32,7 +32,9 @@ export default function render(props) {
}>
{t('confirmToRemove')}
- {
+ {
if (action === 'add') {
const id = addItem({ platformId, config: { App: {} } });
setCreateId(id);
diff --git a/es/components/session/forMessage/index.js b/es/components/session/forMessage/index.js
index 85aa30acf..e51d10291 100644
--- a/es/components/session/forMessage/index.js
+++ b/es/components/session/forMessage/index.js
@@ -20,7 +20,7 @@ export default OakComponent({
properties: {
sessionId: '',
isEntity: false,
- entityDisplay: (data) => [],
+ entityDisplay: (data) => [], // user端,指示如何显示entity对象名称
entityProjection: null, // user端,指示需要取哪些entity的属性来显示entityDisplay
},
methods: {
diff --git a/es/components/session/list/index.js b/es/components/session/list/index.js
index afd4e8a62..c22ab6988 100644
--- a/es/components/session/list/index.js
+++ b/es/components/session/list/index.js
@@ -140,12 +140,12 @@ export default OakComponent({
unSub: undefined,
},
properties: {
- entity: '',
- entityFilter: null,
+ entity: '', // entity端,指示相应的entity
+ entityFilter: null, // entity端,指示相应的entity查询条件
entityFilterSubStr: '',
- entityDisplay: (data) => [],
- entityProjection: null,
- sessionId: '',
+ entityDisplay: (data) => [], // user端,指示如何显示entity对象名称
+ entityProjection: null, // user端,指示需要取哪些entity的属性来显示entityDisplay
+ sessionId: '', // 指示需要打开的默认session
dialog: false,
onItemClick: null,
},
diff --git a/es/components/sessionMessage/list/index.js b/es/components/sessionMessage/list/index.js
index c87698fd2..608733d85 100644
--- a/es/components/sessionMessage/list/index.js
+++ b/es/components/sessionMessage/list/index.js
@@ -102,7 +102,7 @@ export default OakComponent({
dialog: false,
entity: '',
entityId: '',
- entityDisplay: (data) => [],
+ entityDisplay: (data) => [], // user端,指示如何显示entity对象名称
entityProjection: null, // user端,指示需要取哪些entity的属性来显示entityDisplay
},
filters: [
diff --git a/es/components/system/panel/index.js b/es/components/system/panel/index.js
index d55eac850..f5414402e 100644
--- a/es/components/system/panel/index.js
+++ b/es/components/system/panel/index.js
@@ -18,18 +18,6 @@ export default OakComponent({
url: 1,
},
},
- // application$system: {
- // $entity: 'application',
- // data: {
- // id: 1,
- // name: 1,
- // config: 1,
- // description: 1,
- // type: 1,
- // systemId: 1,
- // domainId: 1,
- // }
- // }
},
formData({ data }) {
return data || {};
diff --git a/es/components/system/panel/web.pc.js b/es/components/system/panel/web.pc.js
index c12a32143..7154f66e7 100644
--- a/es/components/system/panel/web.pc.js
+++ b/es/components/system/panel/web.pc.js
@@ -62,7 +62,7 @@ export default function Render(props) {
),
key: 'passport-list',
destroyInactiveTabPane: true,
- children: (),
+ children: (),
},
]}/>
);
diff --git a/es/components/user/login/email/index.js b/es/components/user/login/email/index.js
index dd10e9af6..f87f9b258 100644
--- a/es/components/user/login/email/index.js
+++ b/es/components/user/login/email/index.js
@@ -21,8 +21,8 @@ export default OakComponent({
},
properties: {
disabled: '',
- url: '',
- callback: undefined,
+ url: '', // 登录系统之后要返回的页面
+ callback: undefined, // 登录成功回调,排除微信登录方式
setLoginMode: (value) => undefined,
digit: 4, //验证码位数
},
diff --git a/es/components/user/login/index.js b/es/components/user/login/index.js
index c01994bc6..ee877f16c 100644
--- a/es/components/user/login/index.js
+++ b/es/components/user/login/index.js
@@ -17,15 +17,15 @@ export default OakComponent({
allowPassword: false,
allowWechatMp: false,
setLoginModeMp(value) { this.setLoginMode(value); },
- smsDigit: 4,
+ smsDigit: 4, //短信验证码位数
emailDigit: 4, //邮箱验证码位数
},
properties: {
onlyCaptcha: false,
onlyPassword: false,
disabled: '',
- redirectUri: '',
- url: '',
+ redirectUri: '', // 微信登录后的redirectUri,要指向wechatUser/login去处理
+ url: '', // 登录系统之后要返回的页面
callback: undefined, // 登录成功回调,排除微信登录方式
},
formData({ features, props }) {
diff --git a/es/components/user/login/password/index.js b/es/components/user/login/password/index.js
index df5ec579b..c841c6228 100644
--- a/es/components/user/login/password/index.js
+++ b/es/components/user/login/password/index.js
@@ -18,12 +18,12 @@ export default OakComponent({
},
properties: {
disabled: '',
- redirectUri: '',
- url: '',
- callback: undefined,
+ redirectUri: '', // 微信登录后的redirectUri,要指向wechatUser/login去处理
+ url: '', // 登录系统之后要返回的页面
+ callback: undefined, // 登录成功回调,排除微信登录方式
allowSms: false,
allowEmail: false,
- allowWechatMp: false,
+ allowWechatMp: false, //小程序切换授权登录
setLoginMode: (value) => undefined,
},
lifetimes: {},
diff --git a/es/components/user/login/sms/index.js b/es/components/user/login/sms/index.js
index ce3cbbaac..aa98691ba 100644
--- a/es/components/user/login/sms/index.js
+++ b/es/components/user/login/sms/index.js
@@ -21,10 +21,10 @@ export default OakComponent({
},
properties: {
disabled: '',
- url: '',
- callback: undefined,
- allowPassword: false,
- allowWechatMp: false,
+ url: '', // 登录系统之后要返回的页面
+ callback: undefined, // 登录成功回调,排除微信登录方式
+ allowPassword: false, //小程序切换密码登录
+ allowWechatMp: false, //小程序切换授权登录
setLoginMode: (value) => undefined,
digit: 4 //验证码位数,
},
diff --git a/es/components/userEntityGrant/share/index.js b/es/components/userEntityGrant/share/index.js
index ef5413e9e..66944b893 100644
--- a/es/components/userEntityGrant/share/index.js
+++ b/es/components/userEntityGrant/share/index.js
@@ -17,7 +17,7 @@ export default OakComponent({
id: 1,
entity: 1,
entityId: 1,
- type: 1,
+ type: 1, //类型
ticket: 1,
url: 1,
buffer: 1,
diff --git a/es/components/wechatLogin/qrCode/index.js b/es/components/wechatLogin/qrCode/index.js
index 79865982e..3de7a0234 100644
--- a/es/components/wechatLogin/qrCode/index.js
+++ b/es/components/wechatLogin/qrCode/index.js
@@ -64,7 +64,7 @@ export default OakComponent({
id: 1,
entity: 1,
entityId: 1,
- type: 1,
+ type: 1, //类型
ticket: 1,
url: 1,
buffer: 1,
diff --git a/es/components/wechatQrCode/scan/index.js b/es/components/wechatQrCode/scan/index.js
index 8c8c5e99f..3d25bce61 100644
--- a/es/components/wechatQrCode/scan/index.js
+++ b/es/components/wechatQrCode/scan/index.js
@@ -5,7 +5,7 @@ export default OakComponent({
id: 1,
entity: 1,
entityId: 1,
- type: 1,
+ type: 1, //类型
ticket: 1,
url: 1,
expired: 1,
diff --git a/es/components/wechatQrCode/share/index.js b/es/components/wechatQrCode/share/index.js
index 7fc6fdc2c..3da7cf99c 100644
--- a/es/components/wechatQrCode/share/index.js
+++ b/es/components/wechatQrCode/share/index.js
@@ -4,7 +4,7 @@ export default OakComponent({
id: 1,
entity: 1,
entityId: 1,
- type: 1,
+ type: 1, //类型
ticket: 1,
url: 1,
buffer: 1,
diff --git a/es/data/i18n.js b/es/data/i18n.js
index c401580b2..2840b8b8f 100644
--- a/es/data/i18n.js
+++ b/es/data/i18n.js
@@ -626,7 +626,8 @@ const i18ns = [
"loginWayDisabled": "暂不允许该登录方式",
"hasToSetPassword": "需要设置密码",
"hasToVerifyPassword": "需要验证密码",
- "cantChangeVerifiedUser": "不能修改已经验证身份的用户的敏感信息"
+ "cantChangeVerifiedUser": "不能修改已经验证身份的用户的敏感信息",
+ "emailSuffixIsInvalid": "邮箱后缀不合法"
},
"distinguishUser": "需要鉴别用户身份",
"mobileUnset": "需要先登记手机号",
diff --git a/es/entities/Livestream.js b/es/entities/Livestream.js
index bc927d8d5..4bd3382f0 100644
--- a/es/entities/Livestream.js
+++ b/es/entities/Livestream.js
@@ -3,10 +3,10 @@ export const entityDesc = {
zh_CN: {
name: '直播流',
attr: {
- title: '名称',
+ title: '名称', // 用户定义直播间名称,
streamTitle: '直播流名称',
liveonly: '活跃状态',
- hub: '直播空间名称',
+ hub: '直播空间名称', // 所属直播空间名称
entity: '所属实体',
entityId: '所属实体id',
rtmpPushUrl: '推流地址',
diff --git a/es/entities/Passport.d.ts b/es/entities/Passport.d.ts
index 62d54c047..a64415a18 100644
--- a/es/entities/Passport.d.ts
+++ b/es/entities/Passport.d.ts
@@ -18,6 +18,7 @@ export type EmailConfig = {
html?: string;
codeDuration?: number;
digit?: number;
+ emailSuffix?: string[];
};
export type PfwConfig = {
appId: string;
diff --git a/es/locales/error/zh-CN.json b/es/locales/error/zh-CN.json
index 7808c978f..ebfcbb5b6 100644
--- a/es/locales/error/zh-CN.json
+++ b/es/locales/error/zh-CN.json
@@ -11,7 +11,8 @@
"loginWayDisabled": "暂不允许该登录方式",
"hasToSetPassword": "需要设置密码",
"hasToVerifyPassword": "需要验证密码",
- "cantChangeVerifiedUser": "不能修改已经验证身份的用户的敏感信息"
+ "cantChangeVerifiedUser": "不能修改已经验证身份的用户的敏感信息",
+ "emailSuffixIsInvalid": "邮箱后缀不合法"
},
"distinguishUser": "需要鉴别用户身份",
"mobileUnset": "需要先登记手机号",
diff --git a/es/oak-app-domain/Passport/_baseSchema.d.ts b/es/oak-app-domain/Passport/_baseSchema.d.ts
index ab68caad3..5fb54201c 100644
--- a/es/oak-app-domain/Passport/_baseSchema.d.ts
+++ b/es/oak-app-domain/Passport/_baseSchema.d.ts
@@ -19,6 +19,7 @@ export type EmailConfig = {
html?: string;
codeDuration?: number;
digit?: number;
+ emailSuffix?: string[];
};
export type PfwConfig = {
appId: string;
diff --git a/es/triggers/index.d.ts b/es/triggers/index.d.ts
index fc7f50979..1d6a506c0 100644
--- a/es/triggers/index.d.ts
+++ b/es/triggers/index.d.ts
@@ -1,2 +1,2 @@
-declare const _default: (import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger>)[];
+declare const _default: (import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger>)[];
export default _default;
diff --git a/es/triggers/notification.js b/es/triggers/notification.js
index 0d40730d2..c5859ffeb 100644
--- a/es/triggers/notification.js
+++ b/es/triggers/notification.js
@@ -61,7 +61,7 @@ async function sendNotification(notification, context) {
await instance.sendSubscribedMessage({
templateId: templateId,
data: data,
- openId: data1.openId,
+ openId: data1.openId, // 在notification创建时就赋值了
page,
state: StateDict[process.env.NODE_ENV],
});
diff --git a/es/types/Config.d.ts b/es/types/Config.d.ts
index 5e8796f90..2c34d1f8f 100644
--- a/es/types/Config.d.ts
+++ b/es/types/Config.d.ts
@@ -123,6 +123,15 @@ export type TencentSmsConfig = {
defaultSignName: string;
endpoint: string;
};
+export type EmailConfig = {
+ host: string;
+ port: number;
+ account: string;
+ password: string;
+ passwordExpiredAt?: number;
+ name?: string;
+ secure?: boolean;
+};
export type QrCodeType = 'wechatMpDomainUrl' | 'wechatMpWxaCode' | 'wechatPublic' | 'wechatPublicForMp' | 'webForWechatPublic';
export type Config = {
Account?: {
@@ -165,14 +174,7 @@ export type Config = {
needUploadIDCardPhoto?: boolean;
needManualVerification?: boolean;
};
- Emails?: {
- host: string;
- port: number;
- account: string;
- password: string;
- passwordExpiredAt?: number;
- name?: string;
- }[];
+ Emails?: EmailConfig[];
Security?: {
type?: 'password';
level?: 'weak' | 'medium' | 'strong';
diff --git a/es/types/Email.d.ts b/es/types/Email.d.ts
index 7c3b03c93..12c4f5a23 100644
--- a/es/types/Email.d.ts
+++ b/es/types/Email.d.ts
@@ -26,13 +26,14 @@ interface Attachment extends AttachmentLike {
raw?: string | Buffer | AttachmentLike | undefined;
}
export type EmailOptions = {
- host: string;
- port: number;
- account: string;
- password: string;
- subject: string;
+ host?: string;
+ port?: number;
+ secure?: boolean;
+ account?: string;
+ password?: string;
from: string;
to: string;
+ subject: string;
text?: string;
html?: string;
attachments?: Attachment[];
diff --git a/es/utils/email/node-mailer.d.ts b/es/utils/email/node-mailer.d.ts
index 503f5a093..0d8d8a97b 100644
--- a/es/utils/email/node-mailer.d.ts
+++ b/es/utils/email/node-mailer.d.ts
@@ -1,8 +1,13 @@
import { EntityDict } from '../../oak-app-domain';
import { BRC } from '../../types/RuntimeCxt';
import Email, { EmailOptions } from '../../types/Email';
+import { BackendRuntimeContext } from '../../context/BackendRuntimeContext';
+import { EmailConfig } from '../../types/Config';
export default class Nodemailer implements Email {
name: string;
+ getConfig(context: BackendRuntimeContext, systemId?: string): Promise<{
+ config: EmailConfig;
+ }>;
sendEmail(options: EmailOptions, context: BRC): Promise<{
success: boolean;
error?: undefined;
diff --git a/es/utils/email/node-mailer.js b/es/utils/email/node-mailer.js
index 63242f2b7..0f6e512b9 100644
--- a/es/utils/email/node-mailer.js
+++ b/es/utils/email/node-mailer.js
@@ -1,16 +1,50 @@
//https://www.nodemailer.com/
import nodemailer from 'nodemailer';
+import { assert } from 'oak-domain/lib/utils/assert';
+import { get } from 'oak-domain/lib/utils/lodash';
export default class Nodemailer {
name = 'nodemailer';
+ async getConfig(context, systemId) {
+ let system;
+ if (systemId) {
+ [system] = await context.select('system', {
+ data: {
+ id: 1,
+ config: 1,
+ },
+ filter: {
+ id: systemId,
+ }
+ }, {
+ dontCollect: true,
+ });
+ }
+ else {
+ system = context.getApplication().system;
+ }
+ const { config: systemConfig } = system;
+ const emailConfig = get(systemConfig, 'Emails.0', {});
+ const { host, port, password, account } = emailConfig;
+ assert(host, 'host未配置');
+ assert(port, 'port未配置');
+ assert(account, 'account未配置');
+ assert(password, 'password未配置');
+ return {
+ config: emailConfig,
+ };
+ }
async sendEmail(options, context) {
- const { host, port, account, password, subject, from, text, html, to, attachments } = options;
+ const { subject, from, text, html, to, attachments } = options;
+ const { config, } = await this.getConfig(context);
+ const transport = Object.assign(config, options);
+ const _from = from || (config.name ? `"${config.name}" <${config.account}>` : config.account);
const transporter = nodemailer.createTransport({
- host,
- port,
- secure: port === 465,
+ host: transport.host,
+ port: transport.port,
+ secure: transport.secure,
auth: {
- user: account,
- pass: password,
+ user: transport.account,
+ pass: transport.password,
},
});
async function verifyTransporter() {
@@ -31,7 +65,7 @@ export default class Nodemailer {
console.log('Server is ready to take our messages');
}
let mailOptions = {
- from,
+ from: _from,
to,
subject,
text,
diff --git a/lib/aspects/session.js b/lib/aspects/session.js
index 0f5331d91..6b8e992f5 100644
--- a/lib/aspects/session.js
+++ b/lib/aspects/session.js
@@ -106,7 +106,7 @@ async function createSession(params, context) {
origin: 'wechat',
type: 'image',
tag1: 'image',
- objectId: await (0, uuid_1.generateNewIdAsync)(),
+ objectId: await (0, uuid_1.generateNewIdAsync)(), // 这个域用来标识唯一性
sort: 1000,
uploadState: 'success',
extra1: data.MediaId,
@@ -131,7 +131,7 @@ async function createSession(params, context) {
origin: 'wechat',
type: 'video',
tag1: 'video',
- objectId: await (0, uuid_1.generateNewIdAsync)(),
+ objectId: await (0, uuid_1.generateNewIdAsync)(), // 这个域用来标识唯一性
sort: 1000,
uploadState: 'success',
extra1: data.MediaId,
@@ -153,7 +153,7 @@ async function createSession(params, context) {
origin: 'wechat',
type: 'audio',
tag1: 'audio',
- objectId: await (0, uuid_1.generateNewIdAsync)(),
+ objectId: await (0, uuid_1.generateNewIdAsync)(), // 这个域用来标识唯一性
sort: 1000,
uploadState: 'success',
extra1: data.MediaId,
diff --git a/lib/aspects/token.js b/lib/aspects/token.js
index 3c959c940..307170803 100644
--- a/lib/aspects/token.js
+++ b/lib/aspects/token.js
@@ -480,6 +480,28 @@ async function loginByMobile(params, context) {
}
};
const closeRootMode = context.openRootMode();
+ const application = context.getApplication();
+ const [applicationPassport] = await context.select('applicationPassport', {
+ data: {
+ id: 1,
+ passportId: 1,
+ passport: {
+ id: 1,
+ config: 1,
+ type: 1,
+ },
+ applicationId: 1,
+ },
+ filter: {
+ applicationId: application?.id,
+ passport: {
+ type: 'sms'
+ },
+ }
+ }, {
+ dontCollect: true,
+ });
+ (0, assert_1.assert)(applicationPassport?.passport);
if (disableRegister) {
const [existMobile] = await context.select('mobile', {
data: {
@@ -544,6 +566,49 @@ async function loginByAccount(params, context) {
(0, assert_1.assert)(account);
const accountType = (0, validator_1.isEmail)(account) ? 'email' : ((0, validator_1.isMobile)(account) ? 'mobile' : 'loginName');
if (accountType === 'email') {
+ // const application = context.getApplication();
+ // const { system } = application!;
+ // const [applicationPassport] = await context.select('applicationPassport',
+ // {
+ // data: {
+ // id: 1,
+ // passportId: 1,
+ // passport: {
+ // id: 1,
+ // config: 1,
+ // type: 1,
+ // },
+ // applicationId: 1,
+ // },
+ // filter: {
+ // applicationId: application?.id!,
+ // passport: {
+ // type: 'email'
+ // },
+ // }
+ // },
+ // {
+ // dontCollect: true,
+ // }
+ // );
+ // assert(applicationPassport?.passport);
+ // const config = applicationPassport.passport.config as EmailConfig;
+ // const emailConfig = system?.config.Emails?.find((ele) => ele.account === config.account);
+ // assert(emailConfig);
+ // const emailSuffix = config.emailSuffix;
+ // // 检查邮箱后缀是否满足配置
+ // if (emailSuffix?.length! > 0) {
+ // let isValid = false;
+ // for (const suffix of emailSuffix!) {
+ // if (account.endsWith(suffix)) {
+ // isValid = true;
+ // break;
+ // }
+ // }
+ // if (!isValid) {
+ // throw new OakUserException('error::user.emailSuffixIsInvalid');
+ // }
+ // }
const existEmail = await context.select('email', {
data: {
id: 1,
@@ -828,6 +893,28 @@ async function loginByAccount(params, context) {
}
};
const closeRootMode = context.openRootMode();
+ const application = context.getApplication();
+ const [applicationPassport] = await context.select('applicationPassport', {
+ data: {
+ id: 1,
+ passportId: 1,
+ passport: {
+ id: 1,
+ config: 1,
+ type: 1,
+ },
+ applicationId: 1,
+ },
+ filter: {
+ applicationId: application?.id,
+ passport: {
+ type: 'password'
+ },
+ }
+ }, {
+ dontCollect: true,
+ });
+ (0, assert_1.assert)(applicationPassport?.passport);
const tokenValue = await loginLogic();
const [tokenInfo] = await loadTokenInfo(tokenValue, context);
const { userId } = tokenInfo;
@@ -896,6 +983,46 @@ async function loginByEmail(params, context) {
}
};
const closeRootMode = context.openRootMode();
+ const application = context.getApplication();
+ const { system } = application;
+ const [applicationPassport] = await context.select('applicationPassport', {
+ data: {
+ id: 1,
+ passportId: 1,
+ passport: {
+ id: 1,
+ config: 1,
+ type: 1,
+ },
+ applicationId: 1,
+ },
+ filter: {
+ applicationId: application?.id,
+ passport: {
+ type: 'email'
+ },
+ }
+ }, {
+ dontCollect: true,
+ });
+ (0, assert_1.assert)(applicationPassport?.passport);
+ const config = applicationPassport.passport.config;
+ const emailConfig = system?.config.Emails?.find((ele) => ele.account === config.account);
+ (0, assert_1.assert)(emailConfig);
+ const emailSuffix = config.emailSuffix;
+ // 检查邮箱后缀是否满足配置
+ if (emailSuffix?.length > 0) {
+ let isValid = false;
+ for (const suffix of emailSuffix) {
+ if (email.endsWith(suffix)) {
+ isValid = true;
+ break;
+ }
+ }
+ if (!isValid) {
+ throw new types_1.OakUserException('邮箱后缀不符合要求');
+ }
+ }
if (disableRegister) {
const [existEmail] = await context.select('email', {
data: {
@@ -950,7 +1077,7 @@ async function bindByMobile(params, context) {
throw new types_1.OakUserException('验证码已经过期');
}
// 到这里说明验证码已经通过
- //检查当前user是否已绑定mobile
+ // 检查当前user是否已绑定mobile
const [boundMobile] = await context.select('mobile', {
data: {
id: 1,
@@ -1111,6 +1238,46 @@ async function bindByEmail(params, context) {
}
};
const closeRootMode = context.openRootMode();
+ const application = context.getApplication();
+ const { system } = application;
+ const [applicationPassport] = await context.select('applicationPassport', {
+ data: {
+ id: 1,
+ passportId: 1,
+ passport: {
+ id: 1,
+ config: 1,
+ type: 1,
+ },
+ applicationId: 1,
+ },
+ filter: {
+ applicationId: application?.id,
+ passport: {
+ type: 'email'
+ },
+ }
+ }, {
+ dontCollect: true,
+ });
+ (0, assert_1.assert)(applicationPassport?.passport);
+ const config = applicationPassport.passport.config;
+ const emailConfig = system?.config.Emails?.find((ele) => ele.account === config.account);
+ (0, assert_1.assert)(emailConfig);
+ const emailSuffix = config.emailSuffix;
+ // 检查邮箱后缀是否满足配置
+ if (emailSuffix?.length > 0) {
+ let isValid = false;
+ for (const suffix of emailSuffix) {
+ if (email.endsWith(suffix)) {
+ isValid = true;
+ break;
+ }
+ }
+ if (!isValid) {
+ throw new types_1.OakUserException('邮箱后缀不符合要求');
+ }
+ }
const [otherUserEmail] = await context.select('email', {
data: {
id: 1,
@@ -2043,13 +2210,28 @@ async function sendCaptchaByEmail({ email, env, type: captchaType, }, context) {
const duration = config.codeDuration || 5;
const digit = config.digit || 4;
const mockSend = config.mockSend;
+ const emailSuffix = config.emailSuffix;
+ // 检查邮箱后缀是否满足配置
+ if (emailSuffix?.length > 0) {
+ let isValid = false;
+ for (const suffix of emailSuffix) {
+ if (email.endsWith(suffix)) {
+ isValid = true;
+ break;
+ }
+ }
+ if (!isValid) {
+ throw new types_1.OakUserException('邮箱后缀不符合要求');
+ }
+ }
let emailOptions = {
- host: emailConfig.host,
- port: emailConfig.port,
- account: emailConfig.account,
- password: emailConfig.password,
- subject: config.subject,
+ // host: emailConfig.host,
+ // port: emailConfig.port,
+ // secure: emailConfig.secure,
+ // account: emailConfig.account,
+ // password: emailConfig.password,
from: emailConfig.name ? `"${emailConfig.name}" <${emailConfig.account}>` : emailConfig.account,
+ subject: config.subject,
to: email,
text: config.text,
html: config.html,
@@ -2363,8 +2545,8 @@ async function refreshToken(params, context) {
// 只有server模式去刷新token
// 'development' | 'production' | 'staging'
const intervals = {
- development: 7200 * 1000,
- staging: 600 * 1000,
+ development: 7200 * 1000, // 2小时
+ staging: 600 * 1000, // 十分钟
production: 600 * 1000, // 十分钟
};
let applicationId = token.applicationId;
diff --git a/lib/aspects/wechatQrCode.js b/lib/aspects/wechatQrCode.js
index 02dcaf68e..ab0fc4df0 100644
--- a/lib/aspects/wechatQrCode.js
+++ b/lib/aspects/wechatQrCode.js
@@ -165,7 +165,7 @@ async function createWechatQrCode(options, context) {
permanent,
url,
expired: false,
- expiresAt: Date.now() + 2592000 * 1000,
+ expiresAt: Date.now() + 2592000 * 1000, // wecharQrCode里的过期时间都放到最大,由上层关联对象来主动过期(by Xc, 20230131)
props,
};
// 直接创建
diff --git a/lib/data/i18n.js b/lib/data/i18n.js
index b69070228..f24353d01 100644
--- a/lib/data/i18n.js
+++ b/lib/data/i18n.js
@@ -628,7 +628,8 @@ const i18ns = [
"loginWayDisabled": "暂不允许该登录方式",
"hasToSetPassword": "需要设置密码",
"hasToVerifyPassword": "需要验证密码",
- "cantChangeVerifiedUser": "不能修改已经验证身份的用户的敏感信息"
+ "cantChangeVerifiedUser": "不能修改已经验证身份的用户的敏感信息",
+ "emailSuffixIsInvalid": "邮箱后缀不合法"
},
"distinguishUser": "需要鉴别用户身份",
"mobileUnset": "需要先登记手机号",
diff --git a/lib/entities/Livestream.js b/lib/entities/Livestream.js
index b8849e4a3..729547ff8 100644
--- a/lib/entities/Livestream.js
+++ b/lib/entities/Livestream.js
@@ -6,10 +6,10 @@ exports.entityDesc = {
zh_CN: {
name: '直播流',
attr: {
- title: '名称',
+ title: '名称', // 用户定义直播间名称,
streamTitle: '直播流名称',
liveonly: '活跃状态',
- hub: '直播空间名称',
+ hub: '直播空间名称', // 所属直播空间名称
entity: '所属实体',
entityId: '所属实体id',
rtmpPushUrl: '推流地址',
diff --git a/lib/entities/Passport.d.ts b/lib/entities/Passport.d.ts
index 62d54c047..a64415a18 100644
--- a/lib/entities/Passport.d.ts
+++ b/lib/entities/Passport.d.ts
@@ -18,6 +18,7 @@ export type EmailConfig = {
html?: string;
codeDuration?: number;
digit?: number;
+ emailSuffix?: string[];
};
export type PfwConfig = {
appId: string;
diff --git a/lib/locales/error/zh-CN.json b/lib/locales/error/zh-CN.json
index 7808c978f..ebfcbb5b6 100644
--- a/lib/locales/error/zh-CN.json
+++ b/lib/locales/error/zh-CN.json
@@ -11,7 +11,8 @@
"loginWayDisabled": "暂不允许该登录方式",
"hasToSetPassword": "需要设置密码",
"hasToVerifyPassword": "需要验证密码",
- "cantChangeVerifiedUser": "不能修改已经验证身份的用户的敏感信息"
+ "cantChangeVerifiedUser": "不能修改已经验证身份的用户的敏感信息",
+ "emailSuffixIsInvalid": "邮箱后缀不合法"
},
"distinguishUser": "需要鉴别用户身份",
"mobileUnset": "需要先登记手机号",
diff --git a/lib/oak-app-domain/Passport/_baseSchema.d.ts b/lib/oak-app-domain/Passport/_baseSchema.d.ts
index ab68caad3..5fb54201c 100644
--- a/lib/oak-app-domain/Passport/_baseSchema.d.ts
+++ b/lib/oak-app-domain/Passport/_baseSchema.d.ts
@@ -19,6 +19,7 @@ export type EmailConfig = {
html?: string;
codeDuration?: number;
digit?: number;
+ emailSuffix?: string[];
};
export type PfwConfig = {
appId: string;
diff --git a/lib/triggers/index.d.ts b/lib/triggers/index.d.ts
index fc7f50979..1d6a506c0 100644
--- a/lib/triggers/index.d.ts
+++ b/lib/triggers/index.d.ts
@@ -1,2 +1,2 @@
-declare const _default: (import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger>)[];
+declare const _default: (import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger> | import("oak-domain/lib/types").Trigger>)[];
export default _default;
diff --git a/lib/triggers/notification.js b/lib/triggers/notification.js
index 594d30e40..ca7df6315 100644
--- a/lib/triggers/notification.js
+++ b/lib/triggers/notification.js
@@ -64,7 +64,7 @@ async function sendNotification(notification, context) {
await instance.sendSubscribedMessage({
templateId: templateId,
data: data,
- openId: data1.openId,
+ openId: data1.openId, // 在notification创建时就赋值了
page,
state: StateDict[process.env.NODE_ENV],
});
diff --git a/lib/types/Config.d.ts b/lib/types/Config.d.ts
index 5e8796f90..2c34d1f8f 100644
--- a/lib/types/Config.d.ts
+++ b/lib/types/Config.d.ts
@@ -123,6 +123,15 @@ export type TencentSmsConfig = {
defaultSignName: string;
endpoint: string;
};
+export type EmailConfig = {
+ host: string;
+ port: number;
+ account: string;
+ password: string;
+ passwordExpiredAt?: number;
+ name?: string;
+ secure?: boolean;
+};
export type QrCodeType = 'wechatMpDomainUrl' | 'wechatMpWxaCode' | 'wechatPublic' | 'wechatPublicForMp' | 'webForWechatPublic';
export type Config = {
Account?: {
@@ -165,14 +174,7 @@ export type Config = {
needUploadIDCardPhoto?: boolean;
needManualVerification?: boolean;
};
- Emails?: {
- host: string;
- port: number;
- account: string;
- password: string;
- passwordExpiredAt?: number;
- name?: string;
- }[];
+ Emails?: EmailConfig[];
Security?: {
type?: 'password';
level?: 'weak' | 'medium' | 'strong';
diff --git a/lib/types/Email.d.ts b/lib/types/Email.d.ts
index 7c3b03c93..12c4f5a23 100644
--- a/lib/types/Email.d.ts
+++ b/lib/types/Email.d.ts
@@ -26,13 +26,14 @@ interface Attachment extends AttachmentLike {
raw?: string | Buffer | AttachmentLike | undefined;
}
export type EmailOptions = {
- host: string;
- port: number;
- account: string;
- password: string;
- subject: string;
+ host?: string;
+ port?: number;
+ secure?: boolean;
+ account?: string;
+ password?: string;
from: string;
to: string;
+ subject: string;
text?: string;
html?: string;
attachments?: Attachment[];
diff --git a/lib/utils/email/node-mailer.d.ts b/lib/utils/email/node-mailer.d.ts
index 503f5a093..0d8d8a97b 100644
--- a/lib/utils/email/node-mailer.d.ts
+++ b/lib/utils/email/node-mailer.d.ts
@@ -1,8 +1,13 @@
import { EntityDict } from '../../oak-app-domain';
import { BRC } from '../../types/RuntimeCxt';
import Email, { EmailOptions } from '../../types/Email';
+import { BackendRuntimeContext } from '../../context/BackendRuntimeContext';
+import { EmailConfig } from '../../types/Config';
export default class Nodemailer implements Email {
name: string;
+ getConfig(context: BackendRuntimeContext, systemId?: string): Promise<{
+ config: EmailConfig;
+ }>;
sendEmail(options: EmailOptions, context: BRC): Promise<{
success: boolean;
error?: undefined;
diff --git a/lib/utils/email/node-mailer.js b/lib/utils/email/node-mailer.js
index acb5f51eb..62b6babf3 100644
--- a/lib/utils/email/node-mailer.js
+++ b/lib/utils/email/node-mailer.js
@@ -3,17 +3,51 @@ Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
//https://www.nodemailer.com/
const nodemailer_1 = tslib_1.__importDefault(require("nodemailer"));
+const assert_1 = require("oak-domain/lib/utils/assert");
+const lodash_1 = require("oak-domain/lib/utils/lodash");
class Nodemailer {
name = 'nodemailer';
+ async getConfig(context, systemId) {
+ let system;
+ if (systemId) {
+ [system] = await context.select('system', {
+ data: {
+ id: 1,
+ config: 1,
+ },
+ filter: {
+ id: systemId,
+ }
+ }, {
+ dontCollect: true,
+ });
+ }
+ else {
+ system = context.getApplication().system;
+ }
+ const { config: systemConfig } = system;
+ const emailConfig = (0, lodash_1.get)(systemConfig, 'Emails.0', {});
+ const { host, port, password, account } = emailConfig;
+ (0, assert_1.assert)(host, 'host未配置');
+ (0, assert_1.assert)(port, 'port未配置');
+ (0, assert_1.assert)(account, 'account未配置');
+ (0, assert_1.assert)(password, 'password未配置');
+ return {
+ config: emailConfig,
+ };
+ }
async sendEmail(options, context) {
- const { host, port, account, password, subject, from, text, html, to, attachments } = options;
+ const { subject, from, text, html, to, attachments } = options;
+ const { config, } = await this.getConfig(context);
+ const transport = Object.assign(config, options);
+ const _from = from || (config.name ? `"${config.name}" <${config.account}>` : config.account);
const transporter = nodemailer_1.default.createTransport({
- host,
- port,
- secure: port === 465,
+ host: transport.host,
+ port: transport.port,
+ secure: transport.secure,
auth: {
- user: account,
- pass: password,
+ user: transport.account,
+ pass: transport.password,
},
});
async function verifyTransporter() {
@@ -34,7 +68,7 @@ class Nodemailer {
console.log('Server is ready to take our messages');
}
let mailOptions = {
- from,
+ from: _from,
to,
subject,
text,
diff --git a/src/aspects/token.ts b/src/aspects/token.ts
index f99d6da6d..c79d13edb 100644
--- a/src/aspects/token.ts
+++ b/src/aspects/token.ts
@@ -650,6 +650,32 @@ export async function loginByMobile(
};
const closeRootMode = context.openRootMode();
+ const application = context.getApplication();
+
+ const [applicationPassport] = await context.select('applicationPassport',
+ {
+ data: {
+ id: 1,
+ passportId: 1,
+ passport: {
+ id: 1,
+ config: 1,
+ type: 1,
+ },
+ applicationId: 1,
+ },
+ filter: {
+ applicationId: application?.id!,
+ passport: {
+ type: 'sms'
+ },
+ }
+ },
+ {
+ dontCollect: true,
+ }
+ );
+ assert(applicationPassport?.passport);
if (disableRegister) {
const [existMobile] = await context.select(
'mobile',
@@ -736,6 +762,50 @@ export async function loginByAccount(
assert(account);
const accountType = isEmail(account) ? 'email' : (isMobile(account) ? 'mobile' : 'loginName');
if (accountType === 'email') {
+ // const application = context.getApplication();
+ // const { system } = application!;
+ // const [applicationPassport] = await context.select('applicationPassport',
+ // {
+ // data: {
+ // id: 1,
+ // passportId: 1,
+ // passport: {
+ // id: 1,
+ // config: 1,
+ // type: 1,
+ // },
+ // applicationId: 1,
+ // },
+ // filter: {
+ // applicationId: application?.id!,
+ // passport: {
+ // type: 'email'
+ // },
+ // }
+ // },
+ // {
+ // dontCollect: true,
+ // }
+ // );
+ // assert(applicationPassport?.passport);
+ // const config = applicationPassport.passport.config as EmailConfig;
+ // const emailConfig = system?.config.Emails?.find((ele) => ele.account === config.account);
+ // assert(emailConfig);
+ // const emailSuffix = config.emailSuffix;
+
+ // // 检查邮箱后缀是否满足配置
+ // if (emailSuffix?.length! > 0) {
+ // let isValid = false;
+ // for (const suffix of emailSuffix!) {
+ // if (account.endsWith(suffix)) {
+ // isValid = true;
+ // break;
+ // }
+ // }
+ // if (!isValid) {
+ // throw new OakUserException('error::user.emailSuffixIsInvalid');
+ // }
+ // }
const existEmail = await context.select(
'email',
{
@@ -1057,6 +1127,31 @@ export async function loginByAccount(
}
};
const closeRootMode = context.openRootMode();
+ const application = context.getApplication();
+ const [applicationPassport] = await context.select('applicationPassport',
+ {
+ data: {
+ id: 1,
+ passportId: 1,
+ passport: {
+ id: 1,
+ config: 1,
+ type: 1,
+ },
+ applicationId: 1,
+ },
+ filter: {
+ applicationId: application?.id!,
+ passport: {
+ type: 'password'
+ },
+ }
+ },
+ {
+ dontCollect: true,
+ }
+ );
+ assert(applicationPassport?.passport);
const tokenValue = await loginLogic();
const [ tokenInfo ] = await loadTokenInfo(tokenValue, context);
@@ -1145,6 +1240,51 @@ export async function loginByEmail(
}
};
const closeRootMode = context.openRootMode();
+ const application = context.getApplication();
+ const { system } = application!;
+ const [applicationPassport] = await context.select('applicationPassport',
+ {
+ data: {
+ id: 1,
+ passportId: 1,
+ passport: {
+ id: 1,
+ config: 1,
+ type: 1,
+ },
+ applicationId: 1,
+ },
+ filter: {
+ applicationId: application?.id!,
+ passport: {
+ type: 'email'
+ },
+ }
+ },
+ {
+ dontCollect: true,
+ }
+ );
+ assert(applicationPassport?.passport);
+ const config = applicationPassport.passport.config as EmailConfig;
+ const emailConfig = system?.config.Emails?.find((ele) => ele.account === config.account);
+ assert(emailConfig);
+ const emailSuffix = config.emailSuffix;
+
+ // 检查邮箱后缀是否满足配置
+ if (emailSuffix?.length! > 0) {
+ let isValid = false;
+ for (const suffix of emailSuffix!) {
+ if (email.endsWith(suffix)) {
+ isValid = true;
+ break;
+ }
+ }
+
+ if (!isValid) {
+ throw new OakUserException('邮箱后缀不符合要求')
+ }
+ }
if (disableRegister) {
const [existEmail] = await context.select(
'email',
@@ -1217,7 +1357,7 @@ export async function bindByMobile(
}
// 到这里说明验证码已经通过
- //检查当前user是否已绑定mobile
+ // 检查当前user是否已绑定mobile
const [boundMobile] = await context.select(
'mobile',
{
@@ -1423,6 +1563,52 @@ export async function bindByEmail(
const closeRootMode = context.openRootMode();
+ const application = context.getApplication();
+ const { system } = application!;
+ const [applicationPassport] = await context.select('applicationPassport',
+ {
+ data: {
+ id: 1,
+ passportId: 1,
+ passport: {
+ id: 1,
+ config: 1,
+ type: 1,
+ },
+ applicationId: 1,
+ },
+ filter: {
+ applicationId: application?.id!,
+ passport: {
+ type: 'email'
+ },
+ }
+ },
+ {
+ dontCollect: true,
+ }
+ );
+ assert(applicationPassport?.passport);
+ const config = applicationPassport.passport.config as EmailConfig;
+ const emailConfig = system?.config.Emails?.find((ele) => ele.account === config.account);
+ assert(emailConfig);
+ const emailSuffix = config.emailSuffix;
+
+ // 检查邮箱后缀是否满足配置
+ if (emailSuffix?.length! > 0) {
+ let isValid = false;
+ for (const suffix of emailSuffix!) {
+ if (email.endsWith(suffix)) {
+ isValid = true;
+ break;
+ }
+ }
+
+ if (!isValid) {
+ throw new OakUserException('邮箱后缀不符合要求')
+ }
+ }
+
const [otherUserEmail] = await context.select(
'email',
{
@@ -2714,14 +2900,31 @@ export async function sendCaptchaByEmail(
const duration = config.codeDuration || 5;
const digit = config.digit || 4;
const mockSend = config.mockSend;
+ const emailSuffix = config.emailSuffix;
+
+ // 检查邮箱后缀是否满足配置
+ if (emailSuffix?.length! > 0) {
+ let isValid = false;
+ for (const suffix of emailSuffix!) {
+ if (email.endsWith(suffix)) {
+ isValid = true;
+ break;
+ }
+ }
+
+ if (!isValid) {
+ throw new OakUserException('邮箱后缀不符合要求')
+ }
+ }
let emailOptions: EmailOptions = {
- host: emailConfig.host,
- port: emailConfig.port,
- account: emailConfig.account,
- password: emailConfig.password,
- subject: config.subject,
+ // host: emailConfig.host,
+ // port: emailConfig.port,
+ // secure: emailConfig.secure,
+ // account: emailConfig.account,
+ // password: emailConfig.password,
from: emailConfig.name ? `"${emailConfig.name}" <${emailConfig.account}>` : emailConfig.account,
+ subject: config.subject,
to: email,
text: config.text,
html: config.html,
diff --git a/src/components/config/upsert/email/index.tsx b/src/components/config/upsert/email/index.tsx
index e52b34362..26b4b00bc 100644
--- a/src/components/config/upsert/email/index.tsx
+++ b/src/components/config/upsert/email/index.tsx
@@ -95,6 +95,21 @@ export default function Email(props: {
}}
/>
+
+ <>
+
+ setValue(`${idx}.secure`, checked)
+ }
+ />
+ >
+
diff --git a/src/components/passport/email/index.tsx b/src/components/passport/email/index.tsx
index 1e0d3b19b..5965e7b6b 100644
--- a/src/components/passport/email/index.tsx
+++ b/src/components/passport/email/index.tsx
@@ -1,15 +1,101 @@
-import React, { useEffect, useState } from "react";
+import React, { useEffect, useState, useRef } from "react";
import { EmailConfig, MfwConfig, PfwConfig, SmsConfig } from "../../../entities/Passport";
import { EntityDict } from "../../../oak-app-domain";
-import { Space, Switch, Alert, Typography, Form, Input, Radio, Tag, Select, Tooltip } from 'antd';
+import { Space, Switch, Alert, Typography, Form, Input, Radio, Tag, Select, Tooltip, InputRef, Flex } from 'antd';
import Styles from './web.module.less';
import '@wangeditor/editor/dist/css/style.css'; // 引入 css
import { Editor, Toolbar } from "@wangeditor/editor-for-react";
import { IDomEditor, IEditorConfig, IToolbarConfig } from '@wangeditor/editor';
+import { PlusOutlined, CloseOutlined } from '@ant-design/icons';
const { TextArea } = Input;
const { Text } = Typography;
+function RenderEmailSuffix(props: {
+ emailSuffix?: string[];
+ onChange: (v: string[]) => void;
+ t: (k: string) => string;
+}) {
+ const { emailSuffix, onChange, t } = props;
+ const [inputVisible, setInputVisible] = useState(false);
+ const [inputValue, setInputValue] = useState('');
+ const inputRef = useRef(null);
+ const tagInputStyle: React.CSSProperties = {
+ width: 100,
+ height: 22,
+ marginInlineEnd: 8,
+ verticalAlign: 'top',
+ };
+
+ const tagPlusStyle: React.CSSProperties = {
+ height: 22,
+ borderStyle: 'dashed',
+ };
+
+
+ const handleInputChange = (e: React.ChangeEvent) => {
+ setInputValue(e.target.value);
+ };
+
+ const handleInputConfirm = () => {
+ if (inputValue && !emailSuffix?.includes(inputValue)) {
+ onChange([...emailSuffix || [], inputValue]);
+ }
+ setInputVisible(false);
+ setInputValue('');
+ };
+
+ const handleClose = (removedTag: string) => {
+ const emailSuffix2 = emailSuffix!.filter((tag) => tag !== removedTag);
+ onChange(emailSuffix2);
+ };
+ const showInput = () => {
+ setInputVisible(true);
+ };
+
+ return (
+
+ {(emailSuffix || []).map((tag, index) => {
+ const isLongTag = tag.length > 20;
+ const tagElem = (
+ }
+ key={tag}
+ onClose={() => handleClose(tag)}
+ >
+
+ {isLongTag ? `${tag.slice(0, 20)}...` : tag}
+
+
+ );
+ return isLongTag ? (
+
+ {tagElem}
+
+ ) : (
+ tagElem
+ );
+ })}
+ {inputVisible ? (
+
+ ) : (
+ } onClick={showInput}>
+ {t('common::action.add')}
+
+ )}
+
+ );
+}
+
export default function Email(props: {
passport: EntityDict['passport']['OpSchema'] & { stateColor: string };
t: (k: string, params?: any) => string;
@@ -26,6 +112,7 @@ export default function Email(props: {
const [html, setHtml] = useState(config?.html || '');
const [emailCodeDuration, setEmailCodeDuration] = useState(config?.codeDuration || '');
const [emailDigit, setEmailDigit] = useState(config?.digit || '');
+ const [emailSuffix, setEmailSuffix] = useState(config?.emailSuffix || []);
// editor 实例
const [editor, setEditor] = useState(null) // TS 语法
@@ -54,6 +141,7 @@ export default function Email(props: {
setHtml(config?.html || '');
setEmailCodeDuration(config?.codeDuration || '');
setEmailDigit(config?.digit || '');
+ setEmailSuffix(config?.emailSuffix || []);
if (config?.html) {
setEContentType('html');
} else {
@@ -255,6 +343,20 @@ export default function Email(props: {
}}
/>
+
+ {
+ if (v !== (config as EmailConfig)?.emailSuffix) {
+ updateConfig(id, config!, 'emailSuffix', v, 'email');
+ }
+ }}
+ />
+
}
diff --git a/src/components/platform/panel/index.ts b/src/components/platform/panel/index.ts
index c50786c61..532c90565 100644
--- a/src/components/platform/panel/index.ts
+++ b/src/components/platform/panel/index.ts
@@ -7,6 +7,40 @@ export default OakComponent({
config: 1,
description: 1,
style: 1,
+ // system$platform: {
+ // $entity: 'system',
+ // data: {
+ // id: 1,
+ // name: 1,
+ // config: 1,
+ // description: 1,
+ // super: 1,
+ // folder: 1,
+ // platformId: 1,
+ // style: 1,
+ // domain$system: {
+ // $entity: 'domain',
+ // data: {
+ // id: 1,
+ // systemId: 1,
+ // url: 1,
+ // },
+ // },
+ // application$system: {
+ // $entity: 'application',
+ // data: {
+ // id: 1,
+ // name: 1,
+ // config: 1,
+ // description: 1,
+ // type: 1,
+ // systemId: 1,
+ // domainId: 1,
+ // style: 1,
+ // }
+ // }
+ // }
+ // }
},
formData({ data }) {
return data || {};
diff --git a/src/components/platform/panel/web.pc.module.less b/src/components/platform/panel/web.pc.module.less
index bb24882e6..caa7d4881 100644
--- a/src/components/platform/panel/web.pc.module.less
+++ b/src/components/platform/panel/web.pc.module.less
@@ -1,9 +1,7 @@
.container {
- padding: 8px;
height: 100%;
- .tabLabel {
- // writing-mode: vertical-rl;
- letter-spacing: .2rem;
- }
+ // .tabLabel {
+ // letter-spacing: .2rem;
+ // }
}
\ No newline at end of file
diff --git a/src/components/platform/panel/web.pc.tsx b/src/components/platform/panel/web.pc.tsx
index dc2705ac6..27f6034fc 100644
--- a/src/components/platform/panel/web.pc.tsx
+++ b/src/components/platform/panel/web.pc.tsx
@@ -64,7 +64,7 @@ export default function render(props: WebComponentProps
),
diff --git a/src/components/platform/system/index.ts b/src/components/platform/system/index.ts
index e24a1bd3d..0de3b522c 100644
--- a/src/components/platform/system/index.ts
+++ b/src/components/platform/system/index.ts
@@ -32,6 +32,13 @@ export default OakComponent({
}
}
},
+ filters: [{
+ filter() {
+ return {
+ platformId: this.props.platformId,
+ };
+ },
+ }],
properties: {
platformId: '',
},
diff --git a/src/components/platform/system/web.pc.tsx b/src/components/platform/system/web.pc.tsx
index 42d8d14bd..7fa38ecb2 100644
--- a/src/components/platform/system/web.pc.tsx
+++ b/src/components/platform/system/web.pc.tsx
@@ -66,6 +66,7 @@ export default function render(props: WebComponentProps
{
if (action === 'add') {
const id = addItem({ platformId, config: { App: {} } });
@@ -83,6 +84,7 @@ export default function render(props: WebComponentProps
diff --git a/src/data/i18n.ts b/src/data/i18n.ts
index 44ef54a18..ce40bd118 100644
--- a/src/data/i18n.ts
+++ b/src/data/i18n.ts
@@ -628,7 +628,8 @@ const i18ns: I18n[] = [
"loginWayDisabled": "暂不允许该登录方式",
"hasToSetPassword": "需要设置密码",
"hasToVerifyPassword": "需要验证密码",
- "cantChangeVerifiedUser": "不能修改已经验证身份的用户的敏感信息"
+ "cantChangeVerifiedUser": "不能修改已经验证身份的用户的敏感信息",
+ "emailSuffixIsInvalid": "邮箱后缀不合法"
},
"distinguishUser": "需要鉴别用户身份",
"mobileUnset": "需要先登记手机号",
diff --git a/src/entities/Passport.ts b/src/entities/Passport.ts
index 899b69168..d61778a2e 100644
--- a/src/entities/Passport.ts
+++ b/src/entities/Passport.ts
@@ -20,6 +20,7 @@ export type EmailConfig = {
html?: string;
codeDuration?: number; //验证码有效时间 单位分钟, 不填5分钟
digit?: number; //验证码位数 4~8,默认为4位
+ emailSuffix?: string[] //邮箱后缀,不填则不校验
};
export type PfwConfig = {
diff --git a/src/locales/error/zh-CN.json b/src/locales/error/zh-CN.json
index afc7f3f2b..c3f85bb6c 100644
--- a/src/locales/error/zh-CN.json
+++ b/src/locales/error/zh-CN.json
@@ -11,7 +11,8 @@
"loginWayDisabled": "暂不允许该登录方式",
"hasToSetPassword": "需要设置密码",
"hasToVerifyPassword": "需要验证密码",
- "cantChangeVerifiedUser": "不能修改已经验证身份的用户的敏感信息"
+ "cantChangeVerifiedUser": "不能修改已经验证身份的用户的敏感信息",
+ "emailSuffixIsInvalid": "邮箱后缀不合法"
},
"distinguishUser": "需要鉴别用户身份",
"mobileUnset": "需要先登记手机号",
diff --git a/src/types/Config.ts b/src/types/Config.ts
index a50f61dbf..10673c38f 100644
--- a/src/types/Config.ts
+++ b/src/types/Config.ts
@@ -143,6 +143,16 @@ export type TencentSmsConfig = {
endpoint: string;
};
+export type EmailConfig = {
+ host: string; //主机名
+ port: number; //端口
+ account: string; //账号
+ password: string; //授权码
+ passwordExpiredAt?: number; //授权码过期时间
+ name?: string; //发件人名称
+ secure?: boolean; //是否ssl
+};
+
export type QrCodeType = 'wechatMpDomainUrl' | 'wechatMpWxaCode' | 'wechatPublic' | 'wechatPublicForMp' | 'webForWechatPublic';
export type Config = {
@@ -186,14 +196,7 @@ export type Config = {
needUploadIDCardPhoto?: boolean; // 实名认证是否需要上传身份照片
needManualVerification?: boolean; // 实名认证是否需要人工校验
};
- Emails?: {
- host: string; //主机名
- port: number; //端口
- account: string; //账号
- password: string; //授权码
- passwordExpiredAt?: number; //授权码过期时间
- name?: string; //发件人名称
- }[];
+ Emails?: EmailConfig[];
Security?: {
type?: 'password', // 采用密码作为第一安全元素
level?: 'weak' | 'medium' | 'strong'; // 强度
diff --git a/src/types/Email.ts b/src/types/Email.ts
index 0abd8af1d..7337c3347 100644
--- a/src/types/Email.ts
+++ b/src/types/Email.ts
@@ -28,13 +28,14 @@ interface Attachment extends AttachmentLike {
}
export type EmailOptions = {
- host: string;
- port: number;
- account: string;
- password: string;
- subject: string;
+ host?: string;
+ port?: number;
+ secure?: boolean;
+ account?: string;
+ password?: string;
from: string;
to: string;
+ subject: string;
text?: string;
html?: string;
attachments?: Attachment[]
diff --git a/src/utils/email/node-mailer.ts b/src/utils/email/node-mailer.ts
index a4f673558..0b71f0b90 100644
--- a/src/utils/email/node-mailer.ts
+++ b/src/utils/email/node-mailer.ts
@@ -3,19 +3,64 @@ import nodemailer from 'nodemailer';
import { EntityDict } from '../../oak-app-domain';
import { BRC } from '../../types/RuntimeCxt';
import Email, { EmailOptions } from '../../types/Email';
+import { assert } from 'oak-domain/lib/utils/assert';
+import { BackendRuntimeContext } from '../../context/BackendRuntimeContext';
+import { get } from 'oak-domain/lib/utils/lodash';
+import { EmailConfig } from '../../types/Config';
export default class Nodemailer implements Email {
name = 'nodemailer';
+
+ async getConfig(context: BackendRuntimeContext, systemId?: string) {
+ let system;
+ if (systemId) {
+ [system] = await context.select(
+ 'system',
+ {
+ data: {
+ id: 1,
+ config: 1,
+ },
+ filter: {
+ id: systemId,
+ }
+ },
+ {
+ dontCollect: true,
+ }
+ );
+ } else {
+ system = context.getApplication()!.system;
+ }
+ const { config: systemConfig } = system as EntityDict['system']['Schema'];
+ const emailConfig = get(systemConfig, 'Emails.0', {}) as EmailConfig;
+ const { host, port, password, account } = emailConfig;
+ assert(host, 'host未配置');
+ assert(port, 'port未配置');
+ assert(account, 'account未配置');
+ assert(password, 'password未配置');
+ return {
+ config: emailConfig,
+ };
+ }
async sendEmail(options: EmailOptions, context: BRC) {
- const { host, port, account, password, subject, from, text, html, to, attachments } =
- options;
+ const { subject, from, text, html, to, attachments } = options;
+
+ const {
+ config,
+ } = await this.getConfig(context);
+
+ const transport = Object.assign(config, options);
+
+ const _from = from || (config.name ? `"${config.name}" <${config.account}>` : config.account);
+
const transporter = nodemailer.createTransport({
- host,
- port,
- secure: port === 465, //true for 465, false for other ports
+ host: transport.host,
+ port: transport.port,
+ secure: transport.secure,
auth: {
- user: account,
- pass: password,
+ user: transport.account,
+ pass: transport.password,
},
});
@@ -37,7 +82,7 @@ export default class Nodemailer implements Email {
console.log('Server is ready to take our messages');
}
let mailOptions = {
- from,
+ from: _from,
to,
subject,
text,