diff --git a/LICENSE b/LICENSE
index 261eeb9e9f8b2b4b0d119366dda99c6fd7d35c64..48d4f578282c0a3136ac7045a888248e030fb918 100644
--- a/LICENSE
+++ b/LICENSE
@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
- Copyright [yyyy] [name of copyright owner]
+ Copyright [2020] [JD.com, Inc.]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/README.md b/README.md
index c557dae9e84251d2f2fa25e445fe20a3aa438107..833510e29bc4757927546baad5099bc47ff52f30 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,50 @@
# hotkey
+.png")
-正在京东APP后台灰度了几千台机器,等618海量并发检验后再推广使用,暂时不要下载使用它。
+对任意突发性的无法预先感知的热点数据,包括并不限于热点数据(如突发大量请求同一个商品)、热用户(如恶意爬虫刷子)、热接口(突发海量请求同一个接口)等,进行毫秒级精准探测到。然后对这些热数据、热用户等,推送到所有服务端JVM内存中,以大幅减轻对后端数据存储层的冲击,并可以由使用者决定如何分配、使用这些热key(譬如对热商品做本地缓存、对热用户进行拒绝访问、对热接口进行熔断或返回默认值)。这些热数据在整个服务端集群内保持一致性,并且业务隔离,worker端性能强悍。
-该框架历经多次压测,8核单机worker端每秒可接收处理16万个key探测任务,16核单机至少每秒20万,实际压测达到30万以上,CPU平稳支撑,框架无异常。测试详情可去我[CSDN博客](https://blog.csdn.net/tianyaleixiaowu)查看。
+京东APP后台热数据探测框架,历经多次高压压测和2020年京东618、双11大促考验。
+
+在上线运行的这段时间内,每天探测的key数量数十亿计,精准捕获了大量爬虫、刷子用户,另准确探测大量热门商品并毫秒级推送到各个服务端内存,大幅降低了热数据对数据层的查询压力,提升了应用性能。
+
+在大促期间,hotkey的worker集群秒级吞吐量达到1500万级别,由hotkey探测出的热key进而产生的本地缓存占应用总访问量的50%以上,使得大部分请求进行的是本地查询,减轻了redis层一半以上负担。
+
+该框架历经多次压测,性能指标主要有两个:
+
+1 探测性能:8核单机worker端每秒可接收处理16万个key探测任务,16核单机至少每秒平稳处理30万以上,实际压测达到37万,CPU平稳支撑,框架无异常。
+
+2 推送性能:在高并发写入的同时,对外推送目前性能约平稳推送每秒10-12万次,譬如有1千台server,一台worker上每秒产生了100个热key,那么这1秒会平稳推送100 * 1000 = 10万次,10万次推送会明确在1s内全部送达。如果是写入少,推送多,以纯推送来计数的话,该框架每秒可稳定对外推送40-60万次平稳,80万次极限可撑几秒。
+
+每秒单机吞吐量(写入+对外推送)目前在70万左右稳定。
+
+在真实业务场景中,可用1:1000的比例,即1台worker支撑1000台业务服务端的key探测任务,即可带来极大的数据存储资源节省(如对redis集群的扩充)。测试详情可去我[CSDN博客](https://blog.csdn.net/tianyaleixiaowu)查看。
+
+#### 架架架架架架架架架架架构构构构构构构构构构构设设设设设设设设设设计计计计计计计计计计计计
+ **见京东零售公众号**
+https://mp.weixin.qq.com/s/xOzEj5HtCeh_ezHDPHw6Jw
+
+ **!!!!!!!!!!!!!!!!重要!!!!!!!!!!!!!!!不然看不懂框架原理**
+
+#### 参与开发
+该框架的开发得到了来自于:
+
+京东数科-生活服务研发组-李云峰的大力支持,完成了整个dashboard控制台的研发。
+
+京东零售-平台业务中心-APP后台架构师 杜晓宇的大力支持,参与并帮助解决了诸多技术难题。
+
+京东零售-平台业务中心-PC购物车业务组 任启恒,进行了多次极限压测及功能验证,为框架的健壮性提供了更高的标准。
+
+京东零售-技术与数据中心-订单研发组 姜坤坤,进行了漫长和覆盖更加全面的功能验证,协助进行了bug发现和bugfix。
+
+京东零售-平台业务中心-业务测试部 郭世儒,配合进行了多次军演模拟压力测试演练,协助验证框架在模拟真实流量时的表现。
+
+京东数科-消费金融研发部-账务服务 王小飞,对框架提出了重要的功能新增建议,以及新增功能的协助验证。
+
+外部开发者,来自于[lowbMan](https://gitee.com/lowbMan)提供了关于修改proto序列化方式的建议,以及其他有相当价值的优化建议。
+
+另有京东集团多个部门提供的意见、建议,和对框架的见解、优化方案。
+
+在此一起感谢。
#### 介绍
对任意突发性的无法预先感知的热点请求,包括并不限于热点数据(如突发大量请求同一个商品)、热用户(如爬虫、刷子)、热接口(突发海量请求同一个接口)等,进行毫秒级精准探测到。
@@ -23,64 +65,202 @@
5 接口、用户维度限流
-6 单机接口、用户维度限流限流
+6 单机接口、用户维度限流
7 集群用户维度限流
8 集群接口维度限流
-#### 尚未完成
-控制台功能缺失如下:
+该开源项目战略意义重大,经历百万级并发,参与京东开源中间件项目建设,一直在等你。
+
+#### worker端强悍的性能表现
+每10秒打印一行,totalDealCount代表处理过的key总量,可以看到每10秒处理量在270万-310万之间,对应每秒30万左右QPS。
+
+仅需要很少的机器,即可完成海量key的实时探测计算推送任务。比扩容redis集群规模成本低太多。
+
+
-1.rule的定时保存,当etcd更换时需要能一键导入原有配置
-2.对/jd/count/cartsoa/ 目录下信息进行展示,代表的是每个worker连接的client数量(已完成)
+采用protobuf序列化后性能进一步得到提升。在秒级36万以上时,能稳定在CPU 60%,压测持续时长超过5小时,未见任何异常。30万时,压测时长超过数日,未见任何异常。
+
+
+
-3.对/jd/caffeineSize/ 目录进行展示,里面是每个worker内caffeine缓存的数量
+### 界面效果
+
+### 加微信入群讨论问题,1群已满,请加2群
+
-4.筛选功能,对记录表里做筛选,按时间范围筛选key出现次数大于xx次的数据
-5.导出功能,将key排重后导出的功能,按时间范围筛选
+#### 常见问题
+ **1 worker挂了怎么办**
-6.还有很多
+client根据worker的数量对key进行hash后分发,同一个key一定会被发往同一个worker。譬如4台,挂了一台,key就自动hash到另外3台。那么这个过程中,就会丢失最多一个探测周期内的所有发来的key,譬如2秒10次算热,那么就可能全部被rehash,丢失这2秒的数据。
+它的影响是什么呢?我要不要去存下来所有发来的key呢?很多人都会问的问题。
-该开源项目战略意义重大,要经历百万级并发,参与京东开源中间件项目建设,一直在等你。
+首先挂机,那是极其罕见的事件,即便挂了,对于特别热的key,完全不影响,hash丢几秒,不影响它继续瞬间变热。对于不热的key,它挂不挂,它也热不了。对于那些将热未热的,可能会这次让它热不起来,但没有什么影响,业务服务完全可以吃下这个热key。而加上一堆别的组件如存储、worker间通信传输key等,它的复杂度,性能都会影响很大。
-#### worker端强悍的性能表现
-每10秒打印一行,totalDealCount代表处理过的key总量,可以看到每10秒处理量在270万-310万之间,对应每秒30万左右QPS。
+所以它挂了对系统没有任何影响
-仅需要很少的机器,即可完成海量key的实时探测计算推送任务。比扩容redis集群规模成本低太多了。
-
-
+**2 为什么全部要worker汇总计算,而不是客户端自己计算**
+
+首先,客户端是会本地累加的,在固定的上报周期内,如500ms内,本地就是在累加,每500ms批量上报给worker一次。如果上报频率很高,如10ms一次,那么大概率本地同一个key是没有累加。
+
+有人会说,把这个间隔拉长,譬如本地计算3秒后,本地判定热key,再上报给其他机器。那么这种场景首先对于京东是不可行的,哪怕1秒都不行。譬如一个用户刷子,它在非常频繁地刷接口,一秒刷了500次,而正常用户一秒最多点5次,它已经是非常严重的刷子了。但我们本地还是判断不出来它是不是刷子。为什么?机器多。
+
+随便一个app小组都有数千台机器,一秒500次请求,一个机器连1次都平均不到,大部分是0次,本地如何判断它是刷子呢?总不能访问我一次就算它刷吧。
+然后抢购场景,有些秒杀商品,1-2秒就没了,流量就停了,你本地计算了3秒,才去上报,那活动已经结束了,你的热key再推送已经没价值了。我们就要在活动即将开始之前的可能在10ms内,就要该商品被推送到所有client的jvm里去,根本等不了1秒。
+
+**3 为什么是worker推送,而不是worker发送热key到etcd,客户端直接监听etcd获取热key**
+
+(1) worker和client是长连接,产生热key后,直接推送过去,链路短,耗时少。如果是发到etcd,客户端再通过etcd获取,多了一层中转,耗时明显增加。
+
+(2) etcd性能不够,存在单点风险。譬如我有5000台client,每秒产生100个热key,那么每秒就对应50万次推送。我用2台worker即可轻松完成,随着worker的横向扩展,每秒的推送上限线性增加。但无论是etcd、redis等等任何组件,都不可能做到1秒50万次拉取或推送,会瞬间cpu爆满卡死。因为worker是各自隔离的,而etcd是单点的。实际情况下,也不止5000台client,每秒也不止100个热key,只有当前的架构能支撑。
+
+**4 为什么是etcd,不是zookeeper之类的**
+
+etcd里面具备一个过期删除的功能,你可以设置一个key几秒过期,etcd会自动删除它,删除时还会给所有监听的client回调,这个功能在框架里是在用的,别的配置中心没有这个功能。
+
+etcd的性能和稳定性、低负载等各项指标非常优异,完全满足我们的需求。而zk在很多暴涨流量前和高负载下,并不是那么稳定,性能也差的远。
#### 安装教程
-1. xxxx
-2. xxxx
-3. xxxx
+1. 安装etcd
+
+ 在etcd下载页面下载对应操作系统的etcd,https://github.com/etcd-io/etcd/releases 使用3.4.x以上。相关搭建细节,及常见问题会发布到CSDN博客内。
+
+2. 启动worker(集群)
+ 下载并编译好代码,将worker打包为jar,启动即可。如:
+
+ **` java -jar $JAVA_OPTS worker-0.0.1-SNAPSHOT.jar --etcd.server=${etcdServer}`**
+
+ worker可供配置项如下:
+
+ etcdServer为etcd集群的地址,用逗号分隔
+
+ JAVA_OPTS是配置的JVM相关,可根据实际情况配置
+
+ threadCount为处理key的线程数,不指定时由程序来计算。
+
+ workerPath代表该worker为哪个应用提供计算服务,譬如不同的应用appName需要用不同的worker进行隔离,以避免资源竞争。
+
+3. 启动控制台
+
+ 下载并编译好dashboard项目,创建数据库并导入resource下db.sql文件。 配置一下application.yml里的数据库相关和etcdServer地址。
+
+ 启动dashboard项目,访问ip:8081,即可看到界面。
+
+ 其中节点信息里,即是当前已启动的worker列表。
+
+ 规则配置就是为各app设置规则的地方,初次使用时需要先添加APP。在用户管理菜单中,添加一个新用户,设置他的APP名字,如sample。之后新添加的这个用户就可以登录dashboard给自己的APP设置规则了,登录密码默认123456。
+
+
+ 如图就是一组规则,譬如其中as__开头的热key的规则就是interval-2秒内出现了threshold-10次就认为它是热key,它就会被推送到jvm内存中,并缓存60秒,prefix-true代表前缀匹配。那么在应用中,就可以把一组key,都用as__开头,用来探测。
+
+4. client端接入使用
+
+ 引入client的pom依赖。
+
+ 在应用启动的地方初始化HotKey,譬如
+
+```
+@PostConstruct
+
+public void initHotkey() {
+
+ ClientStarter.Builder builder = new ClientStarter.Builder();
+ ClientStarter starter = builder.setAppName("appName").setEtcdServer("http://1.8.8.4:2379,http://1.1.4.4:2379,http://1.1.1.1:2379").build();
+ starter.startPipeline();
+}
+```
+其中还可以setCaffeineSize(int size)设置本地缓存最大数量,默认5万,setPushPeriod(Long period)设置批量推送key的间隔时间,默认500ms,该值越小,上报热key越频繁,相应越及时,建议根据实际情况调整,如单机每秒qps10个,那么0.5秒上报一次即可,否则是空跑。该值最小为1,即1ms上报一次。
+
+注意:
+
+如果原有项目里使用了guava,需要升级guava为以下版本,否则过低的guava版本可能发生jar包冲突。或者删除自己项目里的guava的maven依赖,guava升级不会影响原有任何逻辑。
+
+```
+
+ com.google.guava
+ guava
+ 28.2-jre
+ compile
+
+```
+
+有时可能项目里没有直接依赖guava,但是引入的某个pom里引了guava,也需要将guava排除掉。
+
+
+
+使用:
+
+主要有如下4个方法可供使用
+
+boolean JdHotKeyStore.isHotKey(String key)
+
+Object JdHotKeyStore.get(String key)
+
+void JdHotKeyStore.smartSet(String key, Object value)
+
+Object JdHotKeyStore.getValue(String key)
+
+
+
+1 boolean isHotKey(String key) ,该方法会返回该key是否是热key,如果是返回true,如果不是返回false,并且会将key上报到探测集群进行数量计算。该方法通常用于判断只需要判断key是否热、不需要缓存value的场景,如刷子用户、接口访问频率等。
+
+2 Object get(String key),该方法返回该key本地缓存的value值,可用于判断是热key后,再去获取本地缓存的value值,通常用于redis热key缓存
+
+3 void smartSet(String key, Object value),方法给热key赋值value,如果是热key,该方法才会赋值,非热key,什么也不做
+
+4 Object getValue(String key),该方法是一个整合方法,相当于isHotKey和get两个方法的整合,该方法直接返回本地缓存的value。
+如果是热key,则存在两种情况,1是返回value,2是返回null。返回null是因为尚未给它set真正的value,返回非null说明已经调用过set方法了,本地缓存value有值了。
+如果不是热key,则返回null,并且将key上报到探测集群进行数量探测。
+
+
+最佳实践:
+
+1 判断用户是否是刷子
+
+ if (JdHotKeyStore.isHotKey(“pin__” + thePin)) {
+ //限流他,do your job
+ }
+2 判断商品id是否是热点
+
+
+ Object skuInfo = JdHotKeyStore.getValue("skuId__" + skuId);
+ if(skuInfo == null) {
+ JdHotKeyStore.smartSet("skuId__" + skuId, theSkuInfo);
+ } else {
+ //使用缓存好的value即可
+ }
+
+ 或者这样:
+
+
+
+ if (JdHotKeyStore.isHotKey(key)) {
+ //注意是get,不是getValue。getValue会获取并上报,get是纯粹的本地获取
+ Object skuInfo = JdHotKeyStore.get("skuId__" + skuId);
+ if(skuInfo == null) {
+ JdHotKeyStore.smartSet("skuId__" + skuId, theSkuInfo);
+ } else {
+ //使用缓存好的value即可
+ }
+
+ }
+
+### 测试环境
-#### 使用说明
+我司为方便大家快速接入试用、查看hotkey效果,提供了1台公网etcd机器(16c16g)、2台worker机器(8c12g),供快速接入测试。
-1. xxxx
-2. xxxx
-3. xxxx
-#### 参与贡献
+etcd地址为: **http://open-etcd.jd.com:2000** ,可以在下载项目后,在sample项目、dashboard项目里yml文件修改etcd连接地址为该地址,然后进入界面控制台,即可看到当前连接的worker,之后可以在sample项目里,进行hotkey测试。
-1. Fork 本仓库
-2. 新建 Feat_xxx 分支
-3. 提交代码
-4. 新建 Pull Request
+控制台公网测试地址:http://hotkey.tianyalei.com:9001/ 账号admin,密码123456
-#### 码云特技
-1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
-2. 码云官方博客 [blog.gitee.com](https://blog.gitee.com)
-3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解码云上的优秀开源项目
-4. [GVP](https://gitee.com/gvp) 全称是码云最有价值开源项目,是码云综合评定出的优秀开源项目
-5. 码云官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
-6. 码云封面人物是一档用来展示码云会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
+测试前,注意在控制台新建自己的app规则。
diff --git a/README.third_party.md b/README.third_party.md
new file mode 100644
index 0000000000000000000000000000000000000000..e56d8fa7fd31a191fb7f7e1b16e39575450d2985
--- /dev/null
+++ b/README.third_party.md
@@ -0,0 +1,22 @@
+我们的项目中依赖了一些第三方lib,现将依赖的第三方库,列在下方:
+
+
+4.1.42.Final
+
+
+3.4.2
+
+
+1.1.7.3
+
+
+1.2.70
+
+
+0.0.14
+
+
+2.8.0
+
+
+5.1.0
diff --git a/client/src/main/java/com/jd/platform/hotkey/client/ClientStarter.java b/client/src/main/java/com/jd/platform/hotkey/client/ClientStarter.java
index 1102d8a19acecf4db3955dd2a5204aa7004f83a1..27474e7bc6c8348ed5ec9e8d28409b043b8d6ece 100644
--- a/client/src/main/java/com/jd/platform/hotkey/client/ClientStarter.java
+++ b/client/src/main/java/com/jd/platform/hotkey/client/ClientStarter.java
@@ -1,6 +1,5 @@
package com.jd.platform.hotkey.client;
-import com.jd.platform.hotkey.client.cache.LocalCache;
import com.jd.platform.hotkey.client.callback.ReceiveNewKeySubscribe;
import com.jd.platform.hotkey.client.core.eventbus.EventBusCenter;
import com.jd.platform.hotkey.client.core.key.PushSchedulerStarter;
@@ -40,9 +39,8 @@ public class ClientStarter {
public static class Builder {
private String appName;
private String etcdServer;
- private LocalCache localCache;
private Long pushPeriod;
- private int caffeineSize = 50000;
+ private int caffeineSize = 200000;
public Builder() {
}
@@ -65,11 +63,6 @@ public class ClientStarter {
return this;
}
- public Builder setLocalCache(LocalCache localCache) {
- this.localCache = localCache;
- return this;
- }
-
public Builder setPushPeriod(Long pushPeriod) {
this.pushPeriod = pushPeriod;
return this;
diff --git a/client/src/main/java/com/jd/platform/hotkey/client/Context.java b/client/src/main/java/com/jd/platform/hotkey/client/Context.java
index f0455a21a20d2c5e146374ed9841c8b12496fea1..fea41067c1047bd1538f6794fc71e03502c47d96 100644
--- a/client/src/main/java/com/jd/platform/hotkey/client/Context.java
+++ b/client/src/main/java/com/jd/platform/hotkey/client/Context.java
@@ -12,5 +12,5 @@ public class Context {
*/
public static boolean NEED_RECONNECT = true;
- public static int CAFFEINE_SIZE = 50000;
+ public static int CAFFEINE_SIZE;
}
diff --git a/client/src/main/java/com/jd/platform/hotkey/client/core/key/NettyKeyPusher.java b/client/src/main/java/com/jd/platform/hotkey/client/core/key/NettyKeyPusher.java
index d5afa72900cffa6c9c37570a83eafd85171b5554..d06b95fcb531a04da0b399b6b1df541125d6c9e5 100644
--- a/client/src/main/java/com/jd/platform/hotkey/client/core/key/NettyKeyPusher.java
+++ b/client/src/main/java/com/jd/platform/hotkey/client/core/key/NettyKeyPusher.java
@@ -10,6 +10,7 @@ import com.jd.platform.hotkey.common.model.MsgBuilder;
import com.jd.platform.hotkey.common.model.typeenum.MessageType;
import com.jd.platform.hotkey.common.tool.FastJsonUtils;
import io.netty.channel.Channel;
+import io.netty.channel.socket.DatagramPacket;
import java.net.InetSocketAddress;
import java.util.ArrayList;
@@ -44,7 +45,8 @@ public class NettyKeyPusher implements IKeyPusher {
for (Channel channel : map.keySet()) {
try {
List batch = map.get(channel);
- channel.writeAndFlush(MsgBuilder.buildByteBuf(new HotKeyMsg(MessageType.REQUEST_NEW_KEY, FastJsonUtils.convertObjectToJSON(batch))));
+ channel.writeAndFlush(new DatagramPacket(MsgBuilder.buildByteBuf(new HotKeyMsg(MessageType.REQUEST_NEW_KEY, FastJsonUtils.convertObjectToJSON(batch))),
+ (InetSocketAddress) channel.remoteAddress())).sync();
} catch (Exception e) {
try {
InetSocketAddress insocket = (InetSocketAddress) channel.remoteAddress();
@@ -78,7 +80,7 @@ public class NettyKeyPusher implements IKeyPusher {
try {
List batch = map.get(channel);
channel.writeAndFlush(MsgBuilder.buildByteBuf(new HotKeyMsg(Context.APP_NAME,
- MessageType.REQUEST_HIT_COUNT, FastJsonUtils.convertObjectToJSON(batch))));
+ MessageType.REQUEST_HIT_COUNT, FastJsonUtils.convertObjectToJSON(batch)))).sync();
} catch (Exception e) {
try {
InetSocketAddress insocket = (InetSocketAddress) channel.remoteAddress();
diff --git a/client/src/main/java/com/jd/platform/hotkey/client/etcd/EtcdStarter.java b/client/src/main/java/com/jd/platform/hotkey/client/etcd/EtcdStarter.java
index c129fb155b99d17303e2d2bcad97056cdc992c52..216160a3b7e0d47f9e70db646018f9066ca87f4d 100644
--- a/client/src/main/java/com/jd/platform/hotkey/client/etcd/EtcdStarter.java
+++ b/client/src/main/java/com/jd/platform/hotkey/client/etcd/EtcdStarter.java
@@ -38,8 +38,6 @@ public class EtcdStarter {
fetchRule();
- fetchExistHotKey();
-
// startWatchWorker();
startWatchRule();
@@ -243,6 +241,9 @@ public class EtcdStarter {
JdLogger.info(getClass(), "trying to connect to etcd and fetch rule info");
boolean success = fetchRuleFromEtcd();
if (success) {
+ //拉取已存在的热key
+ fetchExistHotKey();
+
scheduledExecutorService.shutdown();
}
diff --git a/client/src/main/java/com/jd/platform/hotkey/client/netty/NettyClient.java b/client/src/main/java/com/jd/platform/hotkey/client/netty/NettyClient.java
index d2a36a41c7819ad537e1f8852a2cfe7906573da0..25738b9828d99b0cd977b7a685ecbd1fcd357898 100755
--- a/client/src/main/java/com/jd/platform/hotkey/client/netty/NettyClient.java
+++ b/client/src/main/java/com/jd/platform/hotkey/client/netty/NettyClient.java
@@ -2,8 +2,6 @@ package com.jd.platform.hotkey.client.netty;
import com.jd.platform.hotkey.client.core.worker.WorkerInfoHolder;
import com.jd.platform.hotkey.client.log.JdLogger;
-import com.jd.platform.hotkey.common.coder.Codec;
-import com.jd.platform.hotkey.common.coder.NettyCodec;
import com.jd.platform.hotkey.common.tool.Constant;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
@@ -11,6 +9,7 @@ import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
@@ -28,9 +27,6 @@ public class NettyClient {
private Bootstrap bootstrap;
- private Codec codec = new NettyCodec();
-
-
public static NettyClient getInstance() {
return nettyClient;
}
@@ -47,17 +43,12 @@ public class NettyClient {
Bootstrap bootstrap = new Bootstrap();
NettyClientHandler nettyClientHandler = new NettyClientHandler();
- bootstrap.group(group).channel(NioSocketChannel.class)
- .option(ChannelOption.SO_KEEPALIVE, true)
- .option(ChannelOption.TCP_NODELAY, true)
- .handler(new ChannelInitializer() {
+ bootstrap.group(group).channel(NioDatagramChannel.class)
+ .option(ChannelOption.SO_BROADCAST, true)
+ .handler(new ChannelInitializer() {
@Override
- protected void initChannel(SocketChannel ch) {
- ByteBuf delimiter = Unpooled.copiedBuffer(Constant.DELIMITER.getBytes());
+ protected void initChannel(Channel ch) {
ch.pipeline()
- .addLast(new DelimiterBasedFrameDecoder(Constant.MAX_LENGTH, delimiter))
-// .addLast(codec.newEncoder())
-// .addLast(codec.newDecoder())
.addLast(new StringDecoder())
//10秒没消息时,就发心跳包过去
.addLast(new IdleStateHandler(0, 0, 30))
diff --git a/client/src/main/java/com/jd/platform/hotkey/client/netty/NettyClientHandler.java b/client/src/main/java/com/jd/platform/hotkey/client/netty/NettyClientHandler.java
index aa78dcdeabe0a854f2037d3970dbb3e15fa9562e..b4f52d2a082c07efd47068dd23b7459110565f0f 100755
--- a/client/src/main/java/com/jd/platform/hotkey/client/netty/NettyClientHandler.java
+++ b/client/src/main/java/com/jd/platform/hotkey/client/netty/NettyClientHandler.java
@@ -11,18 +11,19 @@ import com.jd.platform.hotkey.common.model.MsgBuilder;
import com.jd.platform.hotkey.common.model.typeenum.MessageType;
import com.jd.platform.hotkey.common.tool.Constant;
import com.jd.platform.hotkey.common.tool.FastJsonUtils;
-import io.netty.channel.Channel;
-import io.netty.channel.ChannelHandler;
-import io.netty.channel.ChannelHandlerContext;
-import io.netty.channel.SimpleChannelInboundHandler;
+import io.netty.channel.*;
+import io.netty.channel.socket.DatagramPacket;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
+import io.netty.util.CharsetUtil;
+
+import java.net.InetSocketAddress;
/**
* @author wuweifeng wrote on 2019-11-05.
*/
@ChannelHandler.Sharable
-public class NettyClientHandler extends SimpleChannelInboundHandler {
+public class NettyClientHandler extends SimpleChannelInboundHandler {
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
@@ -31,7 +32,8 @@ public class NettyClientHandler extends SimpleChannelInboundHandler {
if (idleStateEvent.state() == IdleState.ALL_IDLE) {
//向服务端发送消息
- ctx.writeAndFlush(MsgBuilder.buildByteBuf(new HotKeyMsg(MessageType.PING, Constant.PING)));
+ ctx.writeAndFlush(new DatagramPacket(MsgBuilder.buildByteBuf(new HotKeyMsg(MessageType.PING, Constant.PING)),
+ (InetSocketAddress) ctx.channel().remoteAddress()));
}
}
@@ -41,7 +43,8 @@ public class NettyClientHandler extends SimpleChannelInboundHandler {
@Override
public void channelActive(ChannelHandlerContext ctx) {
JdLogger.info(getClass(), "channelActive:" + ctx.name());
- ctx.writeAndFlush(MsgBuilder.buildByteBuf(new HotKeyMsg(MessageType.APP_NAME, Context.APP_NAME)));
+ ctx.writeAndFlush(new DatagramPacket(MsgBuilder.buildByteBuf(new HotKeyMsg(MessageType.APP_NAME, Context.APP_NAME)),
+ (InetSocketAddress) ctx.channel().remoteAddress()));
}
@Override
@@ -57,7 +60,8 @@ public class NettyClientHandler extends SimpleChannelInboundHandler {
}
@Override
- protected void channelRead0(ChannelHandlerContext channelHandlerContext, String message) {
+ protected void channelRead0(ChannelHandlerContext channelHandlerContext, DatagramPacket packet) {
+ String message = packet.content().toString(CharsetUtil.UTF_8).replace(Constant.DELIMITER,"");
HotKeyMsg msg = FastJsonUtils.toBean(message, HotKeyMsg.class);
if (MessageType.PONG == msg.getMessageType()) {
JdLogger.info(getClass(), "heart beat");
diff --git a/common/pom.xml b/common/pom.xml
index 59a7d76b70114ca8342f9c9bb936b31764caf036..e11fe7a0b39801872527ad1e513e4d79699a6a5c 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -15,10 +15,9 @@
1.8
4.1.42.Final
- 3.4.2
1.1.7.3
1.2.70
- 0.0.14
+ 0.0.16
2.8.0
5.1.0
@@ -40,13 +39,6 @@
netty-all
${netty4.version}
-
-
- com.lmax
- disruptor
- ${disruptor.version}
-
-
com.alibaba
fastjson
diff --git a/common/src/main/java/com/jd/platform/hotkey/common/coder/Codec.java b/common/src/main/java/com/jd/platform/hotkey/common/coder/Codec.java
deleted file mode 100755
index c61805b9d34c84981d0b2c90891e305ae3b2bd61..0000000000000000000000000000000000000000
--- a/common/src/main/java/com/jd/platform/hotkey/common/coder/Codec.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.jd.platform.hotkey.common.coder;
-
-import io.netty.channel.ChannelHandler;
-
-/**
- * @author wuweifeng wrote on 2019-12-11
- * @version 1.0
- */
-public interface Codec {
-
- ChannelHandler newEncoder();
-
- ChannelHandler newDecoder();
-}
diff --git a/common/src/main/java/com/jd/platform/hotkey/common/coder/MessageDecoder.java b/common/src/main/java/com/jd/platform/hotkey/common/coder/MessageDecoder.java
deleted file mode 100644
index 24cb0ed25000966a1dc925fdacca90f5af8d0e1a..0000000000000000000000000000000000000000
--- a/common/src/main/java/com/jd/platform/hotkey/common/coder/MessageDecoder.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package com.jd.platform.hotkey.common.coder;
-
-import com.jd.platform.hotkey.common.model.HotKeyMsg;
-import com.jd.platform.hotkey.common.model.typeenum.MessageType;
-import com.jd.platform.hotkey.common.tool.Constant;
-import io.netty.buffer.ByteBuf;
-import io.netty.channel.ChannelHandlerContext;
-import io.netty.handler.codec.ByteToMessageDecoder;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.nio.charset.Charset;
-import java.util.List;
-
-/**
- * @author wuweifeng wrote on 2020-01-06
- * @version 1.0
- */
-public class MessageDecoder extends ByteToMessageDecoder {
- private Logger logger = LoggerFactory.getLogger(getClass());
-
- @Override
- protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuf, List
-
- com.jd.common
- sso-uim-spring
- 1.2.0-SNAPSHOT
-
diff --git a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/DashboardApplication.java b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/DashboardApplication.java
index 2b361c66ba6119558b49e80da745a2b5d80fb2d7..3070702bf1a01db93361dbedfabe9ac90740d259 100644
--- a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/DashboardApplication.java
+++ b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/DashboardApplication.java
@@ -1,12 +1,6 @@
package com.jd.platform.hotkey.dashboard;
-import com.alibaba.fastjson.JSON;
-import com.jd.platform.hotkey.common.configcenter.ConfigConstant;
-import com.jd.platform.hotkey.common.configcenter.IConfigCenter;
-import com.jd.platform.hotkey.common.tool.IpUtils;
-import com.jd.platform.hotkey.dashboard.mapper.KeyTimelyMapper;
-import com.jd.platform.hotkey.dashboard.mapper.RulesMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
@@ -15,21 +9,14 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
-import javax.annotation.Resource;
-import java.util.HashMap;
-import java.util.Map;
-
@EnableAsync
@EnableScheduling
@SpringBootApplication
public class DashboardApplication implements CommandLineRunner {
-
private Logger logger = LoggerFactory.getLogger(getClass());
- @Resource
- private KeyTimelyMapper timelyMapper;
public static void main(String[] args) {
try {
@@ -37,13 +24,11 @@ public class DashboardApplication implements CommandLineRunner {
}catch (Exception e){
e.printStackTrace();
}
-
}
+
@Override
- public void run(String... args) {
- int row = timelyMapper.clear();
- logger.info("clear db timely hotKey, effect row : {}",row);
+ public void run(String... args) {
}
}
diff --git a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/cache/CaffeineBuilder.java b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/cache/CaffeineBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..63612e8d8c12c59307c216d598e32ced13d02c49
--- /dev/null
+++ b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/cache/CaffeineBuilder.java
@@ -0,0 +1,30 @@
+package com.jd.platform.hotkey.dashboard.cache;
+
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author wuweifeng wrote on 2019-12-12
+ * @version 1.0
+ */
+public class CaffeineBuilder {
+
+ public static Cache cache(int duration) {
+ return cache(128, 1000000, duration);
+ }
+
+
+ /**
+ * 构建所有来的要缓存的key getCache
+ */
+ public static Cache cache(int minSize, int maxSize, int expireSeconds) {
+ return Caffeine.newBuilder()
+ .initialCapacity(minSize)//初始大小
+ .maximumSize(maxSize)//最大数量
+ .expireAfterWrite(expireSeconds, TimeUnit.SECONDS)//过期时间
+ .build();
+ }
+
+}
diff --git a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/common/config/MyExceptionHandler.java b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/common/config/MyExceptionHandler.java
index 963892110d934ec2163949947443fd9271f29bd5..dc352472c1e147a460465985c34474ce9868c5c1 100644
--- a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/common/config/MyExceptionHandler.java
+++ b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/common/config/MyExceptionHandler.java
@@ -33,8 +33,9 @@ public class MyExceptionHandler {
@ExceptionHandler(value =Exception.class)
@ResponseBody
- public Result exceptionHandler(Exception e){
+ public Result exceptionHandler(Exception e, HttpServletResponse resp){
logger.info("未知异常:",e);
+ resp.setStatus(500);
return Result.error(ResultEnum.BIZ_ERROR);
}
}
diff --git a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/common/domain/Constant.java b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/common/domain/Constant.java
index b11fa73687d71cc9a6a13b968b09885622ef0cf8..952aa2a3ac358f33f592012058fafe35b4d51c37 100644
--- a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/common/domain/Constant.java
+++ b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/common/domain/Constant.java
@@ -35,6 +35,9 @@ public class Constant {
public static final String WORKER_VIEW = "节点信息";
+ public static final String CLEAR_VIEW = "数据清理";
+
+
public static final int MAX_DAY_RANGE = 3;
public static final int VERSION = 1;
@@ -55,6 +58,9 @@ public class Constant {
public static final String INFO = "info";
+ public static final String THIRTY_DAY = "30";
+
+
public static final List HEAD = new ArrayList<>();
static { HEAD.add("热点key"); HEAD.add("次数"); HEAD.add("所属APP");}
diff --git a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/common/domain/EventWrapper.java b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/common/domain/EventWrapper.java
deleted file mode 100644
index 04d4a8325c0eac341df8e743c7d6b5098c951544..0000000000000000000000000000000000000000
--- a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/common/domain/EventWrapper.java
+++ /dev/null
@@ -1,95 +0,0 @@
-package com.jd.platform.hotkey.dashboard.common.domain;
-
-import com.ibm.etcd.api.Event;
-
-import java.io.Serializable;
-import java.util.Date;
-
-/**
- * 包装类,用于保存事件最准确的时间
- */
-public class EventWrapper implements Serializable {
-
- private String key;
-
- private String value;
-
- private Event.EventType eventType;
-
- private Date date;
-
- private Long ttl;
-
- private long version;
-
- private String uuid;
-
- @Override
- public String toString() {
- return "EventWrapper{" +
- "key='" + key + '\'' +
- ", value='" + value + '\'' +
- ", eventType=" + eventType +
- ", date=" + date +
- ", ttl=" + ttl +
- ", version=" + version +
- ", uuid='" + uuid + '\'' +
- '}';
- }
-
- public String getUuid() {
- return uuid;
- }
-
- public void setUuid(String uuid) {
- this.uuid = uuid;
- }
-
- public long getVersion() {
- return version;
- }
-
- public void setVersion(long version) {
- this.version = version;
- }
-
- public String getKey() {
- return key;
- }
-
- public void setKey(String key) {
- this.key = key;
- }
-
- public String getValue() {
- return value;
- }
-
- public void setValue(String value) {
- this.value = value;
- }
-
- public Event.EventType getEventType() {
- return eventType;
- }
-
- public void setEventType(Event.EventType eventType) {
- this.eventType = eventType;
- }
-
- public void setDate(Date date) {
- this.date = date;
- }
-
- public void setTtl(Long ttl) {
- this.ttl = ttl;
- }
-
- public Date getDate() {
- return date;
- }
-
- public Long getTtl() {
- return ttl;
- }
-}
diff --git a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/common/domain/IRecord.java b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/common/domain/IRecord.java
new file mode 100644
index 0000000000000000000000000000000000000000..3585f73c25e12beb3ad6622ca515ad7aac53ef10
--- /dev/null
+++ b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/common/domain/IRecord.java
@@ -0,0 +1,27 @@
+package com.jd.platform.hotkey.dashboard.common.domain;
+
+import java.util.Date;
+
+/**
+ * @author wuweifeng
+ * @version 1.0
+ * @date 2020-09-02
+ */
+public interface IRecord {
+ /**
+ * appName + "/" + key
+ */
+ String appNameKey();
+
+ /**
+ * 手工添加的是时间戳13位,worker传过来的是uuid
+ */
+ String value();
+
+ /**
+ * 0插入,1删除
+ */
+ int type();
+
+ Date createTime();
+}
diff --git a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/common/domain/Page.java b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/common/domain/Page.java
index 5f64c7575f762d7585ac458c3c3cc9b75ee8b94f..d10c3a5169fb88777149563c4a2cb1ace803c823 100644
--- a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/common/domain/Page.java
+++ b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/common/domain/Page.java
@@ -7,9 +7,13 @@ import java.util.List;
public class Page implements Serializable {
private static final long serialVersionUID = 1L;
-
+ /**
+ * 当前第几页
+ */
private Integer page;
-
+ /**
+ * 总共多少条
+ */
private int total;
private List rows;
@@ -20,6 +24,7 @@ public class Page implements Serializable {
this.rows = rows;
}
+
public Integer getPage() {
return page;
}
@@ -43,4 +48,13 @@ public class Page implements Serializable {
public void setRows(List rows) {
this.rows = rows;
}
+
+ @Override
+ public String toString() {
+ return "Page{" +
+ "page=" + page +
+ ", total=" + total +
+ ", rows=" + rows +
+ '}';
+ }
}
diff --git a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/common/domain/vo/ClearCfgVo.java b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/common/domain/vo/ClearCfgVo.java
new file mode 100644
index 0000000000000000000000000000000000000000..14b872b46de2cdc0e1b6e9a16cab8efcba6b7c62
--- /dev/null
+++ b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/common/domain/vo/ClearCfgVo.java
@@ -0,0 +1,47 @@
+package com.jd.platform.hotkey.dashboard.common.domain.vo;
+
+/**
+ * @ProjectName: hotkey
+ * @ClassName: ClearCfgVo
+ * @Description: TODO(一句话描述该类的功能)
+ * @Author: liyunfeng31
+ * @Date: 2020/8/3 9:54
+ */
+public class ClearCfgVo {
+
+ private String app;
+
+ private String ttl;
+
+ private Long version;
+
+ public ClearCfgVo(String app, String ttl, Long version) {
+ this.app = app;
+ this.ttl = ttl;
+ this.version = version;
+ }
+
+ public String getApp() {
+ return app;
+ }
+
+ public void setApp(String app) {
+ this.app = app;
+ }
+
+ public String getTtl() {
+ return ttl;
+ }
+
+ public void setTtl(String ttl) {
+ this.ttl = ttl;
+ }
+
+ public Long getVersion() {
+ return version;
+ }
+
+ public void setVersion(Long version) {
+ this.version = version;
+ }
+}
diff --git a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/common/monitor/DataHandler.java b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/common/monitor/DataHandler.java
index b5b1a1ee09f5439000c2af7727ff82cf67100ea7..bd3a5e0e834bebea4ba5f6d5f43d32cb0002caf8 100644
--- a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/common/monitor/DataHandler.java
+++ b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/common/monitor/DataHandler.java
@@ -1,20 +1,22 @@
package com.jd.platform.hotkey.dashboard.common.monitor;
+import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSON;
-import com.ibm.etcd.api.Event;
+import com.google.common.collect.Queues;
+import com.ibm.etcd.api.KeyValue;
+import com.jd.platform.hotkey.common.configcenter.ConfigConstant;
+import com.jd.platform.hotkey.common.configcenter.IConfigCenter;
import com.jd.platform.hotkey.dashboard.common.domain.Constant;
-import com.jd.platform.hotkey.dashboard.common.domain.EventWrapper;
+import com.jd.platform.hotkey.dashboard.common.domain.IRecord;
import com.jd.platform.hotkey.dashboard.common.domain.req.SearchReq;
import com.jd.platform.hotkey.dashboard.mapper.KeyRecordMapper;
-import com.jd.platform.hotkey.dashboard.mapper.KeyTimelyMapper;
import com.jd.platform.hotkey.dashboard.mapper.StatisticsMapper;
+import com.jd.platform.hotkey.dashboard.mapper.SummaryMapper;
import com.jd.platform.hotkey.dashboard.model.KeyRecord;
-import com.jd.platform.hotkey.dashboard.model.KeyTimely;
import com.jd.platform.hotkey.dashboard.model.Statistics;
import com.jd.platform.hotkey.dashboard.util.DateUtil;
import com.jd.platform.hotkey.dashboard.util.RuleUtil;
-import com.jd.platform.hotkey.dashboard.util.TwoTuple;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
@@ -22,10 +24,13 @@ import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.time.LocalDateTime;
+import java.util.ArrayList;
import java.util.Date;
import java.util.List;
+import java.util.UUID;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
@Component
public class DataHandler {
@@ -35,21 +40,25 @@ public class DataHandler {
@Resource
private KeyRecordMapper keyRecordMapper;
@Resource
- private KeyTimelyMapper keyTimelyMapper;
- @Resource
private StatisticsMapper statisticsMapper;
+ @Resource
+ private SummaryMapper summaryMapper;
+
+ @Resource
+ private IConfigCenter configCenter;
+
/**
* 队列
*/
- private BlockingQueue queue = new LinkedBlockingQueue<>();
+ private BlockingQueue queue = new LinkedBlockingQueue<>();
/**
* 入队
*/
- public void offer(EventWrapper eventWrapper) {
+ public void offer(IRecord record) {
try {
- queue.put(eventWrapper);
+ queue.put(record);
} catch (InterruptedException e) {
e.printStackTrace();
}
@@ -57,35 +66,25 @@ public class DataHandler {
public void insertRecords() {
while (true) {
- TwoTuple twoTuple;
try {
- twoTuple = handHotKey(queue.take());
- if (twoTuple == null) {
+ List records = new ArrayList<>();
+ Queues.drain(queue, records, 1000, 1, TimeUnit.SECONDS);
+ if (CollectionUtil.isEmpty(records)) {
continue;
}
- } catch (Exception e) {
- e.printStackTrace();
- log.error("handHotKey error ," + e.getCause());
- continue;
- }
- KeyRecord keyRecord = twoTuple.getSecond();
- KeyTimely keyTimely = twoTuple.getFirst();
-
- if (keyTimely.getUuid() == null) {
- keyTimelyMapper.deleteByKeyAndApp(keyTimely.getKey(), keyTimely.getAppName());
- } else {
- try {
- keyTimelyMapper.saveOrUpdate(keyTimely);
- } catch (Exception e) {
- log.info("insert timely error",e);
+ List keyRecordList = new ArrayList<>();
+ for (IRecord iRecord : records) {
+ KeyRecord keyRecord = handHotKey(iRecord);
+ if (keyRecord != null) {
+ keyRecordList.add(keyRecord);
+ }
}
- }
- if (keyRecord != null) {
- //插入记录
- keyRecordMapper.insertSelective(keyRecord);
- }
+ keyRecordMapper.batchInsert(keyRecordList);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
}
}
@@ -94,22 +93,19 @@ public class DataHandler {
/**
* 处理热点key和记录
*/
- private TwoTuple handHotKey(EventWrapper eventWrapper) {
- Date date = eventWrapper.getDate();
- long ttl = eventWrapper.getTtl();
- Event.EventType eventType = eventWrapper.getEventType();
- String appKey = eventWrapper.getKey();
- String value = eventWrapper.getValue();
+ private KeyRecord handHotKey(IRecord record) {
+ Date date = record.createTime();
+ String appKey = record.appNameKey();
+ String value = record.value();
//appName+"/"+"key"
String[] arr = appKey.split("/");
String appName = arr[0];
String key = arr[1];
- String uuid = eventWrapper.getUuid();
- int type = eventType.getNumber();
+ String uuid = UUID.randomUUID().toString();
+ int type = record.type();
//组建成对象,供累计后批量插入、删除
- TwoTuple timelyKeyRecordTwoTuple = new TwoTuple<>();
- if (eventType.equals(Event.EventType.PUT)) {
+ if (type == 0) {
//如果是客户端删除时发出的put指令
if (com.jd.platform.hotkey.common.tool.Constant.DEFAULT_DELETE_VALUE.equals(value)) {
log.info("client remove key event : " + appKey);
@@ -117,17 +113,14 @@ public class DataHandler {
}
//手工添加的是时间戳13位,worker传过来的是uuid
String source = value.length() == 13 ? Constant.HAND : Constant.SYSTEM;
- timelyKeyRecordTwoTuple.setFirst(KeyTimely.aKeyTimely().key(key).val(value).appName(appName).duration(ttl).uuid(appKey).createTime(date).build());
String rule = RuleUtil.rule(appKey);
- KeyRecord keyRecord = new KeyRecord(key, rule, appName, ttl, source, type, uuid, date);
+ KeyRecord keyRecord = new KeyRecord(key, rule, appName, 1, source, type, uuid, date);
keyRecord.setRule(rule);
- timelyKeyRecordTwoTuple.setSecond(keyRecord);
- return timelyKeyRecordTwoTuple;
- } else if (eventType.equals(Event.EventType.DELETE)) {
- timelyKeyRecordTwoTuple.setFirst(KeyTimely.aKeyTimely().key(key).appName(appName).build());
- return timelyKeyRecordTwoTuple;
+ return keyRecord;
+ } else {
+ //是删除
+ return null;
}
- return timelyKeyRecordTwoTuple;
}
@@ -210,4 +203,29 @@ public class DataHandler {
}
+
+ /**
+ * 每天根据app的配置清理过期数据
+ */
+ @Scheduled(cron = "0 0 1 * * ?")
+ public void clearExpireData() {
+ try {
+ LocalDateTime now = LocalDateTime.now();
+ List keyValues = configCenter.getPrefix(ConfigConstant.clearCfgPath);
+ for (KeyValue kv : keyValues) {
+ String key = kv.getKey().toStringUtf8();
+ String ttl = kv.getValue().toStringUtf8();
+ String app = key.replace(ConfigConstant.clearCfgPath, "");
+ Date expireDate = DateUtil.ldtToDate(now.minusDays(Integer.parseInt(ttl)));
+ summaryMapper.clearExpireData(app, expireDate);
+ keyRecordMapper.clearExpireData(app, expireDate);
+ statisticsMapper.clearExpireData(app, expireDate);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ }
+
+
}
diff --git a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/controller/ClearController.java b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/controller/ClearController.java
new file mode 100644
index 0000000000000000000000000000000000000000..0ce81f03fe3b3a45d5912efae38bd66663c154fd
--- /dev/null
+++ b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/controller/ClearController.java
@@ -0,0 +1,62 @@
+package com.jd.platform.hotkey.dashboard.controller;
+
+import com.github.pagehelper.PageInfo;
+import com.jd.platform.hotkey.dashboard.common.domain.Constant;
+import com.jd.platform.hotkey.dashboard.common.domain.Page;
+import com.jd.platform.hotkey.dashboard.common.domain.Result;
+import com.jd.platform.hotkey.dashboard.common.domain.req.PageReq;
+import com.jd.platform.hotkey.dashboard.common.domain.vo.ClearCfgVo;
+import com.jd.platform.hotkey.dashboard.service.ClearService;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.ModelMap;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+
+/**
+ * @ProjectName: hotkey
+ * @ClassName: ClearController
+ * @Description: TODO(一句话描述该类的功能)
+ * @Author: liyunfeng31
+ * @Date: 2020/8/3 9:48
+ */
+@Controller
+@RequestMapping("/clear")
+public class ClearController {
+
+
+ private String prefix = "admin/clear";
+
+
+ @Resource
+ private ClearService clearService;
+
+ @GetMapping("/view")
+ public String view(ModelMap modelMap){
+ modelMap.put("title", Constant.CLEAR_VIEW);
+ return prefix + "/list";
+ }
+
+
+ @PostMapping("/list")
+ @ResponseBody
+ public Page list(PageReq page, String app){
+ PageInfo info = clearService.pageClearCfg(page, app);
+ return new Page<>(info.getPageNum(),(int)info.getTotal(),info.getList());
+ }
+
+
+ @GetMapping("/edit/{app}")
+ public String edit(@PathVariable("app") String app, ModelMap modelMap){
+ modelMap.put("clearCfg", clearService.selectClearCfg(app));
+ return prefix + "/edit";
+ }
+
+ @PostMapping("/save")
+ @ResponseBody
+ public Result save(ClearCfgVo cfg){
+ int b = clearService.saveClearCfg(cfg);
+ return b == 0 ? Result.fail():Result.success();
+ }
+
+}
diff --git a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/controller/KeyController.java b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/controller/KeyController.java
index 0b287a83d09533e685d4149a7172e394b5149d38..f3f362dda3825117c07310fb3c1f92ddceceaf7a 100644
--- a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/controller/KeyController.java
+++ b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/controller/KeyController.java
@@ -79,8 +79,7 @@ public class KeyController extends BaseController {
@PostMapping("/listTimely")
@ResponseBody
public Page listTimely(PageReq page, SearchReq searchReq){
- PageInfo info = keyService.pageKeyTimely(page, searchReq);
- return new Page<>(info.getPageNum(),(int)info.getTotal(),info.getList());
+ return keyService.pageKeyTimely(page, searchReq);
}
@@ -123,13 +122,6 @@ public class KeyController extends BaseController {
}
- @GetMapping("/edit/{id}")
- public String edit(@PathVariable("id") Long id, ModelMap modelMap){
- modelMap.put("key", keyService.selectByPk(id));
- return prefix + "/edit";
- }
-
-
@PostMapping("/edit")
@ResponseBody
public Result editSave(KeyTimely key) {
diff --git a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/controller/RuleController.java b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/controller/RuleController.java
index e1869c9fb3d75e981d28c0b561cc30e05f509b67..d187e4bb0d8d7d9090cdda1f7b84517c920bcb59 100644
--- a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/controller/RuleController.java
+++ b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/controller/RuleController.java
@@ -3,7 +3,6 @@ package com.jd.platform.hotkey.dashboard.controller;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONTokener;
-import com.alibaba.fastjson.JSON;
import com.github.pagehelper.PageInfo;
import com.jd.platform.hotkey.dashboard.common.base.BaseController;
import com.jd.platform.hotkey.dashboard.common.domain.Constant;
@@ -14,7 +13,6 @@ import com.jd.platform.hotkey.dashboard.common.domain.req.SearchReq;
import com.jd.platform.hotkey.dashboard.common.domain.vo.HitCountVo;
import com.jd.platform.hotkey.dashboard.common.eunm.ResultEnum;
import com.jd.platform.hotkey.dashboard.common.ex.BizException;
-import com.jd.platform.hotkey.dashboard.model.Rule;
import com.jd.platform.hotkey.dashboard.model.Rules;
import com.jd.platform.hotkey.dashboard.service.RuleService;
import org.springframework.stereotype.Controller;
@@ -31,7 +29,7 @@ import java.util.List;
@Controller
@RequestMapping("/rule")
public class RuleController extends BaseController {
-
+
@Resource
private RuleService ruleService;
diff --git a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/controller/UserController.java b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/controller/UserController.java
index bc571ba53252bac31d25651c221387a27ddf0e94..67053667f47241e683edc25ad4a8c32648dee025 100644
--- a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/controller/UserController.java
+++ b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/controller/UserController.java
@@ -1,14 +1,12 @@
package com.jd.platform.hotkey.dashboard.controller;
-import java.util.*;
-
import com.github.pagehelper.PageInfo;
import com.github.pagehelper.util.StringUtil;
import com.jd.platform.hotkey.dashboard.common.base.BaseController;
import com.jd.platform.hotkey.dashboard.common.domain.Constant;
import com.jd.platform.hotkey.dashboard.common.domain.Page;
-import com.jd.platform.hotkey.dashboard.common.domain.req.PageReq;
import com.jd.platform.hotkey.dashboard.common.domain.Result;
+import com.jd.platform.hotkey.dashboard.common.domain.req.PageReq;
import com.jd.platform.hotkey.dashboard.common.eunm.ResultEnum;
import com.jd.platform.hotkey.dashboard.model.User;
import com.jd.platform.hotkey.dashboard.service.UserService;
@@ -23,6 +21,8 @@ import javax.annotation.Resource;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import java.util.HashMap;
+import java.util.Map;
@Controller
diff --git a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/erp/ErpConfiguration.java b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/erp/ErpConfiguration.java
deleted file mode 100644
index 6b12a85d17330d3175b0efe00331b8d757156f31..0000000000000000000000000000000000000000
--- a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/erp/ErpConfiguration.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.jd.platform.hotkey.dashboard.erp;
-
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-@Configuration
-public class ErpConfiguration {
-
- @Autowired
- ErpProperties erpProperties;
-
- @Bean
- public ErpUimInterceptor erpUimInterceptor() {
- ErpUimInterceptor springSSOInterceptor = new ErpUimInterceptor();
- springSSOInterceptor.setSsoAppKey(erpProperties.getSsoAppKey());
- springSSOInterceptor.setSsoAppToken(erpProperties.getSsoAppToken());
- springSSOInterceptor.setLoginUrl(erpProperties.getLoginUrl());
- springSSOInterceptor.setExcludePath(erpProperties.getExcludePath());
- springSSOInterceptor.setSsoAppUrl(erpProperties.getSsoAppUrl());
- return springSSOInterceptor;
- }
-}
diff --git a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/erp/ErpProperties.java b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/erp/ErpProperties.java
deleted file mode 100644
index 1d4cf23e1875976e8761598f1e1017408ce758ef..0000000000000000000000000000000000000000
--- a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/erp/ErpProperties.java
+++ /dev/null
@@ -1,75 +0,0 @@
-package com.jd.platform.hotkey.dashboard.erp;
-
-import com.jd.platform.hotkey.dashboard.autoconfigure.AbstractProperties;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.stereotype.Component;
-
-/**
- * User: fuxueliang
- * Date: 16/8/17
- * Email: fuxueliang@jd.com
- */
-@Component
-@ConfigurationProperties(prefix = "erp")
-public class ErpProperties extends AbstractProperties {
-
- private String excludePath;
-
- private String loginUrl;
-
- private String ssoAppUrl;
-
- private String ssoAppKey;
-
- private String ssoAppToken;
-
- private Boolean enabled;
-
- public String getExcludePath() {
- return excludePath;
- }
-
- public void setExcludePath(String excludePath) {
- this.excludePath = excludePath;
- }
-
- public String getLoginUrl() {
- return loginUrl;
- }
-
- public void setLoginUrl(String loginUrl) {
- this.loginUrl = loginUrl;
- }
-
- public String getSsoAppUrl() {
- return ssoAppUrl;
- }
-
- public void setSsoAppUrl(String ssoAppUrl) {
- this.ssoAppUrl = ssoAppUrl;
- }
-
- public String getSsoAppKey() {
- return ssoAppKey;
- }
-
- public void setSsoAppKey(String ssoAppKey) {
- this.ssoAppKey = ssoAppKey;
- }
-
- public String getSsoAppToken() {
- return ssoAppToken;
- }
-
- public void setSsoAppToken(String ssoAppToken) {
- this.ssoAppToken = ssoAppToken;
- }
-
- public Boolean getEnabled() {
- return enabled;
- }
-
- public void setEnabled(Boolean enabled) {
- this.enabled = enabled;
- }
-}
diff --git a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/erp/ErpUimInterceptor.java b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/erp/ErpUimInterceptor.java
deleted file mode 100644
index beabf4e29cf2a1b453357ad18d5b0cc05d95f892..0000000000000000000000000000000000000000
--- a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/erp/ErpUimInterceptor.java
+++ /dev/null
@@ -1,56 +0,0 @@
-package com.jd.platform.hotkey.dashboard.erp;
-
-import com.jd.common.springmvc.interceptor.SpringSSOInterceptor;
-import com.jd.common.web.LoginContext;
-import com.jd.platform.hotkey.dashboard.model.User;
-import com.jd.platform.hotkey.dashboard.service.UserService;
-import org.springframework.beans.factory.annotation.Autowired;
-
-import javax.servlet.http.Cookie;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-
-public class ErpUimInterceptor extends SpringSSOInterceptor {
-
- @Autowired
- private UserService userService;
-
- public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
- LoginContext loginContext = LoginContext.getLoginContext();
- if (null == loginContext) {
- super.preHandle(request, response, handler);
- }
- loginContext = LoginContext.getLoginContext();
- if (null == loginContext) {
- return true;
- }
- /*Cookie cookie = new Cookie("erp", loginContext.getNick());
- cookie.setMaxAge(3600*24*7);
- cookie.setPath("/");
- response.addCookie(cookie);*/
- String pin = loginContext.getPin();
- String nickName = loginContext.getNick();
- String orgName = loginContext.getOrgName();
- String orgId = loginContext.getOrgId();
- String mobile = loginContext.getMobile();
- String email = loginContext.getEmail();
- User user = new User();
- user.setAppName("test");
- user.setUserName(pin);
- user.setNickName(nickName);
- user.setPhone(mobile);
- user.setRole("APPUSER");
- Cookie cookie = userService.loginErpUser(user);
- Cookie[] cookies = request.getCookies();
- if(cookies != null){
- for(Cookie tempCookie : cookies){
- if("token".equals(tempCookie.getName()) && tempCookie.getValue() != null){
- return true;
- }
- }
- }
- response.addCookie(cookie);
- return true;
- }
-}
diff --git a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/common/monitor/EtcdMonitor.java b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/etcd/EtcdMonitor.java
similarity index 71%
rename from dashboard/src/main/java/com/jd/platform/hotkey/dashboard/common/monitor/EtcdMonitor.java
rename to dashboard/src/main/java/com/jd/platform/hotkey/dashboard/etcd/EtcdMonitor.java
index 92fa472d0c6b42101ebf2bea6528682feec762af..234e447485a97da513765e9fa53122629284aca5 100644
--- a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/common/monitor/EtcdMonitor.java
+++ b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/etcd/EtcdMonitor.java
@@ -1,4 +1,4 @@
-package com.jd.platform.hotkey.dashboard.common.monitor;
+package com.jd.platform.hotkey.dashboard.etcd;
import cn.hutool.core.util.StrUtil;
import com.ibm.etcd.api.Event;
@@ -6,27 +6,27 @@ import com.ibm.etcd.api.KeyValue;
import com.ibm.etcd.client.kv.KvClient;
import com.jd.platform.hotkey.common.configcenter.ConfigConstant;
import com.jd.platform.hotkey.common.configcenter.IConfigCenter;
+import com.jd.platform.hotkey.common.model.HotKeyModel;
import com.jd.platform.hotkey.common.rule.KeyRule;
import com.jd.platform.hotkey.common.tool.FastJsonUtils;
import com.jd.platform.hotkey.dashboard.common.domain.Constant;
-import com.jd.platform.hotkey.dashboard.common.domain.EventWrapper;
+import com.jd.platform.hotkey.dashboard.common.domain.IRecord;
+import com.jd.platform.hotkey.dashboard.common.monitor.DataHandler;
import com.jd.platform.hotkey.dashboard.mapper.SummaryMapper;
import com.jd.platform.hotkey.dashboard.model.Worker;
+import com.jd.platform.hotkey.dashboard.netty.HotKeyReceiver;
import com.jd.platform.hotkey.dashboard.service.WorkerService;
import com.jd.platform.hotkey.dashboard.util.CommonUtil;
import com.jd.platform.hotkey.dashboard.util.RuleUtil;
import io.grpc.StatusRuntimeException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
-import org.springframework.util.StringUtils;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -55,7 +55,7 @@ public class EtcdMonitor {
private DataHandler dataHandler;
- public static final ExecutorService threadPoolExecutor = Executors.newCachedThreadPool();
+ public static final ExecutorService threadPoolExecutor = Executors.newFixedThreadPool(16);
/**
* 监听新来的热key,该key的产生是来自于手工在控制台添加
@@ -65,48 +65,36 @@ public class EtcdMonitor {
KvClient.WatchIterator watchIterator = configCenter.watchPrefix(ConfigConstant.hotKeyPath);
while (watchIterator.hasNext()) {
Event event = event(watchIterator);
- EventWrapper eventWrapper = build(event);
+ KeyValue kv = event.getKv();
+ String v = kv.getValue().toStringUtf8();
+ Event.EventType eventType = event.getType();
String appKey = event.getKv().getKey().toStringUtf8().replace(ConfigConstant.hotKeyPath, "");
- eventWrapper.setKey(appKey);
- dataHandler.offer(eventWrapper);
- }
- });
- }
+ dataHandler.offer(new IRecord() {
+ @Override
+ public String appNameKey() {
+ return appKey;
+ }
- /**
- * 监听新来的热key,该key的产生是来自于worker集群推送过来的
- */
- public void watchHotKeyRecord() {
- threadPoolExecutor.submit(() -> {
- KvClient.WatchIterator watchIterator = configCenter.watchPrefix(ConfigConstant.hotKeyRecordPath);
- while (watchIterator.hasNext()) {
- Event event = event(watchIterator);
- EventWrapper eventWrapper = build(event);
+ @Override
+ public String value() {
+ return v;
+ }
- String appKey = event.getKv().getKey().toStringUtf8().replace(ConfigConstant.hotKeyRecordPath, "");
- eventWrapper.setKey(appKey);
+ @Override
+ public int type() {
+ return eventType.getNumber();
+ }
- dataHandler.offer(eventWrapper);
+ @Override
+ public Date createTime() {
+ return new Date();
+ }
+ });
}
});
}
- private EventWrapper build(Event event) {
- KeyValue kv = event.getKv();
- long ttl = configCenter.timeToLive(kv.getLease());
- String v = kv.getValue().toStringUtf8();
- Event.EventType eventType = event.getType();
- EventWrapper eventWrapper = new EventWrapper();
- eventWrapper.setValue(v);
- eventWrapper.setDate(new Date());
- eventWrapper.setTtl(ttl);
- eventWrapper.setVersion(kv.getVersion());
- eventWrapper.setEventType(eventType);
- eventWrapper.setUuid(v);
- return eventWrapper;
- }
-
@PostConstruct
public void startWatch() {
@@ -116,26 +104,83 @@ public class EtcdMonitor {
//规则拉取完毕后才能去开始入库
insertRecords();
- //开始监听热key产生
- watchHotKeyRecord();
+ //开始入库
+ dealHotkey();
+ //监听手工创建的key
watchHandOperationHotKey();
//监听rule变化
watchRule();
- watchWorkers();
+// watchWorkers();
//观察热key访问次数和总访问次数,并做统计
watchHitCount();
}
+ /**
+ * 每秒去清理一次caffeine
+ */
+ @Scheduled(fixedRate = 1000)
+ public void cleanCaffeine() {
+ try {
+ HotKeyReceiver.cleanUpCaffeine();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
private void insertRecords() {
threadPoolExecutor.submit(() -> {
dataHandler.insertRecords();
});
}
+
+ /**
+ * 开始消费各worker发来的热key
+ */
+ private void dealHotkey() {
+ threadPoolExecutor.submit(() -> {
+ while (true) {
+ try {
+ //获取发来的这个热key,存入本地caffeine,设置过期时间
+ HotKeyModel model = HotKeyReceiver.take();
+
+ //将该key放入实时热key本地缓存中
+ HotKeyReceiver.put(model);
+
+ dataHandler.offer(new IRecord() {
+ @Override
+ public String appNameKey() {
+ return model.getAppName() + "/" + model.getKey();
+ }
+
+ @Override
+ public String value() {
+ return UUID.randomUUID().toString();
+ }
+
+ @Override
+ public int type() {
+ return 0;
+ }
+
+ @Override
+ public Date createTime() {
+ return new Date();
+ }
+ });
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ });
+ }
+
/**
* 异步监听rule规则变化
*/
@@ -217,7 +262,6 @@ public class EtcdMonitor {
}
-
/**
* 监听热key访问次数和总访问次数
*/
diff --git a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/etcd/EtcdRegister.java b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/etcd/EtcdRegister.java
new file mode 100644
index 0000000000000000000000000000000000000000..d0a44c5690e9fa212317bbc86055bbb3e36994a0
--- /dev/null
+++ b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/etcd/EtcdRegister.java
@@ -0,0 +1,63 @@
+package com.jd.platform.hotkey.dashboard.etcd;
+
+import com.jd.platform.hotkey.common.configcenter.ConfigConstant;
+import com.jd.platform.hotkey.common.configcenter.IConfigCenter;
+import com.jd.platform.hotkey.common.tool.IpUtils;
+import com.jd.platform.hotkey.dashboard.netty.NodesServer;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.Resource;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * dashboard也注册到etcd,供各个worker连接
+ *
+ * @author wuweifeng
+ * @version 1.0
+ * @date 2020-08-28
+ */
+@Component
+public class EtcdRegister {
+ @Resource
+ private IConfigCenter configCenter;
+ @Resource
+ private NodesServer nodesServer;
+
+ /**
+ * 每隔一会去check一下,自己还在不在etcd里
+ */
+ @PostConstruct
+ public void makeSureSelfOn() {
+ new Thread(() -> nodesServer.startNettyServer(ConfigConstant.dashboardPort)).start();
+
+ //开启上传worker信息
+ ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
+ scheduledExecutorService.scheduleAtFixedRate(() -> {
+
+ try {
+ uploadSelfInfo();
+ } catch (Exception e) {
+ //do nothing
+ }
+
+ }, 3, 30, TimeUnit.SECONDS);
+ }
+
+ private void uploadSelfInfo() {
+ configCenter.putAndGrant(buildKey(), buildValue(), 32);
+ }
+
+ private String buildKey() {
+ String hostName = IpUtils.getHostName();
+ return ConfigConstant.dashboardPath + hostName;
+ }
+
+ private String buildValue() {
+ String ip = IpUtils.getIp();
+ return ip + ":" + ConfigConstant.dashboardPort;
+ }
+
+}
diff --git a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/interceptor/JwtInterceptor.java b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/interceptor/JwtInterceptor.java
index 173a46598708858ad5b59aab24f3a855ca6f7ac8..02bc53e79934459c16291fd31c3dd19ce59e8a2f 100644
--- a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/interceptor/JwtInterceptor.java
+++ b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/interceptor/JwtInterceptor.java
@@ -29,9 +29,8 @@ public class JwtInterceptor extends HandlerInterceptorAdapter{
}
String header = request.getHeader("x-requested-with");
if(!StringUtils.isEmpty(header) && "XMLHttpRequest".endsWith(header) && request.getMethod().equals(Constant.POST)){
- final String authHeader = JwtTokenUtil.getAuthHeader(request);
- if (StringUtils.isEmpty(authHeader)
- || !authHeader.startsWith(JwtTokenUtil.TOKEN_PREFIX)) {
+ String authHeader = request.getHeader(JwtTokenUtil.AUTH_HEADER_KEY);
+ if (StringUtils.isEmpty(authHeader) || !authHeader.startsWith(JwtTokenUtil.TOKEN_PREFIX)) {
throw new BizException(ResultEnum.NO_LOGIN);
}
final String token = authHeader.substring(2);
diff --git a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/interceptor/MyWebAppConfigurer.java b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/interceptor/MyWebAppConfigurer.java
index 529e41683a9683b98454a852c53cc84b923dbe76..7360a638195fffb47203f3c5b9909c863e00de07 100644
--- a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/interceptor/MyWebAppConfigurer.java
+++ b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/interceptor/MyWebAppConfigurer.java
@@ -1,22 +1,18 @@
package com.jd.platform.hotkey.dashboard.interceptor;
-import com.jd.platform.hotkey.dashboard.erp.ErpUimInterceptor;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.servlet.config.annotation.*;
+
@Configuration
public class MyWebAppConfigurer implements WebMvcConfigurer {
- @Autowired
- ErpUimInterceptor erpUimInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//拦截路径可自行配置多个 可用 ,分隔开
InterceptorRegistration registration = registry.addInterceptor(new JwtInterceptor()).addPathPatterns("/**");
- registry.addInterceptor(erpUimInterceptor);
- registration.excludePathPatterns("/user/login","/user/getUserName","/error","/static/**","/main/**");
+ registration.excludePathPatterns("/user/login","/error","/static/**","/main/**");
}
diff --git a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/mapper/KeyRecordMapper.java b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/mapper/KeyRecordMapper.java
index 3ef46b31a58494eabc0daadf386cc759c1053c7b..e135ff6dd47281864ce9d86a5b7f8043ff7e68c1 100644
--- a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/mapper/KeyRecordMapper.java
+++ b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/mapper/KeyRecordMapper.java
@@ -1,11 +1,11 @@
package com.jd.platform.hotkey.dashboard.mapper;
-import com.jd.platform.hotkey.dashboard.common.domain.req.ChartReq;
import com.jd.platform.hotkey.dashboard.common.domain.req.SearchReq;
import com.jd.platform.hotkey.dashboard.model.KeyRecord;
import com.jd.platform.hotkey.dashboard.model.Statistics;
import org.apache.ibatis.annotations.Mapper;
+import java.util.Date;
import java.util.List;
/**
@@ -25,4 +25,6 @@ public interface KeyRecordMapper {
List maxHotKey(SearchReq req);
List statisticsByRule(SearchReq req);
+
+ int clearExpireData(String app, Date expireDate);
}
\ No newline at end of file
diff --git a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/mapper/KeyTimelyMapper.java b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/mapper/KeyTimelyMapper.java
deleted file mode 100644
index 3423da34835955cdee38ce5bf8dc47973e4dc682..0000000000000000000000000000000000000000
--- a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/mapper/KeyTimelyMapper.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.jd.platform.hotkey.dashboard.mapper;
-
-import com.jd.platform.hotkey.dashboard.common.domain.req.SearchReq;
-import com.jd.platform.hotkey.dashboard.model.KeyTimely;
-import org.apache.ibatis.annotations.Mapper;
-
-import java.util.List;
-
-@Mapper
-public interface KeyTimelyMapper {
-
- int clear();
-
- int deleteByKeyAndApp(String key,String appName);
-
- int insertSelective(KeyTimely key);
-
- KeyTimely selectByPrimaryKey(Long id);
-
- KeyTimely selectByKey(String key);
-
- int updateByKey(KeyTimely key);
-
- List listKeyTimely(SearchReq param);
-
- int batchInsert(List list);
-
- void batchDeleted(List deleteList);
-
- void saveOrUpdate(KeyTimely keyTimely);
-}
\ No newline at end of file
diff --git a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/mapper/StatisticsMapper.java b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/mapper/StatisticsMapper.java
index dabdfc584a4c4379ac1e31ba9de79704748c5b9b..daf848e4d7e45528e34a8befd512397221885c5b 100644
--- a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/mapper/StatisticsMapper.java
+++ b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/mapper/StatisticsMapper.java
@@ -4,6 +4,7 @@ import com.jd.platform.hotkey.dashboard.common.domain.req.SearchReq;
import com.jd.platform.hotkey.dashboard.model.Statistics;
import org.apache.ibatis.annotations.Mapper;
+import java.util.Date;
import java.util.List;
/**
@@ -42,7 +43,6 @@ public interface StatisticsMapper {
/**
* 清理
- * @param type type
*/
- void clear(int type);
+ int clearExpireData(String app, Date expireDate);
}
\ No newline at end of file
diff --git a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/mapper/SummaryMapper.java b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/mapper/SummaryMapper.java
index 4685e76b4760726c153941015c2cfb274aaff8a6..0305c4c277f760ced757e2a8ef3b6e2cdb2de030 100644
--- a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/mapper/SummaryMapper.java
+++ b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/mapper/SummaryMapper.java
@@ -2,10 +2,10 @@ package com.jd.platform.hotkey.dashboard.mapper;
import com.jd.platform.hotkey.dashboard.common.domain.req.SearchReq;
import com.jd.platform.hotkey.dashboard.common.domain.vo.HitCountVo;
-import com.jd.platform.hotkey.dashboard.model.Statistics;
import com.jd.platform.hotkey.dashboard.model.Summary;
import org.apache.ibatis.annotations.Mapper;
+import java.util.Date;
import java.util.List;
/**
@@ -22,4 +22,6 @@ public interface SummaryMapper {
int saveOrUpdate(Summary records);
List listRuleHitCount(SearchReq req);
+
+ int clearExpireData(String app, Date expireDate);
}
\ No newline at end of file
diff --git a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/model/KeyRecord.java b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/model/KeyRecord.java
index 97f0b4b6ff0e6d2fe8b6e2f8a742497f01535786..e8fdf0f06dbc9524a516944c0845a5c139328dda 100644
--- a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/model/KeyRecord.java
+++ b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/model/KeyRecord.java
@@ -16,7 +16,7 @@ public class KeyRecord implements Serializable {
/**
* 缓存时间
*/
- private Long duration;
+ private int duration;
/**
* 来源: SYSTEM 系统探测;USERNAME创建人
@@ -43,7 +43,7 @@ public class KeyRecord implements Serializable {
public KeyRecord() {
}
- public KeyRecord(String key,String val, String appName, Long duration,
+ public KeyRecord(String key,String val, String appName, int duration,
String source, Integer type,String uuid, Date createTime) {
this.key = key;
this.val = val;
@@ -103,11 +103,11 @@ public class KeyRecord implements Serializable {
this.val = val;
}
- public Long getDuration() {
+ public int getDuration() {
return duration;
}
- public void setDuration(Long duration) {
+ public void setDuration(int duration) {
this.duration = duration;
}
diff --git a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/model/KeyTimely.java b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/model/KeyTimely.java
index 6efd7c5d9b6f9eebe3a88dd9e55718f326feaf9c..67b9d0ac997647e7f8a05db6cc9915a3c286d332 100644
--- a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/model/KeyTimely.java
+++ b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/model/KeyTimely.java
@@ -22,7 +22,6 @@ public class KeyTimely implements Serializable {
private Date createTime;
- private String uuid;
/**
* 该rule的描述
*/
@@ -44,12 +43,25 @@ public class KeyTimely implements Serializable {
public KeyTimely(String key, String val, String appName, Long duration, String uuid, Date createTime) {
this.key = key;
this.val = val;
- this.uuid = uuid;
this.appName = appName;
this.duration = duration;
this.createTime = createTime;
}
+ @Override
+ public String toString() {
+ return "KeyTimely{" +
+ "id=" + id +
+ ", key='" + key + '\'' +
+ ", appName='" + appName + '\'' +
+ ", val='" + val + '\'' +
+ ", duration=" + duration +
+ ", createTime=" + createTime +
+ ", ruleDesc='" + ruleDesc + '\'' +
+ ", updater='" + updater + '\'' +
+ '}';
+ }
+
public String getRuleDesc() {
return ruleDesc;
}
@@ -106,14 +118,6 @@ public class KeyTimely implements Serializable {
this.createTime = createTime;
}
- public String getUuid() {
- return uuid;
- }
-
- public void setUuid(String uuid) {
- this.uuid = uuid;
- }
-
public String getUpdater() {
return updater;
}
@@ -186,7 +190,6 @@ public class KeyTimely implements Serializable {
keyTimely.setVal(val);
keyTimely.setDuration(duration);
keyTimely.setCreateTime(createTime);
- keyTimely.setUuid(uuid);
keyTimely.setRuleDesc(ruleDesc);
keyTimely.setUpdater(updater);
return keyTimely;
diff --git a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/netty/HotKeyReceiver.java b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/netty/HotKeyReceiver.java
new file mode 100644
index 0000000000000000000000000000000000000000..88057c9fabc8548effa88aefa0724dcb33701abb
--- /dev/null
+++ b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/netty/HotKeyReceiver.java
@@ -0,0 +1,175 @@
+package com.jd.platform.hotkey.dashboard.netty;
+
+import cn.hutool.core.util.StrUtil;
+import com.github.benmanes.caffeine.cache.Cache;
+import com.jd.platform.hotkey.common.model.HotKeyModel;
+import com.jd.platform.hotkey.common.rule.KeyRule;
+import com.jd.platform.hotkey.dashboard.cache.CaffeineBuilder;
+import com.jd.platform.hotkey.dashboard.common.domain.req.SearchReq;
+import com.jd.platform.hotkey.dashboard.model.KeyTimely;
+import com.jd.platform.hotkey.dashboard.util.RuleUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.stream.Collectors;
+
+/**
+ * 此处存储所有发来的热key,统一处理入库
+ *
+ * @author wuweifeng
+ * @version 1.0
+ * @date 2020-08-31
+ */
+public class HotKeyReceiver {
+
+ /**
+ * 发来的热key集中营
+ */
+ private static LinkedBlockingQueue hotKeyStoreQueue = new LinkedBlockingQueue<>();
+ /**
+ * 存储实时hotkey,供界面查询实时热key
+ */
+ private static Map> aliveKeyStore = new ConcurrentHashMap<>();
+
+ private static Logger logger = LoggerFactory.getLogger("HotKeyReceiver");
+
+ /**
+ * netty收到的先存这里
+ */
+ public static void push(HotKeyModel model) {
+ hotKeyStoreQueue.offer(model);
+ }
+
+ public static HotKeyModel take() {
+ try {
+ return hotKeyStoreQueue.take();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * 将热key存入本地缓存,设置过期时间
+ */
+ public static void put(HotKeyModel hotKeyModel) {
+ String appNameKey = hotKeyModel.getAppName() + "/" + hotKeyModel.getKey();
+ KeyRule keyRule = RuleUtil.findByKey(appNameKey);
+ if (keyRule == null) {
+ logger.error("rule is null, hotkeyModel " + hotKeyModel.getAppName() + "-" + hotKeyModel.getKey());
+ return;
+ }
+ Cache cache = aliveKeyStore.computeIfAbsent(keyRule.getDuration(), s -> CaffeineBuilder.cache(keyRule.getDuration()));
+ cache.put(appNameKey, hotKeyModel);
+ }
+
+ /**
+ * 展示当前所有的实时热key
+ */
+ public static List list(SearchReq searchReq) {
+ List timelyList = new ArrayList<>();
+
+ long now = System.currentTimeMillis();
+
+ for (Integer duration : aliveKeyStore.keySet()) {
+ Cache cache = aliveKeyStore.get(duration);
+ ConcurrentMap concurrentHashMap = cache.asMap();
+ for (Map.Entry entry : concurrentHashMap.entrySet()) {
+ KeyTimely keyTimely = parse((HotKeyModel) entry.getValue(), now);
+ if (keyTimely == null) {
+ continue;
+ }
+ timelyList.add(keyTimely);
+ }
+ }
+
+ if (searchReq != null) {
+ if (StrUtil.isNotEmpty(searchReq.getApp())) {
+ timelyList = timelyList.parallelStream().filter(keyTimely -> searchReq.getApp().equals(keyTimely.getAppName())).collect(Collectors.toList());
+ }
+ if (StrUtil.isNotEmpty(searchReq.getKey())) {
+ timelyList = timelyList.parallelStream().filter(keyTimely -> keyTimely.getKey().startsWith(searchReq.getKey())).collect(Collectors.toList());
+ }
+
+ }
+
+ timelyList.sort(Comparator.comparing(KeyTimely::getCreateTime).reversed());
+
+ return timelyList;
+ }
+
+ /**
+ * 将hotkeyModel变成前端需要的对象
+ */
+ private static KeyTimely parse(HotKeyModel hotKeyModel, long now) {
+ String appNameKey = hotKeyModel.getAppName() + "/" + hotKeyModel.getKey();
+ KeyRule keyRule = RuleUtil.findByKey(appNameKey);
+ if (keyRule == null) {
+ return null;
+ }
+ long remainTime = keyRule.getDuration() * 1000 - (now - hotKeyModel.getCreateTime());
+ return KeyTimely.aKeyTimely()
+ .key(hotKeyModel.getKey())
+ .val(UUID.randomUUID().toString())
+ .appName(hotKeyModel.getAppName())
+ .duration(remainTime / 1000)
+ .ruleDesc(RuleUtil.ruleDesc(appNameKey))
+ .createTime(new Date(hotKeyModel.getCreateTime())).build();
+ }
+
+// public static void main(String[] args) {
+// List keyTimelyList = new ArrayList<>();
+// for (int i = 0; i < 24; i++) {
+// KeyTimely keyTimely = new KeyTimely();
+// keyTimely.setKey(i + "");
+// keyTimely.setCreateTime(new Date());
+// try {
+// Thread.sleep(10);
+// } catch (InterruptedException e) {
+// e.printStackTrace();
+// }
+// keyTimelyList.add(keyTimely);
+// }
+// keyTimelyList.sort(Comparator.comparing(KeyTimely::getCreateTime).reversed());
+//
+// Page page =PageUtil.pagination(keyTimelyList, 10, 3);
+// System.out.println(page.getTotal());
+// System.out.println(page.getPage());
+// System.out.println(page.getRows());
+// }
+
+
+
+
+
+ /**
+ * 删除实时热key
+ */
+ public static boolean delete(String appNameKey) {
+ KeyRule keyRule = RuleUtil.findByKey(appNameKey);
+ if (keyRule == null) {
+ return false;
+ }
+ Cache cache = aliveKeyStore.get(keyRule.getDuration());
+ if (cache == null) {
+ return false;
+ }
+ cache.invalidate(appNameKey);
+ return true;
+ }
+
+ /**
+ * 定时清理caffeine
+ */
+ public static void cleanUpCaffeine() {
+ for (Integer duration : aliveKeyStore.keySet()) {
+ Cache cache = aliveKeyStore.get(duration);
+ cache.cleanUp();
+ }
+ }
+
+}
diff --git a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/netty/NodesServer.java b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/netty/NodesServer.java
new file mode 100644
index 0000000000000000000000000000000000000000..a3dc7eeb5e78b7d6e1364e0b2aca50ddd1ab809e
--- /dev/null
+++ b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/netty/NodesServer.java
@@ -0,0 +1,78 @@
+package com.jd.platform.hotkey.dashboard.netty;
+
+import com.jd.platform.hotkey.common.tool.Constant;
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.*;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.codec.DelimiterBasedFrameDecoder;
+import io.netty.handler.codec.string.StringDecoder;
+import io.netty.handler.logging.LogLevel;
+import io.netty.handler.logging.LoggingHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 该server用于给各个worker实例连接用。
+ *
+ * @author wuweifeng wrote on 2019-11-05.
+ */
+@Component
+public class NodesServer {
+ private Logger logger = LoggerFactory.getLogger(getClass());
+
+ public void startNettyServer(int port) {
+ //boss单线程
+ EventLoopGroup bossGroup = new NioEventLoopGroup(1);
+ EventLoopGroup workerGroup = new NioEventLoopGroup(4);
+ try {
+ ServerBootstrap bootstrap = new ServerBootstrap();
+ bootstrap.group(bossGroup, workerGroup)
+ .channel(NioServerSocketChannel.class)
+ .handler(new LoggingHandler(LogLevel.INFO))
+ .option(ChannelOption.SO_BACKLOG, 1024)
+ //保持长连接
+ .childOption(ChannelOption.SO_KEEPALIVE, true)
+ //出来网络io事件,如记录日志、对消息编解码等
+ .childHandler(new ChildChannelHandler());
+ //绑定端口,同步等待成功
+ ChannelFuture future = bootstrap.bind(port).sync();
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+ bossGroup.shutdownGracefully (1000, 3000, TimeUnit.MILLISECONDS);
+ workerGroup.shutdownGracefully (1000, 3000, TimeUnit.MILLISECONDS);
+ }));
+ //等待服务器监听端口关闭
+ future.channel().closeFuture().sync();
+ } catch (Exception e) {
+ e.printStackTrace();
+ logger.error("dashboard netty server start failure");
+ } finally {
+ //优雅退出,释放线程池资源
+ bossGroup.shutdownGracefully();
+ workerGroup.shutdownGracefully();
+ }
+ }
+
+ /**
+ * handler类
+ */
+ private class ChildChannelHandler extends ChannelInitializer {
+
+ @Override
+ protected void initChannel(Channel ch) {
+ NodesServerHandler serverHandler = new NodesServerHandler();
+
+ ByteBuf delimiter = Unpooled.copiedBuffer(Constant.DELIMITER.getBytes());
+ ch.pipeline()
+ .addLast(new DelimiterBasedFrameDecoder(Constant.MAX_LENGTH, delimiter))
+ .addLast(new StringDecoder())
+ .addLast(serverHandler);
+ }
+ }
+
+}
diff --git a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/netty/NodesServerHandler.java b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/netty/NodesServerHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..9650c2fd9ef5b517eb0769e75aae5fe2fce7d810
--- /dev/null
+++ b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/netty/NodesServerHandler.java
@@ -0,0 +1,66 @@
+package com.jd.platform.hotkey.dashboard.netty;
+
+import com.jd.platform.hotkey.common.model.HotKeyModel;
+import com.jd.platform.hotkey.common.model.HotKeyMsg;
+import com.jd.platform.hotkey.common.model.MsgBuilder;
+import com.jd.platform.hotkey.common.model.typeenum.MessageType;
+import com.jd.platform.hotkey.common.tool.FastJsonUtils;
+import com.jd.platform.hotkey.common.tool.flush.FlushUtil;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.SimpleChannelInboundHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.StringUtils;
+
+import java.util.List;
+
+import static com.jd.platform.hotkey.common.tool.Constant.PONG;
+
+/**
+ * 这里处理所有netty事件。
+ *
+ * @author wuweifeng wrote on 2019-11-05.
+ */
+public class NodesServerHandler extends SimpleChannelInboundHandler {
+
+ private Logger logger = LoggerFactory.getLogger(getClass());
+
+ @Override
+ protected void channelRead0(ChannelHandlerContext ctx, String message) {
+ if (StringUtils.isEmpty(message)) {
+ return;
+ }
+ try {
+ HotKeyMsg msg = FastJsonUtils.toBean(message, HotKeyMsg.class);
+ if (MessageType.PING == msg.getMessageType()) {
+ String hotMsg = FastJsonUtils.convertObjectToJSON(new HotKeyMsg(MessageType.PONG, PONG));
+ FlushUtil.flush(ctx, MsgBuilder.buildByteBuf(hotMsg));
+ } else if (MessageType.REQUEST_HOT_KEY == msg.getMessageType()) {
+ List list = FastJsonUtils.toList(msg.getBody(), HotKeyModel.class);
+ for (HotKeyModel hotKeyModel : list) {
+ HotKeyReceiver.push(hotKeyModel);
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ }
+
+ @Override
+ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+ logger.error("some thing is error , " + cause.getMessage());
+ }
+
+ @Override
+ public void channelActive(ChannelHandlerContext ctx) throws Exception {
+ super.channelActive(ctx);
+ }
+
+ @Override
+ public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+ ctx.close();
+ super.channelInactive(ctx);
+ }
+
+}
diff --git a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/service/ClearService.java b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/service/ClearService.java
new file mode 100644
index 0000000000000000000000000000000000000000..5266ef348bccc0374fdd2c4f670962b94f353dd8
--- /dev/null
+++ b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/service/ClearService.java
@@ -0,0 +1,23 @@
+package com.jd.platform.hotkey.dashboard.service;
+
+import com.github.pagehelper.PageInfo;
+import com.jd.platform.hotkey.dashboard.common.domain.req.PageReq;
+import com.jd.platform.hotkey.dashboard.common.domain.vo.ClearCfgVo;
+
+/**
+ * @ProjectName: hotkey
+ * @ClassName: ClearService
+ * @Description: TODO(一句话描述该类的功能)
+ * @Author: liyunfeng31
+ * @Date: 2020/8/3 9:51
+ */
+public interface ClearService {
+
+
+ PageInfo pageClearCfg(PageReq page, String app);
+
+ ClearCfgVo selectClearCfg(String app);
+
+ int saveClearCfg(ClearCfgVo cfg);
+
+}
diff --git a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/service/KeyService.java b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/service/KeyService.java
index 3dfcf1892068913c4404faed50bf1d62031b101c..558e814e46d03af4e769728cb5bf5631ee4cb367 100644
--- a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/service/KeyService.java
+++ b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/service/KeyService.java
@@ -1,6 +1,7 @@
package com.jd.platform.hotkey.dashboard.service;
import com.github.pagehelper.PageInfo;
+import com.jd.platform.hotkey.dashboard.common.domain.Page;
import com.jd.platform.hotkey.dashboard.common.domain.req.ChartReq;
import com.jd.platform.hotkey.dashboard.common.domain.req.PageReq;
import com.jd.platform.hotkey.dashboard.common.domain.req.SearchReq;
@@ -29,11 +30,7 @@ public interface KeyService {
int delKeyByUser(KeyTimely keyTimely);
- KeyTimely selectByKey(String key);
-
- KeyTimely selectByPk(Long key);
-
- PageInfo pageKeyTimely(PageReq page, SearchReq param);
+ Page pageKeyTimely(PageReq page, SearchReq param);
PageInfo pageMaxHot(PageReq page, SearchReq param);
diff --git a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/service/impl/ClearServiceImpl.java b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/service/impl/ClearServiceImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..8227a955ecefbbc9d5ec2d89b6ff414fcc280eaf
--- /dev/null
+++ b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/service/impl/ClearServiceImpl.java
@@ -0,0 +1,85 @@
+package com.jd.platform.hotkey.dashboard.service.impl;
+
+import com.github.pagehelper.PageInfo;
+import com.github.pagehelper.util.StringUtil;
+import com.ibm.etcd.api.KeyValue;
+import com.jd.platform.hotkey.common.configcenter.ConfigConstant;
+import com.jd.platform.hotkey.common.configcenter.IConfigCenter;
+import com.jd.platform.hotkey.dashboard.common.domain.Constant;
+import com.jd.platform.hotkey.dashboard.common.domain.req.PageReq;
+import com.jd.platform.hotkey.dashboard.common.domain.vo.ClearCfgVo;
+import com.jd.platform.hotkey.dashboard.service.ClearService;
+import com.jd.platform.hotkey.dashboard.service.UserService;
+import org.apache.commons.collections4.CollectionUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @ProjectName: hotkey
+ * @ClassName: ClearServiceImpl
+ * @Description: TODO(一句话描述该类的功能)
+ * @Author: liyunfeng31
+ * @Date: 2020/8/3 9:57
+ */
+@Service
+public class ClearServiceImpl implements ClearService {
+
+
+ @Resource
+ private IConfigCenter configCenter;
+
+ @Resource
+ private UserService userService;
+
+
+ @Override
+ public PageInfo pageClearCfg(PageReq page, String app) {
+ List keyValues = configCenter.getPrefix(ConfigConstant.clearCfgPath);
+ if(CollectionUtils.isEmpty(keyValues)){
+ List apps = userService.listApp();
+ for (String ap : apps) {
+ configCenter.put(ConfigConstant.clearCfgPath + ap, Constant.THIRTY_DAY);
+ }
+ keyValues = configCenter.getPrefix(ConfigConstant.clearCfgPath);
+ }
+ List cfgVos = new ArrayList<>();
+ for (KeyValue kv : keyValues) {
+ String v = kv.getValue().toStringUtf8();
+ String key = kv.getKey().toStringUtf8();
+ if(StringUtil.isEmpty(v)){
+ configCenter.put(key, Constant.THIRTY_DAY);
+ continue;
+ }
+ long version = kv.getModRevision();
+ String k = key.replace(ConfigConstant.clearCfgPath,"");
+ if(StringUtils.isEmpty(app)){
+ cfgVos.add(new ClearCfgVo(k, v, version));
+ }else{
+ if(k.equals(app)){
+ cfgVos.add(new ClearCfgVo(k, v, version));
+ }
+ }
+ }
+ return new PageInfo<>(cfgVos);
+ }
+
+ @Override
+ public ClearCfgVo selectClearCfg(String app) {
+ KeyValue kv = configCenter.getKv(ConfigConstant.clearCfgPath + app);
+ if(kv == null || kv.getValue() == null){
+ configCenter.put(ConfigConstant.clearCfgPath + app, Constant.THIRTY_DAY);
+ return new ClearCfgVo(app, Constant.THIRTY_DAY, 0L);
+ }
+ String v = kv.getValue().toStringUtf8();
+ return new ClearCfgVo(app,v,kv.getModRevision());
+ }
+
+ @Override
+ public int saveClearCfg(ClearCfgVo cfg) {
+ configCenter.put(ConfigConstant.clearCfgPath + cfg.getApp(), cfg.getTtl());
+ return 1;
+ }
+}
diff --git a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/service/impl/KeyServiceImpl.java b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/service/impl/KeyServiceImpl.java
index 88a4263a3090e97301543dcd8d049fdf155996c7..09dbd32d36a61264e8d7743a9e03557f862e0dfa 100644
--- a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/service/impl/KeyServiceImpl.java
+++ b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/service/impl/KeyServiceImpl.java
@@ -8,24 +8,29 @@ import com.github.pagehelper.PageInfo;
import com.ibm.etcd.api.Event;
import com.jd.platform.hotkey.common.configcenter.ConfigConstant;
import com.jd.platform.hotkey.common.configcenter.IConfigCenter;
+import com.jd.platform.hotkey.common.model.HotKeyModel;
import com.jd.platform.hotkey.dashboard.common.domain.Constant;
+import com.jd.platform.hotkey.dashboard.common.domain.Page;
import com.jd.platform.hotkey.dashboard.common.domain.req.ChartReq;
import com.jd.platform.hotkey.dashboard.common.domain.req.PageReq;
import com.jd.platform.hotkey.dashboard.common.domain.req.SearchReq;
import com.jd.platform.hotkey.dashboard.common.domain.vo.HotKeyLineChartVo;
import com.jd.platform.hotkey.dashboard.mapper.ChangeLogMapper;
import com.jd.platform.hotkey.dashboard.mapper.KeyRecordMapper;
-import com.jd.platform.hotkey.dashboard.mapper.KeyTimelyMapper;
import com.jd.platform.hotkey.dashboard.mapper.StatisticsMapper;
import com.jd.platform.hotkey.dashboard.model.ChangeLog;
import com.jd.platform.hotkey.dashboard.model.KeyRecord;
import com.jd.platform.hotkey.dashboard.model.KeyTimely;
import com.jd.platform.hotkey.dashboard.model.Statistics;
+import com.jd.platform.hotkey.dashboard.netty.HotKeyReceiver;
import com.jd.platform.hotkey.dashboard.service.KeyService;
import com.jd.platform.hotkey.dashboard.service.RuleService;
import com.jd.platform.hotkey.dashboard.util.CommonUtil;
import com.jd.platform.hotkey.dashboard.util.DateUtil;
+import com.jd.platform.hotkey.dashboard.util.PageUtil;
import com.jd.platform.hotkey.dashboard.util.RuleUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@@ -50,16 +55,17 @@ public class KeyServiceImpl implements KeyService {
@Resource
private KeyRecordMapper recordMapper;
@Resource
- private KeyTimelyMapper keyTimelyMapper;
- @Resource
private StatisticsMapper statisticsMapper;
@Resource
private RuleService ruleService;
@Resource
private ChangeLogMapper logMapper;
+ private Logger logger = LoggerFactory.getLogger(getClass());
+
/**
* 折线图
+ *
* @param req req
* @return vo
*/
@@ -68,52 +74,48 @@ public class KeyServiceImpl implements KeyService {
int type = req.getType();
String appReq = req.getApp();
// admin 全查
- if(StrUtil.isNotEmpty(appReq)){
+ if (StrUtil.isNotEmpty(appReq)) {
app = appReq;
}
req.setApp(null);
LocalDateTime now = LocalDateTime.now();
req.setEndTime(req.getEndTime() == null ? DateUtil.ldtToDate(now) : req.getEndTime());
List rules = ruleService.listRules(null);
- if(type == 4){
- LocalDateTime st = req.getStartTime() == null? now.minusMinutes(31) : DateUtil.dateToLdt(req.getStartTime());
+ if (type == 4) {
+ LocalDateTime st = req.getStartTime() == null ? now.minusMinutes(31) : DateUtil.dateToLdt(req.getStartTime());
req.setStartTime(DateUtil.ldtToDate(st));
LocalDateTime et = DateUtil.dateToLdt(req.getEndTime());
- boolean longTime = Duration.between(st,et).toHours()>2;
+ boolean longTime = Duration.between(st, et).toHours() > 2;
req.setType(longTime ? 6 : 5);
List list = statisticsMapper.listOrderByTime(req);
- return CommonUtil.processData(st,et,list,!longTime,rules,app);
+ return CommonUtil.processData(st, et, list, !longTime, rules, app);
}
- if(type == 5){
+ if (type == 5) {
LocalDateTime startTime = now.minusMinutes(31);
req.setStartTime(DateUtil.ldtToDate(startTime));
List list = statisticsMapper.listOrderByTime(req);
- return CommonUtil.processData(startTime,now,list,true,rules,app);
- }else if(type == 6){
+ return CommonUtil.processData(startTime, now, list, true, rules, app);
+ } else if (type == 6) {
LocalDateTime startTime2 = now.minusHours(25);
req.setStartTime(DateUtil.ldtToDate(startTime2));
List list2 = statisticsMapper.listOrderByTime(req);
- return CommonUtil.processData(startTime2,now,list2,false,rules,app);
- }else{
+ return CommonUtil.processData(startTime2, now, list2, false, rules, app);
+ } else {
LocalDateTime startTime3 = now.minusDays(7).minusHours(1);
req.setStartTime(DateUtil.ldtToDate(startTime3));
req.setType(6);
List list3 = statisticsMapper.listOrderByTime(req);
- return CommonUtil.processData(startTime3,now,list3,false,rules,app);
+ return CommonUtil.processData(startTime3, now, list3, false, rules, app);
}
}
@Override
- public PageInfo pageKeyTimely(PageReq page, SearchReq param) {
- PageHelper.startPage(page.getPageNum(), page.getPageSize());
- List listKey = keyTimelyMapper.listKeyTimely(param);
- for (KeyTimely timely : listKey) {
- timely.setKey(CommonUtil.keyName(timely.getKey()));
- timely.setRuleDesc(RuleUtil.ruleDesc(timely.getAppName() + "/" + timely.getKey()));
- }
- return new PageInfo<>(listKey);
+ public Page pageKeyTimely(PageReq page, SearchReq param) {
+ List keyTimelies = HotKeyReceiver.list(param);
+ return PageUtil.pagination(keyTimelies, page.getPageSize(), page.getPageNum()-1);
+
}
@Override
@@ -143,12 +145,12 @@ public class KeyServiceImpl implements KeyService {
Map keyDateMap = keyDateMap(statistics, hours);
// 获取时间x轴
List list = new ArrayList<>();
- for (int i = hours; i >0 ; i--) {
- LocalDateTime time = LocalDateTime.now().minusHours(i-1);
+ for (int i = hours; i > 0; i--) {
+ LocalDateTime time = LocalDateTime.now().minusHours(i - 1);
int hour = time.getHour();
- list.add(hour+"时");
+ list.add(hour + "时");
}
- return new HotKeyLineChartVo(list,keyDateMap);
+ return new HotKeyLineChartVo(list, keyDateMap);
}
@@ -168,6 +170,13 @@ public class KeyServiceImpl implements KeyService {
public int insertKeyByUser(KeyTimely key) {
configCenter.putAndGrant(ConfigConstant.hotKeyPath + key.getAppName() + "/" + key.getKey(),
System.currentTimeMillis() + "", key.getDuration());
+
+ //写入本地缓存,实时热key信息
+ HotKeyModel hotKeyModel = new HotKeyModel();
+ hotKeyModel.setCreateTime(System.currentTimeMillis());
+ hotKeyModel.setAppName(key.getAppName());
+ hotKeyModel.setKey(key.getKey());
+ HotKeyReceiver.put(hotKeyModel);
return logMapper.insertSelective(new ChangeLog(key.getAppName(), Constant.HOTKEY_CHANGE, "",
key.getKey(), key.getUpdater(), SystemClock.now() + ""));
}
@@ -185,56 +194,46 @@ public class KeyServiceImpl implements KeyService {
String[] arr = keyTimely.getKey().split("/");
//删除client监听目录的key
String etcdKey = ConfigConstant.hotKeyPath + arr[0] + "/" + arr[1];
+
+ //删除caffeine里的实时key
+ HotKeyReceiver.delete(arr[0] + "/" + arr[1]);
+
if (configCenter.get(etcdKey) == null) {
//如果手工目录也就是client监听的目录里没有该key,那么就往里面放一个,然后再删掉它,这样client才能监听到删除事件
configCenter.putAndGrant(etcdKey, com.jd.platform.hotkey.common.tool.Constant.DEFAULT_DELETE_VALUE, 1);
}
configCenter.delete(etcdKey);
- //也删除Record目录下的该key,因为不确定要删的key到底在哪
- String recordKey = ConfigConstant.hotKeyRecordPath + arr[0] + "/" + arr[1];
- configCenter.delete(recordKey);
-
- KeyRecord keyRecord = new KeyRecord(arr[1], "", arr[0], 0L, Constant.HAND,
+ KeyRecord keyRecord = new KeyRecord(arr[1], "", arr[0], 0, Constant.HAND,
Event.EventType.DELETE_VALUE, UUID.randomUUID().toString(), new Date());
recordMapper.insertSelective(keyRecord);
- return logMapper.insertSelective(new ChangeLog(arr[0], Constant.HOTKEY_CHANGE, arr[1],"", keyTimely.getUpdater(), SystemClock.now() + ""));
+ return logMapper.insertSelective(new ChangeLog(keyTimely.getKey(), Constant.HOTKEY_CHANGE, keyTimely.getKey(), "", keyTimely.getUpdater(), SystemClock.now() + ""));
}
- @Override
- public KeyTimely selectByKey(String key) {
- return keyTimelyMapper.selectByKey(key);
- }
-
- @Override
- public KeyTimely selectByPk(Long id) {
- return keyTimelyMapper.selectByPrimaryKey(id);
- }
-
-
-
- private Map keyDateMap(List statistics, int hours){
+ private Map keyDateMap(List statistics, int hours) {
Map map = new HashMap<>(10);
Map> listMap = statistics.stream().collect(Collectors.groupingBy(Statistics::getKeyName));
for (Map.Entry> m : listMap.entrySet()) {
int start = DateUtil.preHoursInt(5);
- map.put(m.getKey(),new int[hours]);
+ map.put(m.getKey(), new int[hours]);
int[] data = map.get(m.getKey());
int tmp = 0;
for (int i = 0; i < hours; i++) {
Statistics st;
try {
st = m.getValue().get(tmp);
- if(String.valueOf(start).endsWith("24")){ start = start + 77; }
- if(start != st.getHours()){
+ if (String.valueOf(start).endsWith("24")) {
+ start = start + 77;
+ }
+ if (start != st.getHours()) {
data[i] = 0;
- }else{
- tmp ++;
+ } else {
+ tmp++;
data[i] = st.getCount();
}
start++;
- }catch (Exception e){
+ } catch (Exception e) {
data[i] = 0;
}
}
@@ -243,9 +242,8 @@ public class KeyServiceImpl implements KeyService {
}
-
private void checkParam(SearchReq req) {
- if(req.getStartTime() == null || req.getEndTime() == null){
+ if (req.getStartTime() == null || req.getEndTime() == null) {
req.setStartTime(DateUtil.preTime(5));
req.setEndTime(new Date());
}
@@ -255,173 +253,6 @@ public class KeyServiceImpl implements KeyService {
}*/
}
-
- private static List statisticsList(){
- Random rd = new Random();
- List records = new ArrayList<>();
- // statisticsMapper.clear(1);
- for (int i = 0; i < 35; i++) {
- if(i== 2||i== 3||i== 4||i== 25||i== 12||i== 13||i== 14||i== 30||i==32||i==33||i== 34){
- continue;
- }
- // 每分钟小时 统计一次record 表 结果记录到统计表
- LocalDateTime now = LocalDateTime.now().minusMinutes(35-i);
- Date nowTime = DateUtil.ldtToDate(now);
- int day = DateUtil.nowDay(now);
- int hour = DateUtil.nowHour(now);
- int minus = DateUtil.nowMinus(now);
-
- Statistics s1 = new Statistics();
- s1.setKeyName("k21");
- s1.setApp("key21APP");
- s1.setCreateTime(nowTime);
- s1.setDays(day);
- s1.setHours(hour);
- s1.setCount(rd.nextInt(800));
- s1.setBizType(1);
- s1.setMinutes(minus);
- s1.setUuid(1 + "_" + s1.getKeyName() + "_" + hour+ UUID.randomUUID().toString());
- records.add(s1);
-
- Statistics s11 = new Statistics();
- s11.setKeyName("k22");
- s11.setApp("key22APP");
- s11.setBizType(1);
- s11.setCreateTime(nowTime);
- s11.setDays(day);
- s11.setMinutes(minus);
- s11.setCount(rd.nextInt(600));
- s11.setHours(hour);
- s11.setUuid(2 + "_" + s1.getKeyName() + "_" + hour+ UUID.randomUUID().toString());
- records.add(s11);
- }
-
- if(1==1){
- return records;
- }
- // statisticsMapper.batchInsert(records);
- List list = new ArrayList<>();
- for (int i = 0; i < 30 ; i++) {
- if(i == 12 || i ==13){
- }else{
- Statistics st = new Statistics();
- st.setApp("k21");
- st.setKeyName("k21");
- st.setCount(rd.nextInt(100));
- st.setBizType(1);
- // LocalDateTime ldf = DateUtil.strToLdt("2006082355");
- // System.out.println(ldf.toString()+" - "+DateUtil.strToLdt("2006082355"));
- int time = DateUtil.reviseTime(DateUtil.strToLdt("2006082355",DateUtil.PATTERN_MINUS), i, 1);
- // System.out.println(time);
- st.setMinutes(time);
- // list.add(st);
- }
- }
- List list2 = new ArrayList<>();
- for (int i = 0; i < 30 ; i++) {
- if(i == 0 || i == 1 || i == 18 || i == 19 || i == 25){
-
- }else{
- Statistics st2 = new Statistics();
- st2.setApp("k22");
- st2.setKeyName("k22");
- st2.setCount(rd.nextInt(100));
- st2.setBizType(1);
- st2.setMinutes(DateUtil.reviseTime(DateUtil.strToLdt("2006082355", DateUtil.PATTERN_MINUS),i,1));
- list2.add(st2);
- }
- }
- // list.addAll(list2);
- return list;
-
- }
-
- private List statisticsList1(int type){
- Random rd = new Random();
- List records = new ArrayList<>();
- statisticsMapper.clear(6);
-
- if(type == 3){
- int temp = 1;
- for (int j = 0; j < 9; j++) {
- for (int i = 0; i < 24; i++) {
- // 每分钟小时 统计一次record 表 结果记录到统计表
- LocalDateTime now = LocalDateTime.now().minusHours(9 * 24 - temp);
-
- Date nowTime = DateUtil.ldtToDate(now);
- int day = DateUtil.nowDay(now);
- int hour = DateUtil.nowHour(now);
- Statistics s1 = new Statistics();
- s1.setKeyName("k21");
- s1.setApp("key21APP");
- s1.setCreateTime(nowTime);
- s1.setDays(day);
- s1.setHours(hour);
- s1.setCount(rd.nextInt(200));
- s1.setBizType(6);
- s1.setUuid(1 + "_" + s1.getKeyName() + "_" + hour+ UUID.randomUUID().toString());
- records.add(s1);
-
- Statistics s11 = new Statistics();
- s11.setKeyName("k22");
- s11.setApp("key22APP");
- s11.setBizType(6);
- s11.setCreateTime(nowTime);
- s11.setDays(day);
- s11.setCount(rd.nextInt(500));
- s11.setHours(hour);
- s11.setUuid(2 + "_" + s1.getKeyName() + "_" + hour+ UUID.randomUUID().toString());
- records.add(s11);
-
- Statistics s33 = new Statistics();
- s33.setKeyName("k33");
- s33.setApp("key33APP");
- s33.setBizType(6);
- s33.setCreateTime(nowTime);
- s33.setDays(day);
- s33.setCount(rd.nextInt(300));
- s33.setHours(hour);
- s33.setUuid(3 + "_" + s1.getKeyName() + "_" + hour+ UUID.randomUUID().toString());
- records.add(s33);
- temp++;
- }
- }
-
-
- }else{
- for (int i = 0; i < 30; i++) {
- // 每分钟小时 统计一次record 表 结果记录到统计表
- LocalDateTime now = LocalDateTime.now().minusHours(30-i);
- Date nowTime = DateUtil.ldtToDate(now);
- int day = DateUtil.nowDay(now);
- int hour = DateUtil.nowHour(now);
- Statistics s1 = new Statistics();
- s1.setKeyName("key21");
- s1.setApp("key21APP");
- s1.setCreateTime(nowTime);
- s1.setDays(day);
- s1.setHours(hour);
- s1.setCount(rd.nextInt(800));
- s1.setBizType(2);
- s1.setUuid(1 + "_" + s1.getKeyName() + "_" + hour+ UUID.randomUUID().toString());
- records.add(s1);
-
- Statistics s11 = new Statistics();
- s11.setKeyName("key22");
- s11.setApp("key22APP");
- s11.setBizType(2);
- s11.setCreateTime(nowTime);
- s11.setDays(day);
- s11.setCount(rd.nextInt(600));
- s11.setHours(hour);
- s11.setUuid(2 + "_" + s1.getKeyName() + "_" + hour+ UUID.randomUUID().toString());
- records.add(s11);
- }
- }
- statisticsMapper.batchInsert(records);
- return records;
- }
-
}
diff --git a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/service/impl/RuleServiceImpl.java b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/service/impl/RuleServiceImpl.java
index 9dd493e79c3fce3177b9565f5a78f70e18ada0ef..bc73cf63b78e4f106fac95bd9bb03e7278e17598 100644
--- a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/service/impl/RuleServiceImpl.java
+++ b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/service/impl/RuleServiceImpl.java
@@ -52,13 +52,13 @@ public class RuleServiceImpl implements RuleService {
@Override
public Rules selectRules(String app) {
KeyValue kv = configCenter.getKv(ConfigConstant.rulePath + app);
- if(kv == null || kv.getValue() == null){
+ if (kv == null || kv.getValue() == null) {
return new Rules();
}
String k = kv.getKey().toStringUtf8();
String v = kv.getValue().toStringUtf8();
- List rule = JSON.parseArray(v,Rule.class);
- return new Rules(app,v);
+ List rule = JSON.parseArray(v, Rule.class);
+ return new Rules(app, v);
}
@Transactional(rollbackFor = Exception.class)
@@ -67,7 +67,7 @@ public class RuleServiceImpl implements RuleService {
String app = rules.getApp();
Rules oldRules = rulesMapper.select(app);
String from = JSON.toJSONString(oldRules);
- configCenter.put(ConfigConstant.rulePath+app, rules.getRules());
+ configCenter.put(ConfigConstant.rulePath + app, rules.getRules());
String to = JSON.toJSONString(rules);
logMapper.insertSelective(new ChangeLog(app, 1, from, to,
rules.getUpdateUser(), app, UUID.fastUUID().toString(true)));
@@ -78,7 +78,7 @@ public class RuleServiceImpl implements RuleService {
@Override
public Integer add(Rules rules) {
String app = rules.getApp();
- configCenter.put(ConfigConstant.rulePath+app,rules.getRules());
+ configCenter.put(ConfigConstant.rulePath + app, rules.getRules());
String to = JSON.toJSONString(rules);
logMapper.insertSelective(new ChangeLog(app, 1, "", to,
rules.getUpdateUser(), app, UUID.fastUUID().toString(true)));
@@ -103,15 +103,15 @@ public class RuleServiceImpl implements RuleService {
List rules = new ArrayList<>();
for (KeyValue kv : keyValues) {
String v = kv.getValue().toStringUtf8();
- if(StringUtil.isEmpty(v)){
+ if (StringUtil.isEmpty(v)) {
continue;
}
String key = kv.getKey().toStringUtf8();
- String k = key.replace(ConfigConstant.rulePath,"");
- if(StringUtils.isEmpty(appName)){
+ String k = key.replace(ConfigConstant.rulePath, "");
+ if (StringUtils.isEmpty(appName)) {
rules.add(new Rules(k, v));
- }else{
- if(k.equals(appName)){
+ } else {
+ if (k.equals(appName)) {
rules.add(new Rules(k, v));
}
}
@@ -122,7 +122,12 @@ public class RuleServiceImpl implements RuleService {
@Override
public int save(Rules rules) {
String app = rules.getApp();
- String from = "";
+
+ KeyValue kv = configCenter.getKv(ConfigConstant.rulePath + app);
+ String from = null;
+ if (kv != null) {
+ from = kv.getValue().toStringUtf8();
+ }
String to = JSON.toJSONString(rules);
configCenter.put(ConfigConstant.rulePath + app, rules.getRules());
return 1;
@@ -134,14 +139,14 @@ public class RuleServiceImpl implements RuleService {
List rules = new ArrayList<>();
for (KeyValue kv : keyValues) {
String v = kv.getValue().toStringUtf8();
- if(StringUtil.isEmpty(v)){
+ if (StringUtil.isEmpty(v)) {
continue;
}
String key = kv.getKey().toStringUtf8();
- String appKey = key.replace(ConfigConstant.rulePath,"");
+ String appKey = key.replace(ConfigConstant.rulePath, "");
List rs = JSON.parseArray(v, Rule.class);
for (Rule r : rs) {
- rules.add(appKey+"-"+r.getKey());
+ rules.add(appKey + "-" + r.getKey());
}
}
return rules;
@@ -149,11 +154,10 @@ public class RuleServiceImpl implements RuleService {
@Override
public PageInfo pageRuleHitCount(PageReq pageReq, SearchReq req, String ownApp) {
- PageHelper.startPage(pageReq.getPageNum(),pageReq.getPageSize());
+ PageHelper.startPage(pageReq.getPageNum(), pageReq.getPageSize());
List hitCountVos = summaryMapper.listRuleHitCount(req);
- return new PageInfo<>(hitCountVos);
+ return new PageInfo<>(hitCountVos);
}
-
}
diff --git a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/service/impl/UserServiceImpl.java b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/service/impl/UserServiceImpl.java
index 5cb83e5d0c0a983ba0a78bc40fdf273c15300c2b..c03ea2ed1d6f3bda40edf455614169eab86c4c9e 100644
--- a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/service/impl/UserServiceImpl.java
+++ b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/service/impl/UserServiceImpl.java
@@ -34,7 +34,7 @@ import java.util.Set;
@Service
public class UserServiceImpl implements UserService {
- @Value("${erp.defaultPwd}")
+ @Value("${erp.defaultPwd:123}")
private String defaultPwd;
@Resource
private UserMapper userMapper;
diff --git a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/util/PageUtil.java b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/util/PageUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..cd4a9872edd3f4e64af16860b707aabcf41e2ba5
--- /dev/null
+++ b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/util/PageUtil.java
@@ -0,0 +1,36 @@
+package com.jd.platform.hotkey.dashboard.util;
+
+import com.google.common.collect.Lists;
+import com.jd.platform.hotkey.dashboard.common.domain.Page;
+import org.springframework.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author wuweifeng
+ * @version 1.0
+ * @date 2020-09-01
+ */
+public class PageUtil {
+ /**
+ * 通用分页工具类
+ *
+ * @param data
+ * @param pageSize
+ * @param pageNum
+ * @param
+ * @return
+ */
+ public static Page pagination(final List data, final int pageSize, final int pageNum) {
+ if (CollectionUtils.isEmpty(data)) {
+ return new Page<>(1, 0, new ArrayList<>());
+ }
+ List> lists = Lists.partition(data, pageSize);
+ int localPageNum = pageNum;
+ if (localPageNum >= lists.size()) {
+ localPageNum = lists.size() - 1;
+ }
+ return new Page<>(localPageNum, data.size(), lists.get(localPageNum));
+ }
+}
diff --git a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/util/RuleUtil.java b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/util/RuleUtil.java
index 40d82ff98dbd20e3ffc8994900e679c40abb5a50..d9352dc4595b47c048b4503fb7b51edf71670ea8 100644
--- a/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/util/RuleUtil.java
+++ b/dashboard/src/main/java/com/jd/platform/hotkey/dashboard/util/RuleUtil.java
@@ -33,7 +33,7 @@ public class RuleUtil {
}
/**
- * 根据APP的key,获取该key对应的rule
+ * 根据APP的key,获取该key对应的rule.如 cartpc-pu__
*/
public static String rule(String key) {
try {
@@ -62,7 +62,7 @@ public class RuleUtil {
return "";
}
- private static KeyRule findByKey(String appNameKey) {
+ public static KeyRule findByKey(String appNameKey) {
synchronized (RULE_MAP) {
if (StrUtil.isEmpty(appNameKey)) {
return null;
diff --git a/dashboard/src/main/resources/application.yml b/dashboard/src/main/resources/application.yml
index 1678e74c5fcc2b262ffd71306c084db0ae54475b..fd37556ed5eadb7f619ab20747f881a098232de4 100644
--- a/dashboard/src/main/resources/application.yml
+++ b/dashboard/src/main/resources/application.yml
@@ -1,5 +1,5 @@
server :
- port : 8080
+ port : 8081
servlet :
context-path : /
spring :
@@ -27,26 +27,25 @@ spring :
time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss
datasource:
- username: root
- password: password
- url: jdbc:mysql://localhost:3306/hotkey?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
+ username: ${MYSQL_USER:root}
+ password: ${MYSQL_PASS:JRTEST}
+ url: jdbc:mysql://${MYSQL_HOST:localhost}:3306/luck?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC&useTimezone=true&serverTimezone=GMT%2B8
driver-class-name: com.mysql.cj.jdbc.Driver
+ hikari:
+ max-lifetime: 120000
+ idle-timeout: 60000
+ connection-timeout: 30000
+ maximum-pool-size: 32
+ minimum-idle: 10
pagehelper:
helperDialect: mysql
reasonable: true
supportMethodsArguments: true
params: count=countSql
-mybatis:
- configuration:
- log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+#mybatis:
+# configuration:
+# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
etcd:
server: ${etcdServer:http://127.0.0.1:2379}
-erp:
- enabled: false
- ssoAppKey: fangzhou
- ssoAppToken: 3f48d7ade61940f2ba22079ebc273fb8
- loginUrl: https://ssa.jd.com/sso/login
- excludePath: /login,/assets,/bootstarp,/common,/htmlformat,/images,/jn,/jquery,/user/login,login
- ssoAppUrl: http://ssa.jd.com
- defaultPwd: 123456
+
diff --git a/dashboard/src/main/resources/com/jd/platform/hotkey/dashboard/mapper/KeyRecordMapper.xml b/dashboard/src/main/resources/com/jd/platform/hotkey/dashboard/mapper/KeyRecordMapper.xml
index f8699879fbf7b9253d0f7a1ce4266244184eb3f8..94037b7c2025532f3e3525c332ae28821d08610b 100644
--- a/dashboard/src/main/resources/com/jd/platform/hotkey/dashboard/mapper/KeyRecordMapper.xml
+++ b/dashboard/src/main/resources/com/jd/platform/hotkey/dashboard/mapper/KeyRecordMapper.xml
@@ -199,4 +199,10 @@
+
+ delete from hk_key_record
+ where app_name = #{app}
+
+
+
\ No newline at end of file
diff --git a/dashboard/src/main/resources/com/jd/platform/hotkey/dashboard/mapper/KeyTimelyMapper.xml b/dashboard/src/main/resources/com/jd/platform/hotkey/dashboard/mapper/KeyTimelyMapper.xml
deleted file mode 100644
index 04704cf38f60d6daae9f23ec4c03ed385c63881f..0000000000000000000000000000000000000000
--- a/dashboard/src/main/resources/com/jd/platform/hotkey/dashboard/mapper/KeyTimelyMapper.xml
+++ /dev/null
@@ -1,207 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
- id, key_name, val, uuid, app_name, duration, create_time
-
-
-
-
- delete from hk_key_timely
- where key_name = #{key} and app_name = #{appName}
-
-
-
-
- delete from hk_key_timely
- where (app_name,key_name) in
-
- (#{item.appName},#{item.key})
-
-
-
-
- delete from hk_key_timely where 1 = 1
-
-
-
-
- INSERT ignore INTO
- hk_key_timely(
- key_name,
- val,
- app_name,
- uuid,
- duration,
- create_time)
- VALUES
-
- (#{k.key},
- #{k.val},
- #{k.appName},
- #{k.uuid},
- #{k.duration},
- #{k.createTime})
-
-
-
-
- insert into hk_key_timely
-
-
- id,
-
-
- key_name,
-
-
- val,
-
-
- uuid,
-
-
- app_name,
-
-
- duration,
-
-
- create_time,
-
-
-
-
- #{id,jdbcType=BIGINT},
-
-
- #{key,jdbcType=VARCHAR},
-
-
- #{val,jdbcType=VARCHAR},
-
-
- #{uuid,jdbcType=VARCHAR},
-
-
- #{appName,jdbcType=VARCHAR},
-
-
- #{duration,jdbcType=BIGINT},
-
-
- #{createTime,jdbcType=TIMESTAMP},
-
-
-
-
-
- update hk_key_timely
-
-
- key_name = #{key,jdbcType=VARCHAR},
-
-
- val = #{val,jdbcType=VARCHAR},
-
-
- uuid = #{uuid,jdbcType=VARCHAR},
-
-
- app_name = #{appName,jdbcType=VARCHAR},
-
-
- duration = #{duration,jdbcType=BIGINT},
-
-
- create_time = #{createTime,jdbcType=TIMESTAMP},
-
-
- where key_name = #{key}
-
-
-
-
-
-
-
- insert into hk_key_timely
-
-
- key_name,
-
-
- val,
-
-
- uuid,
-
-
- app_name,
-
-
- duration,
-
-
- create_time,
-
-
-
-
- #{key,jdbcType=VARCHAR},
-
-
- #{val,jdbcType=VARCHAR},
-
-
- #{uuid,jdbcType=VARCHAR},
-
-
- #{appName,jdbcType=VARCHAR},
-
-
- #{duration,jdbcType=BIGINT},
-
-
- #{createTime,jdbcType=TIMESTAMP},
-
-
- on duplicate key update
- create_time = now()
-
-
-
\ No newline at end of file
diff --git a/dashboard/src/main/resources/com/jd/platform/hotkey/dashboard/mapper/StatisticsMapper.xml b/dashboard/src/main/resources/com/jd/platform/hotkey/dashboard/mapper/StatisticsMapper.xml
index 8829d120b9683c00e1195445cda20d2570a7b53a..ac7f464fd2e61fd81f4c5706b0f4c3108561d37b 100644
--- a/dashboard/src/main/resources/com/jd/platform/hotkey/dashboard/mapper/StatisticsMapper.xml
+++ b/dashboard/src/main/resources/com/jd/platform/hotkey/dashboard/mapper/StatisticsMapper.xml
@@ -74,8 +74,9 @@
-
+ where app = #{app}
+
+
\ No newline at end of file
diff --git a/dashboard/src/main/resources/com/jd/platform/hotkey/dashboard/mapper/SummaryMapper.xml b/dashboard/src/main/resources/com/jd/platform/hotkey/dashboard/mapper/SummaryMapper.xml
index 60f41a28ea0779691f0780acb55eafd5bfcfd15c..bd04545b5f34b11a91e30888a524718363ca60ff 100644
--- a/dashboard/src/main/resources/com/jd/platform/hotkey/dashboard/mapper/SummaryMapper.xml
+++ b/dashboard/src/main/resources/com/jd/platform/hotkey/dashboard/mapper/SummaryMapper.xml
@@ -51,4 +51,11 @@
+
+ delete from hk_summary
+ where app = #{app}
+
+
+
+
\ No newline at end of file
diff --git a/dashboard/src/main/resources/db.sql b/dashboard/src/main/resources/db.sql
index 06e6b0c4d8442092708ef8ae15666fd545131932..fbbdc51b779970cbae599afbb28902bf955ecf1a 100644
--- a/dashboard/src/main/resources/db.sql
+++ b/dashboard/src/main/resources/db.sql
@@ -1,3 +1,8 @@
+-- !!! 注意设置sql model 否则可能sql报错 !!!
+-- 查询你的sql_model参数:select @@global.sql_mode; 发现ONLY_FULL_GROUP_BY 则会导致报错
+-- 解决方式:set @@global.sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'
+-- 详情查阅:https://www.cnblogs.com/hjhsblogs/p/11079356.html
+
DROP TABLE IF EXISTS `hk_change_log`;
CREATE TABLE `hk_change_log` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
@@ -31,6 +36,11 @@ CREATE TABLE `hk_user` (
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Compact;
+-- pwd: 123456
+INSERT INTO `hk_user` VALUES (2, 'admin', 'admin', 'e10adc3949ba59abbe56e057f20f883e', '1888888', 'ADMIN', '', '2020-07-28 14:01:03', 1);
+
+
+
DROP TABLE IF EXISTS `hk_key_record`;
CREATE TABLE `hk_key_record` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
diff --git a/dashboard/src/main/resources/logback-spring.xml b/dashboard/src/main/resources/logback-spring.xml
index f97e83e65348a9ba35ad545f5dfc813a328375ac..9e1b14837b90a6f37add61dae0d4768bce25ea62 100644
--- a/dashboard/src/main/resources/logback-spring.xml
+++ b/dashboard/src/main/resources/logback-spring.xml
@@ -6,7 +6,7 @@ debug:当此属性设置为true时,将打印出logback内部日志信息,
-->
-
+
diff --git a/dashboard/src/main/resources/static/admin/assets/js/bootstrap/js/base_list.js b/dashboard/src/main/resources/static/admin/assets/js/bootstrap/js/base_list.js
index 05eb56eb7f5ac72a9d28e8518840d0971a752788..3abf2a411d70da95972e28f5e289cf80cf2367a2 100644
--- a/dashboard/src/main/resources/static/admin/assets/js/bootstrap/js/base_list.js
+++ b/dashboard/src/main/resources/static/admin/assets/js/bootstrap/js/base_list.js
@@ -40,20 +40,13 @@
ajaxOptions:{
headers: {"Authorization": getCookie("token")}
},
- exportOptions:{
- ignoreColumn: [0,1], //忽略某一列的索引
- fileName: '报表导出', //文件名称设置
- worksheetName: 'sheet1', //表格工作区名称
- tableName: '报表导出',
- excelstyles: ['background-color', 'color', 'font-size', 'font-weight']
- },
onLoadSuccess: function(){ //加载成功时执行
var datas = $('#dataTable').bootstrapTable('getData');
console.info("加载成功");
},
onLoadError: function(status){ //加载失败时执行
- let token = getCookie("token");
- if(status === 500 && ( token == "undefined" || token =="")){
+ var token = getCookie("token");
+ if(status === 1000 && ( token == "undefined" || token =="")){
top.location.href = '/user/login';
}
console.info("加载数据失败");
@@ -66,11 +59,19 @@
var search = {};
search.pageSize= params.pageSize;
search.pageNum=params.pageNumber;
- let app = $("#apps").val();
+ var app = $("#apps").val();
if(app!==undefined && app != null && app!==""){
search.app = app;
}
- return search;
+ var time1 = $("#startTime").val();
+ if(time1!==undefined && time1 != null && time1!==""){
+ search.startTime = time1;
+ }
+ var time2 = $("#endTime").val();
+ if(time2!==undefined && time2 != null && time2!==""){
+ search.endTime = time2;
+ }
+ return search;
},
search:function(my){//查询条件
@@ -302,7 +303,7 @@
title: title,
content: url,
success:function(dom){
- let $iframeDom=$(dom[0]).find("iframe").eq(0).contents();
+ var $iframeDom=$(dom[0]).find("iframe").eq(0).contents();
$iframeDom.find("#test").html("我是从父级传来的值哟……")
}
})
@@ -494,19 +495,19 @@ modal_status = {
function getCookie(cname){
- let token = window.localStorage.getItem('token');
+ var token = window.localStorage.getItem('token');
if(token != null && token !==""){
- let time = window.localStorage.getItem('time');
+ var time = window.localStorage.getItem('time');
if(Date.now()-time>7*24*360000){
localStorage.removeItem(time);
return "";
}
return token;
}
- let name = cname + "=";
- let ca = document.cookie.split(';');
- for(let i=0; i {
// only changing strings
diff --git a/dashboard/src/main/resources/templates/admin/changeLog/list.html b/dashboard/src/main/resources/templates/admin/changeLog/list.html
index a6fbf8dec7ad1e188a58550a8bc03b2a912f605f..d6ed97aaea1e10b9a00fb023cc82834083b8bf82 100644
--- a/dashboard/src/main/resources/templates/admin/changeLog/list.html
+++ b/dashboard/src/main/resources/templates/admin/changeLog/list.html
@@ -142,6 +142,11 @@ var options = {
}
}
},
+ { field: 'createTime', title: '创建时间',
+ formatter:function (val,row,index) {
+ return changeDateFormat(val);
+ }
+ },
{
field: 'from',
title: '初始值',
diff --git a/dashboard/src/main/resources/templates/admin/clear/edit.html b/dashboard/src/main/resources/templates/admin/clear/edit.html
new file mode 100644
index 0000000000000000000000000000000000000000..4ced25c9ab226999e767425212a89ae017ef7fba
--- /dev/null
+++ b/dashboard/src/main/resources/templates/admin/clear/edit.html
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dashboard/src/main/resources/templates/admin/clear/list.html b/dashboard/src/main/resources/templates/admin/clear/list.html
new file mode 100644
index 0000000000000000000000000000000000000000..ceb03f10382deb7e137a4bc5da197a75525fb55d
--- /dev/null
+++ b/dashboard/src/main/resources/templates/admin/clear/list.html
@@ -0,0 +1,157 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dashboard/src/main/resources/templates/admin/common/html/footer.html b/dashboard/src/main/resources/templates/admin/common/html/footer.html
index 662d4f30764f30e25475563e15ea89bfeb0405c9..703a3594a77952ccb19a881d1cf944cfee009c22 100644
--- a/dashboard/src/main/resources/templates/admin/common/html/footer.html
+++ b/dashboard/src/main/resources/templates/admin/common/html/footer.html
@@ -10,8 +10,8 @@
- 2020 京东零售-平台业务中心-平台业务研发部-基础业务研发部 版权所有 | 使用支持:武伟峰 | 咚咚:85134158
-
接入文档:https://cf.jd.com/pages/viewpage.action?pageId=277756998
+
diff --git a/dashboard/src/main/resources/templates/admin/common/html/leftMenu.html b/dashboard/src/main/resources/templates/admin/common/html/leftMenu.html
index 29baffe1bc32bf1bf3941635d9002821c4a7e5e2..668341da8da7c624320b607cf617eeeb0432bc43 100644
--- a/dashboard/src/main/resources/templates/admin/common/html/leftMenu.html
+++ b/dashboard/src/main/resources/templates/admin/common/html/leftMenu.html
@@ -52,7 +52,7 @@
-
+
热点记录
变更记录
+
+
+ 数据清理配置
+
diff --git a/dashboard/src/main/resources/templates/admin/common/html/title.html b/dashboard/src/main/resources/templates/admin/common/html/title.html
index 08cf7c852be0a4c250e6364ec7390222649a51e8..aa2fcafcb48c7a172780444f97aa42d28e7e9143 100644
--- a/dashboard/src/main/resources/templates/admin/common/html/title.html
+++ b/dashboard/src/main/resources/templates/admin/common/html/title.html
@@ -36,8 +36,8 @@
Widget