服务列表

1.x
tanghc 6 years ago
parent 9ab2bacf42
commit 44f3c76c78
  1. 37
      sop-admin/sop-admin-front/assets/js/lib.js
  2. 0
      sop-admin/sop-admin-front/assets/lib/layuiext/Form.js
  3. 18
      sop-admin/sop-admin-front/assets/lib/layuiext/module/treetable-lay/treetable.css
  4. 206
      sop-admin/sop-admin-front/assets/lib/layuiext/module/treetable-lay/treetable.js
  5. 2
      sop-admin/sop-admin-front/pages/service/routeManager.js
  6. 32
      sop-admin/sop-admin-front/pages/service/serviceList.html
  7. 53
      sop-admin/sop-admin-front/pages/service/serviceList.js
  8. 7
      sop-admin/sop-admin-server/pom.xml
  9. 90
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/ServiceApi.java
  10. 52
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/result/ServiceInfoVo.java
  11. 14
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/EurekaApplication.java
  12. 13
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/EurekaApplications.java
  13. 11
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/EurekaApps.java
  14. 29
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/EurekaInstance.java
  15. 20
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/EurekaUri.java
  16. 1
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/config/WebConfig.java
  17. 7
      sop-admin/sop-admin-server/src/main/resources/application-dev.yml

