新增监控日志

1.x
tanghc 6 years ago
parent f2692c509e
commit 12fc71dd57
  1. 4
      changelog.md
  2. 51
      doc/docs/_sidebar.md
  3. 44
      doc/docs/files/10093_监控日志.md
  4. BIN
      doc/docs/files/images/10093_1.png
  5. BIN
      doc/docs/files/images/10093_2.png
  6. 13
      sop-1.10.0.sql
  7. 14
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/LimitNewApi.java
  8. 169
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/LogApi.java
  9. 2
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/RouteApi.java
  10. 2
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/ServiceApi.java
  11. 19
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/LogMonitorInstanceAddParam.java
  12. 22
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/result/LogMonitorInstanceVO.java
  13. 28
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/common/QueryUtil.java
  14. 7
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/config/WebConfig.java
  15. 45
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/entity/ConfigCommon.java
  16. 12
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/mapper/ConfigCommonMapper.java
  17. 4
      sop-admin/sop-admin-server/src/main/resources/application-dev.yml
  18. 2
      sop-admin/sop-admin-server/src/main/resources/public/index.html
  19. 2
      sop-admin/sop-admin-server/src/main/resources/public/static/js/app.36a3cb86.js
  20. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-2d0aa95b.447435b3.js
  21. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-2d0aa95b.5ba26eff.js
  22. 2
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-2d22c2e3.46f37153.js
  23. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-4a59cbe4.3309de97.js
  24. 6
      sop-admin/sop-admin-vue/src/router/index.js
  25. 7
      sop-admin/sop-admin-vue/src/utils/global.js
  26. 2
      sop-admin/sop-admin-vue/src/views/service/list/index.vue
  27. 234
      sop-admin/sop-admin-vue/src/views/service/log/index.vue
  28. 8
      sop-admin/sop-admin-vue/src/views/service/route/index.vue
  29. 12
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ApiConfig.java
  30. 15
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ErrorDefinition.java
  31. 32
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ErrorEntity.java
  32. 4
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/SopConstants.java
  33. 15
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/result/GatewayResultExecutor.java
  34. 51
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/DefaultServiceErrorManager.java
  35. 29
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/ServiceErrorManager.java
  36. 8
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ApiParam.java
  37. 99
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/BaseExecutorAdapter.java
  38. 13
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/JsonResult.java
  39. 38
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/result/ZuulResultExecutor.java
  40. 37
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/DefaultGlobalExceptionHandler.java
  41. 6
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/result/DefaultServiceResultBuilder.java
  42. 3
      sop-gateway/src/main/java/com/gitee/sop/gateway/config/ZuulConfig.java
  43. 68
      sop-gateway/src/main/java/com/gitee/sop/gateway/controller/ErrorLogController.java
  44. 2
      sop-gateway/src/main/java/com/gitee/sop/gateway/controller/RedirectController.java
  45. 2
      sop-gateway/src/main/resources/application-dev.yml
  46. 2
      sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/config/WebsiteConfig.java
  47. 12
      sop.sql

@ -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)

@ -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);
...
}
}
```
- 后台预览
![监控日志](images/10093_1.png "10093_1.png")
![监控日志](images/10093_2.png "10093_2.png")

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

@ -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>

@ -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}}]);

@ -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 ,elechild.
data.forEach(d => {
if (d.id === parentId) {
let childArray = d.child
let childArray = d.children
if (!childArray) {
childArray = []
}

@ -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 ,elechild.
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();
return BooleanUtils.toBoolean(mergeResult);
}
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 status;
}
return Integer.valueOf(status);
@Override
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, ?> getApiParam(RequestContext requestContext) {
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,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;

Loading…
Cancel
Save