* #1344 server-addr 支持 http 字符串

* 【issue】 #1425

* #1344 客户端本地缓存目录,剔除特殊字符串

* #1344 移动常量位置

* 格式

* feat: component subscribers
This commit is contained in:
caoyixiong 2019-06-27 21:46:41 -05:00 committed by GitHub
parent aa18efa21b
commit 928c07d690
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 412 additions and 73 deletions

View File

@ -138,6 +138,14 @@ public interface NamingService {
*/
void deregisterInstance(String serviceName, String groupName, String ip, int port, String clusterName) throws NacosException;
/**
* deregister instance with full instance information and default groupName
* @param serviceName
* @param instance
* @throws NacosException
*/
void deregisterInstance(String serviceName, Instance instance) throws NacosException;
/**
* deregister instance with full instance information
*

View File

@ -78,7 +78,7 @@ public class ServerHttpAgent implements HttpAgent {
newHeaders.addAll(headers);
}
HttpResult result = HttpSimpleClient.httpGet(
getUrl(currentServerAddr, path, isSSL), newHeaders, paramValues, encoding,
getUrl(currentServerAddr, path), newHeaders, paramValues, encoding,
readTimeoutMs, isSSL);
if (result.code == HttpURLConnection.HTTP_INTERNAL_ERROR
|| result.code == HttpURLConnection.HTTP_BAD_GATEWAY
@ -133,7 +133,7 @@ public class ServerHttpAgent implements HttpAgent {
}
HttpResult result = HttpSimpleClient.httpPost(
getUrl(currentServerAddr, path, isSSL), newHeaders, paramValues, encoding,
getUrl(currentServerAddr, path), newHeaders, paramValues, encoding,
readTimeoutMs, isSSL);
if (result.code == HttpURLConnection.HTTP_INTERNAL_ERROR
|| result.code == HttpURLConnection.HTTP_BAD_GATEWAY
@ -186,7 +186,7 @@ public class ServerHttpAgent implements HttpAgent {
newHeaders.addAll(headers);
}
HttpResult result = HttpSimpleClient.httpDelete(
getUrl(currentServerAddr, path, isSSL), newHeaders, paramValues, encoding,
getUrl(currentServerAddr, path), newHeaders, paramValues, encoding,
readTimeoutMs, isSSL);
if (result.code == HttpURLConnection.HTTP_INTERNAL_ERROR
|| result.code == HttpURLConnection.HTTP_BAD_GATEWAY
@ -223,12 +223,8 @@ public class ServerHttpAgent implements HttpAgent {
throw new ConnectException("no available server");
}
private String getUrl(String serverAddr, String relativePath, boolean isSSL) {
String httpPrefix = "http://";
if (isSSL) {
httpPrefix = "https://";
}
return httpPrefix + serverAddr + "/" + serverListMgr.getContentPath() + relativePath;
private String getUrl(String serverAddr, String relativePath) {
return serverAddr + "/" + serverListMgr.getContentPath() + relativePath;
}
public static String getAppname() {

View File

@ -21,6 +21,7 @@ import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.client.config.impl.EventDispatcher.ServerlistChangeEvent;
import com.alibaba.nacos.client.config.impl.HttpSimpleClient.HttpResult;
import com.alibaba.nacos.client.config.utils.IOUtils;
import com.alibaba.nacos.client.identify.Constants;
import com.alibaba.nacos.client.utils.*;
import org.slf4j.Logger;
@ -39,6 +40,8 @@ import java.util.concurrent.TimeUnit;
public class ServerListManager {
private static final Logger LOGGER = LogUtils.logger(ServerListManager.class);
private static final String HTTPS = "https://";
private static final String HTTP = "http://";
public ServerListManager() {
isFixed = false;
@ -118,12 +121,16 @@ public class ServerListManager {
isFixed = true;
List<String> serverAddrs = new ArrayList<String>();
String[] serverAddrsArr = serverAddrsStr.split(",");
for (String serverAddr : serverAddrsArr) {
String[] serverAddrArr = serverAddr.split(":");
if (serverAddrArr.length == 1) {
serverAddrs.add(serverAddrArr[0] + ":" + ParamUtil.getDefaultServerPort());
} else {
for (String serverAddr: serverAddrsArr) {
if (serverAddr.startsWith(HTTPS) || serverAddr.startsWith(HTTP)) {
serverAddrs.add(serverAddr);
} else {
String[] serverAddrArr = serverAddr.split(":");
if (serverAddrArr.length == 1) {
serverAddrs.add(HTTP + serverAddrArr[0] + ":" + ParamUtil.getDefaultServerPort());
} else {
serverAddrs.add(HTTP + serverAddr);
}
}
}
serverUrls = serverAddrs;
@ -317,6 +324,7 @@ public class ServerListManager {
String split = "";
for (String serverIp : serverIps) {
sb.append(split);
serverIp = serverIp.replaceAll("http(s)?://", "");
sb.append(serverIp.replaceAll(":", "_"));
split = "-";
}

View File

@ -231,6 +231,11 @@ public class NacosNamingService implements NamingService {
deregisterInstance(serviceName, groupName, instance);
}
@Override
public void deregisterInstance(String serviceName, Instance instance) throws NacosException {
deregisterInstance(serviceName, Constants.DEFAULT_GROUP, instance);
}
@Override
public void deregisterInstance(String serviceName, String groupName, Instance instance) throws NacosException {
beatReactor.removeBeatInfo(NamingUtils.getGroupedName(serviceName, groupName), instance.getIp(), instance.getPort());

View File

@ -29,6 +29,7 @@ import com.alibaba.nacos.api.selector.AbstractSelector;
import com.alibaba.nacos.api.selector.ExpressionSelector;
import com.alibaba.nacos.api.selector.SelectorType;
import com.alibaba.nacos.client.config.impl.SpasAdapter;
import com.alibaba.nacos.client.identify.Constants;
import com.alibaba.nacos.client.monitor.MetricsMonitor;
import com.alibaba.nacos.client.naming.beat.BeatInfo;
import com.alibaba.nacos.client.naming.utils.*;
@ -397,13 +398,15 @@ public class NamingProxy {
List<String> headers = builderHeaders();
String url;
if (!curServer.contains(UtilAndComs.SERVER_ADDR_IP_SPLITER)) {
curServer = curServer + UtilAndComs.SERVER_ADDR_IP_SPLITER + serverPort;
if (curServer.startsWith(UtilAndComs.HTTPS) || curServer.startsWith(UtilAndComs.HTTP)) {
url = curServer + api;
} else {
if (!curServer.contains(UtilAndComs.SERVER_ADDR_IP_SPLITER)) {
curServer = curServer + UtilAndComs.SERVER_ADDR_IP_SPLITER + serverPort;
}
url = HttpClient.getPrefix() + curServer + api;
}
url = HttpClient.getPrefix() + curServer + api;
HttpClient.HttpResult result = HttpClient.request(url, headers, params, UtilAndComs.ENCODING, method);
end = System.currentTimeMillis();
@ -418,8 +421,8 @@ public class NamingProxy {
return StringUtils.EMPTY;
}
throw new NacosException(NacosException.SERVER_ERROR, "failed to req API:" + HttpClient.getPrefix() + curServer
+ api + ". code:"
throw new NacosException(NacosException.SERVER_ERROR, "failed to req API:"
+ curServer + api + ". code:"
+ result.code + " msg: " + result.content);
}
@ -556,6 +559,6 @@ public class NamingProxy {
this.serverPort = Integer.parseInt(sp);
}
}
}

View File

@ -55,4 +55,9 @@ public class UtilAndComs {
public static final int DEFAULT_POLLING_THREAD_COUNT = Runtime.getRuntime()
.availableProcessors() > 1 ? Runtime.getRuntime().availableProcessors() / 2
: 1;
public static final String HTTP = "http://";
public static final String HTTPS = "https://";
}

View File

@ -20,3 +20,6 @@ export const LANGUAGE_SWITCH = 'LANGUAGE_SWITCH';
export const REDUX_DEVTOOLS = '__REDUX_DEVTOOLS_EXTENSION__';
export const GET_STATE = 'GET_STATE';
export const GET_SUBSCRIBERS = 'GET_SUBSCRIBERS';
export const REMOVE_SUBSCRIBERS = 'REMOVE_SUBSCRIBERS';

View File

@ -41,6 +41,7 @@ import ListeningToQuery from './pages/ConfigurationManagement/ListeningToQuery';
import ConfigurationManagement from './pages/ConfigurationManagement/ConfigurationManagement';
import ServiceList from './pages/ServiceManagement/ServiceList';
import ServiceDetail from './pages/ServiceManagement/ServiceDetail';
import SubscriberList from './pages/ServiceManagement/SubscriberList';
import ClusterNodeList from './pages/ClusterManagement/ClusterNodeList';
import reducers from './reducers';
@ -82,6 +83,7 @@ const MENU = [
{ path: '/configurationManagement', component: ConfigurationManagement },
{ path: '/serviceManagement', component: ServiceList },
{ path: '/serviceDetail', component: ServiceDetail },
{ path: '/subscriberList', component: SubscriberList },
{ path: '/clusterManagement', component: ClusterNodeList },
];

View File

@ -83,7 +83,7 @@ class Header extends React.Component {
rel="noopener noreferrer"
>
<img
src="img/TB118jPv_mWBKNjSZFBXXXxUFXa-2000-390.svg"
src="img/logo-2000-390.svg"
className="logo"
alt={siteConfig.name}
title={siteConfig.name}

View File

@ -44,6 +44,7 @@ const I18N_CONF = {
listeningToQuery: 'Listening Query',
serviceManagementVirtual: 'ServiceManagement',
serviceManagement: 'Service List',
subscriberList: 'Subscribers',
serviceDetail: 'Service Details',
namespace: 'Namespace',
clusterManagementVirtual: 'ClusterManagement',
@ -92,6 +93,19 @@ const I18N_CONF = {
promptDelete: 'Do you want to delete the service?',
create: 'Create Service',
},
SubscriberList: {
subscriberList: 'Subscriber List',
serviceName: 'Service Name',
serviceNamePlaceholder: 'Enter Service Name',
groupName: 'Group Name',
groupNamePlaceholder: 'Enter Group Name',
query: 'Search',
pubNoData: 'No results found.',
address: 'Address',
clientVersion: 'Client Version',
appName: 'Application Name',
searchServiceNamePrompt: 'Service name required!',
},
ClusterNodeList: {
clusterNodeList: 'Node List',
nodeIp: 'NodeIp',

View File

@ -44,6 +44,7 @@ const I18N_CONF = {
listeningToQuery: '监听查询',
serviceManagementVirtual: '服务管理',
serviceManagement: '服务列表',
subscriberList: '订阅者列表',
serviceDetail: '服务详情',
namespace: '命名空间',
clusterManagementVirtual: '集群管理',
@ -92,6 +93,19 @@ const I18N_CONF = {
promptDelete: '确定要删除当前服务吗',
create: '创建服务',
},
SubscriberList: {
subscriberList: '订阅者列表',
serviceName: '服务名称',
serviceNamePlaceholder: '请输入服务名称',
groupName: '分组名称',
groupNamePlaceholder: '请输入分组名称',
query: '查询',
pubNoData: '没有数据',
address: '地址',
clientVersion: '客户端版本',
appName: '应用名',
searchServiceNamePrompt: '请输入服务名称',
},
ClusterNodeList: {
clusterNodeList: '节点列表',
nodeIp: '节点Ip',

View File

@ -209,6 +209,21 @@ module.exports = {
},
],
},
{
isExtend: false,
name: '订阅者列表',
title: '订阅者列表',
isVirtual: false,
projectName: 'nacos',
serviceName: 'subscriberList',
link: 'subscriberList',
hasFusion: true,
template: '',
registerName: 'com.alibaba.nacos.page.subscriberList',
useRouter: false,
id: 'subscriberList',
children: [],
},
],
},
{

View File

@ -0,0 +1,217 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import {
Button,
Field,
Form,
Grid,
Input,
Loading,
Pagination,
Table,
Dialog,
Message,
ConfigProvider,
} from '@alifd/next';
import { connect } from 'react-redux';
import { getSubscribers, removeSubscribers } from '../../../reducers/subscribers';
import { request } from '../../../globalLib';
import RegionGroup from '../../../components/RegionGroup';
import './SubscriberList.scss';
const FormItem = Form.Item;
const { Row, Col } = Grid;
const { Column } = Table;
@connect(
state => ({ subscriberData: state.subscribers }),
{ getSubscribers, removeSubscribers }
)
@ConfigProvider.config
class SubscriberList extends React.Component {
static displayName = 'SubscriberList';
static propTypes = {
locale: PropTypes.object,
getSubscribers: PropTypes.func,
removeSubscribers: PropTypes.func,
subscriberData: PropTypes.object,
};
constructor(props) {
super(props);
this.state = {
loading: false,
total: 0,
pageSize: 10,
pageNo: 1,
search: {
serviceName: '',
groupName: '',
},
};
this.field = new Field(this);
}
openLoading() {
this.setState({ loading: true });
}
closeLoading() {
this.setState({ loading: false });
}
querySubscriberList() {
const { searchServiceNamePrompt } = this.props.locale;
const { search, pageSize, pageNo, nowNamespaceId = '' } = this.state;
if (!search.serviceName) {
Message.error(searchServiceNamePrompt);
return;
}
this.props.getSubscribers({
...search,
pageSize,
pageNo,
namespaceId: nowNamespaceId,
});
}
switchNamespace = () => {
this.setState({ search: { serviceName: '', groupName: '' } });
this.props.removeSubscribers();
};
setNowNameSpace = (nowNamespaceName, nowNamespaceId) =>
this.setState({
nowNamespaceName,
nowNamespaceId,
});
render() {
const { locale = {}, subscriberData = {} } = this.props;
const { count = 0, subscribers = [] } = subscriberData;
const {
pubNoData,
subscriberList,
serviceName,
serviceNamePlaceholder,
groupName,
groupNamePlaceholder,
query,
} = locale;
const { search, nowNamespaceName, nowNamespaceId } = this.state;
const { init, getValue } = this.field;
this.init = init;
this.getValue = getValue;
return (
<div className="main-container subscriber-list">
<Loading
shape="flower"
style={{
position: 'relative',
width: '100%',
}}
visible={this.state.loading}
tip="Loading..."
color="#333"
>
<div style={{ marginTop: -15 }}>
<RegionGroup
setNowNameSpace={this.setNowNameSpace}
namespaceCallBack={this.switchNamespace}
/>
</div>
<h3 className="page-title">
<span className="title-item">{subscriberList}</span>
<span className="title-item">|</span>
<span className="title-item">{nowNamespaceName}</span>
<span className="title-item">{nowNamespaceId}</span>
</h3>
<Row
className="demo-row"
style={{
marginBottom: 10,
padding: 0,
}}
>
<Col span="24">
<Form inline field={this.field}>
<FormItem label={serviceName}>
<Input
placeholder={serviceNamePlaceholder}
style={{ width: 200 }}
value={search.serviceName}
onChange={serviceName => this.setState({ search: { ...search, serviceName } })}
onPressEnter={() =>
this.setState({ pageNo: 1 }, () => this.querySubscriberList())
}
/>
</FormItem>
<FormItem label={groupName}>
<Input
placeholder={groupNamePlaceholder}
style={{ width: 200 }}
value={search.groupName}
onChange={groupName => this.setState({ search: { ...search, groupName } })}
onPressEnter={() =>
this.setState({ pageNo: 1 }, () => this.querySubscriberList())
}
/>
</FormItem>
<FormItem label="">
<Button
type="primary"
onClick={() => this.setState({ pageNo: 1 }, () => this.querySubscriberList())}
style={{ marginRight: 10 }}
>
{query}
</Button>
</FormItem>
</Form>
</Col>
</Row>
<Row style={{ padding: 0 }}>
<Col span="24" style={{ padding: 0 }}>
<Table dataSource={subscribers} locale={{ empty: pubNoData }}>
<Column title={locale.address} dataIndex="addrStr" />
<Column title={locale.clientVersion} dataIndex="agent" />
<Column title={locale.appName} dataIndex="app" />
</Table>
</Col>
</Row>
{count > this.state.pageSize && (
<div
style={{
marginTop: 10,
textAlign: 'right',
}}
>
<Pagination
current={this.state.pageNo}
total={count}
pageSize={this.state.pageSize}
onChange={pageNo => this.setState({ pageNo }, () => this.querySubscriberList())}
/>
</div>
)}
</Loading>
</div>
);
}
}
export default SubscriberList;

View File

@ -0,0 +1,30 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.subscriber-list {
.page-title {
height: 30px;
width: 100%;
line-height: 30px;
margin: 0 0 20px;
padding: 0 0 0 10px;
border-left: 3px solid #09c;
color: #ccc;
}
.title-item {
font-size: 14px;
color: #000;
margin-right: 8px;
}
}

View File

@ -0,0 +1,16 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import SubscriberList from './SubscriberList';
export default SubscriberList;

View File

@ -13,5 +13,6 @@
import locale from './locale';
import base from './base';
import subscribers from './subscribers';
export default { locale, base };
export default { locale, base, subscribers };

View File

@ -0,0 +1,41 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import request from '../utils/request';
import { GET_SUBSCRIBERS, REMOVE_SUBSCRIBERS } from '../constants';
const initialState = {
subscribers: {},
};
const getSubscribers = params => dispatch =>
request.get('v1/ns/service/subscribers', { params }).then(data => {
dispatch({
type: GET_SUBSCRIBERS,
data,
});
});
const removeSubscribers = () => dispatch => dispatch({ type: REMOVE_SUBSCRIBERS });
export default (state = initialState, action) => {
switch (action.type) {
case GET_SUBSCRIBERS:
return { ...state, ...action.data };
case REMOVE_SUBSCRIBERS:
return { ...state, subscribers: {} };
default:
return state;
}
};
export { getSubscribers, removeSubscribers };

View File

@ -46,42 +46,3 @@ export const getParameter = (search, name) => {
const [, value = ''] = hit.split('=');
return value;
};
export const encodeKV = (text, separator) =>
text
.split('\r\n')
.filter(row => row.trim() && row.split('=').filter(kv => kv.trim()).length === 2)
.map(row =>
row
.split('=')
.map(kv => encodeURIComponent(kv.trim()))
.join('=')
)
.join(separator);
/**
* 将回车符和空格替换
* @param {*} separator 替换符
*/
export const replaceEnter = (separator = ',') => text => {
if (typeof text !== 'string') {
return text;
}
return encodeKV(text, separator)
.replace(/[\r\n]/g, separator)
.replace(/[\t\s]/g, '');
};
/**
* 处理metaData对象生成可显示对象
*/
export const processMetaData = (separator = ',') => (metadata = {}) => {
if (Object.prototype.toString.call(metadata) !== '[object Object]') {
return '';
}
return Object.keys(metadata)
.map(key => `${key}=${metadata[key]}`)
.join(separator);
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long