mirror of
https://gitee.com/durcframework/SOP.git
synced 2025-08-11 21:57:56 +08:00
新增监控日志
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
# changelog
|
||||
|
||||
## 1.10.0
|
||||
|
||||
- 新增监控日志
|
||||
|
||||
## 1.9.0
|
||||
|
||||
- 改造限流
|
||||
|
@@ -1,27 +1,28 @@
|
||||
* [首页](/?t=1559633402239)
|
||||
* [首页](/?t=1559803778913)
|
||||
* 开发文档
|
||||
* [快速体验](files/10010_快速体验.md?t=1559633402241)
|
||||
* [项目接入到SOP](files/10011_项目接入到SOP.md?t=1559633402260)
|
||||
* [新增接口](files/10020_新增接口.md?t=1559633402260)
|
||||
* [业务参数校验](files/10030_业务参数校验.md?t=1559633402260)
|
||||
* [错误处理](files/10040_错误处理.md?t=1559633402260)
|
||||
* [编写文档](files/10041_编写文档.md?t=1559633402260)
|
||||
* [接口交互详解](files/10050_接口交互详解.md?t=1559633402261)
|
||||
* [easyopen支持](files/10070_easyopen支持.md?t=1559633402261)
|
||||
* [使用签名校验工具](files/10080_使用签名校验工具.md?t=1559633402261)
|
||||
* [ISV管理](files/10085_ISV管理.md?t=1559633402261)
|
||||
* [路由授权](files/10090_路由授权.md?t=1559633402261)
|
||||
* [接口限流](files/10092_接口限流.md?t=1559633402261)
|
||||
* [SDK开发](files/10095_SDK开发.md?t=1559633402261)
|
||||
* [使用SpringCloudGateway](files/10096_使用SpringCloudGateway.md?t=1559633402261)
|
||||
* [应用授权](files/10097_应用授权.md?t=1559633402261)
|
||||
* [更改数据节点名称](files/10099_更改数据节点名称.md?t=1559633402262)
|
||||
* [对接前端](files/10100_对接前端.md?t=1559633402262)
|
||||
* [自定义过滤器](files/10102_自定义过滤器.md?t=1559633402262)
|
||||
* [文件上传](files/10104_文件上传.md?t=1559633402262)
|
||||
* [快速体验](files/10010_快速体验.md?t=1559803778920)
|
||||
* [项目接入到SOP](files/10011_项目接入到SOP.md?t=1559803778940)
|
||||
* [新增接口](files/10020_新增接口.md?t=1559803778940)
|
||||
* [业务参数校验](files/10030_业务参数校验.md?t=1559803778940)
|
||||
* [错误处理](files/10040_错误处理.md?t=1559803778940)
|
||||
* [编写文档](files/10041_编写文档.md?t=1559803778941)
|
||||
* [接口交互详解](files/10050_接口交互详解.md?t=1559803778941)
|
||||
* [easyopen支持](files/10070_easyopen支持.md?t=1559803778941)
|
||||
* [使用签名校验工具](files/10080_使用签名校验工具.md?t=1559803778941)
|
||||
* [ISV管理](files/10085_ISV管理.md?t=1559803778941)
|
||||
* [路由授权](files/10090_路由授权.md?t=1559803778941)
|
||||
* [接口限流](files/10092_接口限流.md?t=1559803778941)
|
||||
* [监控日志](files/10093_监控日志.md?t=1559803778941)
|
||||
* [SDK开发](files/10095_SDK开发.md?t=1559803778941)
|
||||
* [使用SpringCloudGateway](files/10096_使用SpringCloudGateway.md?t=1559803778942)
|
||||
* [应用授权](files/10097_应用授权.md?t=1559803778942)
|
||||
* [更改数据节点名称](files/10099_更改数据节点名称.md?t=1559803778942)
|
||||
* [对接前端](files/10100_对接前端.md?t=1559803778942)
|
||||
* [自定义过滤器](files/10102_自定义过滤器.md?t=1559803778942)
|
||||
* [文件上传](files/10104_文件上传.md?t=1559803778942)
|
||||
* 原理分析
|
||||
* [原理分析之@ApiMapping](files/90010_原理分析之@ApiMapping.md?t=1559633402262)
|
||||
* [原理分析之路由存储](files/90011_原理分析之路由存储.md?t=1559633402262)
|
||||
* [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1559633402262)
|
||||
* [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1559633402262)
|
||||
* [常见问题](files/90100_常见问题.md?t=1559633402262)
|
||||
* [原理分析之@ApiMapping](files/90010_原理分析之@ApiMapping.md?t=1559803778942)
|
||||
* [原理分析之路由存储](files/90011_原理分析之路由存储.md?t=1559803778942)
|
||||
* [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1559803778942)
|
||||
* [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1559803778942)
|
||||
* [常见问题](files/90100_常见问题.md?t=1559803778942)
|
||||
|
44
doc/docs/files/10093_监控日志.md
Normal file
44
doc/docs/files/10093_监控日志.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# 监控日志
|
||||
|
||||
1.10.0开始sop-admin提供了简单的监控日志查询,方便在线排查问题。
|
||||
|
||||
- 错误日志统一在网关负责收集
|
||||
- 只收集未知类型的错误日志,开发人员主动throw的异常不收集
|
||||
- sop-admin通过网关提供的restful接口获取日志内容,然后在后台展示
|
||||
- 收集的日志存放在内存中,重启网关日志会消失
|
||||
- 只会收集20条不同的日志内容,相同内容会count+1。可设置`ApiConfig.storeErrorCapacity`属性扩大容量,默认容量20
|
||||
|
||||
第一次使用需要添加网关服务器实例,前往:`服务管理--监控日志--添加监控服务器`
|
||||
|
||||
## 永久保存日志
|
||||
|
||||
默认收集的日志存放在内存中,重启网关日志会消失(见:`DefaultServiceErrorManager.java`)。如果要永久保存日志内容,需要自己实现`ServiceErrorManager`接口
|
||||
|
||||
```java
|
||||
public class MyServiceErrorManager implements ServiceErrorManager {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
然后在ApiConfig中配置
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
public class ZuulConfig extends AlipayZuulConfiguration {
|
||||
|
||||
static {
|
||||
...
|
||||
ApiConfig.getInstance().setServiceErrorManager(new MyServiceErrorManager());
|
||||
// 日志收集容量,默认20。只会收集20条不同内容的日志
|
||||
ApiConfig.getInstance().setStoreErrorCapacity(20);
|
||||
...
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
- 后台预览
|
||||
|
||||

|
||||
|
||||

|
BIN
doc/docs/files/images/10093_1.png
Normal file
BIN
doc/docs/files/images/10093_1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
BIN
doc/docs/files/images/10093_2.png
Normal file
BIN
doc/docs/files/images/10093_2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 41 KiB |
13
sop-1.10.0.sql
Normal file
13
sop-1.10.0.sql
Normal file
@@ -0,0 +1,13 @@
|
||||
DROP TABLE IF EXISTS `config_common`;
|
||||
|
||||
CREATE TABLE `config_common` (
|
||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`config_group` varchar(64) NOT NULL DEFAULT '' COMMENT '配置分组',
|
||||
`config_key` varchar(64) NOT NULL DEFAULT '' COMMENT '配置key',
|
||||
`content` varchar(128) NOT NULL DEFAULT '' COMMENT '内容',
|
||||
`remark` varchar(128) DEFAULT NULL COMMENT '备注',
|
||||
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_groupkey` (`config_group`,`config_key`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='通用配置表';
|
@@ -9,39 +9,27 @@ import com.gitee.easyopen.util.CopyUtil;
|
||||
import com.gitee.fastmybatis.core.PageInfo;
|
||||
import com.gitee.fastmybatis.core.query.Query;
|
||||
import com.gitee.fastmybatis.core.query.Sort;
|
||||
import com.gitee.fastmybatis.core.query.expression.ValueExpression;
|
||||
import com.gitee.fastmybatis.core.util.MapperUtil;
|
||||
import com.gitee.fastmybatis.core.util.MyBeanUtil;
|
||||
import com.gitee.sop.adminserver.api.service.param.LimitNewAddParam;
|
||||
import com.gitee.sop.adminserver.api.service.param.LimitNewParam;
|
||||
import com.gitee.sop.adminserver.api.service.param.LimitNewUpdateParam;
|
||||
import com.gitee.sop.adminserver.api.service.param.RouteSearchParam;
|
||||
import com.gitee.sop.adminserver.api.service.result.LimitNewVO;
|
||||
import com.gitee.sop.adminserver.bean.ConfigLimitDto;
|
||||
import com.gitee.sop.adminserver.bean.GatewayRouteDefinition;
|
||||
import com.gitee.sop.adminserver.common.LimitEnum;
|
||||
import com.gitee.sop.adminserver.entity.ConfigLimit;
|
||||
import com.gitee.sop.adminserver.mapper.ConfigLimitMapper;
|
||||
import com.gitee.sop.adminserver.service.RouteConfigService;
|
||||
import com.gitee.sop.adminserver.service.RouteService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang.BooleanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 限流
|
||||
*
|
||||
* @author tanghc
|
||||
*/
|
||||
@ApiService
|
||||
@ApiDoc("服务管理")
|
||||
@ApiDoc("服务管理-限流管理")
|
||||
@Slf4j
|
||||
public class LimitNewApi {
|
||||
|
||||
|
@@ -0,0 +1,169 @@
|
||||
package com.gitee.sop.adminserver.api.service;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
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.easyopen.verify.DefaultMd5Verifier;
|
||||
import com.gitee.fastmybatis.core.query.Query;
|
||||
import com.gitee.sop.adminserver.api.service.param.LogMonitorInstanceAddParam;
|
||||
import com.gitee.sop.adminserver.api.service.result.LogMonitorInstanceVO;
|
||||
import com.gitee.sop.adminserver.common.QueryUtil;
|
||||
import com.gitee.sop.adminserver.entity.ConfigCommon;
|
||||
import com.gitee.sop.adminserver.mapper.ConfigCommonMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
@ApiService
|
||||
@ApiDoc("服务管理-日志监控")
|
||||
@Slf4j
|
||||
public class LogApi {
|
||||
|
||||
public static final String LOG_MONITOR_INSTANCE = "log.monitor.instance";
|
||||
public static final String CODE_SUCCESS = "10000";
|
||||
|
||||
@Autowired
|
||||
ConfigCommonMapper configCommonMapper;
|
||||
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
@Value("${zuul.secret}")
|
||||
private String secret;
|
||||
|
||||
@Api(name = "monitor.log.list")
|
||||
@ApiDocMethod(description = "获取监控日志")
|
||||
List<LogMonitorInstanceVO> listLog() {
|
||||
List<ConfigCommon> configCommonList = configCommonMapper.listByColumn("config_group", LOG_MONITOR_INSTANCE);
|
||||
List<LogMonitorInstanceVO> ret = new ArrayList<>();
|
||||
int id = 1;
|
||||
for (ConfigCommon configCommon : configCommonList) {
|
||||
int pid = id++;
|
||||
String ipPort = configCommon.getConfigKey();
|
||||
// 父节点
|
||||
LogMonitorInstanceVO logMonitorInstanceVOParent = new LogMonitorInstanceVO();
|
||||
logMonitorInstanceVOParent.setRawId(configCommon.getId());
|
||||
logMonitorInstanceVOParent.setTreeId(pid);
|
||||
logMonitorInstanceVOParent.setMonitorName(configCommon.getContent());
|
||||
ret.add(logMonitorInstanceVOParent);
|
||||
try {
|
||||
String logData = this.requestLogServer(ipPort, "listErrors");
|
||||
JSONObject jsonObject = JSON.parseObject(logData);
|
||||
if (CODE_SUCCESS.equals(jsonObject.getString("code"))) {
|
||||
int errorTotal = 0;
|
||||
List<LogMonitorInstanceVO> data = JSON.parseArray(jsonObject.getString("data"), LogMonitorInstanceVO.class);
|
||||
for (LogMonitorInstanceVO instanceVO : data) {
|
||||
instanceVO.setTreeId(id++);
|
||||
instanceVO.setParentId(pid);
|
||||
errorTotal += instanceVO.getCount();
|
||||
}
|
||||
ret.addAll(data);
|
||||
logMonitorInstanceVOParent.setCount(errorTotal);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("获取日志信息出错", e);
|
||||
throw new ApiException("获取日志信息出错");
|
||||
}
|
||||
}
|
||||
Collections.sort(ret, Comparator.comparing(LogMonitorInstanceVO::getCount));
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Api(name = "monitor.log.clear")
|
||||
@ApiDocMethod(description = "清空日志")
|
||||
void clearLog(@NotNull(message = "id不能为空") Long id) {
|
||||
ConfigCommon configCommon = configCommonMapper.getById(id);
|
||||
if (configCommon == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
String ipPort = configCommon.getConfigKey();
|
||||
this.requestLogServer(ipPort, "clearErrors");
|
||||
} catch (Exception e) {
|
||||
throw new ApiException("清除失败");
|
||||
}
|
||||
}
|
||||
|
||||
@Api(name = "monitor.instance.list")
|
||||
@ApiDocMethod(description = "获取已添加的监控实例")
|
||||
List<String> listServiceInstance() {
|
||||
List<ConfigCommon> configCommonList = configCommonMapper.listByColumn("config_group", LOG_MONITOR_INSTANCE);
|
||||
return configCommonList.stream()
|
||||
.map(ConfigCommon::getConfigKey)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Api(name = "monitor.instance.add")
|
||||
@ApiDocMethod(description = "添加监控实例")
|
||||
void addServiceInstance(LogMonitorInstanceAddParam param) {
|
||||
String ipPort = param.getIp() + ":" + param.getPort();
|
||||
this.checkInstance(ipPort);
|
||||
|
||||
Query query = new Query();
|
||||
query.eq("config_group", LOG_MONITOR_INSTANCE)
|
||||
.eq("config_key", ipPort);
|
||||
ConfigCommon rec = configCommonMapper.getByQuery(query);
|
||||
if (rec != null) {
|
||||
throw new ApiException("该实例已添加");
|
||||
}
|
||||
ConfigCommon configCommon = new ConfigCommon();
|
||||
configCommon.setConfigGroup(LOG_MONITOR_INSTANCE);
|
||||
configCommon.setConfigKey(ipPort);
|
||||
configCommon.setContent(param.getServiceId() + "(" + ipPort + ")");
|
||||
configCommonMapper.saveIgnoreNull(configCommon);
|
||||
}
|
||||
|
||||
private void checkInstance(String ipPort) {
|
||||
try {
|
||||
String json = this.requestLogServer(ipPort, "listErrors");
|
||||
JSONObject jsonObject = JSON.parseObject(json);
|
||||
if (!CODE_SUCCESS.equals(jsonObject.getString("code"))) {
|
||||
log.error("请求结果:{}", json);
|
||||
throw new ApiException("添加失败");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("添加失败", e);
|
||||
throw new ApiException("添加失败");
|
||||
}
|
||||
}
|
||||
|
||||
private String requestLogServer(String ipPort, String path) throws Exception {
|
||||
DefaultMd5Verifier md5Verifier = new DefaultMd5Verifier();
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("time", System.currentTimeMillis());
|
||||
String sign = md5Verifier.buildSign(params, secret);
|
||||
params.put("sign", sign);
|
||||
String query = QueryUtil.buildQueryString(params);
|
||||
String url = "http://" + ipPort + "/" + path + "?" + query;
|
||||
ResponseEntity<String> entity = restTemplate.getForEntity(url, String.class);
|
||||
if (entity.getStatusCode() != HttpStatus.OK) {
|
||||
throw new IllegalAccessException("无权访问");
|
||||
}
|
||||
return entity.getBody();
|
||||
}
|
||||
|
||||
@Api(name = "monitor.instance.del")
|
||||
@ApiDocMethod(description = "删除监控实例")
|
||||
void delServiceInstance(@NotNull(message = "id不能为空") Long id) {
|
||||
configCommonMapper.deleteById(id);
|
||||
}
|
||||
|
||||
}
|
@@ -43,7 +43,7 @@ import java.util.stream.Collectors;
|
||||
* @author tanghc
|
||||
*/
|
||||
@ApiService
|
||||
@ApiDoc("服务管理")
|
||||
@ApiDoc("服务管理-路由管理")
|
||||
@Slf4j
|
||||
public class RouteApi {
|
||||
|
||||
|
@@ -36,7 +36,7 @@ import java.util.stream.Collectors;
|
||||
* @author tanghc
|
||||
*/
|
||||
@ApiService
|
||||
@ApiDoc("服务管理")
|
||||
@ApiDoc("服务管理-服务列表")
|
||||
@Slf4j
|
||||
public class ServiceApi {
|
||||
|
||||
|
@@ -0,0 +1,19 @@
|
||||
package com.gitee.sop.adminserver.api.service.param;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
@Data
|
||||
public class LogMonitorInstanceAddParam {
|
||||
@NotBlank(message = "serviceId不能为空")
|
||||
private String serviceId;
|
||||
|
||||
@NotBlank(message = "ip不能为空")
|
||||
private String ip;
|
||||
|
||||
private int port;
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
package com.gitee.sop.adminserver.api.service.result;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
@Data
|
||||
public class LogMonitorInstanceVO {
|
||||
private String id;
|
||||
private int treeId;
|
||||
// 表主键
|
||||
private long rawId;
|
||||
private String name;
|
||||
private String version;
|
||||
private String serviceId;
|
||||
private String errorMsg;
|
||||
private long count;
|
||||
|
||||
private int parentId;
|
||||
private String monitorName;
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
package com.gitee.sop.adminserver.common;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public class QueryUtil {
|
||||
|
||||
public static String buildQueryString(Map<String, ?> params) throws UnsupportedEncodingException {
|
||||
if (params == null || params.size() == 0) {
|
||||
return "";
|
||||
}
|
||||
StringBuilder query = new StringBuilder();
|
||||
int i = 0;
|
||||
for (Map.Entry<String, ?> entry : params.entrySet()) {
|
||||
String name = entry.getKey();
|
||||
String value = String.valueOf(entry.getValue());
|
||||
if (i++ > 0) {
|
||||
query.append("&");
|
||||
}
|
||||
query.append(name).append("=").append(URLEncoder.encode(value, "UTF-8"));
|
||||
}
|
||||
return query.toString();
|
||||
}
|
||||
}
|
@@ -32,9 +32,10 @@ public class WebConfig {
|
||||
@Bean
|
||||
ApiConfig apiConfig() {
|
||||
ApiConfig apiConfig = new ApiConfig();
|
||||
apiConfig.setJsonResultSerializer(obj -> {
|
||||
return JSON.toJSONString(obj, SerializerFeature.WriteDateUseDateFormat);
|
||||
});
|
||||
apiConfig.setJsonResultSerializer(obj -> JSON.toJSONString(obj,
|
||||
SerializerFeature.WriteDateUseDateFormat
|
||||
, SerializerFeature.WriteNullStringAsEmpty
|
||||
, SerializerFeature.WriteMapNullValue));
|
||||
ApiSessionManager apiSessionManager = new ApiSessionManager();
|
||||
// session有效期
|
||||
int timeout = NumberUtils.toInt(accessTokenTimeout, 30);
|
||||
|
@@ -0,0 +1,45 @@
|
||||
package com.gitee.sop.adminserver.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
import java.util.Date;
|
||||
|
||||
|
||||
/**
|
||||
* 表名:config_common
|
||||
* 备注:通用配置表
|
||||
*
|
||||
* @author tanghc
|
||||
*/
|
||||
@Table(name = "config_common")
|
||||
@Data
|
||||
public class ConfigCommon {
|
||||
@Id
|
||||
@Column(name = "id")
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
/** 数据库字段:id */
|
||||
private Long id;
|
||||
|
||||
/** 配置分组, 数据库字段:config_group */
|
||||
private String configGroup;
|
||||
|
||||
/** 配置key, 数据库字段:config_key */
|
||||
private String configKey;
|
||||
|
||||
/** 内容, 数据库字段:content */
|
||||
private String content;
|
||||
|
||||
/** 备注, 数据库字段:remark */
|
||||
private String remark;
|
||||
|
||||
/** 数据库字段:gmt_create */
|
||||
private Date gmtCreate;
|
||||
|
||||
/** 数据库字段:gmt_modified */
|
||||
private Date gmtModified;
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
package com.gitee.sop.adminserver.mapper;
|
||||
|
||||
import com.gitee.fastmybatis.core.mapper.CrudMapper;
|
||||
|
||||
import com.gitee.sop.adminserver.entity.ConfigCommon;
|
||||
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public interface ConfigCommonMapper extends CrudMapper<ConfigCommon, Long> {
|
||||
}
|
@@ -48,3 +48,7 @@ logging:
|
||||
mybatis:
|
||||
fill: {com.gitee.fastmybatis.core.support.DateFillInsert: gmt_create,
|
||||
com.gitee.fastmybatis.core.support.DateFillUpdate: gmt_modified}
|
||||
|
||||
# 不用改,如果要改,建议全局替换修改
|
||||
zuul:
|
||||
secret: MZZOUSTua6LzApIWXCwEgbBmxSzpzC
|
@@ -1 +1 @@
|
||||
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><link rel=icon href=favicon.ico><title>SOP Admin</title><link href=static/css/chunk-elementUI.18b11d0e.css rel=stylesheet><link href=static/css/chunk-libs.3dfb7769.css rel=stylesheet><link href=static/css/app.4f0872ef.css rel=stylesheet></head><body><noscript><strong>We're sorry but SOP Admin doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script>(function(e){function t(t){for(var r,o,c=t[0],i=t[1],f=t[2],l=0,s=[];l<c.length;l++)o=c[l],u[o]&&s.push(u[o][0]),u[o]=0;for(r in i)Object.prototype.hasOwnProperty.call(i,r)&&(e[r]=i[r]);d&&d(t);while(s.length)s.shift()();return a.push.apply(a,f||[]),n()}function n(){for(var e,t=0;t<a.length;t++){for(var n=a[t],r=!0,o=1;o<n.length;o++){var c=n[o];0!==u[c]&&(r=!1)}r&&(a.splice(t--,1),e=i(i.s=n[0]))}return e}var r={},o={runtime:0},u={runtime:0},a=[];function c(e){return i.p+"static/js/"+({}[e]||e)+"."+{"chunk-19100986":"3ec98327","chunk-238a81e9":"5955f13d","chunk-29b24c82":"2b3ac7ca","chunk-2d0aa95b":"5ba26eff","chunk-2d221836":"574c9ab6","chunk-2d22c2e3":"0cbb7297","chunk-ea2e58a4":"f3f85b0e"}[e]+".js"}function i(t){if(r[t])return r[t].exports;var n=r[t]={i:t,l:!1,exports:{}};return e[t].call(n.exports,n,n.exports,i),n.l=!0,n.exports}i.e=function(e){var t=[],n={"chunk-19100986":1,"chunk-238a81e9":1,"chunk-29b24c82":1,"chunk-ea2e58a4":1};o[e]?t.push(o[e]):0!==o[e]&&n[e]&&t.push(o[e]=new Promise(function(t,n){for(var r="static/css/"+({}[e]||e)+"."+{"chunk-19100986":"a43114f3","chunk-238a81e9":"e8e2beee","chunk-29b24c82":"857979fb","chunk-2d0aa95b":"31d6cfe0","chunk-2d221836":"31d6cfe0","chunk-2d22c2e3":"31d6cfe0","chunk-ea2e58a4":"d10599db"}[e]+".css",u=i.p+r,a=document.getElementsByTagName("link"),c=0;c<a.length;c++){var f=a[c],l=f.getAttribute("data-href")||f.getAttribute("href");if("stylesheet"===f.rel&&(l===r||l===u))return t()}var s=document.getElementsByTagName("style");for(c=0;c<s.length;c++){f=s[c],l=f.getAttribute("data-href");if(l===r||l===u)return t()}var d=document.createElement("link");d.rel="stylesheet",d.type="text/css",d.onload=t,d.onerror=function(t){var r=t&&t.target&&t.target.src||u,a=new Error("Loading CSS chunk "+e+" failed.\n("+r+")");a.code="CSS_CHUNK_LOAD_FAILED",a.request=r,delete o[e],d.parentNode.removeChild(d),n(a)},d.href=u;var h=document.getElementsByTagName("head")[0];h.appendChild(d)}).then(function(){o[e]=0}));var r=u[e];if(0!==r)if(r)t.push(r[2]);else{var a=new Promise(function(t,n){r=u[e]=[t,n]});t.push(r[2]=a);var f,l=document.createElement("script");l.charset="utf-8",l.timeout=120,i.nc&&l.setAttribute("nonce",i.nc),l.src=c(e),f=function(t){l.onerror=l.onload=null,clearTimeout(s);var n=u[e];if(0!==n){if(n){var r=t&&("load"===t.type?"missing":t.type),o=t&&t.target&&t.target.src,a=new Error("Loading chunk "+e+" failed.\n("+r+": "+o+")");a.type=r,a.request=o,n[1](a)}u[e]=void 0}};var s=setTimeout(function(){f({type:"timeout",target:l})},12e4);l.onerror=l.onload=f,document.head.appendChild(l)}return Promise.all(t)},i.m=e,i.c=r,i.d=function(e,t,n){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},i.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,t){if(1&t&&(e=i(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(i.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)i.d(n,r,function(t){return e[t]}.bind(null,r));return n},i.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="",i.oe=function(e){throw console.error(e),e};var f=window["webpackJsonp"]=window["webpackJsonp"]||[],l=f.push.bind(f);f.push=t,f=f.slice();for(var s=0;s<f.length;s++)t(f[s]);var d=l;n()})([]);</script><script src=static/js/chunk-elementUI.fa810e14.js></script><script src=static/js/chunk-libs.a463a5ed.js></script><script src=static/js/app.bc31495f.js></script></body></html>
|
||||
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><link rel=icon href=favicon.ico><title>SOP Admin</title><link href=static/css/chunk-elementUI.18b11d0e.css rel=stylesheet><link href=static/css/chunk-libs.3dfb7769.css rel=stylesheet><link href=static/css/app.4f0872ef.css rel=stylesheet></head><body><noscript><strong>We're sorry but SOP Admin doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script>(function(e){function t(t){for(var r,o,a=t[0],i=t[1],f=t[2],l=0,d=[];l<a.length;l++)o=a[l],u[o]&&d.push(u[o][0]),u[o]=0;for(r in i)Object.prototype.hasOwnProperty.call(i,r)&&(e[r]=i[r]);s&&s(t);while(d.length)d.shift()();return c.push.apply(c,f||[]),n()}function n(){for(var e,t=0;t<c.length;t++){for(var n=c[t],r=!0,o=1;o<n.length;o++){var a=n[o];0!==u[a]&&(r=!1)}r&&(c.splice(t--,1),e=i(i.s=n[0]))}return e}var r={},o={runtime:0},u={runtime:0},c=[];function a(e){return i.p+"static/js/"+({}[e]||e)+"."+{"chunk-19100986":"3ec98327","chunk-238a81e9":"5955f13d","chunk-29b24c82":"2b3ac7ca","chunk-2d0aa95b":"447435b3","chunk-2d221836":"574c9ab6","chunk-2d22c2e3":"46f37153","chunk-4a59cbe4":"3309de97","chunk-ea2e58a4":"f3f85b0e"}[e]+".js"}function i(t){if(r[t])return r[t].exports;var n=r[t]={i:t,l:!1,exports:{}};return e[t].call(n.exports,n,n.exports,i),n.l=!0,n.exports}i.e=function(e){var t=[],n={"chunk-19100986":1,"chunk-238a81e9":1,"chunk-29b24c82":1,"chunk-ea2e58a4":1};o[e]?t.push(o[e]):0!==o[e]&&n[e]&&t.push(o[e]=new Promise(function(t,n){for(var r="static/css/"+({}[e]||e)+"."+{"chunk-19100986":"a43114f3","chunk-238a81e9":"e8e2beee","chunk-29b24c82":"857979fb","chunk-2d0aa95b":"31d6cfe0","chunk-2d221836":"31d6cfe0","chunk-2d22c2e3":"31d6cfe0","chunk-4a59cbe4":"31d6cfe0","chunk-ea2e58a4":"d10599db"}[e]+".css",u=i.p+r,c=document.getElementsByTagName("link"),a=0;a<c.length;a++){var f=c[a],l=f.getAttribute("data-href")||f.getAttribute("href");if("stylesheet"===f.rel&&(l===r||l===u))return t()}var d=document.getElementsByTagName("style");for(a=0;a<d.length;a++){f=d[a],l=f.getAttribute("data-href");if(l===r||l===u)return t()}var s=document.createElement("link");s.rel="stylesheet",s.type="text/css",s.onload=t,s.onerror=function(t){var r=t&&t.target&&t.target.src||u,c=new Error("Loading CSS chunk "+e+" failed.\n("+r+")");c.code="CSS_CHUNK_LOAD_FAILED",c.request=r,delete o[e],s.parentNode.removeChild(s),n(c)},s.href=u;var h=document.getElementsByTagName("head")[0];h.appendChild(s)}).then(function(){o[e]=0}));var r=u[e];if(0!==r)if(r)t.push(r[2]);else{var c=new Promise(function(t,n){r=u[e]=[t,n]});t.push(r[2]=c);var f,l=document.createElement("script");l.charset="utf-8",l.timeout=120,i.nc&&l.setAttribute("nonce",i.nc),l.src=a(e),f=function(t){l.onerror=l.onload=null,clearTimeout(d);var n=u[e];if(0!==n){if(n){var r=t&&("load"===t.type?"missing":t.type),o=t&&t.target&&t.target.src,c=new Error("Loading chunk "+e+" failed.\n("+r+": "+o+")");c.type=r,c.request=o,n[1](c)}u[e]=void 0}};var d=setTimeout(function(){f({type:"timeout",target:l})},12e4);l.onerror=l.onload=f,document.head.appendChild(l)}return Promise.all(t)},i.m=e,i.c=r,i.d=function(e,t,n){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},i.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,t){if(1&t&&(e=i(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(i.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)i.d(n,r,function(t){return e[t]}.bind(null,r));return n},i.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="",i.oe=function(e){throw console.error(e),e};var f=window["webpackJsonp"]=window["webpackJsonp"]||[],l=f.push.bind(f);f.push=t,f=f.slice();for(var d=0;d<f.length;d++)t(f[d]);var s=l;n()})([]);</script><script src=static/js/chunk-elementUI.fa810e14.js></script><script src=static/js/chunk-libs.a463a5ed.js></script><script src=static/js/app.36a3cb86.js></script></body></html>
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d22c2e3"],{f1ac:function(t,e,a){"use strict";a.r(e);var n=function(){var t=this,e=t.$createElement,a=t._self._c||e;return a("div",{staticClass:"app-container"},[a("el-form",{staticClass:"demo-form-inline",attrs:{inline:!0,model:t.searchFormData,size:"mini"}},[a("el-form-item",{attrs:{label:"serviceId"}},[a("el-input",{staticStyle:{width:"250px"},attrs:{clearable:!0,placeholder:"serviceId"},model:{value:t.searchFormData.serviceId,callback:function(e){t.$set(t.searchFormData,"serviceId",e)},expression:"searchFormData.serviceId"}})],1),t._v(" "),a("el-form-item",[a("el-button",{attrs:{type:"primary",icon:"el-icon-search"},on:{click:t.onSearchTable}},[t._v("查询")])],1)],1),t._v(" "),a("el-table",{staticStyle:{width:"100%","margin-bottom":"20px"},attrs:{data:t.tableData,border:"","row-key":"id"}},[a("el-table-column",{attrs:{prop:"name",label:"服务名称(serviceId)",width:"200"}}),t._v(" "),a("el-table-column",{attrs:{prop:"instanceId",label:"instanceId",width:"250"}}),t._v(" "),a("el-table-column",{attrs:{prop:"ipAddr",label:"IP地址",width:"150"}}),t._v(" "),a("el-table-column",{attrs:{prop:"serverPort",label:"端口号",width:"100"}}),t._v(" "),a("el-table-column",{attrs:{prop:"status",label:"服务状态",width:"100"},scopedSlots:t._u([{key:"default",fn:function(e){return[e.row.parentId>0&&"UP"===e.row.status?a("el-tag",{attrs:{type:"success"}},[t._v("已上线")]):t._e(),t._v(" "),e.row.parentId>0&&"STARTING"===e.row.status?a("el-tag",{attrs:{type:"info"}},[t._v("正在启动")]):t._e(),t._v(" "),e.row.parentId>0&&"UNKNOWN"===e.row.status?a("el-tag",[t._v("未知")]):t._e(),t._v(" "),e.row.parentId>0&&("OUT_OF_SERVICE"===e.row.status||"DOWN"===e.row.status)?a("el-tag",{attrs:{type:"danger"}},[t._v("已下线")]):t._e()]}}])}),t._v(" "),a("el-table-column",{attrs:{prop:"updateTime",label:"最后更新时间",width:"160"}}),t._v(" "),a("el-table-column",{attrs:{label:"操作",width:"100"},scopedSlots:t._u([{key:"default",fn:function(e){return[e.row.parentId>0&&"UP"===e.row.status?a("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(a){return t.onOffline(e.row)}}},[t._v("下线")]):t._e(),t._v(" "),e.row.parentId>0&&"OUT_OF_SERVICE"===e.row.status?a("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(a){return t.onOnline(e.row)}}},[t._v("上线")]):t._e()]}}])})],1)],1)},r=[],i=(a("7f7f"),a("ac6a"),{data:function(){return{searchFormData:{serviceId:""},tableData:[]}},created:function(){this.loadTable()},methods:{loadTable:function(){this.post("service.instance.list",this.searchFormData,function(t){this.tableData=this.buildTreeData(t.data)})},buildTreeData:function(t){return t.forEach(function(e){var a=e.parentId;0===a||t.forEach(function(t){if(t.id===a){var n=t.child;n||(n=[]),n.push(e),t.children=n}})}),t=t.filter(function(t){return 0===t.parentId}),t},onSearchTable:function(){this.loadTable()},onOffline:function(t){this.confirm("确定要下线【"+t.name+"】吗?",function(e){var a={serviceId:t.name,instanceId:t.instanceId};this.post("service.instance.offline",a,function(){this.tip("下线成功"),e()})})},onOnline:function(t){this.confirm("确定要上线【"+t.name+"】吗?",function(e){var a={serviceId:t.name,instanceId:t.instanceId};this.post("service.instance.online",a,function(){this.tip("上线成功"),e()})})}}}),s=i,o=a("2877"),l=Object(o["a"])(s,n,r,!1,null,null,null);e["default"]=l.exports}}]);
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d22c2e3"],{f1ac:function(t,e,a){"use strict";a.r(e);var n=function(){var t=this,e=t.$createElement,a=t._self._c||e;return a("div",{staticClass:"app-container"},[a("el-form",{staticClass:"demo-form-inline",attrs:{inline:!0,model:t.searchFormData,size:"mini"}},[a("el-form-item",{attrs:{label:"serviceId"}},[a("el-input",{staticStyle:{width:"250px"},attrs:{clearable:!0,placeholder:"serviceId"},model:{value:t.searchFormData.serviceId,callback:function(e){t.$set(t.searchFormData,"serviceId",e)},expression:"searchFormData.serviceId"}})],1),t._v(" "),a("el-form-item",[a("el-button",{attrs:{type:"primary",icon:"el-icon-search"},on:{click:t.onSearchTable}},[t._v("查询")])],1)],1),t._v(" "),a("el-table",{staticStyle:{width:"100%","margin-bottom":"20px"},attrs:{data:t.tableData,border:"","row-key":"id"}},[a("el-table-column",{attrs:{prop:"name",label:"服务名称(serviceId)",width:"200"}}),t._v(" "),a("el-table-column",{attrs:{prop:"instanceId",label:"instanceId",width:"250"}}),t._v(" "),a("el-table-column",{attrs:{prop:"ipAddr",label:"IP地址",width:"150"}}),t._v(" "),a("el-table-column",{attrs:{prop:"serverPort",label:"端口号",width:"100"}}),t._v(" "),a("el-table-column",{attrs:{prop:"status",label:"服务状态",width:"100"},scopedSlots:t._u([{key:"default",fn:function(e){return[e.row.parentId>0&&"UP"===e.row.status?a("el-tag",{attrs:{type:"success"}},[t._v("已上线")]):t._e(),t._v(" "),e.row.parentId>0&&"STARTING"===e.row.status?a("el-tag",{attrs:{type:"info"}},[t._v("正在启动")]):t._e(),t._v(" "),e.row.parentId>0&&"UNKNOWN"===e.row.status?a("el-tag",[t._v("未知")]):t._e(),t._v(" "),e.row.parentId>0&&("OUT_OF_SERVICE"===e.row.status||"DOWN"===e.row.status)?a("el-tag",{attrs:{type:"danger"}},[t._v("已下线")]):t._e()]}}])}),t._v(" "),a("el-table-column",{attrs:{prop:"updateTime",label:"最后更新时间",width:"160"}}),t._v(" "),a("el-table-column",{attrs:{label:"操作",width:"100"},scopedSlots:t._u([{key:"default",fn:function(e){return[e.row.parentId>0&&"UP"===e.row.status?a("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(a){return t.onOffline(e.row)}}},[t._v("下线")]):t._e(),t._v(" "),e.row.parentId>0&&"OUT_OF_SERVICE"===e.row.status?a("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(a){return t.onOnline(e.row)}}},[t._v("上线")]):t._e()]}}])})],1)],1)},r=[],i=(a("7f7f"),a("ac6a"),{data:function(){return{searchFormData:{serviceId:""},tableData:[]}},created:function(){this.loadTable()},methods:{loadTable:function(){this.post("service.instance.list",this.searchFormData,function(t){this.tableData=this.buildTreeData(t.data)})},buildTreeData:function(t){return t.forEach(function(e){var a=e.parentId;0===a||t.forEach(function(t){if(t.id===a){var n=t.children;n||(n=[]),n.push(e),t.children=n}})}),t=t.filter(function(t){return 0===t.parentId}),t},onSearchTable:function(){this.loadTable()},onOffline:function(t){this.confirm("确定要下线【"+t.name+"】吗?",function(e){var a={serviceId:t.name,instanceId:t.instanceId};this.post("service.instance.offline",a,function(){this.tip("下线成功"),e()})})},onOnline:function(t){this.confirm("确定要上线【"+t.name+"】吗?",function(e){var a={serviceId:t.name,instanceId:t.instanceId};this.post("service.instance.online",a,function(){this.tip("上线成功"),e()})})}}}),s=i,o=a("2877"),l=Object(o["a"])(s,n,r,!1,null,null,null);e["default"]=l.exports}}]);
|
File diff suppressed because one or more lines are too long
@@ -78,6 +78,12 @@ export const constantRoutes = [
|
||||
name: 'Limit',
|
||||
component: () => import('@/views/service/limit/index2'),
|
||||
meta: { title: '限流管理' }
|
||||
},
|
||||
{
|
||||
path: 'log',
|
||||
name: 'Log',
|
||||
component: () => import('@/views/service/log/index'),
|
||||
meta: { title: '监控日志' }
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@@ -35,7 +35,7 @@ Object.assign(Vue.prototype, {
|
||||
const resp = response.data
|
||||
const code = resp.code
|
||||
if (!code || code === '-9') {
|
||||
that.$message.error('系统错误')
|
||||
that.$message.error(resp.msg || '系统错误')
|
||||
return
|
||||
}
|
||||
if (code === '-100' || code === '18' || code === '21') { // 未登录
|
||||
@@ -96,6 +96,9 @@ Object.assign(Vue.prototype, {
|
||||
},
|
||||
logout: function() {
|
||||
removeToken()
|
||||
this.$router.push({ path: `/login?redirect=${this.$route.fullPath}` })
|
||||
const fullPath = this.$route.fullPath
|
||||
if (fullPath.indexOf('login?redirect') === -1) {
|
||||
this.$router.push({ path: `/login?redirect=${fullPath}` })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@@ -92,7 +92,7 @@ export default {
|
||||
// 如果ele是子元素的话 ,把ele扔到他的父亲的child数组中.
|
||||
data.forEach(d => {
|
||||
if (d.id === parentId) {
|
||||
let childArray = d.child
|
||||
let childArray = d.children
|
||||
if (!childArray) {
|
||||
childArray = []
|
||||
}
|
||||
|
234
sop-admin/sop-admin-vue/src/views/service/log/index.vue
Normal file
234
sop-admin/sop-admin-vue/src/views/service/log/index.vue
Normal file
@@ -0,0 +1,234 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form :inline="true" :model="searchFormData" class="demo-form-inline" size="mini">
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-plus" @click="onAddServer">添加监控服务器</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table
|
||||
:data="tableData"
|
||||
style="width: 100%;margin-bottom: 20px;"
|
||||
border
|
||||
row-key="treeId"
|
||||
empty-text="请添加监控服务器"
|
||||
>
|
||||
<el-table-column
|
||||
prop="monitorName"
|
||||
label="网关实例"
|
||||
width="300"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<span v-if="scope.row.parentId === 0">{{ scope.row.monitorName }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="serviceId"
|
||||
label="serviceId"
|
||||
width="200"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<span v-if="scope.row.parentId > 0">{{ scope.row.serviceId }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="name"
|
||||
label="接口名 (版本号)"
|
||||
width="200"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
{{ scope.row.name + (scope.row.version ? ' (' + scope.row.version + ')' : '') }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="count"
|
||||
label="出错次数"
|
||||
width="100"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="errorMsg"
|
||||
label="报错信息"
|
||||
width="300"
|
||||
>
|
||||
<template v-if="scope.row.parentId > 0" slot-scope="scope">
|
||||
<div style="display: inline-block;" v-html="showErrorMsg(scope.row)"></div> <el-button type="text" size="mini" @click="onShowErrorDetail(scope.row)">详情</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
width="180"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<el-button v-if="scope.row.parentId === 0 && scope.row.children" type="text" size="mini" @click="onClearLog(scope.row)">清空日志</el-button>
|
||||
<el-button v-if="scope.row.parentId === 0" type="text" size="mini" @click="onDelete(scope.row)">删除实例</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- dialog -->
|
||||
<el-dialog
|
||||
title="选择服务器实例"
|
||||
:visible.sync="logDialogInstanceVisible"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form
|
||||
ref="logDialogForm"
|
||||
:model="logDialogFormData"
|
||||
:rules="rulesLog"
|
||||
label-width="150px"
|
||||
size="mini"
|
||||
>
|
||||
<el-form-item>
|
||||
<p style="color: #878787;">只能选择网关实例,其它实例不支持</p>
|
||||
</el-form-item>
|
||||
<el-form-item prop="instanceData" label="服务器实例">
|
||||
<el-select v-model="logDialogFormData.instanceData" value-key="id" style="width: 400px;">
|
||||
<el-option
|
||||
v-for="item in serviceData"
|
||||
:key="item.id"
|
||||
:label="item.name + '(' + item.ipAddr + ':' + item.serverPort + ')'"
|
||||
:value="item"
|
||||
:disabled="isOptionDisabled(item)"
|
||||
>
|
||||
<span style="float: left">{{ item.name }} <span v-if="isOptionDisabled(item)">(已添加)</span></span>
|
||||
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.ipAddr + ':' + item.serverPort }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="logDialogInstanceVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="onLogDialogSave">保 存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
<el-dialog
|
||||
title="错误详情"
|
||||
:visible.sync="logDetailVisible"
|
||||
width="60%"
|
||||
>
|
||||
<div style="overflow-x: auto" v-html="errorMsgDetail"></div>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="logDetailVisible = false">关 闭</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
searchFormData: {},
|
||||
tableData: [],
|
||||
serviceData: [],
|
||||
// 已经添加的实例
|
||||
addedInstanceList: [],
|
||||
logDialogFormData: {
|
||||
instanceData: null
|
||||
},
|
||||
logDialogInstanceVisible: false,
|
||||
logDetailVisible: false,
|
||||
rulesLog: {
|
||||
instanceData: [
|
||||
{ required: true, message: '不能为空', trigger: 'blur' }
|
||||
]
|
||||
},
|
||||
errorMsgDetail: ''
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadServiceInstance()
|
||||
this.loadTable()
|
||||
},
|
||||
methods: {
|
||||
loadServiceInstance: function() {
|
||||
this.post('service.instance.list', {}, function(resp) {
|
||||
this.serviceData = resp.data.filter(el => {
|
||||
return el.instanceId && el.instanceId.length > 0
|
||||
})
|
||||
})
|
||||
this.post('monitor.instance.list', {}, function(resp) {
|
||||
this.addedInstanceList = resp.data
|
||||
})
|
||||
},
|
||||
loadTable: function() {
|
||||
this.post('monitor.log.list', {}, function(resp) {
|
||||
this.tableData = this.buildTreeData(resp.data)
|
||||
})
|
||||
},
|
||||
isOptionDisabled: function(item) {
|
||||
const ipPort = item.ipAddr + ':' + item.serverPort
|
||||
const index = this.addedInstanceList.findIndex((value, index, arr) => {
|
||||
return value === ipPort
|
||||
})
|
||||
return index > -1
|
||||
},
|
||||
buildTreeData: function(data) {
|
||||
data.forEach(ele => {
|
||||
const parentId = ele.parentId
|
||||
if (parentId === 0) {
|
||||
// 是根元素 ,不做任何操作,如果是正常的for-i循环,可以直接continue.
|
||||
} else {
|
||||
// 如果ele是子元素的话 ,把ele扔到他的父亲的child数组中.
|
||||
data.forEach(d => {
|
||||
if (d.treeId === parentId) {
|
||||
let childArray = d.children
|
||||
if (!childArray) {
|
||||
childArray = []
|
||||
}
|
||||
childArray.push(ele)
|
||||
d.children = childArray
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
// 去除重复元素
|
||||
data = data.filter(ele => ele.parentId === 0)
|
||||
return data
|
||||
},
|
||||
showErrorMsg: function(row) {
|
||||
const msg = row.errorMsg.replace(/\<br\>/g, '')
|
||||
return msg.substring(0, 30) + '...'
|
||||
},
|
||||
onAddServer: function() {
|
||||
this.logDialogInstanceVisible = true
|
||||
},
|
||||
onDelete: function(row) {
|
||||
this.confirm('确定要删除实例【' + row.monitorName + '】吗?', function(done) {
|
||||
this.post('monitor.instance.del', { id: row.rawId }, function(resp) {
|
||||
done()
|
||||
this.tip('删除成功')
|
||||
this.loadTable()
|
||||
})
|
||||
})
|
||||
},
|
||||
onClearLog: function(row) {
|
||||
this.confirm('确定要清空日志吗?', function(done) {
|
||||
this.post('monitor.log.clear', { id: row.rawId }, function(resp) {
|
||||
done()
|
||||
this.tip('清空成功')
|
||||
this.loadTable()
|
||||
})
|
||||
})
|
||||
},
|
||||
onShowErrorDetail: function(row) {
|
||||
this.errorMsgDetail = row.errorMsg
|
||||
this.logDetailVisible = true
|
||||
},
|
||||
onLogDialogSave: function() {
|
||||
this.$refs['logDialogForm'].validate((valid) => {
|
||||
if (valid) {
|
||||
const instanceData = this.logDialogFormData.instanceData
|
||||
const data = {
|
||||
serviceId: instanceData.serviceId,
|
||||
ip: instanceData.ipAddr,
|
||||
port: instanceData.serverPort
|
||||
}
|
||||
this.post('monitor.instance.add', data, function(resp) {
|
||||
this.logDialogInstanceVisible = false
|
||||
this.loadTable()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@@ -79,12 +79,12 @@
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="mergeResult"
|
||||
label="合并结果"
|
||||
width="80"
|
||||
label="统一格式输出"
|
||||
width="120"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<span v-if="scope.row.mergeResult === 1" style="color:#67C23A">合并</span>
|
||||
<span v-if="scope.row.mergeResult === 0" style="color:#E6A23C">不合并</span>
|
||||
<span v-if="scope.row.mergeResult === 1" style="color:#67C23A">是</span>
|
||||
<span v-if="scope.row.mergeResult === 0" style="color:#E6A23C">否</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
|
@@ -8,9 +8,11 @@ import com.gitee.sop.gatewaycommon.limit.LimitManager;
|
||||
import com.gitee.sop.gatewaycommon.manager.DefaultIsvRoutePermissionManager;
|
||||
import com.gitee.sop.gatewaycommon.manager.DefaultLimitConfigManager;
|
||||
import com.gitee.sop.gatewaycommon.manager.DefaultRouteConfigManager;
|
||||
import com.gitee.sop.gatewaycommon.manager.DefaultServiceErrorManager;
|
||||
import com.gitee.sop.gatewaycommon.manager.IsvRoutePermissionManager;
|
||||
import com.gitee.sop.gatewaycommon.manager.LimitConfigManager;
|
||||
import com.gitee.sop.gatewaycommon.manager.RouteConfigManager;
|
||||
import com.gitee.sop.gatewaycommon.manager.ServiceErrorManager;
|
||||
import com.gitee.sop.gatewaycommon.param.ParamBuilder;
|
||||
import com.gitee.sop.gatewaycommon.result.DataNameBuilder;
|
||||
import com.gitee.sop.gatewaycommon.result.DefaultDataNameBuilder;
|
||||
@@ -128,6 +130,11 @@ public class ApiConfig {
|
||||
*/
|
||||
private ResultAppender resultAppender;
|
||||
|
||||
/**
|
||||
* 处理错误信息
|
||||
*/
|
||||
private ServiceErrorManager serviceErrorManager = new DefaultServiceErrorManager();
|
||||
|
||||
// -------- fields ---------
|
||||
|
||||
/**
|
||||
@@ -162,6 +169,11 @@ public class ApiConfig {
|
||||
*/
|
||||
private boolean showReturnSign = true;
|
||||
|
||||
/**
|
||||
* 保存错误信息容器的容量
|
||||
*/
|
||||
private int storeErrorCapacity = 20;
|
||||
|
||||
public void addAppSecret(Map<String, String> appSecretPair) {
|
||||
for (Map.Entry<String, String> entry : appSecretPair.entrySet()) {
|
||||
this.isvManager.update(new IsvDefinition(entry.getKey(), entry.getValue()));
|
||||
|
@@ -0,0 +1,15 @@
|
||||
package com.gitee.sop.gatewaycommon.bean;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
@Data
|
||||
public class ErrorDefinition {
|
||||
private String name;
|
||||
private String version;
|
||||
private String serviceId;
|
||||
private String errorMsg;
|
||||
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
package com.gitee.sop.gatewaycommon.bean;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
public class ErrorEntity {
|
||||
private String id;
|
||||
private String name;
|
||||
private String version;
|
||||
private String serviceId;
|
||||
private String errorMsg;
|
||||
private long count;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
ErrorEntity that = (ErrorEntity) o;
|
||||
return id.equals(that.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id);
|
||||
}
|
||||
}
|
@@ -33,7 +33,9 @@ public class SopConstants {
|
||||
|
||||
public static final String CACHE_API_PARAM = "cacheApiParam";
|
||||
|
||||
public static final String X_BIZ_ERROR_CODE = "x-biz-error-code";
|
||||
public static final String X_SERVICE_ERROR_CODE = "x-service-error-code";
|
||||
|
||||
public static final String X_SERVICE_ERROR_MESSAGE = "x-service-error-message";
|
||||
|
||||
public static final int BIZ_ERROR_STATUS = 4000;
|
||||
|
||||
|
@@ -28,7 +28,7 @@ public class GatewayResultExecutor extends BaseExecutorAdapter<ServerWebExchange
|
||||
@Override
|
||||
public int getResponseStatus(ServerWebExchange exchange) {
|
||||
int responseStatus = HttpStatus.OK.value();
|
||||
List<String> errorCodeList = exchange.getResponse().getHeaders().get(SopConstants.X_BIZ_ERROR_CODE);
|
||||
List<String> errorCodeList = exchange.getResponse().getHeaders().get(SopConstants.X_SERVICE_ERROR_CODE);
|
||||
if (!CollectionUtils.isEmpty(errorCodeList)) {
|
||||
String errorCode = errorCodeList.get(0);
|
||||
responseStatus = Integer.valueOf(errorCode);
|
||||
@@ -37,7 +37,18 @@ public class GatewayResultExecutor extends BaseExecutorAdapter<ServerWebExchange
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ?> getApiParam(ServerWebExchange exchange) {
|
||||
public String getResponseErrorMessage(ServerWebExchange exchange) {
|
||||
String errorMsg = null;
|
||||
List<String> errorMessageList = exchange.getResponse().getHeaders().get(SopConstants.X_SERVICE_ERROR_MESSAGE);
|
||||
if (!CollectionUtils.isEmpty(errorMessageList)) {
|
||||
errorMsg = errorMessageList.get(0);
|
||||
}
|
||||
exchange.getResponse().getHeaders().remove(SopConstants.X_SERVICE_ERROR_MESSAGE);
|
||||
return errorMsg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getApiParam(ServerWebExchange exchange) {
|
||||
return exchange.getAttribute(SopConstants.CACHE_REQUEST_BODY_FOR_MAP);
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,51 @@
|
||||
package com.gitee.sop.gatewaycommon.manager;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
|
||||
import com.gitee.sop.gatewaycommon.bean.ErrorDefinition;
|
||||
import com.gitee.sop.gatewaycommon.bean.ErrorEntity;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public class DefaultServiceErrorManager implements ServiceErrorManager {
|
||||
|
||||
private static Map<String, ErrorEntity> store = new ConcurrentHashMap<>(128);
|
||||
|
||||
@Override
|
||||
public Collection<ErrorEntity> listAllErrors() {
|
||||
return store.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveError(ErrorDefinition errorDefinition) {
|
||||
boolean hasCapacity = store.size() < ApiConfig.getInstance().getStoreErrorCapacity();
|
||||
// 这里还可以做其它事情,比如错误量到达一定数目后,自动发送邮件/微信给开发人员,方便及时获取异常情况
|
||||
String id = this.buildId(errorDefinition);
|
||||
ErrorEntity errorEntity = store.get(id);
|
||||
if (errorEntity == null && hasCapacity) {
|
||||
errorEntity = new ErrorEntity();
|
||||
BeanUtils.copyProperties(errorDefinition, errorEntity);
|
||||
errorEntity.setId(id);
|
||||
store.put(id, errorEntity);
|
||||
}
|
||||
if (errorEntity != null) {
|
||||
errorEntity.setCount(errorEntity.getCount() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
store.clear();
|
||||
}
|
||||
|
||||
protected String buildId(ErrorDefinition errorDefinition) {
|
||||
return DigestUtils.md5Hex(errorDefinition.getServiceId() + errorDefinition.getErrorMsg());
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
package com.gitee.sop.gatewaycommon.manager;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.bean.ErrorDefinition;
|
||||
import com.gitee.sop.gatewaycommon.bean.ErrorEntity;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public interface ServiceErrorManager {
|
||||
|
||||
/**
|
||||
* 保存错误信息
|
||||
* @param errorDefinition
|
||||
*/
|
||||
void saveError(ErrorDefinition errorDefinition);
|
||||
|
||||
/**
|
||||
* 清除日志
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* 获取所有错误信息
|
||||
* @return
|
||||
*/
|
||||
Collection<ErrorEntity> listAllErrors();
|
||||
}
|
@@ -29,6 +29,14 @@ public class ApiParam extends JSONObject implements Param {
|
||||
|
||||
private transient ApiUploadContext apiUploadContext;
|
||||
|
||||
public static ApiParam build(Map<String, ?> map) {
|
||||
ApiParam apiParam = new ApiParam();
|
||||
for (Map.Entry<String, ?> entry : map.entrySet()) {
|
||||
apiParam.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
return apiParam;
|
||||
}
|
||||
|
||||
@JSONField(serialize = false)
|
||||
public String getRouteId() {
|
||||
return this.fetchNameVersion();
|
||||
|
@@ -4,17 +4,24 @@ import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
|
||||
import com.gitee.sop.gatewaycommon.bean.ApiContext;
|
||||
import com.gitee.sop.gatewaycommon.bean.BaseRouteDefinition;
|
||||
import com.gitee.sop.gatewaycommon.bean.BaseServiceRouteInfo;
|
||||
import com.gitee.sop.gatewaycommon.bean.ErrorDefinition;
|
||||
import com.gitee.sop.gatewaycommon.bean.SopConstants;
|
||||
import com.gitee.sop.gatewaycommon.bean.TargetRoute;
|
||||
import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext;
|
||||
import com.gitee.sop.gatewaycommon.message.ErrorEnum;
|
||||
import com.gitee.sop.gatewaycommon.message.ErrorMeta;
|
||||
import com.gitee.sop.gatewaycommon.param.ParamNames;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.apache.commons.lang.BooleanUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
@@ -33,17 +40,27 @@ public abstract class BaseExecutorAdapter<T, R> implements ResultExecutor<T, R>
|
||||
|
||||
/**
|
||||
* 获取业务方约定的返回码
|
||||
*
|
||||
* @param t
|
||||
* @return 返回返回码
|
||||
*/
|
||||
public abstract int getResponseStatus(T t);
|
||||
|
||||
/**
|
||||
* 获取微服务端的错误信息
|
||||
*
|
||||
* @param t
|
||||
* @return
|
||||
*/
|
||||
public abstract String getResponseErrorMessage(T t);
|
||||
|
||||
/**
|
||||
* 返回Api参数
|
||||
*
|
||||
* @param t
|
||||
* @return 返回api参数
|
||||
*/
|
||||
public abstract Map<String, ?> getApiParam(T t);
|
||||
public abstract Map<String, Object> getApiParam(T t);
|
||||
|
||||
@Override
|
||||
public String mergeResult(T request, String serviceResult) {
|
||||
@@ -65,6 +82,7 @@ public abstract class BaseExecutorAdapter<T, R> implements ResultExecutor<T, R>
|
||||
jsonObjectService.put(GATEWAY_CODE_NAME, ISP_BIZ_ERROR.getCode());
|
||||
jsonObjectService.put(GATEWAY_MSG_NAME, ISP_BIZ_ERROR.getError().getMsg());
|
||||
} else {
|
||||
this.storeError(request);
|
||||
// 微服务端有可能返回500错误
|
||||
// {"path":"/book/getBook3","error":"Internal Server Error","message":"id不能为空","timestamp":"2019-02-13T07:41:00.495+0000","status":500}
|
||||
jsonObjectService = new JSONObject();
|
||||
@@ -74,8 +92,24 @@ public abstract class BaseExecutorAdapter<T, R> implements ResultExecutor<T, R>
|
||||
return this.merge(request, jsonObjectService);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存错误信息
|
||||
*
|
||||
* @param request
|
||||
*/
|
||||
protected void storeError(T request) {
|
||||
ApiInfo apiInfo = this.getApiInfo(request);
|
||||
String errorMsg = this.getResponseErrorMessage(request);
|
||||
ErrorDefinition errorDefinition = new ErrorDefinition();
|
||||
BeanUtils.copyProperties(apiInfo, errorDefinition);
|
||||
errorDefinition.setErrorMsg(errorMsg);
|
||||
ApiConfig.getInstance().getServiceErrorManager().saveError(errorDefinition);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 该路由是否合并结果
|
||||
*
|
||||
* @param request
|
||||
* @return true:需要合并
|
||||
*/
|
||||
@@ -85,22 +119,43 @@ public abstract class BaseExecutorAdapter<T, R> implements ResultExecutor<T, R>
|
||||
if (defaultSetting != null) {
|
||||
return defaultSetting;
|
||||
}
|
||||
Map<String, ?> params = this.getApiParam(request);
|
||||
if (params == null) {
|
||||
return true;
|
||||
}
|
||||
Object name = params.get(ParamNames.API_NAME);
|
||||
Object version = params.get(ParamNames.VERSION_NAME);
|
||||
if(name == null || version == null) {
|
||||
return true;
|
||||
}
|
||||
TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(String.valueOf(name) + version);
|
||||
if (targetRoute == null) {
|
||||
return true;
|
||||
} else {
|
||||
int mergeResult = targetRoute.getRouteDefinition().getMergeResult();
|
||||
ApiInfo apiInfo = this.getApiInfo(request);
|
||||
BaseRouteDefinition baseRouteDefinition = apiInfo.baseRouteDefinition;
|
||||
return Optional.ofNullable(baseRouteDefinition)
|
||||
.map(routeDefinition -> {
|
||||
int mergeResult = baseRouteDefinition.getMergeResult();
|
||||
return BooleanUtils.toBoolean(mergeResult);
|
||||
})
|
||||
.orElse(true);
|
||||
}
|
||||
|
||||
protected ApiInfo getApiInfo(T request) {
|
||||
Map<String, Object> params = this.getApiParam(request);
|
||||
String name = Optional.ofNullable(params)
|
||||
.map(map -> (String) map.get(ParamNames.API_NAME))
|
||||
.orElse("method.unknown");
|
||||
|
||||
String version = Optional.ofNullable(params)
|
||||
.map(map -> (String) map.get(ParamNames.VERSION_NAME))
|
||||
.orElse("version.unknown");
|
||||
|
||||
TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(name + version);
|
||||
|
||||
String serviceId = Optional.ofNullable(targetRoute)
|
||||
.flatMap(route -> Optional.ofNullable(route.getServiceRouteInfo()))
|
||||
.map(BaseServiceRouteInfo::getServiceId)
|
||||
.orElse("serviceId.unknown");
|
||||
|
||||
BaseRouteDefinition baseRouteDefinition = Optional.ofNullable(targetRoute)
|
||||
.map(route -> route.getRouteDefinition())
|
||||
.orElse(null);
|
||||
|
||||
ApiInfo apiInfo = new ApiInfo();
|
||||
apiInfo.name = name;
|
||||
apiInfo.version = version;
|
||||
apiInfo.serviceId = serviceId;
|
||||
apiInfo.baseRouteDefinition = baseRouteDefinition;
|
||||
return apiInfo;
|
||||
}
|
||||
|
||||
protected String wrapResult(String serviceResult) {
|
||||
@@ -150,6 +205,7 @@ public abstract class BaseExecutorAdapter<T, R> implements ResultExecutor<T, R>
|
||||
|
||||
/**
|
||||
* 这里需要使用平台的私钥生成一个sign,需要配置两套公私钥。目前暂未实现
|
||||
*
|
||||
* @param appKey
|
||||
* @return
|
||||
*/
|
||||
@@ -158,4 +214,13 @@ public abstract class BaseExecutorAdapter<T, R> implements ResultExecutor<T, R>
|
||||
return null;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
protected static class ApiInfo {
|
||||
private String name;
|
||||
private String version;
|
||||
private String serviceId;
|
||||
private BaseRouteDefinition baseRouteDefinition;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,13 @@
|
||||
package com.gitee.sop.gatewaycommon.result;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class JsonResult extends ApiResult {
|
||||
private Object data;
|
||||
}
|
@@ -15,7 +15,6 @@ import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
@@ -26,23 +25,36 @@ public class ZuulResultExecutor extends BaseExecutorAdapter<RequestContext, Stri
|
||||
@Override
|
||||
public int getResponseStatus(RequestContext requestContext) {
|
||||
List<Pair<String, String>> bizHeaders = requestContext.getZuulResponseHeaders();
|
||||
Optional<String> first = bizHeaders.stream()
|
||||
.filter(header -> {
|
||||
return SopConstants.X_BIZ_ERROR_CODE.equals(header.first());
|
||||
}).map(header -> {
|
||||
return header.second();
|
||||
}).findFirst();
|
||||
int status = bizHeaders.stream()
|
||||
.filter(header -> SopConstants.X_SERVICE_ERROR_CODE.equals(header.first()))
|
||||
.map(header -> Integer.valueOf(header.second()))
|
||||
.findFirst()
|
||||
.orElse(requestContext.getResponseStatusCode());
|
||||
|
||||
String status = first.orElseGet(() -> {
|
||||
int respStatus = requestContext.getResponseStatusCode();
|
||||
return String.valueOf(respStatus);
|
||||
});
|
||||
|
||||
return Integer.valueOf(status);
|
||||
return status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ?> getApiParam(RequestContext requestContext) {
|
||||
public String getResponseErrorMessage(RequestContext requestContext) {
|
||||
List<Pair<String, String>> bizHeaders = requestContext.getZuulResponseHeaders();
|
||||
int index = -1;
|
||||
String errorMsg = null;
|
||||
for (int i = 0; i < bizHeaders.size(); i++) {
|
||||
Pair<String, String> header = bizHeaders.get(i);
|
||||
if (SopConstants.X_SERVICE_ERROR_MESSAGE.equals(header.first())) {
|
||||
errorMsg = header.second();
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (index > -1) {
|
||||
requestContext.getZuulResponseHeaders().remove(index);
|
||||
}
|
||||
return errorMsg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getApiParam(RequestContext requestContext) {
|
||||
return ZuulContext.getApiParam();
|
||||
}
|
||||
|
||||
|
@@ -20,6 +20,17 @@ import javax.servlet.http.HttpServletResponse;
|
||||
@Slf4j
|
||||
public class DefaultGlobalExceptionHandler implements GlobalExceptionHandler {
|
||||
|
||||
/**
|
||||
* 与网关约定好的状态码,表示业务出错
|
||||
*/
|
||||
public static final int BIZ_ERROR_CODE = 4000;
|
||||
/**
|
||||
* 系统错误
|
||||
*/
|
||||
public static final int SYSTEM_ERROR_CODE = 5050;
|
||||
|
||||
public static final String X_SERVICE_ERROR_CODE = "x-service-error-code";
|
||||
|
||||
@RequestMapping("/error")
|
||||
@ResponseBody
|
||||
public Object error(HttpServletRequest request, HttpServletResponse response) {
|
||||
@@ -27,16 +38,42 @@ public class DefaultGlobalExceptionHandler implements GlobalExceptionHandler {
|
||||
return serviceResultBuilder.buildError(request, response, new ServiceException("系统繁忙"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 捕获手动抛出的异常
|
||||
* @param request
|
||||
* @param response
|
||||
* @param exception
|
||||
* @return
|
||||
*/
|
||||
@ExceptionHandler(ServiceException.class)
|
||||
@ResponseBody
|
||||
public Object serviceExceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception exception) {
|
||||
response.addHeader(X_SERVICE_ERROR_CODE, String.valueOf(BIZ_ERROR_CODE));
|
||||
return this.processError(request, response, exception);
|
||||
}
|
||||
|
||||
/**
|
||||
* 捕获未知异常
|
||||
* @param request
|
||||
* @param response
|
||||
* @param exception
|
||||
* @return
|
||||
*/
|
||||
@ExceptionHandler(Exception.class)
|
||||
@ResponseBody
|
||||
public Object exceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception exception) {
|
||||
response.addHeader(X_SERVICE_ERROR_CODE, String.valueOf(SYSTEM_ERROR_CODE));
|
||||
log.error("系统错误", exception);
|
||||
StringBuilder msg = new StringBuilder();
|
||||
msg.append(exception.getMessage());
|
||||
StackTraceElement[] stackTrace = exception.getStackTrace();
|
||||
// 取5行错误内容
|
||||
int lineCount = 5;
|
||||
for (int i = 0; i < stackTrace.length && i < lineCount ; i++) {
|
||||
StackTraceElement stackTraceElement = stackTrace[i];
|
||||
msg.append("<br> at ").append(stackTraceElement.toString());
|
||||
}
|
||||
response.setHeader("x-service-error-message", msg.toString());
|
||||
return this.processError(request, response, new ServiceException("系统繁忙"));
|
||||
}
|
||||
|
||||
|
@@ -16,15 +16,9 @@ import javax.servlet.http.HttpServletResponse;
|
||||
public class DefaultServiceResultBuilder implements ServiceResultBuilder {
|
||||
|
||||
public static final String ISP_UNKNOWN_ERROR = "isp.unknown-error";
|
||||
/**
|
||||
* 与网关约定好的状态码,表示业务出错
|
||||
*/
|
||||
public static final int BIZ_ERROR_CODE = 4000;
|
||||
public static final String X_BIZ_ERROR_CODE = "x-biz-error-code";
|
||||
|
||||
@Override
|
||||
public Object buildError(HttpServletRequest request, HttpServletResponse response, Throwable throwable) {
|
||||
response.addHeader(X_BIZ_ERROR_CODE, String.valueOf(BIZ_ERROR_CODE));
|
||||
String subCode, subMsg;
|
||||
if (throwable instanceof ServiceException) {
|
||||
ServiceException ex = (ServiceException) throwable;
|
||||
|
@@ -41,6 +41,9 @@ public class ZuulConfig extends AlipayZuulConfiguration {
|
||||
//public class ZuulConfig extends EasyopenZuulConfiguration {
|
||||
// static {
|
||||
// new ManagerInitializer();
|
||||
// Map<String, String> appSecretMap = new HashMap<>();
|
||||
// appSecretMap.put("easyopen_test", "G9w0BAQEFAAOCAQ8AMIIBCgKCA");
|
||||
// ApiConfig.getInstance().addAppSecret(appSecretMap);
|
||||
// }
|
||||
//
|
||||
//}
|
||||
|
@@ -0,0 +1,68 @@
|
||||
package com.gitee.sop.gateway.controller;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
|
||||
import com.gitee.sop.gatewaycommon.bean.ErrorEntity;
|
||||
import com.gitee.sop.gatewaycommon.manager.ServiceErrorManager;
|
||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||
import com.gitee.sop.gatewaycommon.result.ApiResult;
|
||||
import com.gitee.sop.gatewaycommon.result.JsonResult;
|
||||
import com.gitee.sop.gatewaycommon.util.RequestUtil;
|
||||
import com.gitee.sop.gatewaycommon.validate.taobao.TaobaoSigner;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
@RestController
|
||||
public class ErrorLogController {
|
||||
|
||||
TaobaoSigner signer = new TaobaoSigner();
|
||||
|
||||
@Value("${zuul.secret}")
|
||||
private String secret;
|
||||
|
||||
@GetMapping("listErrors")
|
||||
public ApiResult listErrors(HttpServletRequest request) {
|
||||
try {
|
||||
this.check(request);
|
||||
ServiceErrorManager serviceErrorManager = ApiConfig.getInstance().getServiceErrorManager();
|
||||
Collection<ErrorEntity> allErrors = serviceErrorManager.listAllErrors();
|
||||
JsonResult apiResult = new JsonResult();
|
||||
apiResult.setData(allErrors);
|
||||
return apiResult;
|
||||
} catch (Exception e) {
|
||||
ApiResult apiResult = new ApiResult();
|
||||
apiResult.setCode("505050");
|
||||
apiResult.setMsg(e.getMessage());
|
||||
return apiResult;
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("clearErrors")
|
||||
public ApiResult clearErrors(HttpServletRequest request) {
|
||||
try {
|
||||
this.check(request);
|
||||
ServiceErrorManager serviceErrorManager = ApiConfig.getInstance().getServiceErrorManager();
|
||||
serviceErrorManager.clear();
|
||||
return new ApiResult();
|
||||
} catch (Exception e) {
|
||||
ApiResult apiResult = new ApiResult();
|
||||
apiResult.setCode("505050");
|
||||
apiResult.setMsg(e.getMessage());
|
||||
return apiResult;
|
||||
}
|
||||
}
|
||||
|
||||
private void check(HttpServletRequest request) {
|
||||
Map<String, String> params = RequestUtil.convertRequestParamsToMap(request);
|
||||
ApiParam apiParam = ApiParam.build(params);
|
||||
signer.checkSign(apiParam, secret);
|
||||
}
|
||||
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
package com.gitee.sop.gateway;
|
||||
package com.gitee.sop.gateway.controller;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.bean.SopConstants;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
@@ -11,6 +11,8 @@ zuul:
|
||||
Servlet30WrapperFilter:
|
||||
pre:
|
||||
disable: true
|
||||
# 不用改,如果要改,建议全局替换修改
|
||||
secret: MZZOUSTua6LzApIWXCwEgbBmxSzpzC
|
||||
|
||||
# 注册中心,根据实际情况修改
|
||||
eureka:
|
||||
|
@@ -28,7 +28,7 @@ public class WebsiteConfig implements ApplicationRunner {
|
||||
public HttpMessageConverters fastJsonConfigure(){
|
||||
FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
|
||||
FastJsonConfig fastJsonConfig = new FastJsonConfig();
|
||||
fastJsonConfig.setSerializerFeatures(SerializerFeature.WriteNullStringAsEmpty);
|
||||
fastJsonConfig.setSerializerFeatures(SerializerFeature.WriteNullStringAsEmpty, SerializerFeature.WriteMapNullValue);
|
||||
// 日期格式化
|
||||
fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
converter.setFastJsonConfig(fastJsonConfig);
|
||||
|
12
sop.sql
12
sop.sql
@@ -12,6 +12,7 @@ DROP TABLE IF EXISTS `config_route_limit`;
|
||||
DROP TABLE IF EXISTS `config_route_base`;
|
||||
DROP TABLE IF EXISTS `config_limit`;
|
||||
DROP TABLE IF EXISTS `admin_user_info`;
|
||||
DROP TABLE IF EXISTS `config_common`;
|
||||
|
||||
|
||||
CREATE TABLE `admin_user_info` (
|
||||
@@ -134,6 +135,17 @@ CREATE TABLE `user_info` (
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='用户信息表';
|
||||
|
||||
|
||||
CREATE TABLE `config_common` (
|
||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`config_group` varchar(64) NOT NULL DEFAULT '' COMMENT '配置分组',
|
||||
`config_key` varchar(64) NOT NULL DEFAULT '' COMMENT '配置key',
|
||||
`content` varchar(128) NOT NULL DEFAULT '' COMMENT '内容',
|
||||
`remark` varchar(128) DEFAULT NULL COMMENT '备注',
|
||||
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_groupkey` (`config_group`,`config_key`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='通用配置表';
|
||||
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = @PREVIOUS_FOREIGN_KEY_CHECKS;
|
||||
|
Reference in New Issue
Block a user