parent
							
								
									4ce2fc826d
								
							
						
					
					
						commit
						80003c44d3
					
				| Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 84 KiB | 
| Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 68 KiB | 
| Before Width: | Height: | Size: 187 KiB | 
| Before Width: | Height: | Size: 315 KiB | 
| @ -0,0 +1,79 @@ | ||||
| package com.gitee.sop.adminserver.api.service; | ||||
| 
 | ||||
| import com.gitee.easyopen.annotation.Api; | ||||
| import com.gitee.easyopen.annotation.ApiService; | ||||
| import com.gitee.fastmybatis.core.query.Query; | ||||
| import com.gitee.fastmybatis.core.query.Sort; | ||||
| import com.gitee.fastmybatis.core.support.PageEasyui; | ||||
| import com.gitee.fastmybatis.core.util.MapperUtil; | ||||
| import com.gitee.sop.adminserver.api.service.param.InstanceMonitorSearchParam; | ||||
| import com.gitee.sop.adminserver.api.service.param.MonitorErrorMsgParam; | ||||
| import com.gitee.sop.adminserver.api.service.param.MonitorInfoErrorSolveParam; | ||||
| import com.gitee.sop.adminserver.api.service.param.MonitorSearchParam; | ||||
| import com.gitee.sop.adminserver.bean.RouteErrorCount; | ||||
| import com.gitee.sop.adminserver.entity.MonitorInfoError; | ||||
| import com.gitee.sop.adminserver.entity.MonitorSummary; | ||||
| import com.gitee.sop.adminserver.mapper.MonitorInfoErrorMapper; | ||||
| import com.gitee.sop.adminserver.mapper.MonitorInfoMapper; | ||||
| import com.github.pagehelper.PageHelper; | ||||
| import com.github.pagehelper.PageInfo; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| 
 | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.stream.Collectors; | ||||
| 
 | ||||
