diff --git a/README.md b/README.md
index 2086926..11e7bcd 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,9 @@
-
+
-
-
+
+
@@ -22,7 +22,7 @@
* 极简封装了多租户底层,用更少的代码换来拓展性更强的SaaS多租户系统。
* 借鉴OAuth2,实现了多终端认证系统,可控制子系统的token权限互相隔离。
* 借鉴Security,封装了Secure模块,采用JWT做Token认证,可拓展集成Redis等细颗粒度控制方案。
-* 稳定生产了一年,经历了从Camden -> Greenwich的技术架构,也经历了从fat jar -> docker -> k8s + jenkins的部署架构
+* 稳定生产了一年,经历了从Camden -> Hoxton的技术架构,也经历了从fat jar -> docker -> k8s + jenkins的部署架构
* 项目分包明确,规范微服务的开发模式,使包与包之间的分工清晰。
## 架构图
@@ -59,7 +59,8 @@ SpringBlade
* 交流一群:`477853168`(满)
* 交流二群:`751253339`(满)
* 交流三群:`784729540`(满)
-* 交流四群:`1034621754`
+* 交流四群:`1034621754`(满)
+* 交流五群:`946350912`
## 在线演示
* Saber-基于Vue:[https://saber.bladex.vip](https://saber.bladex.vip)
diff --git a/config/router.config.js b/config/router.config.js
index 5e7c708..25a60c8 100644
--- a/config/router.config.js
+++ b/config/router.config.js
@@ -15,7 +15,7 @@ export default [
path: '/',
component: '../layouts/BasicLayout',
Routes: ['src/pages/Authorized'],
- authority: ['administrator', 'admin', 'user', 'test'],
+ authority: ['administrator', 'admin', 'user', 'test', 'guest'],
routes: [
// dashboard
{ path: '/', redirect: '/dashboard/workplace' },
diff --git a/package.json b/package.json
index 2553693..63489cc 100644
--- a/package.json
+++ b/package.json
@@ -1,13 +1,13 @@
{
"name": "sword",
- "version": "2.7.0",
+ "version": "2.7.2",
"description": "An out-of-box UI solution for enterprise applications",
"private": true,
"scripts": {
"presite": "cd functions && npm install",
- "start": "cross-env MOCK=none PORT=8888 umi dev",
- "start:no-mock": "cross-env MOCK=none PORT=8888 umi dev",
- "start:mock": "cross-env APP_TYPE=site PORT=8888 umi dev",
+ "start": "cross-env MOCK=none PORT=1888 umi dev",
+ "start:no-mock": "cross-env MOCK=none PORT=1888 umi dev",
+ "start:mock": "cross-env APP_TYPE=site PORT=1888 umi dev",
"build": "umi build",
"site": "npm run presite && cross-env APP_TYPE=site npm run build && firebase deploy && npm run docker:push",
"analyze": "cross-env ANALYZE=1 umi build",
diff --git a/src/components/Login/LoginItem.js b/src/components/Login/LoginItem.js
index 6761f89..5e25847 100644
--- a/src/components/Login/LoginItem.js
+++ b/src/components/Login/LoginItem.js
@@ -47,7 +47,7 @@ class WrapFormItem extends Component {
refreshCaptcha = () => {
// 获取验证码
getCaptchaImage().then(resp => {
- const {data} = resp;
+ const { data } = resp;
if (data.key) {
this.setState({ image: data.image });
setCaptchaKey(data.key);
diff --git a/src/components/ThirdRegister/index.js b/src/components/ThirdRegister/index.js
new file mode 100644
index 0000000..c57294e
--- /dev/null
+++ b/src/components/ThirdRegister/index.js
@@ -0,0 +1,181 @@
+import React, { PureComponent } from 'react';
+import { connect } from 'dva';
+import { Card, Col, Form, Input, Modal, Button, Row, message } from 'antd';
+import styles from '@/layouts/Sword.less';
+import { getCurrentUser, removeAll } from '@/utils/authority';
+import { validateNull } from '@/utils/utils';
+import { tenantMode } from '@/defaultSettings';
+import { getUserInfo, registerGuest } from '@/services/user';
+import router from 'umi/router';
+
+const FormItem = Form.Item;
+
+@connect(({ tenant }) => ({
+ tenant,
+}))
+@Form.create()
+class ThirdRegister extends PureComponent {
+ state = {
+ loading: false,
+ visible: false,
+ user: {},
+ };
+
+ componentDidMount() {
+ const user = getCurrentUser();
+ if (validateNull(user) || validateNull(user.userId) || user.userId < 0) {
+ // 第三方注册用户,弹出注册框
+ this.setState({ visible: true, user });
+ } else {
+ // 获取用户信息,也可用于校验当前用户token是否有效
+ getUserInfo().then(resp => {
+ window.console.log(resp);
+ });
+ }
+ }
+
+ handleSubmit = e => {
+ e.preventDefault();
+ const { form } = this.props;
+ const user = getCurrentUser();
+ form.validateFieldsAndScroll((err, values) => {
+ if (!err) {
+ const password = form.getFieldValue('password');
+ const password2 = form.getFieldValue('password2');
+ if (password !== password2) {
+ message.warning('两次密码输入不一致');
+ } else {
+ registerGuest(values, user.oauthId).then(resp => {
+ if (resp.success) {
+ this.setState({ visible: false });
+ Modal.success({ content: '注册申请已提交,请耐心等待管理员通过!' });
+ removeAll();
+ router.push('/user/login');
+ }
+ form.resetFields();
+ });
+ }
+ }
+ });
+ };
+
+ render() {
+ const {
+ form,
+ } = this.props;
+
+ const { loading, visible, user } = this.state;
+
+ const { getFieldDecorator } = form;
+
+ const tenantVisible = tenantMode;
+
+ const formItemLayout = {
+ labelCol: {
+ span: 8,
+ },
+ wrapperCol: {
+ span: 16,
+ },
+ };
+
+ const formAllItemLayout = {
+ labelCol: {
+ span: 4,
+ },
+ wrapperCol: {
+ span: 20,
+ },
+ };
+
+ return (
+
+ 注册
+ ,
+ ]}
+ >
+
+
+ );
+ }
+}
+export default ThirdRegister;
diff --git a/src/defaultSettings.js b/src/defaultSettings.js
index 81398ba..054a86a 100644
--- a/src/defaultSettings.js
+++ b/src/defaultSettings.js
@@ -16,4 +16,6 @@ module.exports = {
disableLocal: false,
},
pwa: true,
+ // 第三方登陆授权地址
+ authUrl: 'http://localhost/blade-auth/oauth/render',
};
diff --git a/src/locales/en-US/login.js b/src/locales/en-US/login.js
index d1b6029..6a23299 100644
--- a/src/locales/en-US/login.js
+++ b/src/locales/en-US/login.js
@@ -6,6 +6,7 @@ export default {
'app.login.message-invalid-verification-code': 'Invalid verification code',
'app.login.tab-login-credentials': 'Credentials',
'app.login.tab-login-mobile': 'Mobile number',
+ 'app.login.tab-login-social': 'Social System',
'app.login.remember-me': 'Remember me',
'app.login.forgot-password': 'Forgot your password?',
'app.login.sign-in-with': 'Sign in with',
diff --git a/src/locales/zh-CN/login.js b/src/locales/zh-CN/login.js
index aae59fe..0dac0f2 100644
--- a/src/locales/zh-CN/login.js
+++ b/src/locales/zh-CN/login.js
@@ -6,6 +6,7 @@ export default {
'app.login.message-invalid-verification-code': '验证码错误',
'app.login.tab-login-credentials': '账户密码登录',
'app.login.tab-login-mobile': '手机号登录',
+ 'app.login.tab-login-social': '第三方系统登陆',
'app.login.remember-me': '自动登录',
'app.login.forgot-password': '忘记密码',
'app.login.sign-in-with': '其他登录方式',
diff --git a/src/locales/zh-TW/login.js b/src/locales/zh-TW/login.js
index 1ccf9ca..9a41519 100644
--- a/src/locales/zh-TW/login.js
+++ b/src/locales/zh-TW/login.js
@@ -6,6 +6,7 @@ export default {
'app.login.message-invalid-verification-code': '驗證碼錯誤',
'app.login.tab-login-credentials': '賬戶密碼登錄',
'app.login.tab-login-mobile': '手機號登錄',
+ 'app.login.tab-login-social': '第三方系統登陸',
'app.login.remember-me': '自動登錄',
'app.login.forgot-password': '忘記密碼',
'app.login.sign-in-with': '其他登錄方式',
diff --git a/src/models/login.js b/src/models/login.js
index dbeca4a..a8720ab 100644
--- a/src/models/login.js
+++ b/src/models/login.js
@@ -1,7 +1,8 @@
import { routerRedux } from 'dva/router';
+import { notification } from 'antd';
import { stringify } from 'qs';
import { getFakeCaptcha } from '../services/api';
-import { accountLogin } from '../services/user';
+import { accountLogin, socialLogin } from '../services/user';
import { dynamicRoutes, dynamicButtons } from '../services/menu';
import {
setAuthority,
@@ -14,6 +15,7 @@ import {
} from '../utils/authority';
import { getPageQuery, formatRoutes, formatButtons } from '../utils/utils';
import { reloadAuthorized } from '../utils/Authorized';
+import { getTopUrl } from '../utils/utils';
export default {
namespace: 'login',
@@ -63,7 +65,29 @@ export default {
yield put(routerRedux.replace(redirect || '/'));
}
},
-
+ *socialLogin({ payload }, { call, put }) {
+ const response = yield call(socialLogin, payload);
+ if (response.success) {
+ yield put({
+ type: 'changeLoginStatus',
+ payload: {
+ status: true,
+ type: 'login',
+ data: { ...response.data },
+ },
+ });
+ reloadAuthorized();
+ const topUrl = getTopUrl();
+ const redirectUrl = '/oauth/redirect/';
+ // eslint-disable-next-line prefer-destructuring
+ window.location.href = topUrl.split(redirectUrl)[0];
+ yield put(routerRedux.replace('/'));
+ } else {
+ notification.error({
+ message: response.msg,
+ });
+ }
+ },
*getCaptcha({ payload }, { call }) {
yield call(getFakeCaptcha, payload);
},
@@ -95,16 +119,15 @@ export default {
reducers: {
changeLoginStatus(state, { payload }) {
const { status, type } = payload;
-
if (status) {
const {
- data: { tokenType, accessToken, authority, account, userName, avatar },
+ data: { tokenType, accessToken, authority, account, userId, oauthId, userName, avatar },
} = payload;
const token = `${tokenType} ${accessToken}`;
setToken(token);
setAccessToken(accessToken);
setAuthority(authority);
- setCurrentUser({ avatar, account, name: userName, authority });
+ setCurrentUser({ avatar, userId, oauthId, account, name: userName, authority });
} else {
removeAll();
}
diff --git a/src/pages/Base/Region/Region.js b/src/pages/Base/Region/Region.js
index da0a2b8..911de4c 100644
--- a/src/pages/Base/Region/Region.js
+++ b/src/pages/Base/Region/Region.js
@@ -236,11 +236,7 @@ class Region extends PureComponent {
const buttons = getButton('region');
- const {
- treeData,
- treeCascader,
- debugVisible,
- } = this.state;
+ const { treeData, treeCascader, debugVisible } = this.state;
const formItemLayout = {
labelCol: {
diff --git a/src/pages/Dashboard/Workplace.js b/src/pages/Dashboard/Workplace.js
index dbf5814..b5c8bc9 100644
--- a/src/pages/Dashboard/Workplace.js
+++ b/src/pages/Dashboard/Workplace.js
@@ -2,7 +2,8 @@ import React, { PureComponent } from 'react';
import { Card, Col, Collapse, Row, Divider, Tag } from 'antd';
import styles from '../../layouts/Sword.less';
-import PageHeaderWrapper from '@/components/PageHeaderWrapper';
+import PageHeaderWrapper from '../../components/PageHeaderWrapper';
+import ThirdRegister from '../../components/ThirdRegister';
const { Panel } = Collapse;
@@ -11,6 +12,11 @@ class Workplace extends PureComponent {
return (
+
+
+
+
+
@@ -211,13 +217,15 @@ class Workplace extends PureComponent {
1.升级至 SpringCloud Hoxton.SR5
2.升级至 SpringBoot 2.2.7.RELEASE
3.升级至 Seata 1.2.0
-
4.升级至 FastJson 1.2.70
-
5.升级至 Avue 2.5.3
-
6.新增行政区划管理模块
-
7.优化用户导入的密码配置逻辑
-
8.优化INode结构支持懒加载数据格式
-
9.优化代码生成模板,支持最新版Saber结构
-
10.修复Log模块在多线程、异步场景下报错的问题
+
4.升级至 MybatisPlus 3.3.2
+
5.升级至 Kinfe4j 2.0.3
+
6.升级至 FastJson 1.2.70
+
7.升级至 Avue 2.5.3
+
8.新增行政区划管理模块
+
9.优化用户导入的密码配置逻辑
+
10.优化INode结构支持懒加载数据格式
+
11.优化代码生成模板,支持最新版Saber结构
+
12.修复Log模块在多线程、异步场景下报错的问题
1.升级至 SpringCloud Hoxton.SR3
diff --git a/src/pages/Login/Login.js b/src/pages/Login/Login.js
index fef01a0..4244a50 100644
--- a/src/pages/Login/Login.js
+++ b/src/pages/Login/Login.js
@@ -1,10 +1,11 @@
import React, { Component } from 'react';
import { connect } from 'dva';
import { formatMessage, FormattedMessage } from 'umi/locale';
-import { Checkbox, Alert } from 'antd';
+import { Checkbox, Alert, Icon, Row, Col, Card, Spin } from 'antd';
import Login from '../../components/Login';
import styles from './Login.less';
-import { tenantMode, captchaMode } from '../../defaultSettings';
+import { tenantMode, captchaMode, authUrl } from '../../defaultSettings';
+import { getQueryString, getTopUrl, validateNull } from '@/utils/utils';
const { Tab, TenantId, UserName, Password, Captcha, Submit } = Login;
@@ -18,6 +19,36 @@ class LoginPage extends Component {
autoLogin: true,
};
+ componentDidMount() {
+ const domain = getTopUrl();
+ const redirectUrl = '/oauth/redirect/';
+ const {
+ dispatch,
+ route: { routes, authority },
+ } = this.props;
+
+ let source = getQueryString('source');
+ const code = getQueryString('code');
+ const state = getQueryString('state');
+ if (validateNull(source) && domain.includes(redirectUrl)) {
+ // eslint-disable-next-line prefer-destructuring
+ source = domain.split('?')[0];
+ // eslint-disable-next-line prefer-destructuring
+ source = source.split(redirectUrl)[1];
+ }
+ if (!validateNull(source) && !validateNull(code) && !validateNull(state)) {
+ dispatch({
+ type: 'login/socialLogin',
+ payload: { source, code, state, tenantId: '000000' },
+ });
+ } else {
+ dispatch({
+ type: 'menu/fetchMenuData',
+ payload: { routes, authority },
+ });
+ }
+ }
+
onTabChange = type => {
this.setState({ type });
};
@@ -53,6 +84,10 @@ class LoginPage extends Component {
}
};
+ handleClick = source => {
+ window.location.href = `${authUrl}/${source}`;
+ };
+
changeAutoLogin = e => {
this.setState({
autoLogin: e.target.checked,
@@ -65,7 +100,7 @@ class LoginPage extends Component {
render() {
const { login, submitting } = this.props;
- const { type, autoLogin } = this.state;
+ const { type, autoLogin, loading } = this.state;
return (
{captchaMode ?
: null}
+
+
+
+
+ {
+ this.handleClick('github');
+ }}
+ />
+
+
+ {
+ this.handleClick('gitee');
+ }}
+ />
+
+
+ {
+ this.handleClick('wechat_open');
+ }}
+ />
+
+
+ {
+ this.handleClick('dingtalk');
+ }}
+ />
+
+
+ {
+ this.handleClick('alipay');
+ }}
+ />
+
+
+ {
+ this.handleClick('taobao');
+ }}
+ />
+
+
+
+
diff --git a/src/pages/Login/Login.less b/src/pages/Login/Login.less
index 8eba81c..e4a9037 100644
--- a/src/pages/Login/Login.less
+++ b/src/pages/Login/Login.less
@@ -29,4 +29,14 @@
float: right;
}
}
+
+ .card {
+ margin-bottom: 24px;
+ }
+
+ .iconPreview {
+ font-size: 45px;
+ text-align: center;
+ cursor: pointer;
+ }
}
diff --git a/src/pages/System/User/User.js b/src/pages/System/User/User.js
index c44d59f..8b0e34a 100644
--- a/src/pages/System/User/User.js
+++ b/src/pages/System/User/User.js
@@ -1,17 +1,6 @@
import React, { PureComponent } from 'react';
import { connect } from 'dva';
-import {
- Upload,
- Icon,
- Button,
- Col,
- Form,
- Input,
- message,
- Modal,
- Row,
- Tree,
-} from 'antd';
+import { Upload, Icon, Button, Col, Form, Input, message, Modal, Row, Tree } from 'antd';
import Panel from '../../../components/Panel';
import Grid from '../../../components/Sword/Grid';
import { USER_INIT, USER_LIST, USER_ROLE_GRANT } from '../../../actions/user';
@@ -249,7 +238,6 @@ class User extends PureComponent {
);
-
render() {
const code = 'user';
@@ -269,7 +257,7 @@ class User extends PureComponent {
headers: {
'Blade-Auth': getToken(),
},
- action: "/api/blade-user/import-user",
+ action: '/api/blade-user/import-user',
};
const formItemLayout = {
diff --git a/src/pages/System/User/UserEdit.js b/src/pages/System/User/UserEdit.js
index ce04bb0..e469c69 100644
--- a/src/pages/System/User/UserEdit.js
+++ b/src/pages/System/User/UserEdit.js
@@ -23,7 +23,7 @@ class UserEdit extends PureComponent {
params: { id },
},
} = this.props;
- dispatch(USER_DETAIL(id)).then(()=>{
+ dispatch(USER_DETAIL(id)).then(() => {
const {
user: { detail },
} = this.props;
@@ -227,7 +227,7 @@ class UserEdit extends PureComponent {
{getFieldDecorator('code', {
- initialValue: detail.code
+ initialValue: detail.code,
})()}
diff --git a/src/services/user.js b/src/services/user.js
index 377a5af..8180a91 100644
--- a/src/services/user.js
+++ b/src/services/user.js
@@ -20,6 +20,25 @@ export async function accountLogin(params) {
});
}
+export async function socialLogin(params) {
+ const values = params;
+ values.grantType = 'social';
+ values.scope = 'all';
+ return request('/api/blade-auth/token', {
+ method: 'POST',
+ body: func.toFormData(values),
+ });
+}
+
+export async function registerGuest(form, oauthId) {
+ const values = form;
+ values.oauthId = oauthId;
+ return request('/api/blade-user/register-guest', {
+ method: 'POST',
+ body: func.toFormData(values),
+ });
+}
+
export async function query() {
return request('/api/users');
}
diff --git a/src/utils/Func.js b/src/utils/Func.js
index 9c427ec..e9855cd 100644
--- a/src/utils/Func.js
+++ b/src/utils/Func.js
@@ -88,6 +88,9 @@ export default class Func {
* @returns {string}
*/
static split(str) {
+ if (String(str) === '-1') {
+ return null;
+ }
return str ? String(str).split(',') : '';
}
}
diff --git a/src/utils/getPageTitle.js b/src/utils/getPageTitle.js
new file mode 100644
index 0000000..0dd1e62
--- /dev/null
+++ b/src/utils/getPageTitle.js
@@ -0,0 +1,27 @@
+import { formatMessage } from 'umi/locale';
+import pathToRegexp from 'path-to-regexp';
+import isEqual from 'lodash/isEqual';
+import memoizeOne from 'memoize-one';
+import { menu, title } from '../defaultSettings';
+
+export const matchParamsPath = (pathname, breadcrumbNameMap) => {
+ const pathKey = Object.keys(breadcrumbNameMap).find(key => pathToRegexp(key).test(pathname));
+ return breadcrumbNameMap[pathKey];
+};
+
+const getPageTitle = (pathname, breadcrumbNameMap) => {
+ const currRouterData = matchParamsPath(pathname, breadcrumbNameMap);
+ if (!currRouterData) {
+ return title;
+ }
+ const pageName = menu.disableLocal
+ ? currRouterData.name
+ : formatMessage({
+ id: currRouterData.locale || currRouterData.name,
+ defaultMessage: currRouterData.name,
+ });
+
+ return `${pageName} - ${title}`;
+};
+
+export default memoizeOne(getPageTitle, isEqual);
diff --git a/src/utils/utils.js b/src/utils/utils.js
index cdd87fd..bbaf03c 100644
--- a/src/utils/utils.js
+++ b/src/utils/utils.js
@@ -212,3 +212,45 @@ export function formatButtons(buttons) {
};
});
}
+
+/**
+ * 判断是否为空
+ */
+export function validateNull(val) {
+ if (typeof val === 'boolean') {
+ return false;
+ }
+ if (typeof val === 'number') {
+ return false;
+ }
+ if (val instanceof Array) {
+ if (val.length === 0) return true;
+ } else if (val instanceof Object) {
+ if (JSON.stringify(val) === '{}') return true;
+ } else {
+ if (val === 'null' || val == null || val === 'undefined' || val === undefined || val === '') {
+ return true;
+ }
+ return false;
+ }
+ return false;
+}
+
+/**
+ * 获取顶部地址栏地址
+ */
+export function getTopUrl() {
+ return window.location.href.split('/#/')[0];
+}
+
+/**
+ * 获取url参数
+ * @param name 参数名
+ */
+export function getQueryString(name) {
+ // eslint-disable-next-line no-shadow
+ const reg = new RegExp(`(^|&)${name}=([^&]*)(&|$)`, 'i');
+ const r = window.location.search.substr(1).match(reg);
+ if (r != null) return unescape(decodeURI(r[2]));
+ return null;
+}