@ -13,12 +13,12 @@ var lib = (function () {
'../../assets/lib/jquery/3.2.1/jquery.min.js'
,'../../assets/lib/layui/layui.js'
,'../../assets/lib/easyopen/sdk.js'
,'../../assets/lib/layuiext/Form.js'
,'../../assets/js/ApiUtil.js'
,'../../assets/js/Form.js'
,'../../assets/js/profile.js'
]
var jsArr = [];
var layuiConfig, layuiExtend;
/**
* 加载js
@ -26,7 +26,7 @@ var lib = (function () {
* @param require layui依赖模块
* @param loadSuccess 加载成功后触发
*/
function layuiInit(jsArr, require, loadSuccess) {
function loadJs(jsArr, loadSuccess) {
// 接着加载自定义的js
jsArr = addVersion(jsArr);
@ -36,7 +36,7 @@ var lib = (function () {
.script(jsArr) // 接着加载自定义的js
// 全部加载成后执行
.wait(function () {
layui.use(require, loadSuccess);
loadSuccess && loadSuccess();
});
}
@ -62,16 +62,37 @@ var lib = (function () {
* @returns {lib}
*/
importJs: function (jsPath) {
jsArr = jsPath || [];
if (jsPath) {
if (typeof jsPath === 'string') {
jsPath = [jsPath];
}
jsArr = jsArr.concat(jsPath);
}
return this;
}
, config: function (cfg) {
layuiConfig = cfg;
return this;
}
, extend: function (ext) {
layuiExtend = ext;
return this;
}
/**
* layui初始化同layui.use()
* @param require
* @param loadSuccess
* @param layuiInitFun
*/
, use: function (require, loadSuccess) {
layuiInit(jsArr, require, loadSuccess);
, use: function (require, layuiInitFun) {
loadJs(jsArr, function () {
if (layuiConfig) {
layui.config(layuiConfig);
}
if (layuiExtend) {
layui.extend(layuiExtend);
}
layui.use(require, layuiInitFun);
});
}
}
})();

@ -0,0 +1,18 @@
.treeTable-empty {
width: 20px;
display: inline-block;
}
.treeTable-icon {
cursor: pointer;
}
.treeTable-icon .layui-icon-triangle-d:before {
content: "\e623";
}
.treeTable-icon.open .layui-icon-triangle-d:before {
content: "\e625";
background-color: transparent;
}

@ -0,0 +1,206 @@
layui.define(['layer', 'table'], function (exports) {
var $ = layui.jquery;
var layer = layui.layer;
var table = layui.table;
var treetable = {
// 渲染树形表格
render: function (param) {
// 检查参数
if (!treetable.checkParam(param)) {
return;
}
// 获取数据
if (param.data) {
treetable.init(param, param.data);
} else {
$.getJSON(param.url, param.where, function (res) {
treetable.init(param, res.data);
});
}
},
// 渲染表格
init: function (param, data) {
var mData = [];
var doneCallback = param.done;
var tNodes = data;
// 补上id和pid字段
for (var i = 0; i < tNodes.length; i++) {
var tt = tNodes[i];
if (!tt.id) {
if (!param.treeIdName) {
layer.msg('参数treeIdName不能为空', {icon: 5});
return;
}
tt.id = tt[param.treeIdName];
}
if (!tt.pid) {
if (!param.treePidName) {
layer.msg('参数treePidName不能为空', {icon: 5});
return;
}
tt.pid = tt[param.treePidName];
}
}
// 对数据进行排序
var sort = function (s_pid, data) {
for (var i = 0; i < data.length; i++) {
if (data[i].pid == s_pid) {
var len = mData.length;
if (len > 0 && mData[len - 1].id == s_pid) {
mData[len - 1].isParent = true;
}
mData.push(data[i]);
sort(data[i].id, data);
}
}
};
sort(param.treeSpid, tNodes);
// 重写参数
param.url = undefined;
param.data = mData;
param.page = {
count: param.data.length,
limit: param.data.length
};
param.cols[0][param.treeColIndex].templet = function (d) {
var mId = d.id;
var mPid = d.pid;
var isDir = d.isParent;
var emptyNum = treetable.getEmptyNum(mPid, mData);
var iconHtml = '';
for (var i = 0; i < emptyNum; i++) {
iconHtml += '<span class="treeTable-empty"></span>';
}
if (isDir) {
iconHtml += '<i class="layui-icon layui-icon-triangle-d"></i> <i class="layui-icon layui-icon-layer"></i>';
} else {
iconHtml += '<i class="layui-icon layui-icon-file"></i>';
}
iconHtml += '&nbsp;&nbsp;';
var ttype = isDir ? 'dir' : 'file';
var vg = '<span class="treeTable-icon open" lay-tid="' + mId + '" lay-tpid="' + mPid + '" lay-ttype="' + ttype + '">';
return vg + iconHtml + d[param.cols[0][param.treeColIndex].field] + '</span>'
};
param.done = function (res, curr, count) {
$(param.elem).next().addClass('treeTable');
$('.treeTable .layui-table-page').css('display', 'none');
$(param.elem).next().attr('treeLinkage', param.treeLinkage);
// 绑定事件换成对body绑定
/*$('.treeTable .treeTable-icon').click(function () {
treetable.toggleRows($(this), param.treeLinkage);
});*/
if (param.treeDefaultClose) {
treetable.foldAll(param.elem);
}
if (doneCallback) {
doneCallback(res, curr, count);
}
};
// 渲染表格
table.render(param);
},
// 计算缩进的数量
getEmptyNum: function (pid, data) {
var num = 0;
if (!pid) {
return num;
}
var tPid;
for (var i = 0; i < data.length; i++) {
if (pid == data[i].id) {
num += 1;
tPid = data[i].pid;
break;
}
}
return num + treetable.getEmptyNum(tPid, data);
},
// 展开/折叠行
toggleRows: function ($dom, linkage) {
var type = $dom.attr('lay-ttype');
if ('file' == type) {
return;
}
var mId = $dom.attr('lay-tid');
var isOpen = $dom.hasClass('open');
if (isOpen) {
$dom.removeClass('open');
} else {
$dom.addClass('open');
}
$dom.closest('tbody').find('tr').each(function () {
var $ti = $(this).find('.treeTable-icon');
var pid = $ti.attr('lay-tpid');
var ttype = $ti.attr('lay-ttype');
var tOpen = $ti.hasClass('open');
if (mId == pid) {
if (isOpen) {
$(this).hide();
if ('dir' == ttype && tOpen == isOpen) {
$ti.trigger('click');
}
} else {
$(this).show();
if (linkage && 'dir' == ttype && tOpen == isOpen) {
$ti.trigger('click');
}
}
}
});
},
// 检查参数
checkParam: function (param) {
if (!param.treeSpid && param.treeSpid != 0) {
layer.msg('参数treeSpid不能为空', {icon: 5});
return false;
}
if (!param.treeColIndex && param.treeColIndex != 0) {
layer.msg('参数treeColIndex不能为空', {icon: 5});
return false;
}
return true;
},
// 展开所有
expandAll: function (dom) {
$(dom).next('.treeTable').find('.layui-table-body tbody tr').each(function () {
var $ti = $(this).find('.treeTable-icon');
var ttype = $ti.attr('lay-ttype');
var tOpen = $ti.hasClass('open');
if ('dir' == ttype && !tOpen) {
$ti.trigger('click');
}
});
},
// 折叠所有
foldAll: function (dom) {
$(dom).next('.treeTable').find('.layui-table-body tbody tr').each(function () {
var $ti = $(this).find('.treeTable-icon');
var ttype = $ti.attr('lay-ttype');
var tOpen = $ti.hasClass('open');
if ('dir' == ttype && tOpen) {
$ti.trigger('click');
}
});
}
};
layui.link(layui.cache.base + 'treetable-lay/treetable.css');
// 给图标列绑定事件
$('body').on('click', '.treeTable .treeTable-icon', function () {
var treeLinkage = $(this).parents('.treeTable').attr('treeLinkage');
if ('true' == treeLinkage) {
treetable.toggleRows($(this), true);
} else {
treetable.toggleRows($(this), false);
}
});
exports('treetable', treetable);
});

@ -1,4 +1,4 @@
lib.use(['element', 'table', 'tree', 'form'], function () {
lib.importJs('../../assets/js/profile.js').use(['element', 'table', 'tree', 'form'], function () {
var ROUTE_STATUS = {
'0': '待审核'
,'1': '<span class="x-green">已启用</span>'

@ -32,43 +32,17 @@
</form>
</div>
<table class="layui-hide" id="table"></table>
<script type="text/html" id="optBar">
<table class="layui-hide" id="treeTable"></table>
<script type="text/html" id="optCol">
<a class="layui-btn layui-btn-xs layui-btn-normal" lay-event="edit">修改</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">下线</a>
</script>
</div>
<script type="text/javascript" src="../../assets/js/lib.js"></script>
<script type="text/javascript" src="serviceList.js"></script>
<script>
lib.use(['element', 'table', 'form'], function () {
var table = layui.table;
var form = layui.form;
var serverTable = table.render({
elem: '#table'
, url: ApiUtil.createUrl('service.list')
, cellMinWidth: 80 //全局定义常规单元格的最小宽度,layui 2.2.1 新增
, cols: [[
{field: 'serviceId', title: '服务名称', sort: true}
, {field: 'description', title: '描述'}
, {field: 'createTime', title: '添加时间', sort: true}
, {field: 'updateTime', title: '修改时间', sort: true}
, {fixed: 'right', title:'操作', toolbar: '#optBar', width:150}
]]
});
form.on('submit(searchFilter)', function(data){
var param = data.field;
serverTable.reload({
where: {
data: JSON.stringify(param)
}
})
return false;
});
});
</script>
</body>
</html>

@ -0,0 +1,53 @@
lib.config({
base: '../../assets/lib/layuiext/module/'
}).extend({
treetable: 'treetable-lay/treetable'
}).use(['element', 'table', 'form', 'treetable'], function () {
var layer = layui.layer;
var form = layui.form;
var treetable = layui.treetable;
var serverTable;
// 渲染表格
var renderTable = function () {
layer.load(2);
serverTable = treetable.render({
treeColIndex: 1,
treeSpid: 0,
treeIdName: 'id',
treePidName: 'parentId',
treeDefaultClose: true,
treeLinkage: false,
elem: '#treeTable',
url: ApiUtil.createUrl('service.instance.list'),
page: false,
cols: [[
{type: 'numbers'},
{field: 'name', title: '服务名称', width: 200},
{field: 'instanceId', title: 'instanceId', width: 220},
{field: 'ipAddr', title: 'IP地址', width: 150},
{field: 'serverPort', title: '端口号', width: 100},
{field: 'status', title: 'status', width: 100},
{field: 'updateTime', title: '最后更新时间', width: 150},
{fixed: 'right', templet: '#optCol', title: '操作', width: 200}
]],
done: function () {
layer.closeAll('loading');
}
});
};
renderTable();
form.on('submit(searchFilter)', function(data){
var param = data.field;
serverTable.reload({
where: {
data: JSON.stringify(param)
}
})
return false;
});
});

@ -23,6 +23,7 @@
<java.version>1.8</java.version>
<zookeeper.version>3.4.12</zookeeper.version>
<curator-recipes.version>4.0.1</curator-recipes.version>
<okhttp.version>3.11.0</okhttp.version>
</properties>
<dependencies>
@ -65,6 +66,12 @@
</exclusions>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>${okhttp.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>

@ -5,16 +5,30 @@ import com.gitee.easyopen.annotation.Api;
import com.gitee.easyopen.annotation.ApiService;
import com.gitee.easyopen.doc.annotation.ApiDoc;
import com.gitee.easyopen.doc.annotation.ApiDocMethod;
import com.gitee.easyopen.exception.ApiException;
import com.gitee.sop.adminserver.api.service.param.ServiceSearchParam;
import com.gitee.sop.adminserver.api.service.result.ServiceInfo;
import com.gitee.sop.adminserver.api.service.result.ServiceInfoVo;
import com.gitee.sop.adminserver.bean.EurekaApplication;
import com.gitee.sop.adminserver.bean.EurekaApps;
import com.gitee.sop.adminserver.bean.EurekaInstance;
import com.gitee.sop.adminserver.bean.EurekaUri;
import com.gitee.sop.adminserver.bean.ZookeeperContext;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.commons.lang.StringUtils;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import javax.annotation.PostConstruct;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
/**
@ -26,12 +40,22 @@ import java.util.stream.Collectors;
public class ServiceApi {
OkHttpClient client = new OkHttpClient();
@Autowired
private Environment environment;
private String eurekaUrl;
// eureka.client.serviceUrl.defaultZone
@Api(name = "service.list")
@ApiDocMethod(description = "服务列表", elementClass = ServiceInfo.class)
List<ServiceInfo> listServiceInfo(ServiceSearchParam param) throws Exception {
String json = this.requestEurekaServer(EurekaUri.QUERY_APPS);
EurekaApps eurekaApps = JSON.parseObject(json, EurekaApps.class);
String routeRootPath = ZookeeperContext.getSopRouteRootPath(param.getProfile());
List<ChildData> childDataList = ZookeeperContext.getChildrenData(routeRootPath);
List<ServiceInfo> serviceInfoList = childDataList.stream()
@ -53,4 +77,70 @@ public class ServiceApi {
return serviceInfoList;
}
@Api(name = "service.instance.list")
@ApiDocMethod(description = "服务列表", elementClass = ServiceInfo.class)
List<ServiceInfoVo> listService(ServiceSearchParam param) throws Exception {
String json = this.requestEurekaServer(EurekaUri.QUERY_APPS);
EurekaApps eurekaApps = JSON.parseObject(json, EurekaApps.class);
List<ServiceInfoVo> serviceInfoVoList = new ArrayList<>();
List<EurekaApplication> applicationList = eurekaApps.getApplications().getApplication();
AtomicInteger idGen = new AtomicInteger(1);
applicationList.stream()
.forEach(eurekaApplication -> {
int pid = idGen.getAndIncrement();
String name = eurekaApplication.getName();
ServiceInfoVo parent = new ServiceInfoVo();
parent.setId(pid);
parent.setName(name);
parent.setParentId(0);
serviceInfoVoList.add(parent);
List<EurekaInstance> instanceList = eurekaApplication.getInstance();
for (EurekaInstance instance : instanceList) {
ServiceInfoVo vo = new ServiceInfoVo();
BeanUtils.copyProperties(instance, vo);
int id = idGen.getAndIncrement();
vo.setId(id);
vo.setName(name);
vo.setParentId(pid);
vo.setServerPort(instance.fetchPort());
serviceInfoVoList.add(vo);
}
});
return serviceInfoVoList;
}
private String requestEurekaServer(EurekaUri uri) throws IOException {
Request request = this.buildRequest(EurekaUri.QUERY_APPS);
Response response = client.newCall(request).execute();
if (response.isSuccessful()) {
return response.body().string();
} else {
return "{}";
}
}
private Request buildRequest(EurekaUri uri) {
String apiUrl = this.eurekaUrl + uri.getUri();
Request request = new Request.Builder()
.url(apiUrl)
.addHeader("Content-Type", "application/json")
.addHeader("Accept", "application/json")
.build();
return request;
}
@PostConstruct
protected void after() {
String eurekaUrls = environment.getProperty("eureka.client.serviceUrl.defaultZone");
if (StringUtils.isBlank(eurekaUrls)) {
throw new ApiException("未指定eureka.client.serviceUrl.defaultZone参数");
}
String url = eurekaUrls.split("\\,")[0];
if (url.endsWith("/")) {
url = eurekaUrls.substring(0, eurekaUrls.length() - 1);
}
this.eurekaUrl = url;
}
}

@ -0,0 +1,52 @@
package com.gitee.sop.adminserver.api.service.result;
import com.alibaba.fastjson.annotation.JSONField;
import com.gitee.easyopen.doc.annotation.ApiDocField;
import lombok.Data;
import org.apache.commons.lang.StringUtils;
import java.util.Date;
/**
* @author tanghc
*/
@Data
public class ServiceInfoVo {
@ApiDocField(description = "id")
private Integer id;
@ApiDocField(description = "服务名称")
private String name;
@ApiDocField(description = "instanceId")
private String instanceId;
@ApiDocField(description = "ip")
private String ipAddr;
@ApiDocField(description = "端口")
private String serverPort;
@ApiDocField(description = "status")
private String status;
@ApiDocField(description = "statusPageUrl")
private String statusPageUrl;
@ApiDocField(description = "healthCheckUrl")
private String healthCheckUrl;
@ApiDocField(description = "最后更新时间")
private String lastUpdatedTimestamp;
@ApiDocField(description = "parentId")
private Integer parentId;
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
public Date getUpdateTime() {
if (StringUtils.isBlank(lastUpdatedTimestamp)) {
return null;
}
return new Date(Long.valueOf(lastUpdatedTimestamp));
}
}

@ -0,0 +1,14 @@
package com.gitee.sop.adminserver.bean;
import lombok.Data;
import java.util.List;
/**
* @author tanghc
*/
@Data
public class EurekaApplication {
private String name;
private List<EurekaInstance> instance;
}

@ -0,0 +1,13 @@
package com.gitee.sop.adminserver.bean;
import lombok.Data;
import java.util.List;
/**
* @author tanghc
*/
@Data
public class EurekaApplications {
private List<EurekaApplication> application;
}

@ -0,0 +1,11 @@
package com.gitee.sop.adminserver.bean;
import lombok.Data;
/**
* @author tanghc
*/
@Data
public class EurekaApps {
private EurekaApplications applications;
}

@ -0,0 +1,29 @@
package com.gitee.sop.adminserver.bean;
import lombok.Data;
import org.springframework.util.CollectionUtils;
import java.util.Map;
/**
* @author tanghc
*/
@Data
public class EurekaInstance {
private String instanceId;
private String ipAddr;
private Map<String, Object> port;
private String status;
private String statusPageUrl;
private String healthCheckUrl;
private String lastUpdatedTimestamp;
public String fetchPort() {
if (CollectionUtils.isEmpty(port)) {
return "";
}
return String.valueOf(port.getOrDefault("$", ""));
}
}

@ -0,0 +1,20 @@
package com.gitee.sop.adminserver.bean;
/**
* https://github.com/Netflix/eureka/wiki/Eureka-REST-operations
* @author tanghc
*/
public enum EurekaUri {
/** Query for all instances */
QUERY_APPS("/apps"),
;
String uri;
EurekaUri(String uri) {
this.uri = uri;
}
public String getUri() {
return uri;
}}

@ -19,6 +19,7 @@ public class WebConfig extends WebMvcConfigurationSupport {
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//配置映射关系
registry.addResourceHandler("/conf/**").addResourceLocations("classpath:/META-INF/resources/webjars/sop-admin-front/1.0.0-SNAPSHOT/");
registry.addResourceHandler("/opendoc/**").addResourceLocations("classpath:/META-INF/resources/opendoc/");
}
@Controller

@ -22,6 +22,13 @@ easyopen:
sop-admin:
profiles: default,prod,dev,test
# 注册中心地址,这里只是参数,并不会去注册
eureka:
port: 1111
host: localhost
client:
serviceUrl:
defaultZone: http://${eureka.host}:${eureka.port}/eureka/
logging:
level:

Loading…
Cancel
Save