| /** | ||||
|  * @author tanghc | ||||
|  */ | ||||
| @ApiService | ||||
| public class MonitorNewApi { | ||||
| 
 | ||||
|     @Autowired | ||||
|     private MonitorInfoMapper monitorInfoMapper; | ||||
| 
 | ||||
|     @Autowired | ||||
|     private MonitorInfoErrorMapper monitorInfoErrorMapper; | ||||
| 
 | ||||
|     @Api(name = "monitornew.data.page") | ||||
|     PageInfo<Object> listMonitor(MonitorSearchParam param) { | ||||
|         Query query = Query.build(param); | ||||
|         query.orderby("errorCount", Sort.DESC) | ||||
|                 .orderby("avgTime", Sort.DESC); | ||||
|         return PageHelper.offsetPage(query.getStart(), query.getLimit()) | ||||
|                 .doSelectPage(() -> monitorInfoMapper.listMonitorSummary(query)) | ||||
|                 .toPageInfo(); | ||||
|     } | ||||
| 
 | ||||
|     @Api(name = "monitornew.routeid.data.get") | ||||
|     List<MonitorSummary> listInstanceMonitor(InstanceMonitorSearchParam param) { | ||||
|         Query query = Query.build(param); | ||||
|         query.orderby("errorCount", Sort.DESC) | ||||
|                 .orderby("avgTime", Sort.DESC); | ||||
|         return monitorInfoMapper.listInstanceMonitorInfo(query); | ||||
|     } | ||||
| 
 | ||||
|     private Map<String, Integer> getRouteErrorCount() { | ||||
|         List<RouteErrorCount> routeErrorCounts = monitorInfoErrorMapper.listRouteErrorCount(); | ||||
|         return routeErrorCounts.stream() | ||||
|                 .collect(Collectors.toMap(RouteErrorCount::getRouteId, RouteErrorCount::getCount)); | ||||
|     } | ||||
| 
 | ||||
|     @Api(name = "monitornew.error.page") | ||||
|     PageEasyui<MonitorInfoError> listError(MonitorErrorMsgParam param) { | ||||
|         Query query = param.toQuery() | ||||
|                 .orderby("gmt_modified", Sort.DESC); | ||||
|         return MapperUtil.queryForEasyuiDatagrid(monitorInfoErrorMapper, query); | ||||
|     } | ||||
| 
 | ||||
|     @Api(name = "monitornew.error.solve") | ||||
|     void solve(MonitorInfoErrorSolveParam param) { | ||||
|         Query query = Query.build(param); | ||||
|         Map<String, Object> set = new HashMap<>(4); | ||||
|         set.put("is_deleted", 1); | ||||
|         set.put("count", 0); | ||||
|         monitorInfoErrorMapper.updateByMap(set, query); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,15 @@ | ||||
| package com.gitee.sop.adminserver.api.service.param; | ||||
| 
 | ||||
| import com.gitee.fastmybatis.core.query.annotation.Condition; | ||||
| import lombok.Getter; | ||||
| import lombok.Setter; | ||||
| 
 | ||||
| /** | ||||
|  * @author tanghc | ||||
|  */ | ||||
| @Getter | ||||
| @Setter | ||||
| public class InstanceMonitorSearchParam { | ||||
|     @Condition(column = "t.route_id") | ||||
|     private String routeId; | ||||
| } | ||||
| @ -0,0 +1,21 @@ | ||||
| package com.gitee.sop.adminserver.api.service.param; | ||||
| 
 | ||||
| import com.gitee.fastmybatis.core.query.annotation.Condition; | ||||
| import com.gitee.fastmybatis.core.query.param.PageParam; | ||||
| import lombok.Getter; | ||||
| import lombok.Setter; | ||||
| 
 | ||||
| import javax.validation.constraints.NotBlank; | ||||
| 
 | ||||
| /** | ||||
|  * @author tanghc | ||||
|  */ | ||||
| @Getter | ||||
| @Setter | ||||
| public class MonitorErrorMsgParam extends PageParam { | ||||
|     @NotBlank(message = "routeId不能为空") | ||||
|     private String routeId; | ||||
| 
 | ||||
|     @Condition(ignoreEmptyString = true) | ||||
|     private String instanceId; | ||||
| } | ||||
| @ -0,0 +1,19 @@ | ||||
| package com.gitee.sop.adminserver.api.service.param; | ||||
| 
 | ||||
| import com.gitee.fastmybatis.core.query.annotation.Condition; | ||||
| import lombok.Data; | ||||
| 
 | ||||
| import javax.validation.constraints.NotBlank; | ||||
| 
 | ||||
| /** | ||||
|  * @author tanghc | ||||
|  */ | ||||
| @Data | ||||
| public class MonitorInfoErrorSolveParam { | ||||
|     /** 错误id,md5(error_msg), 数据库字段:error_id */ | ||||
|     @NotBlank | ||||
|     @Condition(index = 1) | ||||
|     private String errorId; | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,23 @@ | ||||
| package com.gitee.sop.adminserver.api.service.param; | ||||
| 
 | ||||
| import com.gitee.easyopen.doc.annotation.ApiDocField; | ||||
| import com.gitee.fastmybatis.core.query.Operator; | ||||
| import com.gitee.fastmybatis.core.query.annotation.Condition; | ||||
| import com.gitee.fastmybatis.core.query.param.PageParam; | ||||
| import lombok.Getter; | ||||
| import lombok.Setter; | ||||
| 
 | ||||
| /** | ||||
|  * @author tanghc | ||||
|  */ | ||||
| @Getter | ||||
| @Setter | ||||
| public class MonitorSearchParam extends PageParam { | ||||
|     @ApiDocField(description = "服务名serviceId") | ||||
|     @Condition(column = "service_id", operator = Operator.like, ignoreEmptyString = true) | ||||
|     private String serviceId; | ||||
| 
 | ||||
|     @ApiDocField(description = "路由id") | ||||
|     @Condition(column = "route_id", operator = Operator.like, ignoreEmptyString = true) | ||||
|     private String routeId; | ||||
| } | ||||
| @ -0,0 +1,26 @@ | ||||
| package com.gitee.sop.adminserver.api.service.result; | ||||
| 
 | ||||
| import lombok.Data; | ||||
| 
 | ||||
| import java.util.Date; | ||||
| 
 | ||||
| /** | ||||
|  * @author tanghc | ||||
|  */ | ||||
| @Data | ||||
| public class MonitorInfoErrorMsgResult { | ||||
| 
 | ||||
|     private String routeId; | ||||
| 
 | ||||
|     private String errorId; | ||||
| 
 | ||||
|     /** 错误信息, 数据库字段:error_msg */ | ||||
|     private String errorMsg; | ||||
| 
 | ||||
|     private Integer errorStatus; | ||||
| 
 | ||||
|     private Integer count; | ||||
| 
 | ||||
|     /**  数据库字段:gmt_modified */ | ||||
|     private Date gmtModified; | ||||
| } | ||||
| @ -0,0 +1,13 @@ | ||||
| package com.gitee.sop.adminserver.bean; | ||||
| 
 | ||||
| import lombok.Data; | ||||
| 
 | ||||
| /** | ||||
|  * @author tanghc | ||||
|  */ | ||||
| @Data | ||||
| public class RouteErrorCount { | ||||
| 
 | ||||
|     private String routeId; | ||||
|     private Integer count; | ||||
| } | ||||
| @ -0,0 +1,75 @@ | ||||
| package com.gitee.sop.adminserver.common; | ||||
| 
 | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.beans.BeanUtils; | ||||
| 
 | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| import java.util.Objects; | ||||
| import java.util.function.Consumer; | ||||
| import java.util.function.Supplier; | ||||
| import java.util.stream.Collectors; | ||||
| 
 | ||||
| /** | ||||
|  * @author tanghc | ||||
|  */ | ||||
| @Slf4j | ||||
| public class CopyUtil { | ||||
| 
 | ||||
|     public static void copyProperties(Object from, Object to) { | ||||
|         BeanUtils.copyProperties(from, to); | ||||
|     } | ||||
| 
 | ||||
|     public static <T> T copyBean(Object from, Supplier<T> supplier) { | ||||
|         Objects.requireNonNull(from); | ||||
|         T to = supplier.get(); | ||||
|         BeanUtils.copyProperties(from, to); | ||||
|         return to; | ||||
|     } | ||||
| 
 | ||||
|     public static <T> T copyBeanNullable(Object from, Supplier<T> supplier) { | ||||
|         if (from == null) { | ||||
|             return supplier.get(); | ||||
|         } | ||||
|         T to = supplier.get(); | ||||
|         BeanUtils.copyProperties(from, to); | ||||
|         return to; | ||||
|     } | ||||
| 
 | ||||
|     public static <T> T copyBean(Object from, Supplier<T> supplier, Consumer<T> after) { | ||||
|         if (from == null) { | ||||
|             return null; | ||||
|         } | ||||
|         T to = supplier.get(); | ||||
|         BeanUtils.copyProperties(from, to); | ||||
|         after.accept(to); | ||||
|         return to; | ||||
|     } | ||||
| 
 | ||||
|     public static <T> List<T> copyList(List<?> fromList, Supplier<T> toElement) { | ||||
|         if (fromList == null) { | ||||
|             return Collections.emptyList(); | ||||
|         } | ||||
|         return fromList.stream() | ||||
|                 .map(source -> { | ||||
|                     T target = toElement.get(); | ||||
|                     BeanUtils.copyProperties(source, target); | ||||
|                     return target; | ||||
|                 }) | ||||
|                 .collect(Collectors.toList()); | ||||
|     } | ||||
| 
 | ||||
|     public static <T> List<T> copyList(List<?> fromList, Supplier<T> toElement, Consumer<T> after) { | ||||
|         if (fromList == null) { | ||||
|             return Collections.emptyList(); | ||||
|         } | ||||
|         return fromList.stream() | ||||
|                 .map(source -> { | ||||
|                     T target = toElement.get(); | ||||
|                     BeanUtils.copyProperties(source, target); | ||||
|                     after.accept(target); | ||||
|                     return target; | ||||
|                 }) | ||||
|                 .collect(Collectors.toList()); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,66 @@ | ||||
| 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; | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * 表名:monitor_info | ||||
|  * 备注:接口监控信息 | ||||
|  * | ||||
|  * @author tanghc | ||||
|  */ | ||||
| @Table(name = "monitor_info") | ||||
| @Data | ||||
| public class MonitorInfo { | ||||
|     /**  数据库字段:id */ | ||||
|     @Id | ||||
|     @Column(name = "id") | ||||
|     @GeneratedValue(strategy = GenerationType.IDENTITY) | ||||
|     private Long id; | ||||
| 
 | ||||
|     /** 路由id, 数据库字段:route_id */ | ||||
|     private String routeId; | ||||
| 
 | ||||
|     /** 接口名, 数据库字段:name */ | ||||
|     private String name; | ||||
| 
 | ||||
|     /** 版本号, 数据库字段:version */ | ||||
|     private String version; | ||||
| 
 | ||||
|     /**  数据库字段:service_id */ | ||||
|     private String serviceId; | ||||
| 
 | ||||
|     /**  数据库字段:instance_id */ | ||||
|     private String instanceId; | ||||
| 
 | ||||
|     /** 请求耗时最长时间, 数据库字段:max_time */ | ||||
|     private Integer maxTime; | ||||
| 
 | ||||
|     /** 请求耗时最小时间, 数据库字段:min_time */ | ||||
|     private Integer minTime; | ||||
| 
 | ||||
|     /** 总时长,毫秒, 数据库字段:total_time */ | ||||
|     private Long totalTime; | ||||
| 
 | ||||
|     /** 总调用次数, 数据库字段:total_request_count */ | ||||
|     private Long totalRequestCount; | ||||
| 
 | ||||
|     /** 成功次数, 数据库字段:success_count */ | ||||
|     private Long successCount; | ||||
| 
 | ||||
|     /** 失败次数(业务主动抛出的异常算作成功,如参数校验,未知的错误算失败), 数据库字段:error_count */ | ||||
|     private Long errorCount; | ||||
| 
 | ||||
|     /**  数据库字段:gmt_create */ | ||||
|     private Date gmtCreate; | ||||
| 
 | ||||
|     /**  数据库字段:gmt_modified */ | ||||
|     private Date gmtModified; | ||||
| } | ||||
| @ -0,0 +1,54 @@ | ||||
| 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; | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * 表名:monitor_info_error | ||||
|  * | ||||
|  * @author tanghc | ||||
|  */ | ||||
| @Table(name = "monitor_info_error") | ||||
| @Data | ||||
| public class MonitorInfoError { | ||||
|     /**  数据库字段:id */ | ||||
|     @Id | ||||
|     @Column(name = "id") | ||||
|     @GeneratedValue(strategy = GenerationType.IDENTITY) | ||||
|     private Long id; | ||||
| 
 | ||||
|     /** 错误id,md5Hex(instanceId + routeId + errorMsg), 数据库字段:error_id */ | ||||
|     private String errorId; | ||||
| 
 | ||||
|     /** 实例id, 数据库字段:instance_id */ | ||||
|     private String instanceId; | ||||
| 
 | ||||
|     /**  数据库字段:route_id */ | ||||
|     private String routeId; | ||||
| 
 | ||||
|     /**  数据库字段:error_msg */ | ||||
|     private String errorMsg; | ||||
| 
 | ||||
|     /** http status,非200错误, 数据库字段:error_status */ | ||||
|     private Integer errorStatus; | ||||
| 
 | ||||
|     /** 错误次数, 数据库字段:count */ | ||||
|     private Integer count; | ||||
| 
 | ||||
|     /**  数据库字段:is_deleted */ | ||||
|     @com.gitee.fastmybatis.core.annotation.LogicDelete | ||||
|     private Byte isDeleted; | ||||
| 
 | ||||
|     /**  数据库字段:gmt_create */ | ||||
|     private Date gmtCreate; | ||||
| 
 | ||||
|     /**  数据库字段:gmt_modified */ | ||||
|     private Date gmtModified; | ||||
| } | ||||
| @ -0,0 +1,61 @@ | ||||
| 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.Collections; | ||||
| import java.util.Date; | ||||
| import java.util.List; | ||||
| import java.util.concurrent.atomic.AtomicInteger; | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * @author tanghc | ||||
|  */ | ||||
| @Data | ||||
| public class MonitorSummary { | ||||
| 
 | ||||
|     private static final AtomicInteger i = new AtomicInteger(); | ||||
| 
 | ||||
|     private Integer id = i.incrementAndGet(); | ||||
| 
 | ||||
|     private String routeId; | ||||
| 
 | ||||
|     private String name; | ||||
|     private String version; | ||||
| 
 | ||||
|     /**  数据库字段:service_id */ | ||||
|     private String serviceId; | ||||
| 
 | ||||
|     /**  数据库字段:instance_id */ | ||||
|     private String instanceId; | ||||
| 
 | ||||
|     /** 请求耗时最长时间, 数据库字段:max_time */ | ||||
|     private Integer maxTime; | ||||
| 
 | ||||
|     /** 请求耗时最小时间, 数据库字段:min_time */ | ||||
|     private Integer minTime; | ||||
| 
 | ||||
|     /** 总时长,毫秒, 数据库字段:total_time */ | ||||
|     private Long totalTime; | ||||
| 
 | ||||
|     /** 总调用次数, 数据库字段:total_request_count */ | ||||
|     private Long totalRequestCount; | ||||
| 
 | ||||
|     /** 成功次数, 数据库字段:success_count */ | ||||
|     private Long successCount; | ||||
| 
 | ||||
|     /** 失败次数(业务主动抛出的异常算作成功,如参数校验,未知的错误算失败), 数据库字段:error_count */ | ||||
|     private Long errorCount; | ||||
| 
 | ||||
|     /** 未解决的错误数量 */ | ||||
|     private Long unsolvedErrorCount; | ||||
| 
 | ||||
|     private Float avgTime; | ||||
| 
 | ||||
|     private Boolean hasChildren; | ||||
| } | ||||
| @ -0,0 +1,22 @@ | ||||
| package com.gitee.sop.adminserver.mapper; | ||||
| 
 | ||||
| import com.gitee.fastmybatis.core.mapper.CrudMapper; | ||||
| import com.gitee.sop.adminserver.bean.RouteErrorCount; | ||||
| import com.gitee.sop.adminserver.entity.MonitorInfoError; | ||||
| import org.apache.ibatis.annotations.Select; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * @author tanghc | ||||
|  */ | ||||
| public interface MonitorInfoErrorMapper extends CrudMapper<MonitorInfoError, Long> { | ||||
| 
 | ||||
| 
 | ||||
|     @Select("SELECT route_id routeId, count(*) `count` FROM monitor_info_error \n" + | ||||
|             "WHERE is_deleted=0 \n" + | ||||
|             "GROUP BY route_id") | ||||
|     List<RouteErrorCount> listRouteErrorCount(); | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,19 @@ | ||||
| package com.gitee.sop.adminserver.mapper; | ||||
| 
 | ||||
| import com.gitee.fastmybatis.core.mapper.CrudMapper; | ||||
| import com.gitee.fastmybatis.core.query.Query; | ||||
| import com.gitee.sop.adminserver.entity.MonitorInfo; | ||||
| import com.gitee.sop.adminserver.entity.MonitorSummary; | ||||
| import org.apache.ibatis.annotations.Param; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * @author tanghc | ||||
|  */ | ||||
| public interface MonitorInfoMapper extends CrudMapper<MonitorInfo, Long> { | ||||
|     List<MonitorSummary> listMonitorSummary(@Param("query") Query query); | ||||
| 
 | ||||
|     List<MonitorSummary> listInstanceMonitorInfo(@Param("query") Query query); | ||||
| } | ||||
| @ -0,0 +1,63 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> | ||||
| <!-- 注意:文件名必须跟Dao类名字一致,因为是根据文件名做关联。 --> | ||||
| <mapper namespace="com.gitee.sop.adminserver.mapper.MonitorInfoMapper"> | ||||
| 
 | ||||
| 
 | ||||
|     <select id="listMonitorSummary" resultType="com.gitee.sop.adminserver.entity.MonitorSummary"> | ||||
|         SELECT t.*, IFNULL(t2.unsolvedErrorCount, 0) unsolvedErrorCount | ||||
|         FROM ( | ||||
|             SELECT | ||||
|             t.`service_id` serviceId, | ||||
|             CONCAT(t.name, t.version) routeId, | ||||
|             t.`name` name, | ||||
|             t.`version` version, | ||||
|             SUM(t.`max_time`) maxTime, | ||||
|             SUM(t.`min_time`) minTime, | ||||
|             SUM(t.`total_time`) totalTime, | ||||
|             SUM(t.`total_request_count`) totalRequestCount, | ||||
|             SUM(t.`success_count`) successCount, | ||||
|             SUM(t.`error_count`) errorCount, | ||||
|             SUM(t.`total_time`)/SUM(t.`total_request_count`) avgTime, | ||||
|             1 hasChildren | ||||
|             FROM | ||||
|             `monitor_info` t | ||||
|             <include refid="common.where"/> | ||||
|             GROUP BY t.service_id, t.name, t.version | ||||
|             <include refid="common.orderBy"/> | ||||
|             ) t | ||||
|         LEFT JOIN | ||||
|         ( | ||||
|         SELECT route_id, count(*) unsolvedErrorCount | ||||
|         FROM monitor_info_error WHERE is_deleted=0 GROUP BY route_id | ||||
|         ) t2 on t.routeId = t2.route_id | ||||
|     </select> | ||||
| 
 | ||||
|     <select id="listInstanceMonitorInfo" resultType="com.gitee.sop.adminserver.entity.MonitorSummary"> | ||||
|         SELECT | ||||
|         t.`service_id` serviceId, | ||||
|         t.`instance_id` instanceId, | ||||
|         t.route_id routeId, | ||||
|         t.`name` name, | ||||
|         t.`version` version, | ||||
|         t.`max_time` maxTime, | ||||
|         t.`min_time` minTime, | ||||
|         t.`total_time` totalTime, | ||||
|         t.`total_request_count` totalRequestCount, | ||||
|         t.`success_count` successCount, | ||||
|         t.`error_count` errorCount, | ||||
|         IFNULL(t2.unsolvedErrorCount, 0) unsolvedErrorCount, | ||||
|         t.`total_time`/t.`total_request_count` avgTime, | ||||
|         0 hasChildren | ||||
|         FROM | ||||
|         `monitor_info` t | ||||
|         LEFT JOIN | ||||
|         ( | ||||
|         SELECT instance_id, route_id, count(*) unsolvedErrorCount | ||||
|         FROM monitor_info_error WHERE is_deleted=0 GROUP BY instance_id, route_id | ||||
|         ) t2 on t.route_id = t2.route_id AND t.instance_id = t2.instance_id | ||||
|         <include refid="common.where" /> | ||||
|         <include refid="common.orderBy" /> | ||||
|     </select> | ||||
| 
 | ||||
| </mapper> | ||||
| @ -0,0 +1,10 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> | ||||
| <configuration> | ||||
| 
 | ||||
| 	<plugins> | ||||
| 		<!-- com.github.pagehelper为PageHelper类所在包名 --> | ||||
| 		<plugin interceptor="com.github.pagehelper.PageInterceptor" /> | ||||
| 	</plugins> | ||||
| 
 | ||||
| </configuration> | ||||
| @ -0,0 +1,273 @@ | ||||
| <template> | ||||
|   <div class="app-container"> | ||||
|     <el-form :inline="true" :model="searchFormData" class="demo-form-inline" size="mini" @submit.native.prevent> | ||||
|       <el-form-item label="接口名"> | ||||
|         <el-input v-model="searchFormData.routeId" :clearable="true" style="width: 250px;" /> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="serviceId"> | ||||
|         <el-input v-model="searchFormData.serviceId" :clearable="true" style="width: 250px;" /> | ||||
|       </el-form-item> | ||||
|       <el-form-item> | ||||
|         <el-button type="primary" icon="el-icon-search" native-type="submit" @click="loadTable">查询</el-button> | ||||
|       </el-form-item> | ||||
|     </el-form> | ||||
|     <el-table | ||||
|       :data="pageInfo.list" | ||||
|       row-key="id" | ||||
|       lazy | ||||
|       empty-text="无数据" | ||||
|       :load="loadInstanceMonitorInfo" | ||||
|     > | ||||
|       <el-table-column | ||||
|         fixed | ||||
|         prop="instanceId" | ||||
|         label="网关实例" | ||||
|         width="200" | ||||
|       > | ||||
|         <template slot-scope="scope"> | ||||
|           <span v-if="!scope.row.children">{{ scope.row.instanceId }}</span> | ||||
|         </template> | ||||
|       </el-table-column> | ||||
|       <el-table-column | ||||
|         fixed | ||||
|         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="serviceId" | ||||
|         label="serviceId" | ||||
|         width="150" | ||||
|       /> | ||||
|       <el-table-column | ||||
|         prop="maxTime" | ||||
|         label="最大耗时(ms)" | ||||
|       > | ||||
|         <template slot="header"> | ||||
|           最大耗时(ms) | ||||
|           <el-tooltip content="耗时计算:签名验证成功后开始,应用返回结果后结束" placement="top"> | ||||
|             <i class="el-icon-question" style="cursor: pointer"></i> | ||||
|           </el-tooltip> | ||||
|         </template> | ||||
|       </el-table-column> | ||||
|       <el-table-column | ||||
|         prop="minTime" | ||||
|         label="最小耗时(ms)" | ||||
|       /> | ||||
|       <el-table-column | ||||
|         prop="avgTime" | ||||
|         label="平均耗时(ms)" | ||||
|       > | ||||
|         <template slot-scope="scope"> | ||||
|           {{ scope.row.avgTime.toFixed(1) }} | ||||
|         </template> | ||||
|       </el-table-column> | ||||
|       <el-table-column | ||||
|         prop="totalRequestCount" | ||||
|         label="总调用次数" | ||||
|       /> | ||||
|       <el-table-column | ||||
|         prop="successCount" | ||||
|         label="成功次数" | ||||
|       /> | ||||
|       <el-table-column | ||||
|         prop="errorCount" | ||||
|         label="失败次数" | ||||
|       /> | ||||
|       <el-table-column | ||||
|         prop="unsolvedErrorCount" | ||||
|         label="未解决错误" | ||||
|       > | ||||
|         <template slot-scope="scope"> | ||||
|           <el-link | ||||
|             v-if="scope.row.unsolvedErrorCount > 0" | ||||
|             :underline="false" | ||||
|             type="danger" | ||||
|             style="text-decoration: underline;" | ||||
|             @click="onShowErrorDetail(scope.row)" | ||||
|           > | ||||
|             {{ scope.row.unsolvedErrorCount }} | ||||
|           </el-link> | ||||
|           <span v-if="scope.row.unsolvedErrorCount === 0">0</span> | ||||
|         </template> | ||||
|       </el-table-column> | ||||
|     </el-table> | ||||
|     <el-pagination | ||||
|       background | ||||
|       style="margin-top: 5px" | ||||
|       :current-page="searchFormData.pageIndex" | ||||
|       :page-size="searchFormData.pageSize" | ||||
|       :page-sizes="[5, 10, 20, 40]" | ||||
|       :total="pageInfo.total" | ||||
|       layout="total, sizes, prev, pager, next" | ||||
|       @size-change="onSizeChange" | ||||
|       @current-change="onPageIndexChange" | ||||
|     /> | ||||
|     <!-- dialog --> | ||||
|     <el-dialog | ||||
|       :title="errorMsgData.title" | ||||
|       :visible.sync="logDetailVisible" | ||||
|       :close-on-click-modal="false" | ||||
|       width="70%" | ||||
|       @close="onCloseErrorDlg" | ||||
|     > | ||||
|       <el-alert | ||||
|         title="修复错误后请标记解决" | ||||
|         :closable="false" | ||||
|         class="el-alert-tip" | ||||
|       /> | ||||
|       <el-table | ||||
|         :data="errorMsgData.pageInfo.rows" | ||||
|         empty-text="无错误日志" | ||||
|       > | ||||
|         <el-table-column | ||||
|           type="expand" | ||||
|         > | ||||
|           <template slot-scope="props"> | ||||
|             <el-input v-model="props.row.errorMsg" type="textarea" :rows="8" readonly /> | ||||
|           </template> | ||||
|         </el-table-column> | ||||
|         <el-table-column | ||||
|           prop="errorMsg" | ||||
|           label="错误内容" | ||||
|         > | ||||
|           <template slot-scope="props"> | ||||
|             <span v-if="props.row.errorMsg.length > 50">{{ props.row.errorMsg.substring(0, 50) }}...</span> | ||||
|             <span v-else>{{ props.row.errorMsg }}</span> | ||||
|           </template> | ||||
|         </el-table-column> | ||||
|         <el-table-column | ||||
|           prop="instanceId" | ||||
|           label="实例ID" | ||||
|           width="150px" | ||||
|         /> | ||||
|         <el-table-column | ||||
|           prop="count" | ||||
|           label="报错次数" | ||||
|           width="80px" | ||||
|         /> | ||||
|         <el-table-column | ||||
|           prop="gmtModified" | ||||
|           label="报错时间" | ||||
|           width="160px" | ||||
|         /> | ||||
|         <el-table-column | ||||
|           label="操作" | ||||
|           width="120" | ||||
|         > | ||||
|           <template slot-scope="scope"> | ||||
|             <el-link type="primary" @click="onSolve(scope.row)">标记解决</el-link> | ||||
|           </template> | ||||
|         </el-table-column> | ||||
|       </el-table> | ||||
|       <el-pagination | ||||
|         background | ||||
|         style="margin-top: 5px" | ||||
|         :current-page="errorMsgFormData.pageIndex" | ||||
|         :page-size="errorMsgFormData.pageSize" | ||||
|         :page-sizes="[5, 10, 20, 40]" | ||||
|         :total="errorMsgData.pageInfo.total" | ||||
|         layout="total, sizes, prev, pager, next" | ||||
|         @size-change="onSizeChange" | ||||
|         @current-change="onPageIndexChange" | ||||
|       /> | ||||
|       <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: { | ||||
|         routeId: '', | ||||
|         serviceId: '', | ||||
|         pageIndex: 1, | ||||
|         pageSize: 20 | ||||
|       }, | ||||
|       pageInfo: { | ||||
|         list: [], | ||||
|         total: 0 | ||||
|       }, | ||||
|       logDetailVisible: false, | ||||
|       errorMsgFormData: { | ||||
|         routeId: '', | ||||
|         instanceId: '', | ||||
|         pageIndex: 1, | ||||
|         pageSize: 5 | ||||
|       }, | ||||
|       errorMsgData: { | ||||
|         title: '', | ||||
|         name: '', | ||||
|         version: '', | ||||
|         pageInfo: { | ||||
|           rows: [], | ||||
|           total: 0 | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   created() { | ||||
|     this.loadTable() | ||||
|   }, | ||||
|   methods: { | ||||
|     loadTable: function() { | ||||
|       this.post('monitornew.data.page', this.searchFormData, function(resp) { | ||||
|         this.pageInfo = resp.data | ||||
|       }) | ||||
|     }, | ||||
|     loadErrorData: function() { | ||||
|       this.post('monitornew.error.page', this.errorMsgFormData, function(resp) { | ||||
|         this.errorMsgData.pageInfo = resp.data | ||||
|         this.logDetailVisible = true | ||||
|       }) | ||||
|     }, | ||||
|     loadInstanceMonitorInfo(row, treeNode, resolve) { | ||||
|       this.post('monitornew.routeid.data.get', { routeId: row.routeId }, resp => { | ||||
|         const children = resp.data | ||||
|         row.children = children | ||||
|         resolve(children) | ||||
|       }) | ||||
|     }, | ||||
|     onShowErrorDetail: function(row) { | ||||
|       this.errorMsgData.title = `错误日志 ${row.name}(${row.version})` | ||||
|       this.errorMsgData.name = row.name | ||||
|       this.errorMsgData.version = row.version | ||||
|       this.errorMsgFormData.routeId = row.routeId | ||||
|       this.errorMsgFormData.instanceId = row.instanceId | ||||
|       this.loadErrorData() | ||||
|     }, | ||||
|     onSolve: function(row) { | ||||
|       this.confirm('确认标记为已解决吗?', function(done) { | ||||
|         this.post('monitornew.error.solve', { routeId: row.routeId, errorId: row.errorId }, function(resp) { | ||||
|           done() | ||||
|           this.loadErrorData() | ||||
|         }) | ||||
|       }) | ||||
|     }, | ||||
|     onCloseErrorDlg: function() { | ||||
|       this.loadTable() | ||||
|     }, | ||||
|     onSizeChange: function(size) { | ||||
|       this.searchFormData.pageSize = size | ||||
|       this.loadTable() | ||||
|     }, | ||||
|     onAdd: function() { | ||||
|       this.dialogTitle = '新增IP' | ||||
|       this.dialogVisible = true | ||||
|       this.dialogFormData.id = 0 | ||||
|     }, | ||||
|     onPageIndexChange: function(pageIndex) { | ||||
|       this.searchFormData.pageIndex = pageIndex | ||||
|       this.loadTable() | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
| @ -0,0 +1,43 @@ | ||||
| package com.gitee.sop.gatewaycommon.bean; | ||||
| 
 | ||||
| import java.util.Collection; | ||||
| import java.util.LinkedHashMap; | ||||
| import java.util.Map; | ||||
| import java.util.function.Function; | ||||
| 
 | ||||
| /** | ||||
|  * LRU缓存 | ||||
|  * LinkedHashMap 本身内部有一个触发条件则自动执行的方法:删除最老元素(最近最少使用的元素) | ||||
|  * 由于最近最少使用元素是 LinkedHashMap 内部处理 | ||||
|  * 故我们不再需要维护 最近访问元素放在链尾,get 时直接访问/ put 时直接存储 | ||||
|  * created by Ethan-Walker on 2019/2/16 | ||||
|  */ | ||||
| public class LRUCache<K, V> { | ||||
|     private final Map<K, V> map; | ||||
| 
 | ||||
|     public LRUCache(int capacity) { | ||||
|         map = new LinkedHashMap<K, V>(capacity, 0.75f, true) { | ||||
|             @Override | ||||
|             protected boolean removeEldestEntry(Map.Entry eldest) { | ||||
|                 // 容量大于capacity 时就删除
 | ||||
|                 return size() > capacity; | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
|     public V get(K key) { | ||||
|         return map.get(key); | ||||
|     } | ||||
| 
 | ||||
|     public V computeIfAbsent(K key, | ||||
|                                 Function<? super K, ? extends V> mappingFunction) { | ||||
|         return map.computeIfAbsent(key, mappingFunction); | ||||
|     } | ||||
| 
 | ||||
|     public V put(K key, V value) { | ||||
|         return map.put(key, value); | ||||
|     } | ||||
| 
 | ||||
|     public Collection<V> values() { | ||||
|         return map.values(); | ||||
|     } | ||||
| } | ||||
| @ -1,96 +0,0 @@ | ||||
| package com.gitee.sop.gatewaycommon.interceptor; | ||||
| 
 | ||||
| import com.gitee.sop.gatewaycommon.bean.ApiConfig; | ||||
| import com.gitee.sop.gatewaycommon.monitor.MonitorInfo; | ||||
| import com.gitee.sop.gatewaycommon.monitor.MonitorManager; | ||||
| import com.gitee.sop.gatewaycommon.param.ApiParam; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.concurrent.LinkedBlockingQueue; | ||||
| import java.util.concurrent.ThreadPoolExecutor; | ||||
| import java.util.concurrent.TimeUnit; | ||||
| import java.util.concurrent.atomic.AtomicLong; | ||||
| 
 | ||||
| /** | ||||
|  * SOP默认的拦截器,用于收集监控数据 | ||||
|  * | ||||
|  * @author tanghc | ||||
|  */ | ||||
| public class MonitorRouteInterceptor implements RouteInterceptor { | ||||
| 
 | ||||
|     private ThreadPoolExecutor threadPoolExecutor; | ||||
| 
 | ||||
|     public MonitorRouteInterceptor(int threadPoolSize) { | ||||
|         threadPoolExecutor = new ThreadPoolExecutor(threadPoolSize, threadPoolSize, | ||||
|                 0L, TimeUnit.MILLISECONDS, | ||||
|                 new LinkedBlockingQueue<>()); | ||||
|     } | ||||
| 
 | ||||
|     public MonitorRouteInterceptor() { | ||||
|         this(4); | ||||
|     } | ||||
| 
 | ||||
|     public MonitorRouteInterceptor(ThreadPoolExecutor threadPoolExecutor) { | ||||
|         this.threadPoolExecutor = threadPoolExecutor; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void preRoute(RouteInterceptorContext context) { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void afterRoute(RouteInterceptorContext context) { | ||||
|         threadPoolExecutor.execute(() -> this.storeRequestInfo(context)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 记录接口调用流量,最大时间,最小时间,总时长,平均时长,调用次数,成功次数,失败次数. | ||||
|      * 需要考虑并发情况。 | ||||
|      */ | ||||
|     protected void storeRequestInfo(RouteInterceptorContext context) { | ||||
|         MonitorManager monitorManager = ApiConfig.getInstance().getMonitorManager(); | ||||
|         ApiParam apiParam = context.getApiParam(); | ||||
|         String routeId = apiParam.fetchNameVersion(); | ||||
|         long spendTime = context.getFinishTimeMillis() - context.getBeginTimeMillis(); | ||||
|         // 这步操作是线程安全的,底层调用了ConcurrentHashMap.computeIfAbsent
 | ||||
|         MonitorInfo monitorInfo = monitorManager.getMonitorInfo(routeId, (k) -> this.createMonitorInfo(apiParam)); | ||||
| 
 | ||||
|         monitorInfo.storeMaxTime(spendTime); | ||||
|         monitorInfo.storeMinTime(spendTime); | ||||
|         monitorInfo.getTotalCount().incrementAndGet(); | ||||
|         monitorInfo.getTotalTime().addAndGet(spendTime); | ||||
|         monitorInfo.getTotalRequestDataSize().addAndGet(context.getRequestDataSize()); | ||||
|         monitorInfo.getTotalResponseDataSize().addAndGet(context.getResponseDataSize()); | ||||
|         if (context.isSuccessRequest()) { | ||||
|             monitorInfo.getSuccessCount().incrementAndGet(); | ||||
|         } else { | ||||
|             monitorInfo.getErrorCount().incrementAndGet(); | ||||
|             String errorMsg = context.getServiceErrorMsg(); | ||||
|             monitorInfo.addErrorMsg(errorMsg); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private MonitorInfo createMonitorInfo(ApiParam apiParam) { | ||||
|         MonitorInfo monitorInfo = new MonitorInfo(); | ||||
|         monitorInfo.setName(apiParam.fetchName()); | ||||
|         monitorInfo.setVersion(apiParam.fetchVersion()); | ||||
|         monitorInfo.setServiceId(apiParam.fetchServiceId()); | ||||
|         monitorInfo.setTotalRequestDataSize(new AtomicLong()); | ||||
|         monitorInfo.setTotalResponseDataSize(new AtomicLong()); | ||||
|         monitorInfo.setTotalTime(new AtomicLong()); | ||||
|         monitorInfo.setMaxTime(0L); | ||||
|         monitorInfo.setMinTime(0L); | ||||
|         monitorInfo.setSuccessCount(new AtomicLong()); | ||||
|         monitorInfo.setTotalCount(new AtomicLong()); | ||||
|         monitorInfo.setErrorCount(new AtomicLong()); | ||||
|         monitorInfo.setErrorMsgList(new ArrayList<>(10)); | ||||
|         return monitorInfo; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     @Override | ||||
|     public int getOrder() { | ||||
|         return -1000; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,63 @@ | ||||
| package com.gitee.sop.gatewaycommon.monitor; | ||||
| 
 | ||||
| import lombok.Data; | ||||
| 
 | ||||
| import java.util.Collection; | ||||
| 
 | ||||
| /** | ||||
|  * 每个接口 总调用流量,最大时间,最小时间,总时长,平均时长,调用次数,成功次数,失败次数,错误查看。 | ||||
|  * | ||||
|  * @author tanghc | ||||
|  */ | ||||
| @Data | ||||
| public class MonitorDTO { | ||||
| 
 | ||||
|     /** | ||||
|      * 路由id | ||||
|      */ | ||||
|     private String routeId; | ||||
| 
 | ||||
|     /** | ||||
|      * 接口名 | ||||
|      */ | ||||
|     private String name; | ||||
|     /** | ||||
|      * 版本号 | ||||
|      */ | ||||
|     private String version; | ||||
|     /** | ||||
|      * serviceId | ||||
|      */ | ||||
|     private String serviceId; | ||||
| 
 | ||||
|     /** | ||||
|      * 实例id | ||||
|      */ | ||||
|     private String instanceId; | ||||
| 
 | ||||
|     /** 请求耗时最长时间, 数据库字段:max_time */ | ||||
|     private Integer maxTime; | ||||
| 
 | ||||
|     /** 请求耗时最小时间, 数据库字段:min_time */ | ||||
|     private Integer minTime; | ||||
| 
 | ||||
|     /** | ||||
|      * 总时长 | ||||
|      */ | ||||
|     private Long totalTime; | ||||
| 
 | ||||
|     /** 总调用次数, 数据库字段:total_request_count */ | ||||
|     private Long totalRequestCount; | ||||
| 
 | ||||
|     /** 成功次数, 数据库字段:success_count */ | ||||
|     private Long successCount; | ||||
| 
 | ||||
|     /** 失败次数(业务主动抛出的异常算作成功,如参数校验,未知的错误算失败), 数据库字段:error_count */ | ||||
|     private Long errorCount; | ||||
| 
 | ||||
|     /** | ||||
|      * 错误信息 | ||||
|      */ | ||||
|     private Collection<MonitorErrorMsg> errorMsgList; | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,106 @@ | ||||
| package com.gitee.sop.gatewaycommon.monitor; | ||||
| 
 | ||||
| import com.gitee.sop.gatewaycommon.bean.LRUCache; | ||||
| import lombok.Data; | ||||
| import org.apache.commons.codec.digest.DigestUtils; | ||||
| 
 | ||||
| import java.time.LocalDateTime; | ||||
| import java.util.Map; | ||||
| import java.util.concurrent.atomic.AtomicInteger; | ||||
| 
 | ||||
| /** | ||||
|  * 每个接口 总调用流量,最大时间,最小时间,总时长,平均时长,调用次数,成功次数,失败次数,错误查看。 | ||||
|  * | ||||
|  * @author tanghc | ||||
|  */ | ||||
| @Data | ||||
| public class MonitorData { | ||||
| 
 | ||||
|     public static final int LIMIT_SIZE = 50; | ||||
| 
 | ||||
| 
 | ||||
|     private String routeId; | ||||
| 
 | ||||
|     /** | ||||
|      * 接口名 | ||||
|      */ | ||||
|     private String name; | ||||
|     /** | ||||
|      * 版本号 | ||||
|      */ | ||||
|     private String version; | ||||
|     /** | ||||
|      * serviceId | ||||
|      */ | ||||
|     private String serviceId; | ||||
| 
 | ||||
|     private String instanceId; | ||||
| 
 | ||||
|     /** | ||||
|      * 请求耗时最长时间 | ||||
|      */ | ||||
|     private Integer maxTime; | ||||
|     /** | ||||
|      * 请求耗时最小时间 | ||||
|      */ | ||||
|     private Integer minTime; | ||||
|     /** | ||||
|      * 总时长 | ||||
|      */ | ||||
|     private AtomicInteger totalTime; | ||||
|     /** | ||||
|      * 总调用次数 | ||||
|      */ | ||||
|     private AtomicInteger totalRequestCount; | ||||
|     /** | ||||
|      * 成功次数 | ||||
|      */ | ||||
|     private AtomicInteger successCount; | ||||
|     /** | ||||
|      * 失败次数(业务主动抛出的异常算作成功,如参数校验,未知的错误算失败) | ||||
|      */ | ||||
|     private AtomicInteger errorCount; | ||||
| 
 | ||||
|     /** | ||||
|      * 错误信息,key: errorId | ||||
|      */ | ||||
|     private LRUCache<String, MonitorErrorMsg> monitorErrorMsgMap; | ||||
| 
 | ||||
|     /** | ||||
|      * 下一次刷新到数据库的时间 | ||||
|      */ | ||||
|     private LocalDateTime flushTime; | ||||
| 
 | ||||
|     public synchronized void storeMaxTime(int spendTime) { | ||||
|         if (spendTime > maxTime) { | ||||
|             maxTime = spendTime; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public synchronized void storeMinTime(int spendTime) { | ||||
|         if (minTime == 0 || spendTime < minTime) { | ||||
|             minTime = spendTime; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public void addErrorMsg(String errorMsg, int httpStatus) { | ||||
|         if (errorMsg == null || "".equals(errorMsg)) { | ||||
|             return; | ||||
|         } | ||||
|         synchronized (this) { | ||||
|             String errorId = DigestUtils.md5Hex(instanceId + routeId + errorMsg); | ||||
|             MonitorErrorMsg monitorErrorMsg = monitorErrorMsgMap.computeIfAbsent(errorId, (k) -> { | ||||
|                 MonitorErrorMsg value = new MonitorErrorMsg(); | ||||
|                 value.setErrorId(errorId); | ||||
|                 value.setInstanceId(instanceId); | ||||
|                 value.setRouteId(routeId); | ||||
|                 value.setErrorMsg(errorMsg); | ||||
|                 value.setErrorStatus(httpStatus); | ||||
|                 value.setCount(0); | ||||
|                 return value; | ||||
|             }); | ||||
|             monitorErrorMsg.setCount(monitorErrorMsg.getCount() + 1); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,30 @@ | ||||
| package com.gitee.sop.gatewaycommon.monitor; | ||||
| 
 | ||||
| import lombok.Data; | ||||
| 
 | ||||
| /** | ||||
|  * @author thc | ||||
|  */ | ||||
| @Data | ||||
| public class MonitorErrorMsg { | ||||
|     /** 错误id,md5(error_msg), 数据库字段:error_id */ | ||||
|     private String errorId; | ||||
| 
 | ||||
|     /** 实例id, 数据库字段:instance_id */ | ||||
|     private String instanceId; | ||||
| 
 | ||||
|     /**  数据库字段:route_id */ | ||||
|     private String routeId; | ||||
| 
 | ||||
|     /**  数据库字段:isp_id */ | ||||
|     private Long ispId; | ||||
| 
 | ||||
|     /**  数据库字段:error_msg */ | ||||
|     private String errorMsg; | ||||
| 
 | ||||
|     /** http status,非200错误 */ | ||||
|     private Integer errorStatus; | ||||
| 
 | ||||
|     /** 错误次数, 数据库字段:count */ | ||||
|     private Integer count; | ||||
| } | ||||
| @ -1,88 +0,0 @@ | ||||
| package com.gitee.sop.gatewaycommon.monitor; | ||||
| 
 | ||||
| import lombok.Data; | ||||
| 
 | ||||
| import java.util.List; | ||||
| import java.util.concurrent.atomic.AtomicLong; | ||||
| 
 | ||||
| /** | ||||
|  * 每个接口 总调用流量,最大时间,最小时间,总时长,平均时长,调用次数,成功次数,失败次数,错误查看。 | ||||
|  * | ||||
|  * @author tanghc | ||||
|  */ | ||||
| @Data | ||||
| public class MonitorInfo { | ||||
| 
 | ||||
|     /** | ||||
|      * 接口名 | ||||
|      */ | ||||
|     private String name; | ||||
|     /** | ||||
|      * 版本号 | ||||
|      */ | ||||
|     private String version; | ||||
|     /** | ||||
|      * serviceId | ||||
|      */ | ||||
|     private String serviceId; | ||||
|     /** | ||||
|      * 总请求数据量 | ||||
|      */ | ||||
|     private AtomicLong totalRequestDataSize; | ||||
|     /** | ||||
|      * 总返回数据量 | ||||
|      */ | ||||
|     private AtomicLong totalResponseDataSize; | ||||
|     /** | ||||
|      * 请求耗时最长时间 | ||||
|      */ | ||||
|     private Long maxTime; | ||||
|     /** | ||||
|      * 请求耗时最小时间 | ||||
|      */ | ||||
|     private Long minTime; | ||||
|     /** | ||||
|      * 总时长 | ||||
|      */ | ||||
|     private AtomicLong totalTime; | ||||
|     /** | ||||
|      * 总调用次数 | ||||
|      */ | ||||
|     private AtomicLong totalCount; | ||||
|     /** | ||||
|      * 成功次数 | ||||
|      */ | ||||
|     private AtomicLong successCount; | ||||
|     /** | ||||
|      * 失败次数(业务主动抛出的异常算作成功,如参数校验,未知的错误算失败) | ||||
|      */ | ||||
|     private AtomicLong errorCount; | ||||
|     /** | ||||
|      * 错误信息 | ||||
|      */ | ||||
|     private List<String> errorMsgList; | ||||
| 
 | ||||
|     public synchronized void storeMaxTime(long spendTime) { | ||||
|         if (spendTime > maxTime) { | ||||
|             maxTime = spendTime; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public synchronized void storeMinTime(long spendTime) { | ||||
|         if (minTime == 0 || spendTime < minTime) { | ||||
|             minTime = spendTime; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public void addErrorMsg(String errorMsg) { | ||||
|         if (errorMsg == null || "".equals(errorMsg)) { | ||||
|             return; | ||||
|         } | ||||
|         synchronized (this) { | ||||
|             if (errorMsgList != null && errorMsgList.size() < 10) { | ||||
|                 errorMsgList.add(errorMsg); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,13 @@ | ||||
| package com.gitee.sop.gatewaycommon.monitor; | ||||
| 
 | ||||
| import lombok.Data; | ||||
| 
 | ||||
| /** | ||||
|  * @author tanghc | ||||
|  */ | ||||
| @Data | ||||
| public class RouteErrorCount { | ||||
| 
 | ||||
|     private String routeId; | ||||
|     private Integer count; | ||||
| } | ||||
| @ -0,0 +1,37 @@ | ||||
| package com.gitee.sop.gatewaycommon.sync; | ||||
| 
 | ||||
| import java.util.concurrent.ThreadFactory; | ||||
| import java.util.concurrent.atomic.AtomicInteger; | ||||
| 
 | ||||
| /** | ||||
|  * @author thc | ||||
|  */ | ||||
| public class MyNamedThreadFactory implements ThreadFactory { | ||||
|     private static final AtomicInteger POOL_NUMBER = new AtomicInteger(1); | ||||
|     private final AtomicInteger threadNumber = new AtomicInteger(1); | ||||
|     private final ThreadGroup group; | ||||
|     private final String namePrefix; | ||||
| 
 | ||||
|     public MyNamedThreadFactory(String name) { | ||||
| 
 | ||||
|         SecurityManager s = System.getSecurityManager(); | ||||
|         group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); | ||||
|         if (null == name || name.isEmpty()) { | ||||
|             name = "pool"; | ||||
|         } | ||||
| 
 | ||||
|         namePrefix = name + "-" + POOL_NUMBER.getAndIncrement() + "-thread-"; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Thread newThread(Runnable r) { | ||||
|         Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement()); | ||||
|         if (t.isDaemon()) { | ||||
|             t.setDaemon(false); | ||||
|         } | ||||
|         if (t.getPriority() != Thread.NORM_PRIORITY) { | ||||
|             t.setPriority(Thread.NORM_PRIORITY); | ||||
|         } | ||||
|         return t; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,41 @@ | ||||
| package com.gitee.sop.gatewaycommon.sync; | ||||
| 
 | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; | ||||
| import org.springframework.scheduling.annotation.AsyncConfigurer; | ||||
| 
 | ||||
| import java.lang.reflect.Method; | ||||
| import java.util.Arrays; | ||||
| import java.util.concurrent.Executor; | ||||
| import java.util.concurrent.LinkedBlockingQueue; | ||||
| import java.util.concurrent.ThreadPoolExecutor; | ||||
| import java.util.concurrent.TimeUnit; | ||||
| 
 | ||||
| /** | ||||
|  * 异步执行配置 | ||||
|  * | ||||
|  * @author tanghc | ||||
|  */ | ||||
| @Slf4j | ||||
| public class SopAsyncConfigurer implements AsyncConfigurer { | ||||
| 
 | ||||
|     private final ThreadPoolExecutor threadPoolExecutor; | ||||
| 
 | ||||
|     public SopAsyncConfigurer(String threadName, int poolSize) { | ||||
|         threadPoolExecutor = new ThreadPoolExecutor(poolSize, poolSize, | ||||
|                 0L, TimeUnit.MILLISECONDS, | ||||
|                 new LinkedBlockingQueue<>(), new MyNamedThreadFactory(threadName)); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Executor getAsyncExecutor() { | ||||
|         return threadPoolExecutor; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { | ||||
|         return (Throwable e, Method method, Object... args) -> { | ||||
|             log.error("异步运行方法出错, method:{}, args:{}, message:{}", method, Arrays.deepToString(args), e.getMessage(), e); | ||||
|         }; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,79 @@ | ||||
| package com.gitee.sop.gatewaycommon.util; | ||||
| 
 | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.beans.BeanUtils; | ||||
| 
 | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| import java.util.Objects; | ||||
| import java.util.function.Consumer; | ||||
| import java.util.function.Supplier; | ||||
| import java.util.stream.Collectors; | ||||
| 
 | ||||
| /** | ||||
|  * @author tanghc | ||||
|  */ | ||||
| @Slf4j | ||||
| public class CopyUtil { | ||||
| 
 | ||||
|     public static void copyPropertiesIgnoreNull(Object from, Object to) { | ||||
|         MyBeanUtil.copyPropertiesIgnoreNull(from, to); | ||||
|     } | ||||
| 
 | ||||
|     public static void copyProperties(Object from, Object to) { | ||||
|         BeanUtils.copyProperties(from, to); | ||||
|     } | ||||
| 
 | ||||
|     public static <T> T copyBean(Object from, Supplier<T> supplier) { | ||||
|         Objects.requireNonNull(from); | ||||
|         T to = supplier.get(); | ||||
|         BeanUtils.copyProperties(from, to); | ||||
|         return to; | ||||
|     } | ||||
| 
 | ||||
|     public static <T> T copyBeanNullable(Object from, Supplier<T> supplier) { | ||||
|         if (from == null) { | ||||
|             return supplier.get(); | ||||
|         } | ||||
|         T to = supplier.get(); | ||||
|         BeanUtils.copyProperties(from, to); | ||||
|         return to; | ||||
|     } | ||||
| 
 | ||||
|     public static <T> T copyBean(Object from, Supplier<T> supplier, Consumer<T> after) { | ||||
|         if (from == null) { | ||||
|             return null; | ||||
|         } | ||||
|         T to = supplier.get(); | ||||
|         BeanUtils.copyProperties(from, to); | ||||
|         after.accept(to); | ||||
|         return to; | ||||
|     } | ||||
| 
 | ||||
|     public static <T> List<T> copyList(List<?> fromList, Supplier<T> toElement) { | ||||
|         if (fromList == null) { | ||||
|             return Collections.emptyList(); | ||||
|         } | ||||
|         return fromList.stream() | ||||
|                 .map(source -> { | ||||
|                     T target = toElement.get(); | ||||
|                     BeanUtils.copyProperties(source, target); | ||||
|                     return target; | ||||
|                 }) | ||||
|                 .collect(Collectors.toList()); | ||||
|     } | ||||
| 
 | ||||
|     public static <T> List<T> copyList(List<?> fromList, Supplier<T> toElement, Consumer<T> after) { | ||||
|         if (fromList == null) { | ||||
|             return Collections.emptyList(); | ||||
|         } | ||||
|         return fromList.stream() | ||||
|                 .map(source -> { | ||||
|                     T target = toElement.get(); | ||||
|                     BeanUtils.copyProperties(source, target); | ||||
|                     after.accept(target); | ||||
|                     return target; | ||||
|                 }) | ||||
|                 .collect(Collectors.toList()); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,67 @@ | ||||
| package com.gitee.sop.gateway.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.time.LocalDateTime; | ||||
| import java.util.Date; | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * 表名:monitor_info | ||||
|  * 备注:接口监控信息 | ||||
|  * | ||||
|  * @author tanghc | ||||
|  */ | ||||
| @Table(name = "monitor_info") | ||||
| @Data | ||||
| public class MonitorInfo { | ||||
|     /**  数据库字段:id */ | ||||
|     @Id | ||||
|     @Column(name = "id") | ||||
|     @GeneratedValue(strategy = GenerationType.IDENTITY) | ||||
|     private Long id; | ||||
| 
 | ||||
|     /** 路由id, 数据库字段:route_id */ | ||||
|     private String routeId; | ||||
| 
 | ||||
|     /** 接口名, 数据库字段:name */ | ||||
|     private String name; | ||||
| 
 | ||||
|     /** 版本号, 数据库字段:version */ | ||||
|     private String version; | ||||
| 
 | ||||
|     /**  数据库字段:service_id */ | ||||
|     private String serviceId; | ||||
| 
 | ||||
|     /**  数据库字段:instance_id */ | ||||
|     private String instanceId; | ||||
| 
 | ||||
|     /** 请求耗时最长时间, 数据库字段:max_time */ | ||||
|     private Integer maxTime; | ||||
| 
 | ||||
|     /** 请求耗时最小时间, 数据库字段:min_time */ | ||||
|     private Integer minTime; | ||||
| 
 | ||||
|     /** 总时长,毫秒, 数据库字段:total_time */ | ||||
|     private Long totalTime; | ||||
| 
 | ||||
|     /** 总调用次数, 数据库字段:total_request_count */ | ||||
|     private Long totalRequestCount; | ||||
| 
 | ||||
|     /** 成功次数, 数据库字段:success_count */ | ||||
|     private Long successCount; | ||||
| 
 | ||||
|     /** 失败次数(业务主动抛出的异常算作成功,如参数校验,未知的错误算失败), 数据库字段:error_count */ | ||||
|     private Long errorCount; | ||||
| 
 | ||||
|     /**  数据库字段:gmt_create */ | ||||
|     private Date gmtCreate; | ||||
| 
 | ||||
|     /**  数据库字段:gmt_modified */ | ||||
|     private Date gmtModified; | ||||
| } | ||||
| @ -0,0 +1,55 @@ | ||||
| package com.gitee.sop.gateway.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.time.LocalDateTime; | ||||
| import java.util.Date; | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * 表名:monitor_info_error | ||||
|  * | ||||
|  * @author tanghc | ||||
|  */ | ||||
| @Table(name = "monitor_info_error") | ||||
| @Data | ||||
| public class MonitorInfoError { | ||||
|     /**  数据库字段:id */ | ||||
|     @Id | ||||
|     @Column(name = "id") | ||||
|     @GeneratedValue(strategy = GenerationType.IDENTITY) | ||||
|     private Long id; | ||||
| 
 | ||||
|     /** 错误id,md5Hex(instanceId + routeId + errorMsg), 数据库字段:error_id */ | ||||
|     private String errorId; | ||||
| 
 | ||||
|     /** 实例id, 数据库字段:instance_id */ | ||||
|     private String instanceId; | ||||
| 
 | ||||
|     /**  数据库字段:route_id */ | ||||
|     private String routeId; | ||||
| 
 | ||||
|     /**  数据库字段:error_msg */ | ||||
|     private String errorMsg; | ||||
| 
 | ||||
|     /** http status,非200错误, 数据库字段:error_status */ | ||||
|     private Integer errorStatus; | ||||
| 
 | ||||
|     /** 错误次数, 数据库字段:count */ | ||||
|     private Integer count; | ||||
| 
 | ||||
|     /**  数据库字段:is_deleted */ | ||||
|     @com.gitee.fastmybatis.core.annotation.LogicDelete | ||||
|     private Byte isDeleted; | ||||
| 
 | ||||
|     /**  数据库字段:gmt_create */ | ||||
|     private Date gmtCreate; | ||||
| 
 | ||||
|     /**  数据库字段:gmt_modified */ | ||||
|     private Date gmtModified; | ||||
| } | ||||
| @ -0,0 +1,43 @@ | ||||
| package com.gitee.sop.gateway.interceptor; | ||||
| 
 | ||||
| import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptor; | ||||
| import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptorContext; | ||||
| import com.gitee.sop.gatewaycommon.param.ApiParam; | ||||
| import com.gitee.sop.gatewaycommon.sync.SopAsyncConfigurer; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.stereotype.Component; | ||||
| 
 | ||||
| /** | ||||
|  * 用于收集监控数据 | ||||
|  * | ||||
|  * @author tanghc | ||||
|  */ | ||||
| @Component | ||||
| @Slf4j | ||||
| public class MonitorRouteInterceptor implements RouteInterceptor { | ||||
| 
 | ||||
|     @Autowired | ||||
|     SopAsyncConfigurer sopAsyncConfigurer; | ||||
| 
 | ||||
|     @Autowired | ||||
|     MonitorRouteInterceptorService monitorRouteInterceptorService; | ||||
| 
 | ||||
|     @Override | ||||
|     public void preRoute(RouteInterceptorContext context) { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void afterRoute(RouteInterceptorContext context) { | ||||
|         sopAsyncConfigurer.getAsyncExecutor().execute(()-> { | ||||
|             monitorRouteInterceptorService.storeRequestInfo(context); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public int getOrder() { | ||||
|         return -1000; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,164 @@ | ||||
| package com.gitee.sop.gateway.interceptor; | ||||
| 
 | ||||
| import com.gitee.sop.gateway.mapper.DbMonitorInfoManager; | ||||
| import com.gitee.sop.gatewaycommon.bean.LRUCache; | ||||
| import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptorContext; | ||||
| import com.gitee.sop.gatewaycommon.monitor.MonitorDTO; | ||||
| import com.gitee.sop.gatewaycommon.monitor.MonitorData; | ||||
| import com.gitee.sop.gatewaycommon.monitor.MonitorManager; | ||||
| import com.gitee.sop.gatewaycommon.param.ApiParam; | ||||
| import com.gitee.sop.gatewaycommon.sync.MyNamedThreadFactory; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.beans.factory.annotation.Value; | ||||
| import org.springframework.cloud.client.ServiceInstance; | ||||
| import org.springframework.stereotype.Service; | ||||
| 
 | ||||
| import javax.annotation.PostConstruct; | ||||
| import java.time.LocalDateTime; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.Random; | ||||
| import java.util.concurrent.ScheduledThreadPoolExecutor; | ||||
| import java.util.concurrent.TimeUnit; | ||||
| import java.util.concurrent.atomic.AtomicInteger; | ||||
| 
 | ||||
| /** | ||||
|  * @author tanghc | ||||
|  */ | ||||
| @Service | ||||
| @Slf4j | ||||
| public class MonitorRouteInterceptorService { | ||||
| 
 | ||||
|     /** | ||||
|      * 刷新到数据库时间间隔,秒 | ||||
|      */ | ||||
|     @Value("${sop.monitor.flush-period-seconds:30}") | ||||
|     int flushPeriodSeconds; | ||||
| 
 | ||||
|     /** | ||||
|      * 定时任务每n秒,执行一次 | ||||
|      */ | ||||
|     @Value("${sop.monitor.schedule-period-seconds:30}") | ||||
|     int schedulePeriodSeconds; | ||||
| 
 | ||||
|     /** | ||||
|      * 错误数量容量 | ||||
|      */ | ||||
|     @Value("${sop.monitor.error-count-capacity:50}") | ||||
|     int monitorErrorCapacity; | ||||
| 
 | ||||
|     @Autowired | ||||
|     DbMonitorInfoManager dbMonitorInfoManager; | ||||
| 
 | ||||
|     @Autowired | ||||
|     MonitorManager monitorManager; | ||||
| 
 | ||||
|     /** | ||||
|      * 记录接口调用流量,最大时间,最小时间,总时长,平均时长,调用次数,成功次数,失败次数. | ||||
|      * 需要考虑并发情况。 | ||||
|      */ | ||||
|     public synchronized void storeRequestInfo(RouteInterceptorContext context) { | ||||
|         ApiParam apiParam = context.getApiParam(); | ||||
|         ServiceInstance serviceInstance = context.getServiceInstance(); | ||||
|         String routeId = apiParam.getRouteId(); | ||||
|         int spendTime = (int)(context.getFinishTimeMillis() - context.getBeginTimeMillis()); | ||||
|         // 这步操作是线程安全的,底层调用了ConcurrentHashMap.computeIfAbsent
 | ||||
|         String key = getMonitorKey(routeId, serviceInstance.getInstanceId()); | ||||
|         MonitorData monitorData = monitorManager.getMonitorInfo(key, (k) -> this.createMonitorInfo(apiParam, serviceInstance)); | ||||
|         monitorData.storeMaxTime(spendTime); | ||||
|         monitorData.storeMinTime(spendTime); | ||||
|         monitorData.getTotalRequestCount().incrementAndGet(); | ||||
|         monitorData.getTotalTime().addAndGet(spendTime); | ||||
|         if (context.isSuccessRequest()) { | ||||
|             monitorData.getSuccessCount().incrementAndGet(); | ||||
|         } else { | ||||
|             monitorData.getErrorCount().incrementAndGet(); | ||||
|             String errorMsg = context.getServiceErrorMsg(); | ||||
|             monitorData.addErrorMsg(errorMsg, context.getResponseStatus()); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private String getMonitorKey(String routeId, String instanceId) { | ||||
|         return routeId + instanceId; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 刷新到数据库 | ||||
|      */ | ||||
|     private synchronized void flushDb() { | ||||
|         Map<String, MonitorData> monitorData = monitorManager.getMonitorData(); | ||||
|         if (monitorData.isEmpty()) { | ||||
|             return; | ||||
|         } | ||||
|         LocalDateTime checkTime = LocalDateTime.now(); | ||||
|         List<String> tobeRemoveKeys = new ArrayList<>(); | ||||
|         List<MonitorDTO> tobeSaveBatch = new ArrayList<>(monitorData.size()); | ||||
|         monitorData.forEach((key, value) -> { | ||||
|             LocalDateTime flushTime = value.getFlushTime(); | ||||
|             if (flushTime.isEqual(checkTime) || flushTime.isBefore(checkTime)) { | ||||
|                 log.debug("刷新监控数据到数据库, MonitorData:{}", value); | ||||
|                 tobeRemoveKeys.add(key); | ||||
|                 MonitorDTO monitorDTO = getMonitorDTO(value); | ||||
|                 tobeSaveBatch.add(monitorDTO); | ||||
|             } | ||||
|         }); | ||||
|         dbMonitorInfoManager.saveMonitorInfoBatch(tobeSaveBatch); | ||||
| 
 | ||||
|         for (String key : tobeRemoveKeys) { | ||||
|             monitorData.remove(key); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private MonitorDTO getMonitorDTO(MonitorData monitorData) { | ||||
|         MonitorDTO monitorDTO = new MonitorDTO(); | ||||
|         monitorDTO.setRouteId(monitorData.getRouteId()); | ||||
|         monitorDTO.setName(monitorData.getName()); | ||||
|         monitorDTO.setVersion(monitorData.getVersion()); | ||||
|         monitorDTO.setServiceId(monitorData.getServiceId()); | ||||
|         monitorDTO.setInstanceId(monitorData.getInstanceId()); | ||||
|         monitorDTO.setMaxTime(monitorData.getMaxTime()); | ||||
|         monitorDTO.setMinTime(monitorData.getMinTime()); | ||||
|         monitorDTO.setTotalTime(monitorData.getTotalTime().longValue()); | ||||
|         monitorDTO.setTotalRequestCount(monitorData.getTotalRequestCount().longValue()); | ||||
|         monitorDTO.setSuccessCount(monitorData.getSuccessCount().longValue()); | ||||
|         monitorDTO.setErrorCount(monitorData.getErrorCount().longValue()); | ||||
|         monitorDTO.setErrorMsgList(monitorData.getMonitorErrorMsgMap().values()); | ||||
|         return monitorDTO; | ||||
|     } | ||||
| 
 | ||||
|     private MonitorData createMonitorInfo(ApiParam apiParam, ServiceInstance serviceInstance) { | ||||
|         MonitorData monitorData = new MonitorData(); | ||||
|         monitorData.setRouteId(apiParam.getRouteId()); | ||||
|         monitorData.setName(apiParam.fetchName()); | ||||
|         monitorData.setVersion(apiParam.fetchVersion()); | ||||
|         monitorData.setServiceId(apiParam.fetchServiceId()); | ||||
|         monitorData.setInstanceId(serviceInstance.getInstanceId()); | ||||
|         monitorData.setTotalTime(new AtomicInteger()); | ||||
|         monitorData.setMaxTime(0); | ||||
|         monitorData.setMinTime(0); | ||||
|         monitorData.setSuccessCount(new AtomicInteger()); | ||||
|         monitorData.setTotalRequestCount(new AtomicInteger()); | ||||
|         monitorData.setErrorCount(new AtomicInteger()); | ||||
|         monitorData.setFlushTime(getFlushTime()); | ||||
|         monitorData.setMonitorErrorMsgMap(new LRUCache<>(monitorErrorCapacity)); | ||||
|         return monitorData; | ||||
|     } | ||||
| 
 | ||||
|     private LocalDateTime getFlushTime() { | ||||
|         return LocalDateTime.now() | ||||
|                 .plusSeconds(flushPeriodSeconds); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     @PostConstruct | ||||
|     public void after() { | ||||
|         // 每隔schedulePeriodSeconds秒执行一次
 | ||||
|         ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(1, new MyNamedThreadFactory("monitorSchedule")); | ||||
|         // 延迟执行,随机5~14秒
 | ||||
|         int delay = 5 +  new Random().nextInt(10); | ||||
|         scheduledThreadPoolExecutor.scheduleWithFixedDelay(this::flushDb, delay, schedulePeriodSeconds, TimeUnit.SECONDS); | ||||
| 
 | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,62 @@ | ||||
| package com.gitee.sop.gateway.mapper; | ||||
| 
 | ||||
| import com.gitee.sop.gatewaycommon.monitor.MonitorDTO; | ||||
| import com.gitee.sop.gatewaycommon.monitor.MonitorErrorMsg; | ||||
| import com.gitee.sop.gatewaycommon.monitor.RouteErrorCount; | ||||
| import org.apache.commons.collections.CollectionUtils; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.beans.factory.annotation.Value; | ||||
| import org.springframework.stereotype.Service; | ||||
| 
 | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.Objects; | ||||
| import java.util.stream.Collectors; | ||||
| 
 | ||||
| /** | ||||
|  * @author tanghc | ||||
|  */ | ||||
| @Service | ||||
| public class DbMonitorInfoManager { | ||||
| 
 | ||||
|     @Autowired | ||||
|     private MonitorInfoMapper monitorInfoMapper; | ||||
| 
 | ||||
|     @Autowired | ||||
|     private MonitorInfoErrorMapper monitorInfoErrorMapper; | ||||
| 
 | ||||
|     @Value("${sop.monitor.error-count-capacity:50}") | ||||
|     int limitCount; | ||||
| 
 | ||||
|     public void saveMonitorInfoBatch(List<MonitorDTO> list) { | ||||
|         if (CollectionUtils.isEmpty(list)) { | ||||
|             return; | ||||
|         } | ||||
|         monitorInfoMapper.saveMonitorInfoBatch(list); | ||||
|         this.saveMonitorInfoErrorBatch(list); | ||||
|     } | ||||
| 
 | ||||
|     private void saveMonitorInfoErrorBatch(List<MonitorDTO> list) { | ||||
|         List<RouteErrorCount> routeErrorCounts = monitorInfoErrorMapper.listRouteErrorCountAll(); | ||||
|         // 路由id对应的错误次数,key:routeId,value:错误次数
 | ||||
|         Map<String, Integer> routeErrorCountsMap = routeErrorCounts.stream() | ||||
|                 .collect(Collectors.toMap(RouteErrorCount::getRouteId, RouteErrorCount::getCount)); | ||||
|         List<MonitorErrorMsg> monitorErrorMsgList = list.stream() | ||||
|                 .filter(monitorDTO -> CollectionUtils.isNotEmpty(monitorDTO.getErrorMsgList())) | ||||
|                 .flatMap(monitorDTO -> { | ||||
|                     int limit = limitCount - routeErrorCountsMap.getOrDefault(monitorDTO.getRouteId(), 0); | ||||
|                     // 容量已满
 | ||||
|                     if (limit <= 0) { | ||||
|                         return null; | ||||
|                     } | ||||
|                     // 截取剩余
 | ||||
|                     return monitorDTO.getErrorMsgList().stream().limit(limit); | ||||
|                 }) | ||||
|                 .filter(Objects::nonNull) | ||||
|                 .collect(Collectors.toList()); | ||||
|         if (CollectionUtils.isNotEmpty(monitorErrorMsgList)) { | ||||
|             monitorInfoErrorMapper.saveMonitorInfoErrorBatch(monitorErrorMsgList); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,36 @@ | ||||
| package com.gitee.sop.gateway.mapper; | ||||
| 
 | ||||
| import com.gitee.fastmybatis.core.mapper.CrudMapper; | ||||
| import com.gitee.sop.gateway.entity.MonitorInfoError; | ||||
| import com.gitee.sop.gatewaycommon.monitor.MonitorErrorMsg; | ||||
| import com.gitee.sop.gatewaycommon.monitor.RouteErrorCount; | ||||
| import org.apache.ibatis.annotations.Param; | ||||
| import org.apache.ibatis.annotations.Select; | ||||
| import org.apache.ibatis.annotations.Update; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * @author tanghc | ||||
|  */ | ||||
| public interface MonitorInfoErrorMapper extends CrudMapper<MonitorInfoError, Long> { | ||||
| 
 | ||||
|     @Update("UPDATE monitor_info_error " + | ||||
|             "SET is_deleted=0 " + | ||||
|             ",count=count + 1 " + | ||||
|             "WHERE route_id=#{routeId} AND error_id=#{errorId}") | ||||
|     int updateError(@Param("routeId") String routeId,@Param("errorId")  String errorId); | ||||
| 
 | ||||
|     int saveMonitorInfoErrorBatch(@Param("list") List<MonitorErrorMsg> list); | ||||
| 
 | ||||
|     @Select("SELECT route_id routeId, count(*) `count` FROM monitor_info_error \n" + | ||||
|             "WHERE is_deleted=0 \n" + | ||||
|             "GROUP BY route_id") | ||||
|     List<RouteErrorCount> listRouteErrorCount(); | ||||
| 
 | ||||
|     @Select("SELECT route_id routeId, count(*) `count` FROM monitor_info_error \n" + | ||||
|             "WHERE is_deleted=0 \n" + | ||||
|             "GROUP BY route_id") | ||||
|     List<RouteErrorCount> listRouteErrorCountAll(); | ||||
| } | ||||
| @ -0,0 +1,39 @@ | ||||
| package com.gitee.sop.gateway.mapper; | ||||
| 
 | ||||
| import com.gitee.fastmybatis.core.mapper.CrudMapper; | ||||
| import com.gitee.sop.gateway.entity.MonitorInfo; | ||||
| import com.gitee.sop.gatewaycommon.monitor.MonitorDTO; | ||||
| import org.apache.ibatis.annotations.Param; | ||||
| import org.apache.ibatis.annotations.Update; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * @author tanghc | ||||
|  */ | ||||
| public interface MonitorInfoMapper extends CrudMapper<MonitorInfo, Long> { | ||||
| 
 | ||||
|     /** | ||||
|      * 更新监控状态 | ||||
|      * | ||||
|      * @return 返回影响行数 | ||||
|      */ | ||||
|     @Update("UPDATE monitor_info " + | ||||
|             "set max_time=case when max_time < #{maxTime} then #{maxTime} else max_time end " + | ||||
|             ",min_time=case when min_time > #{minTime} then #{minTime} else min_time end " + | ||||
|             ",total_request_count=total_request_count + #{totalRequestCount} " + | ||||
|             ",total_time=total_time + #{totalTime} " + | ||||
|             ",success_count=success_count + #{successCount} " + | ||||
|             ",error_count=error_count + #{errorCount} " + | ||||
|             "where route_id=#{routeId}") | ||||
|     int updateMonitorInfo(MonitorDTO monitorDTO); | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * 批量插入监控数据 | ||||
|      * @param list 监控数据 | ||||
|      * @return 返回影响行数 | ||||
|      */ | ||||
|     int saveMonitorInfoBatch(@Param("list") List<MonitorDTO> list); | ||||
| } | ||||
| @ -0,0 +1,30 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> | ||||
| <!-- 注意:文件名必须跟Dao类名字一致,因为是根据文件名做关联。 --> | ||||
| <mapper namespace="com.gitee.sop.gateway.mapper.MonitorInfoErrorMapper"> | ||||
| 
 | ||||
|     <insert id="saveMonitorInfoErrorBatch"> | ||||
|         INSERT INTO `monitor_info_error` ( | ||||
|         `error_id`, | ||||
|         `instance_id`, | ||||
|         `route_id`, | ||||
|         `error_msg`, | ||||
|         `error_status`, | ||||
|         `count`) | ||||
|         VALUES | ||||
|         <foreach collection="list" item="data" separator="," > | ||||
|             (#{data.errorId}, | ||||
|             #{data.instanceId}, | ||||
|             #{data.routeId}, | ||||
|             #{data.errorMsg}, | ||||
|             #{data.errorStatus}, | ||||
|             #{data.count}) | ||||
|         </foreach> | ||||
|         ON DUPLICATE KEY UPDATE | ||||
|             error_msg = VALUES(error_msg) | ||||
|             , error_status = VALUES(error_status) | ||||
|             , `count`= `count` + VALUES(count) | ||||
|             , is_deleted = 0 | ||||
|     </insert> | ||||
| 
 | ||||
| </mapper> | ||||
| @ -0,0 +1,43 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> | ||||
| <!-- 注意:文件名必须跟Dao类名字一致,因为是根据文件名做关联。 --> | ||||
| <mapper namespace="com.gitee.sop.gateway.mapper.MonitorInfoMapper"> | ||||
| 
 | ||||
|     <insert id="saveMonitorInfoBatch"> | ||||
|         INSERT INTO `monitor_info` ( | ||||
|         `route_id`, | ||||
|         `name`, | ||||
|         `version`, | ||||
|         `service_id`, | ||||
|         `instance_id`, | ||||
|         `max_time`, | ||||
|         `min_time`, | ||||
|         `total_time`, | ||||
|         `total_request_count`, | ||||
|         `success_count`, | ||||
|         `error_count`) | ||||
|         VALUES | ||||
|         <foreach collection="list" item="data" separator="," > | ||||
|             (#{data.routeId}, | ||||
|             #{data.name}, | ||||
|             #{data.version}, | ||||
|             #{data.serviceId}, | ||||
|             #{data.instanceId}, | ||||
|             #{data.maxTime}, | ||||
|             #{data.minTime}, | ||||
|             #{data.totalTime}, | ||||
|             #{data.totalRequestCount}, | ||||
|             #{data.successCount}, | ||||
|             #{data.errorCount}) | ||||
|         </foreach> | ||||
|         <![CDATA[ | ||||
|         ON DUPLICATE KEY UPDATE | ||||
|             max_time = case when max_time < VALUES(max_time) then VALUES(max_time) else max_time end | ||||
|             ,min_time = case when min_time > VALUES(min_time) then VALUES(min_time) else min_time end | ||||
|             ,total_time = total_time + VALUES(total_time) | ||||
|             ,total_request_count = total_request_count + VALUES(total_request_count) | ||||
|             ,success_count = success_count + VALUES(success_count) | ||||
|             ,error_count = error_count + VALUES(error_count) | ||||
|     ]]></insert> | ||||
| 
 | ||||
| </mapper> | ||||
| @ -0,0 +1,38 @@ | ||||
| -- 4.1.0升级脚本 | ||||
| 
 | ||||
| use sop; | ||||
| 
 | ||||
| CREATE TABLE `monitor_info` ( | ||||
|   `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, | ||||
|   `route_id` varchar(128) NOT NULL DEFAULT '' COMMENT '路由id', | ||||
|   `name` varchar(128) NOT NULL DEFAULT '' COMMENT '接口名', | ||||
|   `version` varchar(64) NOT NULL DEFAULT '' COMMENT '版本号', | ||||
|   `service_id` varchar(64) NOT NULL DEFAULT '', | ||||
|   `instance_id` varchar(128) NOT NULL DEFAULT '', | ||||
|   `max_time` int(11) NOT NULL DEFAULT '0' COMMENT '请求耗时最长时间', | ||||
|   `min_time` int(11) NOT NULL DEFAULT '0' COMMENT '请求耗时最小时间', | ||||
|   `total_time` bigint(20) NOT NULL DEFAULT '0' COMMENT '总时长,毫秒', | ||||
|   `total_request_count` bigint(20) NOT NULL DEFAULT '0' COMMENT '总调用次数', | ||||
|   `success_count` bigint(20) NOT NULL DEFAULT '0' COMMENT '成功次数', | ||||
|   `error_count` bigint(20) NOT NULL DEFAULT '0' COMMENT '失败次数(业务主动抛出的异常算作成功,如参数校验,未知的错误算失败)', | ||||
|   `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP, | ||||
|   `gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, | ||||
|   PRIMARY KEY (`id`), | ||||
|   UNIQUE KEY `uk_routeid` (`route_id`) USING BTREE, | ||||
|   KEY `idex_name` (`name`) USING BTREE | ||||
| ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='接口监控信息'; | ||||
| 
 | ||||
| CREATE TABLE `monitor_info_error` ( | ||||
|   `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, | ||||
|   `error_id` varchar(64) NOT NULL DEFAULT '' COMMENT '错误id,md5Hex(instanceId + routeId + errorMsg)', | ||||
|   `route_id` varchar(128) NOT NULL DEFAULT '', | ||||
|   `error_msg` text NOT NULL, | ||||
|   `error_status` int(11) NOT NULL DEFAULT '0' COMMENT 'http status,非200错误', | ||||
|   `count` int(11) NOT NULL DEFAULT '0' COMMENT '错误次数', | ||||
|   `is_deleted` tinyint(4) NOT NULL DEFAULT '0', | ||||
|   `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP, | ||||
|   `gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, | ||||
|   PRIMARY KEY (`id`), | ||||
|   UNIQUE KEY `uk_erririd` (`error_id`) USING BTREE, | ||||
|   KEY `idx_routeid` (`route_id`) USING BTREE | ||||
| ) ENGINE=InnoDB DEFAULT CHARSET=utf8; | ||||
					Loading…
					
					
				
		Reference in new issue