Merge pull request #160 from acmenlt/develop

Merge main
pull/161/head
龙台 Long Tai 3 years ago committed by GitHub
commit 59c15dba5d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,190 +1,125 @@
<div align=center>
<img src="https://images-machen.oss-cn-beijing.aliyuncs.com/Dynamic-Thread-Pool-Main.jpeg" />
</div>
![](https://images-machen.oss-cn-beijing.aliyuncs.com/hippo4j-logo-logoly.png)
<p align="center">
<strong> :fire: &nbsp; 动态线程池DTP系统包含 <a href="https://github.com/acmenlt/dynamic-threadpool/tree/develop/dynamic-threadpool-server">Server</a> 端及 SpringBoot Client 端需引入的 <a href="https://github.com/acmenlt/dynamic-threadpool/tree/develop/dynamic-threadpool-spring-boot-starter">Starter</a>.</strong>
</p>
<p align="center">
<img src="https://img.shields.io/badge/Author-龙台-blue.svg" />
<a target="_blank" href="http://mp.weixin.qq.com/s?__biz=Mzg4NDU0Mjk5OQ==&mid=100007373&idx=1&sn=3b375f97a576820e3e540810e720aeb0&chksm=4fb7c6b578c04fa35fab488d8dd6ddd12cfd0ef70290f3b285261fba0750785ea2725a50d508&scene=18#wechat_redirect">
<img src="https://img.shields.io/badge/公众号-龙台 blog-yellow.svg" />
</a>
<a target="_blank" href="https://github.com/acmenlt/dynamic-threadpool">
<img src="https://img.shields.io/badge/⭐-github-orange.svg" />
</a>
<a href="https://github.com/acmenlt/dynamic-threadpool/blob/develop/LICENSE">
<p>
<a href="https://github.com/acmenlt/dynamic-threadpool" target="_blank">
<img alt="GitHub" src="https://img.shields.io/github/stars/acmenlt/dynamic-threadpool?label=Stars&style=flat-square&logo=GitHub">
</a>
<a href="https://github.com/acmenlt/dynamic-threadpool/blob/develop/LICENSE">
<img src="https://img.shields.io/github/license/acmenlt/dynamic-threadpool?color=42b883&style=flat-square" alt="LICENSE">
</a>
<img src="https://img.shields.io/badge/JDK-1.8+-green?logo=appveyor" />
<img src="https://tokei.rs/b1/github/acmenlt/dynamic-threadpool?category=lines" />
<img src="https://img.shields.io/badge/version-v0.4.0-DeepSkyBlue.svg" />
<img src="https://img.shields.io/github/stars/acmenlt/dynamic-threadpool.svg" />
</a>
<a title="Hits" target="_blank" href="https://github.com/acmenlt/dynamic-threadpool">
<img src="https://hits.b3log.org/acmenlt/dynamic-threadpool.svg">
</a>
</p>
<br/>
## 为什么写这个项目?
[美团线程池文章](https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html "美团线程池文章") 介绍中,因为业务对线程池参数没有合理配置,触发过几起生产事故,进而引发了一系列思考。最终决定封装线程池动态参数调整,扩展线程池监控以及消息报警等功能
在开源平台找了挺多动态线程池项目,从功能性以及健壮性而言,个人感觉不满足企业级应用
## Hippo4J
因为对动态线程池比较感兴趣,加上想写一个有意义的项目,所以决定自己来造一个轻量级的轮子
Hippo4J 基于 **美团动态线程池** 设计理念开发,针对线程池增强 **动态调参、监控、报警功能**。
想给项目起一个简单易记的名字,类似于 Eureka、Nacos、Redis后和朋友商量决定以动物命名**Hippo**
通过 Web 控制台对线程池参数进行动态调整,支持 **集群内线程池的差异化配置**。内置线程池参数变更通知,以及 **运行过载报警** 功能(支持多通知平台)。
![](https://user-images.githubusercontent.com/77398366/139575361-87a0a1b5-716a-4c98-b467-f8f130d30163.png)
按照租户、项目、线程池的维度划分,配合系统权限,让不同的开发、管理人员负责自己系统的线程池。
<br/>
自 1.1.0 版本发布后Hippo4J 分为两种使用模式,用一张图来说明两者的使用差别。
## 它解决了什么问题?
![](https://images-machen.oss-cn-beijing.aliyuncs.com/image-20220319154626314.png)
线程池在业务系统应该都有使用到,帮助业务流程提升效率以及管理线程,多数场景应用于大量的异步任务处理
### hippo4j-core
虽然线程池提供了我们许多便利,但也并非尽善尽美,比如下面这些问题就无法很好解决
**轻量级动态线程池管理**,依赖 Apollo、Nacos 等三方配置中心(任选其一)完成线程池参数动态变更,同样包含运行时报警、监控功能。
![](https://images-machen.oss-cn-beijing.aliyuncs.com/image-20211023160830084.png)
> 监控功能配置详见:[线程池监控](https://hippox.cn/pages/2f67ll)
<br/>
![](https://images-machen.oss-cn-beijing.aliyuncs.com/image-202203271737049821.png)
如果线程池的配置涉及到上述问题,那么就有可能需要发布业务系统来解决;如果发布后参数仍不合理,继续发布......
### hippo4j-server
Hippo 很好解决了这个问题,它将业务中所有线程池统一管理,遇到上述问题不需要发布系统就可以替换线程池参数
**部署 hippo4j-server 服务**,通过可视化 Web 界面完成线程池的创建、变更以及查看,不依赖三方中间件。
![](https://images-machen.oss-cn-beijing.aliyuncs.com/image-20211023142726818.png)
相比较 hippo4j-core功能会更强大但是也引入了一定的复杂性。需要部署一个 Java 服务,以及 MySQL 数据库。
<br/>
## 它有什么特性?
### 使用总结
应用系统中线程池并不容易管理。参考美团的设计Hippo 按照租户、项目、线程池的维度划分。再加上系统权限,让不同的开发、管理人员负责自己系统的线程池操作
| | hippo4j-core | hippo4j-server |
| ---- | ---------------------------------------------------- | ------------------------------------------------------------ |
| 依赖 | Nacos、Apollo 等配置中心(任选其一) | 部署 Hippo4J Server内部无依赖中间件 |
| 使用 | 配置中心补充线程池相关参数 | Hippo4J Server Web 控制台添加线程池记录 |
| 功能 | 包含基础功能:参数动态化、运行时监控、报警等 | 基础功能之外扩展控制台界面、线程池堆栈查看、线程池运行信息实时查看、历史运行信息查看、线程池配置集群个性化等 |
举个例子,小编在一家公司的公共组件团队,团队中负责消息、短链接网关等项目。公共组件是租户,消息或短链接就是项目
使用建议:根据公司情况选择,如果基本功能可以满足使用,选择 hippo4j-core 使用即可;如果希望更多的功能,可以选择 hippo4j-server。
<br/>
**两者在进行替换的时候,无需修改业务代码**。
| 模块 | 模块名称 | 注释 |
| -------------------------------------- | ------------------ | ---------------------------------------- |
| dynamic-threadpool-auth | 用户权限 | - |
| dynamic-threadpool-common | 公共模块 | - |
| dynamic-threadpool-config | 配置中心 | 提供线程池准实时更新功能 |
| dynamic-threadpool-console | 控制台 | 对接前端项目 |
| dynamic-threadpool-discovery | 注册中心 | 提供线程池项目实例注册、续约、下线等功能 |
| dynamic-threadpool-spring-boot-starter | SpringBoot Starter | - |
| dynamic-threadpool-example | 示例项目 | - |
| dynamic-threadpool-server | 服务端 | Server 集成各组件 |
| dynamic-threadpool-tools | 抽象工具类 | GitHub 变更监控、操作日志等组件 |
<br/>
## 解决什么问题
Hippo 除去动态修改线程池,还包含实时查看线程池运行时指标、负载报警、配置日志管理等
简单来说Hippo4J 主要为我们解决了下面这些使用原生线程池存在的问题:
- **原生线程池创建时无法合理评估参数问题**。比如功能使用到线程池遇到突发流量洪峰频繁拒绝任务。Hippo4J 提供动态修改参数功能,**避免修改线程池参数后重启线上应用**
- 当线程池运行过程中无法再接受新的任务,此时你想知道 **线程池内线程都在做什么**Hippo4J 提供查看线程池堆栈功能;
- 某接口频繁超时,内部依赖线程池执行,想要 **查看过去一段时间线程池运行参数情况**。Hippo4J 提供历史数据图表查看功能;
- **原生线程池无任务报警策略**。Hippo4J 内置四种报警策略,分别是:活跃度报警、队列容量报警、拒绝策略报警和运行时间过长报警。
![](https://images-machen.oss-cn-beijing.aliyuncs.com/image-20211023101844619.png)
## 报警通知
<br/>
## 如何运行 Demo
目前动态线程池功能已经完成,可以直接把代码拉到本地运行。导入 [Hippo 初始化 SQL 语句](https://github.com/acmenlt/dynamic-threadpool/blob/develop/server/src/main/resources/hippo_manager.sql)
1. 启动 `dynamic-threadpool-server` 模块下 ServerApplication 应用类
2. 启动 `dynamic-threadpool-example` 模块下 ExampleApplication 应用类
<br/>
通过接口修改线程池中的配置。HTTP POST 路径http://localhost:6691/v1/cs/configs Body 请求体如下:
```json
{
"ignore": "tenantId、itemId、tpId 代表唯一线程池,请不要修改",
"tenantId": "prescription",
"itemId": "dynamic-threadpool-example",
"tpId": "message-produce",
"coreSize": 10,
"maxSize": 15,
"queueType": 9,
"capacity": 100,
"keepAliveTime": 10,
"rejectedType": 3,
"isAlarm": 0,
"capacityAlarm": 81,
"livenessAlarm": 82
}
```
<br/>
接口调用成功后,观察 dynamic-threadpool-example 控制台日志输出,日志输出包括不限于此信息即为成功
```tex
[🔥 MESSAGE-PRODUCE] Changed thread pool. coreSize :: [11=>10], maxSize :: [15=>15], queueType :: [9=>9]
capacity :: [100=>100], keepAliveTime :: [10000=>10000], rejectedType :: [7=>7]
```
<br/>
现阶段已集成钉钉消息推送后续会持续接入企业微信、邮箱、飞书、短信等通知渠道。可以通过添加钉钉群体验消息推送群号31764717
Hippo4J 已接入钉钉、企业微信以及飞书平台,提供了 **线程池参数变更通知** 和 **运行时报警** 功能。示例如下:
<table>
<tr>
<td align="center" style="width: 200px;">
<td align="center" style="width: 400px;">
<a href="https://github.com/acmenlt">
<img src="https://images-machen.oss-cn-beijing.aliyuncs.com/image-20211013122816688.png" style="width: 400px;"><br>
<img src="https://images-machen.oss-cn-beijing.aliyuncs.com/image-20211203213443242.png" style="width: 400px;"><br>
<sub>配置变更</sub>
</a><br>
</td>
<td align="center" style="width: 200px;">
<td align="center" style="width: 400px;">
<a href="https://github.com/acmenlt">
<img src="https://images-machen.oss-cn-beijing.aliyuncs.com/image-20211013113649068.png" style="width: 400px;"><br>
<img src="https://images-machen.oss-cn-beijing.aliyuncs.com/image-20211203213512019.png" style="width: 400px;"><br>
<sub>报警通知</sub>
</a><br>
</td>
</tr>
</table>
<br/>
## 快速开始
项目代码功能还在持续开发,初定发布 1.0.0 RELEASE 完成以下功能。部署了 Server 服务,只需要引入 Starter 组件到业务系统中,即可完成动态修改、监控、报警等特性
[运行 Hippo4J 自带 Demo 参考文档](https://hippox.cn/pages/793dcb/)
<br/>
[在线体验地址](http://console.hippox.cn:6691/index.html) 用户名密码hippo4j / hippo4j
## 查看源码能收获什么?
## 联系我
目前还没有发布 Release 版本,小伙伴可以阅读框架源码,查看框架中好的设计理念或者编码技巧
对于这个项目,是否有什么不一样看法,同 [作者](https://hippox.cn/pages/dd137d/) 或者创建 [Issues](https://github.com/acmenlt/dynamic-threadpool/issues) 沟通。
在项目开发过程中,借鉴了 Nacos、Eureka、Seata、ShardingSphere 等中间件项目的优雅设计
![](https://images-machen.oss-cn-beijing.aliyuncs.com/image-20211023143632685.png)
## 公众号
<br/>
如果大家想要实时关注 Hippo4J 最新动态以及干货分享的话,可以关注我的公众号。
![](https://images-machen.oss-cn-beijing.aliyuncs.com/43_65f6020ed111b6bb3808ec338576bd6b.png)
## Github Stars 趋势
## Stars 趋势
如果小伙伴查看源码设计有所收获,辛苦点个 🚀 Star ,方便后续查看
[![Stargazers over time](https://starchart.cc/acmenlt/dynamic-threadpool.svg)](https://starchart.cc/acmenlt/dynamic-threadpool)
[![Stargazers over time](https://starchart.cc/acmenlt/dynamic-threadpool.svg)](https://starchart.cc/acmenlt/dynamic-threadpool)
<br/>
## 友情链接
- [**JavaGuide**](https://github.com/Snailclimb/JavaGuide)「Java学习+面试指南」一份涵盖大部分 Java 程序员所需要掌握的核心知识。准备 Java 面试,首选 JavaGuide
- [**Guide-Rpc-Framework**](https://github.com/Snailclimb/guide-rpc-framework)A custom RPC framework implemented by Netty+Kyro+Zookeeper.(一款基于 Netty+Kyro+Zookeeper 实现的自定义 RPC 框架-附详细实现过程和相关教程。)
- [**toBeBetterJavaer**](https://github.com/itwanger/toBeBetterJavaer)Java 程序员进阶之路,据说每一个优秀的 Java 程序员都喜欢她,风趣幽默、通俗易懂。内容包括 Java 基础、Java 并发编程、Java 虚拟机、Java 企业级开发、Java 面试等核心知识点
- [**Austin**](https://github.com/ZhongFuCheng3y/austin):消息推送平台📝 推送下发【邮件】【短信】【微信服务号】【微信小程序】等消息类型。所使用的技术栈包括SpringBoot、SpringDataJPA、MySQL、Docker、docker-compose、Kafka、Redis、Apollo、prometheus、Grafana、GrayLog、Flink、Xxl-job、Echarts等等
## 最后
## 鸣谢
小编是个有代码洁癖的程序员,项目中的代码开发完全遵守阿里巴巴代码规约,也推荐大家使用,培养好的编码习惯
对于这个项目,是否有什么不一样看法,欢迎在 Issue 一起沟通交流;或者添加小编微信进交流群
Hippo4J 项目基于或参考以下项目:[**Nacos**](https://github.com/alibaba/nacos)、[**Eureka**](https://github.com/Netflix/Eureka)、[**Mzt-Biz-Log**](https://github.com/mouzt/mzt-biz-log)、[**Equator**](https://github.com/dadiyang/equator)。
感谢 JetBrains 提供的免费开源 License
![](https://user-images.githubusercontent.com/77398366/138920260-e9dd1268-797f-4d42-9abb-62353d08ea6a.png)
<p>
<img src="https://images.gitee.com/uploads/images/2020/0406/220236_f5275c90_5531506.png" alt="图片引用自lets-mica" style="float:left;">
</p>

@ -1,86 +0,0 @@
package com.github.dynamic.threadpool.auth.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.crypto.SecureUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.github.dynamic.threadpool.auth.mapper.UserMapper;
import com.github.dynamic.threadpool.auth.model.UserInfo;
import com.github.dynamic.threadpool.auth.model.biz.user.UserQueryPageReqDTO;
import com.github.dynamic.threadpool.auth.model.biz.user.UserRespDTO;
import com.github.dynamic.threadpool.auth.service.UserService;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
/**
* User service impl.
*
* @author chen.ma
* @date 2021/10/30 21:40
*/
@Service
@AllArgsConstructor
public class UserServiceImpl implements UserService {
private final UserMapper userMapper;
@Override
public IPage<UserRespDTO> listUser(int pageNo, int pageSize) {
UserQueryPageReqDTO queryPage = new UserQueryPageReqDTO(pageNo, pageSize);
IPage<UserInfo> selectPage = userMapper.selectPage(queryPage, null);
return selectPage.convert(each -> BeanUtil.toBean(each, UserRespDTO.class));
}
@Override
public void addUser(String userName, String password) {
LambdaQueryWrapper<UserInfo> queryWrapper = Wrappers.lambdaQuery(UserInfo.class)
.eq(UserInfo::getUserName, userName);
UserInfo existUserInfo = userMapper.selectOne(queryWrapper);
if (existUserInfo != null) {
throw new RuntimeException("用户名重复");
}
UserInfo insertUser = new UserInfo();
insertUser.setUserName(userName);
// TODO 暂定为 Md5 加密
insertUser.setPassword(SecureUtil.md5(password));
userMapper.insert(insertUser);
}
@Override
public void updateUser(String userName, String password) {
UserInfo userInfo = new UserInfo();
userInfo.setUserName(userName);
userInfo.setPassword(SecureUtil.md5(password));
LambdaUpdateWrapper<UserInfo> updateWrapper = Wrappers.lambdaUpdate(UserInfo.class)
.eq(UserInfo::getUserName, userName);
userMapper.update(userInfo, updateWrapper);
}
@Override
public void deleteUser(String userName) {
LambdaUpdateWrapper<UserInfo> updateWrapper = Wrappers.lambdaUpdate(UserInfo.class)
.eq(UserInfo::getUserName, userName);
userMapper.delete(updateWrapper);
}
@Override
public List<String> getUserLikeUsername(String userName) {
LambdaQueryWrapper<UserInfo> queryWrapper = Wrappers.lambdaQuery(UserInfo.class)
.like(UserInfo::getUserName, userName)
.select(UserInfo::getUserName);
List<UserInfo> userInfos = userMapper.selectList(queryWrapper);
List<String> userNames = userInfos.stream().map(UserInfo::getUserName).collect(Collectors.toList());
return userNames;
}
}

@ -1,99 +0,0 @@
package com.github.dynamic.threadpool.common.toolkit;
import org.springframework.util.StringUtils;
/**
* Group key.
*
* @author chen.ma
* @date 2021/6/24 21:12
*/
public class GroupKey {
public static String getKey(String dataId, String group) {
return getKey(dataId, group, "");
}
public static String getKey(String dataId, String group, String datumStr) {
return doGetKey(dataId, group, datumStr);
}
public static String getKeyTenant(String dataId, String group, String tenant) {
return doGetKey(dataId, group, tenant);
}
private static String doGetKey(String dataId, String group, String datumStr) {
StringBuilder sb = new StringBuilder();
urlEncode(dataId, sb);
sb.append('+');
urlEncode(group, sb);
if (!StringUtils.isEmpty(datumStr)) {
sb.append('+');
urlEncode(datumStr, sb);
}
return sb.toString();
}
public static String[] parseKey(String groupKey) {
StringBuilder sb = new StringBuilder();
String dataId = null;
String group = null;
String tenant = null;
for (int i = 0; i < groupKey.length(); ++i) {
char c = groupKey.charAt(i);
if ('+' == c) {
if (null == dataId) {
dataId = sb.toString();
sb.setLength(0);
} else if (null == group) {
group = sb.toString();
sb.setLength(0);
} else {
throw new IllegalArgumentException("invalid groupkey:" + groupKey);
}
} else if ('%' == c) {
char next = groupKey.charAt(++i);
char nextnext = groupKey.charAt(++i);
if ('2' == next && 'B' == nextnext) {
sb.append('+');
} else if ('2' == next && '5' == nextnext) {
sb.append('%');
} else {
throw new IllegalArgumentException("invalid groupkey:" + groupKey);
}
} else {
sb.append(c);
}
}
if (StringUtils.isEmpty(group)) {
group = sb.toString();
if (group.length() == 0) {
throw new IllegalArgumentException("invalid groupkey:" + groupKey);
}
} else {
tenant = sb.toString();
if (group.length() == 0) {
throw new IllegalArgumentException("invalid groupkey:" + groupKey);
}
}
return new String[]{dataId, group, tenant};
}
public static void urlEncode(String str, StringBuilder sb) {
for (int idx = 0; idx < str.length(); ++idx) {
char c = str.charAt(idx);
if ('+' == c) {
sb.append("%2B");
} else if ('%' == c) {
sb.append("%25");
} else {
sb.append(c);
}
}
}
}

@ -1,42 +0,0 @@
package com.github.dynamic.threadpool.common.web.base;
import com.github.dynamic.threadpool.common.web.exception.ErrorCodeEnum;
import com.github.dynamic.threadpool.common.web.exception.ServiceException;
/**
* Results.
*
* @author chen.ma
* @date 2021/3/19 16:12
*/
public final class Results {
public static Result<Void> success() {
return new Result<Void>()
.setCode(Result.SUCCESS_CODE);
}
public static <T> Result<T> success(T data) {
return new Result<T>()
.setCode(Result.SUCCESS_CODE)
.setData(data);
}
public static <T> Result<T> failure(ServiceException serviceException) {
return new Result<T>().setCode(ErrorCodeEnum.SERVICE_ERROR.getCode())
.setMessage(serviceException.getMessage());
}
public static <T> Result<T> failure(String code, String message) {
return new Result<T>()
.setCode(code)
.setMessage(message);
}
public static <T> Result<T> failure(ErrorCodeEnum errorCode) {
return new Result<T>()
.setCode(errorCode.getCode())
.setMessage(errorCode.getMessage());
}
}

@ -1,47 +0,0 @@
package com.github.dynamic.threadpool.common.web.exception;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* Service exception.
*
* @author chen.ma
* @date 2021/3/19 16:14
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class ServiceException extends RuntimeException {
private static final long serialVersionUID = -8667394300356618037L;
private String detail;
public ServiceException(String message) {
super(message);
}
public ServiceException(String message, String detail) {
super(message);
this.detail = detail;
}
public ServiceException(String message, Throwable cause) {
super(message, cause);
this.detail = cause.getMessage();
}
public ServiceException(String message, String detail, Throwable cause) {
super(message, cause);
this.detail = detail;
}
@Override
public String toString() {
return "ServiceException{" +
"message='" + getMessage() + "'," +
"detail='" + getDetail() + "'" +
'}';
}
}

@ -1,21 +0,0 @@
package com.github.dynamic.threadpool.config.config;
import com.github.dynamic.threadpool.common.config.ApplicationContextHolder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Common config.
*
* @author chen.ma
* @date 2021/7/19 21:03
*/
@Configuration
public class CommonConfig {
@Bean
public ApplicationContextHolder simpleApplicationContextHolder() {
return new ApplicationContextHolder();
}
}

@ -1,17 +0,0 @@
package com.github.dynamic.threadpool.config.event;
/**
* Local data change event.
*
* @author chen.ma
* @date 2021/6/23 19:13
*/
public class LocalDataChangeEvent extends Event {
public final String groupKey;
public LocalDataChangeEvent(String groupKey) {
this.groupKey = groupKey;
}
}

@ -1,47 +0,0 @@
package com.github.dynamic.threadpool.config.notify;
import cn.hutool.core.collection.ConcurrentHashSet;
import com.github.dynamic.threadpool.config.notify.listener.Subscriber;
import com.github.dynamic.threadpool.config.event.Event;
import com.github.dynamic.threadpool.config.event.SlowEvent;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Default share publisher.
*
* @author chen.ma
* @date 2021/6/23 19:05
*/
public class DefaultSharePublisher extends DefaultPublisher {
private final Map<Class<? extends SlowEvent>, Set<Subscriber>> subMappings = new ConcurrentHashMap();
protected final ConcurrentHashSet<Subscriber> subscribers = new ConcurrentHashSet();
private final Lock lock = new ReentrantLock();
public void addSubscriber(Subscriber subscriber, Class<? extends Event> subscribeType) {
Class<? extends SlowEvent> subSlowEventType = (Class<? extends SlowEvent>) subscribeType;
subscribers.add(subscriber);
lock.lock();
try {
Set<Subscriber> sets = subMappings.get(subSlowEventType);
if (sets == null) {
Set<Subscriber> newSet = new ConcurrentHashSet();
newSet.add(subscriber);
subMappings.put(subSlowEventType, newSet);
return;
}
sets.add(subscriber);
} finally {
lock.unlock();
}
}
}

@ -1,22 +0,0 @@
package com.github.dynamic.threadpool.config.notify.listener;
import com.github.dynamic.threadpool.config.event.Event;
import java.util.List;
/**
* Subscribers to multiple events can be listened to.
*
* @author chen.ma
* @date 2021/6/23 19:02
*/
public abstract class SmartSubscriber extends Subscriber {
/**
* Subscribe types.
*
* @return
*/
public abstract List<Class<? extends Event>> subscribeTypes();
}

@ -1,96 +0,0 @@
package com.github.dynamic.threadpool.config.service;
import com.github.dynamic.threadpool.config.service.biz.ConfigService;
import com.github.dynamic.threadpool.common.config.ApplicationContextHolder;
import com.github.dynamic.threadpool.common.constant.Constants;
import com.github.dynamic.threadpool.common.toolkit.Md5Util;
import com.github.dynamic.threadpool.config.event.LocalDataChangeEvent;
import com.github.dynamic.threadpool.config.model.CacheItem;
import com.github.dynamic.threadpool.config.model.ConfigAllInfo;
import com.github.dynamic.threadpool.config.notify.NotifyCenter;
import org.springframework.util.StringUtils;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
/**
* Config cache service.
*
* @author chen.ma
* @date 2021/6/24 21:19
*/
public class ConfigCacheService {
static ConfigService configService = null;
private static final ConcurrentHashMap<String, CacheItem> CACHE = new ConcurrentHashMap();
public static boolean isUpdateData(String groupKey, String md5, String ip) {
String contentMd5 = ConfigCacheService.getContentMd5IsNullPut(groupKey, ip);
return Objects.equals(contentMd5, md5);
}
/**
* Get Md5.
* TODOAdd IP, different IP thread pool rewrite
* TODOgroupKey && Md5 Cache
*
* @param groupKey
* @param ip
* @return
*/
private static String getContentMd5IsNullPut(String groupKey, String ip) {
CacheItem cacheItem = CACHE.get(groupKey);
if (cacheItem != null) {
return cacheItem.md5;
}
if (configService == null) {
configService = ApplicationContextHolder.getBean(ConfigService.class);
}
String[] split = groupKey.split("\\+");
ConfigAllInfo config = configService.findConfigAllInfo(split[0], split[1], split[2]);
if (config != null && !StringUtils.isEmpty(config.getTpId())) {
String md5 = Md5Util.getTpContentMd5(config);
cacheItem = new CacheItem(groupKey, md5);
CACHE.put(groupKey, cacheItem);
}
return (cacheItem != null) ? cacheItem.md5 : Constants.NULL;
}
public static String getContentMd5(String groupKey) {
if (configService == null) {
configService = ApplicationContextHolder.getBean(ConfigService.class);
}
String[] split = groupKey.split("\\+");
ConfigAllInfo config = configService.findConfigAllInfo(split[0], split[1], split[2]);
if (config == null || StringUtils.isEmpty(config.getTpId())) {
String errorMessage = String.format("config is null. tpId :: %s, itemId :: %s, tenantId :: %s", split[0], split[1], split[2]);
throw new RuntimeException(errorMessage);
}
return Md5Util.getTpContentMd5(config);
}
public static void updateMd5(String groupKey, String md5, long lastModifiedTs) {
CacheItem cache = makeSure(groupKey);
if (cache.md5 == null || !cache.md5.equals(md5)) {
cache.md5 = md5;
cache.lastModifiedTs = lastModifiedTs;
NotifyCenter.publishEvent(new LocalDataChangeEvent(groupKey));
}
}
static CacheItem makeSure(final String groupKey) {
CacheItem item = CACHE.get(groupKey);
if (null != item) {
return item;
}
CacheItem tmp = new CacheItem(groupKey);
item = CACHE.putIfAbsent(groupKey, tmp);
return (null == item) ? tmp : item;
}
}

@ -1,30 +0,0 @@
package com.github.dynamic.threadpool.config.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
/**
* Config servlet inner.
*
* @author chen.ma
* @date 2021/6/22 23:13
*/
@Service
public class ConfigServletInner {
@Autowired
private LongPollingService longPollingService;
public String doPollingConfig(HttpServletRequest request, HttpServletResponse response, Map<String, String> clientMd5Map, int probeRequestSize) {
if (LongPollingService.isSupportLongPolling(request)) {
longPollingService.addLongPollingClient(request, response, clientMd5Map, probeRequestSize);
return HttpServletResponse.SC_OK + "";
}
return HttpServletResponse.SC_OK + "";
}
}

@ -1,85 +0,0 @@
package com.github.dynamic.threadpool.config.service.biz.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import com.github.dynamic.threadpool.common.toolkit.ContentUtil;
import com.github.dynamic.threadpool.common.toolkit.Md5Util;
import com.github.dynamic.threadpool.config.event.LocalDataChangeEvent;
import com.github.dynamic.threadpool.config.mapper.ConfigInfoMapper;
import com.github.dynamic.threadpool.config.model.ConfigAllInfo;
import com.github.dynamic.threadpool.config.service.ConfigChangePublisher;
import com.github.dynamic.threadpool.config.service.biz.ConfigService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* Config service impl.
*
* @author chen.ma
* @date 2021/6/20 15:21
*/
@Slf4j
@Service
@AllArgsConstructor
public class ConfigServiceImpl implements ConfigService {
private final ConfigInfoMapper configInfoMapper;
@Override
public ConfigAllInfo findConfigAllInfo(String tpId, String itemId, String tenantId) {
LambdaQueryWrapper<ConfigAllInfo> wrapper = Wrappers.lambdaQuery(ConfigAllInfo.class)
.eq(!StringUtils.isBlank(tpId), ConfigAllInfo::getTpId, tpId)
.eq(!StringUtils.isBlank(itemId), ConfigAllInfo::getItemId, itemId)
.eq(!StringUtils.isBlank(tenantId), ConfigAllInfo::getTenantId, tenantId);
ConfigAllInfo configAllInfo = configInfoMapper.selectOne(wrapper);
return configAllInfo;
}
@Override
public void insertOrUpdate(ConfigAllInfo configAllInfo) {
try {
addConfigInfo(configAllInfo);
} catch (Exception ex) {
updateConfigInfo(configAllInfo);
}
ConfigChangePublisher.notifyConfigChange(new LocalDataChangeEvent(ContentUtil.getGroupKey(configAllInfo)));
}
private Integer addConfigInfo(ConfigAllInfo config) {
config.setContent(ContentUtil.getPoolContent(config));
config.setMd5(Md5Util.getTpContentMd5(config));
try {
if (SqlHelper.retBool(configInfoMapper.insert(config))) {
return config.getId();
}
} catch (Exception ex) {
log.error("[db-error] message :: {}", ex.getMessage(), ex);
throw ex;
}
return null;
}
private void updateConfigInfo(ConfigAllInfo config) {
LambdaUpdateWrapper<ConfigAllInfo> wrapper = Wrappers.lambdaUpdate(ConfigAllInfo.class)
.eq(ConfigAllInfo::getTpId, config.getTpId())
.eq(ConfigAllInfo::getItemId, config.getItemId())
.eq(ConfigAllInfo::getTenantId, config.getTenantId());
config.setGmtCreate(null);
config.setContent(ContentUtil.getPoolContent(config));
config.setMd5(Md5Util.getTpContentMd5(config));
try {
configInfoMapper.update(config, wrapper);
} catch (Exception ex) {
log.error("[db-error] message :: {}", ex.getMessage(), ex);
throw ex;
}
}
}

@ -1,63 +0,0 @@
package com.github.dynamic.threadpool.config.service.biz.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.github.dynamic.threadpool.config.enums.DelEnum;
import com.github.dynamic.threadpool.config.mapper.ConfigInfoMapper;
import com.github.dynamic.threadpool.config.model.ConfigAllInfo;
import com.github.dynamic.threadpool.config.model.biz.threadpool.ThreadPoolQueryReqDTO;
import com.github.dynamic.threadpool.config.model.biz.threadpool.ThreadPoolRespDTO;
import com.github.dynamic.threadpool.config.model.biz.threadpool.ThreadPoolSaveOrUpdateReqDTO;
import com.github.dynamic.threadpool.config.service.biz.ConfigService;
import com.github.dynamic.threadpool.config.service.biz.ThreadPoolService;
import com.github.dynamic.threadpool.config.toolkit.BeanUtil;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* Thread pool service impl.
*
* @author chen.ma
* @date 2021/6/30 21:26
*/
@Service
@AllArgsConstructor
public class ThreadPoolServiceImpl implements ThreadPoolService {
private final ConfigService configService;
private final ConfigInfoMapper configInfoMapper;
@Override
public IPage<ThreadPoolRespDTO> queryThreadPoolPage(ThreadPoolQueryReqDTO reqDTO) {
LambdaQueryWrapper<ConfigAllInfo> wrapper = Wrappers.lambdaQuery(ConfigAllInfo.class)
.eq(!StringUtils.isBlank(reqDTO.getTenantId()), ConfigAllInfo::getTenantId, reqDTO.getTenantId())
.eq(!StringUtils.isBlank(reqDTO.getItemId()), ConfigAllInfo::getItemId, reqDTO.getItemId())
.eq(!StringUtils.isBlank(reqDTO.getTpId()), ConfigAllInfo::getTpId, reqDTO.getTpId())
.eq(ConfigAllInfo::getDelFlag, DelEnum.NORMAL);
return configInfoMapper.selectPage(reqDTO, wrapper).convert(each -> BeanUtil.convert(each, ThreadPoolRespDTO.class));
}
@Override
public ThreadPoolRespDTO getThreadPool(ThreadPoolQueryReqDTO reqDTO) {
ConfigAllInfo configAllInfo = configService.findConfigAllInfo(reqDTO.getTpId(), reqDTO.getItemId(), reqDTO.getTenantId());
return BeanUtil.convert(configAllInfo, ThreadPoolRespDTO.class);
}
@Override
public List<ThreadPoolRespDTO> getThreadPoolByItemId(String itemId) {
List<ConfigAllInfo> selectList = configInfoMapper
.selectList(Wrappers.lambdaUpdate(ConfigAllInfo.class).eq(ConfigAllInfo::getItemId, itemId));
return BeanUtil.convert(selectList, ThreadPoolRespDTO.class);
}
@Override
public void saveOrUpdateThreadPoolConfig(ThreadPoolSaveOrUpdateReqDTO reqDTO) {
configService.insertOrUpdate(BeanUtil.convert(reqDTO, ConfigAllInfo.class));
}
}

@ -1,31 +0,0 @@
package com.github.dynamic.threadpool.config.toolkit;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiFunction;
/**
* Map util.
*
* @author chen.ma
* @date 2021/6/23 19:09
*/
public class MapUtil {
public static Object computeIfAbsent(Map target, Object key, BiFunction mappingFunction, Object param1, Object param2) {
Objects.requireNonNull(target, "target");
Objects.requireNonNull(key, "key");
Objects.requireNonNull(mappingFunction, "mappingFunction");
Objects.requireNonNull(param1, "param1");
Objects.requireNonNull(param2, "param2");
Object val = target.get(key);
if (val == null) {
Object ret = mappingFunction.apply(param1, param2);
target.put(key, ret);
return ret;
}
return val;
}
}

@ -1,30 +0,0 @@
package com.github.dynamic.threadpool.config.toolkit;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
/**
* Request util.
*
* @author chen.ma
* @date 2021/6/23 18:28
*/
public class RequestUtil {
private static final String X_REAL_IP = "X-Real-IP";
private static final String X_FORWARDED_FOR = "X-Forwarded-For";
private static final String X_FORWARDED_FOR_SPLIT_SYMBOL = ",";
public static String getRemoteIp(HttpServletRequest request) {
String xForwardedFor = request.getHeader(X_FORWARDED_FOR);
if (!StringUtils.isEmpty(xForwardedFor)) {
return xForwardedFor.split(X_FORWARDED_FOR_SPLIT_SYMBOL)[0].trim();
}
String nginxHeader = request.getHeader(X_REAL_IP);
return StringUtils.isEmpty(nginxHeader) ? request.getRemoteAddr() : nginxHeader;
}
}

@ -1,44 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.github.dynamic-threadpool</groupId>
<artifactId>parent</artifactId>
<version>${revision}</version>
</parent>
<artifactId>console</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>${project.artifactId}</description>
<dependencies>
<dependency>
<groupId>com.github.dynamic-threadpool</groupId>
<artifactId>config</artifactId>
</dependency>
<dependency>
<groupId>com.github.dynamic-threadpool</groupId>
<artifactId>discovery</artifactId>
</dependency>
<dependency>
<groupId>com.github.dynamic-threadpool</groupId>
<artifactId>auth</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

@ -1,56 +0,0 @@
package com.github.dynamic.threadpool.console.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.github.dynamic.threadpool.common.constant.Constants;
import com.github.dynamic.threadpool.common.web.base.Result;
import com.github.dynamic.threadpool.common.web.base.Results;
import com.github.dynamic.threadpool.config.model.biz.tenant.TenantQueryReqDTO;
import com.github.dynamic.threadpool.config.model.biz.tenant.TenantRespDTO;
import com.github.dynamic.threadpool.config.model.biz.tenant.TenantSaveReqDTO;
import com.github.dynamic.threadpool.config.model.biz.tenant.TenantUpdateReqDTO;
import com.github.dynamic.threadpool.config.service.biz.TenantService;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
/**
* Tenant controller.
*
* @author chen.ma
* @date 2021/6/25 18:31
*/
@RestController
@AllArgsConstructor
@RequestMapping(Constants.BASE_PATH + "/tenant")
public class TenantController {
private final TenantService tenantService;
@PostMapping("/query/page")
public Result<IPage<TenantRespDTO>> queryNameSpacePage(@RequestBody TenantQueryReqDTO reqDTO) {
return Results.success(tenantService.queryTenantPage(reqDTO));
}
@GetMapping("/query/{tenantId}")
public Result<TenantRespDTO> queryNameSpace(@PathVariable("tenantId") String tenantId) {
return Results.success(tenantService.getTenantByTenantId(tenantId));
}
@PostMapping("/save")
public Result saveNameSpace(@RequestBody TenantSaveReqDTO reqDTO) {
tenantService.saveTenant(reqDTO);
return Results.success();
}
@PostMapping("/update")
public Result updateNameSpace(@RequestBody TenantUpdateReqDTO reqDTO) {
tenantService.updateTenant(reqDTO);
return Results.success();
}
@DeleteMapping("/delete/{tenantId}")
public Result deleteNameSpace(@PathVariable("tenantId") String tenantId) {
tenantService.deleteTenantById(tenantId);
return Results.success();
}
}

@ -1,56 +0,0 @@
package com.github.dynamic.threadpool.console.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.github.dynamic.threadpool.common.constant.Constants;
import com.github.dynamic.threadpool.common.model.InstanceInfo;
import com.github.dynamic.threadpool.common.web.base.Result;
import com.github.dynamic.threadpool.common.web.base.Results;
import com.github.dynamic.threadpool.config.model.biz.threadpool.ThreadPoolQueryReqDTO;
import com.github.dynamic.threadpool.config.model.biz.threadpool.ThreadPoolRespDTO;
import com.github.dynamic.threadpool.config.model.biz.threadpool.ThreadPoolSaveOrUpdateReqDTO;
import com.github.dynamic.threadpool.config.service.biz.ThreadPoolService;
import com.github.dynamic.threadpool.discovery.core.BaseInstanceRegistry;
import com.github.dynamic.threadpool.discovery.core.Lease;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* Thread pool controller.
*
* @author chen.ma
* @date 2021/6/30 20:54
*/
@RestController
@AllArgsConstructor
@RequestMapping(Constants.BASE_PATH + "/thread")
public class ThreadPoolController {
private final ThreadPoolService threadPoolService;
private final BaseInstanceRegistry baseInstanceRegistry;
@PostMapping("/pool/query/page")
public Result<IPage<ThreadPoolRespDTO>> queryNameSpacePage(@RequestBody ThreadPoolQueryReqDTO reqDTO) {
return Results.success(threadPoolService.queryThreadPoolPage(reqDTO));
}
@PostMapping("/pool/query")
public Result<ThreadPoolRespDTO> queryNameSpace(@RequestBody ThreadPoolQueryReqDTO reqDTO) {
return Results.success(threadPoolService.getThreadPool(reqDTO));
}
@PostMapping("/pool/save_or_update")
public Result saveOrUpdateThreadPoolConfig(@RequestBody ThreadPoolSaveOrUpdateReqDTO reqDTO) {
threadPoolService.saveOrUpdateThreadPoolConfig(reqDTO);
return Results.success();
}
@GetMapping("/pool/list/instance/{itemId}")
public Result<List<Lease<InstanceInfo>>> listInstance(@PathVariable("itemId") String itemId) {
List<Lease<InstanceInfo>> leases = baseInstanceRegistry.listInstance(itemId);
return Results.success(leases);
}
}

@ -1,56 +0,0 @@
package com.github.dynamic.threadpool.console.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.github.dynamic.threadpool.auth.model.biz.user.UserRespDTO;
import com.github.dynamic.threadpool.auth.service.UserService;
import com.github.dynamic.threadpool.common.web.base.Result;
import com.github.dynamic.threadpool.common.web.base.Results;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* User controller.
*
* @author chen.ma
* @date 2021/10/30 21:15
*/
@RestController
@AllArgsConstructor
@RequestMapping({"/v1/auth", "/v1/auth/users"})
public class UserController {
private final UserService userService;
@GetMapping("/{pageNo}/{pageSize}")
public Result<IPage<UserRespDTO>> listUser(@PathVariable("pageNo") int pageNo, @PathVariable("pageSize") int pageSize) {
IPage<UserRespDTO> resultUserPage = userService.listUser(pageNo, pageSize);
return Results.success(resultUserPage);
}
@PostMapping("/{userName}/{password}")
public Result<Void> addUser(@PathVariable("userName") String userName, @PathVariable("password") String password) {
userService.addUser(userName, password);
return Results.success();
}
@PutMapping("/{userName}/{password}")
public Result<Void> updateUser(@PathVariable("userName") String userName, @PathVariable("password") String password) {
userService.updateUser(userName, password);
return Results.success();
}
@DeleteMapping("/{userName}")
public Result<Void> deleteUser(@PathVariable("userName") String userName) {
userService.deleteUser(userName);
return Results.success();
}
@GetMapping("/search/{userName}")
public Result<List<String>> searchUsersLikeUserName(@PathVariable("userName") String userName) {
List<String> resultUserNames = userService.getUserLikeUsername(userName);
return Results.success(resultUserNames);
}
}

@ -1,289 +0,0 @@
package com.github.dynamic.threadpool.discovery.core;
import com.github.dynamic.threadpool.common.model.InstanceInfo;
import com.github.dynamic.threadpool.common.model.InstanceInfo.InstanceStatus;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import static com.github.dynamic.threadpool.common.constant.Constants.EVICTION_INTERVAL_TIMER_IN_MS;
import static com.github.dynamic.threadpool.common.constant.Constants.SCHEDULED_THREAD_CORE_NUM;
/**
* Base instance registry.
*
* @author chen.ma
* @date 2021/8/8 22:46
*/
@Slf4j
@Service
public class BaseInstanceRegistry implements InstanceRegistry<InstanceInfo> {
private final int CONTAINER_SIZE = 1024;
private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final Lock read = readWriteLock.readLock();
protected final Object lock = new Object();
private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry = new ConcurrentHashMap(CONTAINER_SIZE);
protected volatile int expectedNumberOfClientsSendingRenews;
private final CircularQueue<Pair<Long, String>> recentRegisteredQueue;
private final CircularQueue<Pair<Long, String>> recentCanceledQueue;
private ConcurrentLinkedQueue<RecentlyChangedItem> recentlyChangedQueue = new ConcurrentLinkedQueue();
protected final ConcurrentMap<String, InstanceStatus> overriddenInstanceStatusMap = CacheBuilder
.newBuilder().initialCapacity(512)
.expireAfterAccess(1, TimeUnit.HOURS)
.<String, InstanceStatus>build().asMap();
public BaseInstanceRegistry() {
this.recentRegisteredQueue = new CircularQueue(CONTAINER_SIZE);
this.recentCanceledQueue = new CircularQueue(CONTAINER_SIZE);
}
@Override
public List<Lease<InstanceInfo>> listInstance(String appName) {
Map<String, Lease<InstanceInfo>> appNameLeaseMap = registry.get(appName);
if (CollectionUtils.isEmpty(appNameLeaseMap)) {
return Lists.newArrayList();
}
List<Lease<InstanceInfo>> appNameLeaseList = Lists.newArrayList();
appNameLeaseMap.values().forEach(each -> appNameLeaseList.add(each));
return appNameLeaseList;
}
@Override
public void register(InstanceInfo registrant) {
read.lock();
try {
Map<String, Lease<InstanceInfo>> registerMap = registry.get(registrant.getAppName());
if (registerMap == null) {
ConcurrentHashMap<String, Lease<InstanceInfo>> registerNewMap = new ConcurrentHashMap(12);
registerMap = registry.putIfAbsent(registrant.getAppName(), registerNewMap);
if (registerMap == null) {
registerMap = registerNewMap;
}
}
Lease<InstanceInfo> existingLease = registerMap.get(registrant.getInstanceId());
if (existingLease != null && (existingLease.getHolder() != null)) {
Long existingLastDirtyTimestamp = existingLease.getHolder().getLastDirtyTimestamp();
Long registrationLastDirtyTimestamp = registrant.getLastDirtyTimestamp();
if (existingLastDirtyTimestamp > registrationLastDirtyTimestamp) {
registrant = existingLease.getHolder();
}
}
Lease<InstanceInfo> lease = new Lease(registrant);
if (existingLease != null) {
lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());
}
registerMap.put(registrant.getInstanceId(), lease);
recentRegisteredQueue.add(new Pair(
System.currentTimeMillis(),
registrant.getAppName() + "(" + registrant.getInstanceId() + ")"));
InstanceStatus overriddenStatusFromMap = overriddenInstanceStatusMap.get(registrant.getInstanceId());
if (overriddenStatusFromMap != null) {
log.info("Storing overridden status :: {} from map", overriddenStatusFromMap);
registrant.setOverriddenStatus(overriddenStatusFromMap);
}
if (InstanceStatus.UP.equals(registrant.getStatus())) {
lease.serviceUp();
}
registrant.setActionType(InstanceInfo.ActionType.ADDED);
recentlyChangedQueue.add(new RecentlyChangedItem(lease));
registrant.setLastUpdatedTimestamp();
} finally {
read.unlock();
}
}
@Override
public boolean renew(InstanceInfo.InstanceRenew instanceRenew) {
String appName = instanceRenew.getAppName();
String instanceId = instanceRenew.getInstanceId();
Map<String, Lease<InstanceInfo>> registryMap = registry.get(appName);
Lease<InstanceInfo> leaseToRenew = null;
if (registryMap == null || (leaseToRenew = registryMap.get(instanceId)) == null) {
return false;
}
leaseToRenew.renew();
return true;
}
static class CircularQueue<E> extends AbstractQueue<E> {
private final ArrayBlockingQueue<E> delegate;
public CircularQueue(int capacity) {
this.delegate = new ArrayBlockingQueue(capacity);
}
@Override
public Iterator<E> iterator() {
return delegate.iterator();
}
@Override
public int size() {
return delegate.size();
}
@Override
public boolean offer(E e) {
while (!delegate.offer(e)) {
delegate.poll();
}
return true;
}
@Override
public E poll() {
return delegate.poll();
}
@Override
public E peek() {
return delegate.peek();
}
@Override
public void clear() {
delegate.clear();
}
@Override
public Object[] toArray() {
return delegate.toArray();
}
}
private static final class RecentlyChangedItem {
private long lastUpdateTime;
private Lease<InstanceInfo> leaseInfo;
public RecentlyChangedItem(Lease<InstanceInfo> lease) {
this.leaseInfo = lease;
lastUpdateTime = System.currentTimeMillis();
}
public long getLastUpdateTime() {
return this.lastUpdateTime;
}
public Lease<InstanceInfo> getLeaseInfo() {
return this.leaseInfo;
}
}
public void evict(long additionalLeaseMs) {
List<Lease<InstanceInfo>> expiredLeases = new ArrayList();
for (Map.Entry<String, Map<String, Lease<InstanceInfo>>> groupEntry : registry.entrySet()) {
Map<String, Lease<InstanceInfo>> leaseMap = groupEntry.getValue();
if (leaseMap != null) {
for (Map.Entry<String, Lease<InstanceInfo>> leaseEntry : leaseMap.entrySet()) {
Lease<InstanceInfo> lease = leaseEntry.getValue();
if (lease.isExpired(additionalLeaseMs) && lease.getHolder() != null) {
expiredLeases.add(lease);
}
}
}
}
for (Lease<InstanceInfo> expiredLease : expiredLeases) {
String appName = expiredLease.getHolder().getAppName();
String id = expiredLease.getHolder().getInstanceId();
internalCancel(appName, id, false);
}
}
protected boolean internalCancel(String appName, String id, boolean isReplication) {
read.lock();
try {
Map<String, Lease<InstanceInfo>> registerMap = registry.get(appName);
if (!CollectionUtils.isEmpty(registerMap)) {
registerMap.remove(id);
}
} finally {
read.unlock();
}
return true;
}
public class EvictionTask extends TimerTask {
private final AtomicLong lastExecutionNanosRef = new AtomicLong(0L);
@Override
public void run() {
try {
long compensationTimeMs = getCompensationTimeMs();
log.info("Running the evict task with compensationTime {} ms", compensationTimeMs);
evict(compensationTimeMs);
} catch (Throwable e) {
log.error("Could not run the evict task", e);
}
}
long getCompensationTimeMs() {
long currNanos = getCurrentTimeNano();
long lastNanos = lastExecutionNanosRef.getAndSet(currNanos);
if (lastNanos == 0L) {
return 0L;
}
long elapsedMs = TimeUnit.NANOSECONDS.toMillis(currNanos - lastNanos);
long compensationTime = elapsedMs - EVICTION_INTERVAL_TIMER_IN_MS;
return compensationTime <= 0L ? 0L : compensationTime;
}
long getCurrentTimeNano() {
return System.nanoTime();
}
}
private final ScheduledExecutorService scheduledExecutorService =
new ScheduledThreadPoolExecutor(
SCHEDULED_THREAD_CORE_NUM,
new ThreadFactoryBuilder()
.setNameFormat("registry-eviction")
.setDaemon(true)
.build()
);
private final AtomicReference<EvictionTask> evictionTaskRef = new AtomicReference();
public void postInit() {
evictionTaskRef.set(new BaseInstanceRegistry.EvictionTask());
scheduledExecutorService.scheduleWithFixedDelay(evictionTaskRef.get(),
EVICTION_INTERVAL_TIMER_IN_MS, EVICTION_INTERVAL_TIMER_IN_MS, TimeUnit.MILLISECONDS);
}
}

@ -1,36 +0,0 @@
package com.github.dynamic.threadpool.discovery.core;
/**
* Pair.
*
* @author chen.ma
* @date 2021/8/8 23:04
*/
public class Pair<E1, E2> {
public E1 first() {
return first;
}
public void setFirst(E1 first) {
this.first = first;
}
public E2 second() {
return second;
}
public void setSecond(E2 second) {
this.second = second;
}
private E1 first;
private E2 second;
public Pair(E1 first, E2 second) {
this.first = first;
this.second = second;
}
}

@ -1,36 +0,0 @@
package com.github.dynamic.threadpool.discovery.core;
import com.github.dynamic.threadpool.common.model.InstanceInfo.InstanceStatus;
/**
* Status override result.
*
* @author chen.ma
* @date 2021/8/8 23:11
*/
public class StatusOverrideResult {
public static StatusOverrideResult NO_MATCH = new StatusOverrideResult(false, null);
public static StatusOverrideResult matchingStatus(InstanceStatus status) {
return new StatusOverrideResult(true, status);
}
private final boolean matches;
private final InstanceStatus status;
private StatusOverrideResult(boolean matches, InstanceStatus status) {
this.matches = matches;
this.status = status;
}
public boolean matches() {
return matches;
}
public InstanceStatus status() {
return status;
}
}

@ -1,44 +0,0 @@
package com.github.dynamic.threadpool.starter.alarm;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import java.util.concurrent.TimeUnit;
/**
* .
*
* @author chen.ma
* @date 2021/10/28 21:24
*/
public class AlarmControlHandler {
private final Cache<String, String> cache;
public AlarmControlHandler(long alarmInterval) {
cache = CacheBuilder.newBuilder()
.expireAfterWrite(alarmInterval, TimeUnit.MINUTES)
.build();
}
/**
* .
*
* @param alarmControl
* @return
*/
public boolean isSend(AlarmControlDTO alarmControl) {
String pkId = cache.getIfPresent(alarmControl.buildPk());
if (StrUtil.isBlank(pkId)) {
// val 无意义
cache.put(alarmControl.buildPk(), IdUtil.simpleUUID());
return true;
}
return false;
}
}

@ -1,59 +0,0 @@
package com.github.dynamic.threadpool.starter.alarm;
import com.github.dynamic.threadpool.common.config.ApplicationContextHolder;
import com.github.dynamic.threadpool.common.model.PoolParameterInfo;
import com.github.dynamic.threadpool.starter.toolkit.thread.CustomThreadPoolExecutor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Base send message service.
*
* @author chen.ma
* @date 2021/8/15 15:34
*/
@Slf4j
@RequiredArgsConstructor
public class BaseSendMessageService implements InitializingBean, SendMessageService {
@NonNull
private final List<NotifyConfig> notifyConfigs;
private final List<SendMessageHandler> sendMessageHandlers = new ArrayList(4);
@Override
public void sendAlarmMessage(CustomThreadPoolExecutor threadPoolExecutor) {
for (SendMessageHandler messageHandler : sendMessageHandlers) {
try {
messageHandler.sendAlarmMessage(notifyConfigs, threadPoolExecutor);
} catch (Exception ex) {
log.warn("Failed to send thread pool alarm notification.", ex);
}
}
}
@Override
public void sendChangeMessage(PoolParameterInfo parameter) {
for (SendMessageHandler messageHandler : sendMessageHandlers) {
try {
messageHandler.sendChangeMessage(notifyConfigs, parameter);
} catch (Exception ex) {
log.warn("Failed to send thread pool change notification.", ex);
}
}
}
@Override
public void afterPropertiesSet() {
Map<String, SendMessageHandler> sendMessageHandlerMap =
ApplicationContextHolder.getBeansOfType(SendMessageHandler.class);
sendMessageHandlerMap.values().forEach(each -> sendMessageHandlers.add(each));
}
}

@ -1,220 +0,0 @@
package com.github.dynamic.threadpool.starter.alarm;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.dingtalk.api.DefaultDingTalkClient;
import com.dingtalk.api.DingTalkClient;
import com.dingtalk.api.request.OapiRobotSendRequest;
import com.github.dynamic.threadpool.common.model.InstanceInfo;
import com.github.dynamic.threadpool.common.model.PoolParameterInfo;
import com.github.dynamic.threadpool.starter.core.GlobalThreadPoolManage;
import com.github.dynamic.threadpool.starter.toolkit.thread.CustomThreadPoolExecutor;
import com.github.dynamic.threadpool.starter.toolkit.thread.QueueTypeEnum;
import com.github.dynamic.threadpool.starter.toolkit.thread.RejectedTypeEnum;
import com.github.dynamic.threadpool.starter.wrap.DynamicThreadPoolWrap;
import com.google.common.base.Joiner;
import com.taobao.api.ApiException;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
/**
* Send ding notification message.
*
* @author chen.ma
* @date 2021/8/15 15:49
*/
@Slf4j
@AllArgsConstructor
public class DingSendMessageHandler implements SendMessageHandler {
private String active;
private Long alarmInterval;
private InstanceInfo instanceInfo;
@Override
public String getType() {
return SendMessageEnum.DING.name();
}
@Override
public void sendAlarmMessage(List<NotifyConfig> notifyConfigs, CustomThreadPoolExecutor pool) {
Optional<NotifyConfig> notifyConfigOptional = notifyConfigs.stream()
.filter(each -> Objects.equals(each.getType(), getType()))
.findFirst();
notifyConfigOptional.ifPresent(each -> dingAlarmSendMessage(each, pool));
}
@Override
public void sendChangeMessage(List<NotifyConfig> notifyConfigs, PoolParameterInfo parameter) {
Optional<NotifyConfig> changeConfigOptional = notifyConfigs.stream()
.filter(each -> Objects.equals(each.getType(), getType()))
.findFirst();
changeConfigOptional.ifPresent(each -> dingChangeSendMessage(each, parameter));
}
private void dingAlarmSendMessage(NotifyConfig notifyConfig, CustomThreadPoolExecutor pool) {
List<String> receives = StrUtil.split(notifyConfig.getReceives(), ',');
String afterReceives = Joiner.on(", @").join(receives);
BlockingQueue<Runnable> queue = pool.getQueue();
String text = String.format(
"<font color='#FF0000'>[警报] </font>%s - 动态线程池运行告警 \n\n" +
" --- \n\n " +
"<font color='#708090' size=2>线程池ID%s</font> \n\n " +
"<font color='#778899' size=2>应用实例:%s</font> \n\n " +
" --- \n\n " +
"<font color='#708090' size=2>核心线程数:%d</font> \n\n " +
"<font color='#708090' size=2>最大线程数:%d</font> \n\n " +
"<font color='#708090' size=2>当前线程数:%d</font> \n\n " +
"<font color='#708090' size=2>活跃线程数:%d</font> \n\n " +
"<font color='#708090' size=2>最大线程数:%d</font> \n\n " +
"<font color='#708090' size=2>线程池任务总量:%d</font> \n\n " +
" --- \n\n " +
"<font color='#708090' size=2>队列类型:%s</font> \n\n " +
"<font color='#708090' size=2>队列容量:%d</font> \n\n " +
"<font color='#708090' size=2>队列元素个数:%d</font> \n\n " +
"<font color='#708090' size=2>队列剩余个数:%d</font> \n\n " +
" --- \n\n " +
"<font color='#708090' size=2>拒绝策略:%s</font> \n\n" +
"<font color='#708090' size=2>拒绝策略执行次数:</font><font color='#FF0000' size=2>%d</font> \n\n " +
"<font color='#708090' size=2>OWNER@%s</font> \n\n" +
"<font color='#708090' size=2>提示:%d 分钟内此线程池不会重复告警(可配置)</font> \n\n" +
" --- \n\n " +
"**播报时间:%s**",
// 环境
active.toUpperCase(),
// 线程池ID
pool.getThreadPoolId(),
// 节点信息
instanceInfo.getIpApplicationName(),
// 核心线程数
pool.getCorePoolSize(),
// 最大线程数
pool.getMaximumPoolSize(),
// 当前线程数
pool.getPoolSize(),
// 活跃线程数
pool.getActiveCount(),
// 最大任务数
pool.getLargestPoolSize(),
// 线程池任务总量
pool.getCompletedTaskCount(),
// 队列类型名称
queue.getClass().getSimpleName(),
// 队列容量
queue.size() + queue.remainingCapacity(),
// 队列元素个数
queue.size(),
// 队列剩余个数
queue.remainingCapacity(),
// 拒绝策略名称
pool.getRejectedExecutionHandler().getClass().getSimpleName(),
// 拒绝策略次数
pool.getRejectCount(),
// 告警手机号
afterReceives,
// 报警频率
alarmInterval,
// 当前时间
DateUtil.now()
);
execute(notifyConfig, "动态线程池告警", text, receives);
}
private void dingChangeSendMessage(NotifyConfig notifyConfig, PoolParameterInfo parameter) {
String threadPoolId = parameter.getTpId();
DynamicThreadPoolWrap poolWrap = GlobalThreadPoolManage.getExecutorService(threadPoolId);
if (poolWrap == null) {
log.warn("Thread pool is empty when sending change notification, threadPoolId :: {}", threadPoolId);
return;
}
List<String> receives = StrUtil.split(notifyConfig.getReceives(), ',');
String afterReceives = Joiner.on(", @").join(receives);
CustomThreadPoolExecutor customPool = poolWrap.getPool();
/**
* hesitant e.g.
*/
String text = String.format(
"<font color='#2a9d8f'>[通知] </font>%s - 动态线程池参数变更 \n\n" +
" --- \n\n " +
"<font color='#708090' size=2>线程池ID%s</font> \n\n " +
"<font color='#778899' size=2>应用实例:%s</font> \n\n " +
" --- \n\n " +
"<font color='#708090' size=2>核心线程数:%s</font> \n\n " +
"<font color='#708090' size=2>最大线程数:%s</font> \n\n " +
"<font color='#708090' size=2>线程存活时间:%s / SECONDS</font> \n\n" +
" --- \n\n " +
"<font color='#708090' size=2>队列类型:%s</font> \n\n " +
"<font color='#708090' size=2>队列容量:%s</font> \n\n " +
" --- \n\n " +
"<font color='#708090' size=2>AGO 拒绝策略:%s</font> \n\n" +
"<font color='#708090' size=2>NOW 拒绝策略:%s</font> \n\n" +
" --- \n\n " +
"<font color='#708090' size=2>提示:动态线程池配置变更实时通知(无限制)</font> \n\n" +
"<font color='#708090' size=2>OWNER@%s</font> \n\n" +
" --- \n\n " +
"**播报时间:%s**",
// 环境
active.toUpperCase(),
// 线程池名称
threadPoolId,
// 节点信息
instanceInfo.getIpApplicationName(),
// 核心线程数
customPool.getCorePoolSize() + " ➲ " + parameter.getCoreSize(),
// 最大线程数
customPool.getMaximumPoolSize() + " ➲ " + parameter.getMaxSize(),
// 线程存活时间
customPool.getKeepAliveTime(TimeUnit.SECONDS) + " ➲ " + parameter.getKeepAliveTime(),
// 阻塞队列
QueueTypeEnum.getBlockingQueueNameByType(parameter.getQueueType()),
// 阻塞队列容量
(customPool.getQueue().size() + customPool.getQueue().remainingCapacity()) + " ➲ " + parameter.getCapacity(),
// 拒绝策略
customPool.getRejectedExecutionHandler().getClass().getSimpleName(),
RejectedTypeEnum.getRejectedNameByType(parameter.getRejectedType()),
// 告警手机号
afterReceives,
// 当前时间
DateUtil.now()
);
execute(notifyConfig, "动态线程池通知", text, receives);
}
private void execute(NotifyConfig notifyConfigs, String title, String text, List<String> mobiles) {
String serverUrl = notifyConfigs.getUrl() + notifyConfigs.getToken();
DingTalkClient dingTalkClient = new DefaultDingTalkClient(serverUrl);
OapiRobotSendRequest request = new OapiRobotSendRequest();
request.setMsgtype("markdown");
OapiRobotSendRequest.Markdown markdown = new OapiRobotSendRequest.Markdown();
markdown.setTitle(title);
markdown.setText(text);
OapiRobotSendRequest.At at = new OapiRobotSendRequest.At();
at.setAtMobiles(mobiles);
request.setAt(at);
request.setMarkdown(markdown);
try {
dingTalkClient.execute(request);
} catch (ApiException ex) {
log.error("Ding failed to send message", ex.getMessage());
}
}
}

@ -1,31 +0,0 @@
package com.github.dynamic.threadpool.starter.alarm;
/**
* Message type enum.
*
* @author chen.ma
* @date 2021/8/16 20:50
*/
public enum MessageTypeEnum {
/**
*
*/
CHANGE,
/**
*
*/
CAPACITY,
/**
*
*/
LIVENESS,
/**
*
*/
REJECT
}

@ -1,34 +0,0 @@
package com.github.dynamic.threadpool.starter.alarm;
import lombok.Data;
/**
* Alarm config.
*
* @author chen.ma
* @date 2021/8/15 16:09
*/
@Data
public class NotifyConfig {
/**
* type
*/
private String type;
/**
* url
*/
private String url;
/**
* token
*/
private String token;
/**
* receives
*/
private String receives;
}

@ -1,13 +0,0 @@
package com.github.dynamic.threadpool.starter.alarm;
/**
* Send message enum.
*
* @author chen.ma
* @date 2021/8/15 15:50
*/
public enum SendMessageEnum {
DING
}

@ -1,39 +0,0 @@
package com.github.dynamic.threadpool.starter.alarm;
import com.github.dynamic.threadpool.common.model.PoolParameterInfo;
import com.github.dynamic.threadpool.starter.toolkit.thread.CustomThreadPoolExecutor;
import java.util.List;
/**
* Send message handler.
*
* @author chen.ma
* @date 2021/8/15 15:44
*/
public interface SendMessageHandler {
/**
* Get type.
*
* @return
*/
String getType();
/**
* Send alarm message.
*
* @param notifyConfigs
* @param threadPoolExecutor
*/
void sendAlarmMessage(List<NotifyConfig> notifyConfigs, CustomThreadPoolExecutor threadPoolExecutor);
/**
* Send change message.
*
* @param notifyConfigs
* @param parameter
*/
void sendChangeMessage(List<NotifyConfig> notifyConfigs, PoolParameterInfo parameter);
}

@ -1,28 +0,0 @@
package com.github.dynamic.threadpool.starter.alarm;
import com.github.dynamic.threadpool.common.model.PoolParameterInfo;
import com.github.dynamic.threadpool.starter.toolkit.thread.CustomThreadPoolExecutor;
/**
* Send msg.
*
* @author chen.ma
* @date 2021/8/15 15:31
*/
public interface SendMessageService {
/**
* Send alarm message.
*
* @param threadPoolExecutor
*/
void sendAlarmMessage(CustomThreadPoolExecutor threadPoolExecutor);
/**
* Send change message.
*
* @param parameter
*/
void sendChangeMessage(PoolParameterInfo parameter);
}

@ -1,31 +0,0 @@
package com.github.dynamic.threadpool.starter.alarm;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* Thread pool alarm.
*
* @author chen.ma
* @date 2021/8/15 13:13
*/
@Data
@AllArgsConstructor
public class ThreadPoolAlarm {
/**
* isAlarm
*/
private Boolean isAlarm;
/**
* livenessAlarm
*/
private Integer livenessAlarm;
/**
* capacityAlarm
*/
private Integer capacityAlarm;
}

@ -1,129 +0,0 @@
package com.github.dynamic.threadpool.starter.alarm;
import com.github.dynamic.threadpool.common.config.ApplicationContextHolder;
import com.github.dynamic.threadpool.common.model.PoolParameterInfo;
import com.github.dynamic.threadpool.starter.config.MessageAlarmConfig;
import com.github.dynamic.threadpool.starter.toolkit.CalculateUtil;
import com.github.dynamic.threadpool.starter.toolkit.thread.CustomThreadPoolExecutor;
import com.github.dynamic.threadpool.starter.toolkit.thread.ResizableCapacityLinkedBlockIngQueue;
import lombok.extern.slf4j.Slf4j;
import java.util.Optional;
/**
* Alarm manage.
*
* @author chen.ma
* @date 2021/8/15 14:13
*/
@Slf4j
public class ThreadPoolAlarmManage {
/**
*
*/
private static final SendMessageService SEND_MESSAGE_SERVICE;
/**
*
*/
private static final AlarmControlHandler ALARM_CONTROL_HANDLER;
static {
SEND_MESSAGE_SERVICE = Optional.ofNullable(ApplicationContextHolder.getInstance())
.map(each -> each.getBean(MessageAlarmConfig.SEND_MESSAGE_BEAN_NAME, SendMessageService.class))
.orElse(null);
ALARM_CONTROL_HANDLER = Optional.ofNullable(ApplicationContextHolder.getInstance())
.map(each -> each.getBean(AlarmControlHandler.class))
.orElse(null);
}
/**
* Check thread pool capacity alarm.
*
* @param threadPoolExecutor
*/
public static void checkPoolCapacityAlarm(CustomThreadPoolExecutor threadPoolExecutor) {
if (SEND_MESSAGE_SERVICE == null) {
return;
}
ThreadPoolAlarm threadPoolAlarm = threadPoolExecutor.getThreadPoolAlarm();
ResizableCapacityLinkedBlockIngQueue blockIngQueue =
(ResizableCapacityLinkedBlockIngQueue) threadPoolExecutor.getQueue();
int queueSize = blockIngQueue.size();
int capacity = queueSize + blockIngQueue.remainingCapacity();
int divide = CalculateUtil.divide(queueSize, capacity);
boolean isSend = divide > threadPoolAlarm.getCapacityAlarm()
&& isSendMessage(threadPoolExecutor, MessageTypeEnum.CAPACITY);
if (isSend) {
SEND_MESSAGE_SERVICE.sendAlarmMessage(threadPoolExecutor);
}
}
/**
* Check thread pool activity alarm.
*
* @param isCore
* @param threadPoolExecutor
*/
public static void checkPoolLivenessAlarm(boolean isCore, CustomThreadPoolExecutor threadPoolExecutor) {
if (isCore || SEND_MESSAGE_SERVICE == null || !isSendMessage(threadPoolExecutor, MessageTypeEnum.LIVENESS)) {
return;
}
int activeCount = threadPoolExecutor.getActiveCount();
int maximumPoolSize = threadPoolExecutor.getMaximumPoolSize();
int divide = CalculateUtil.divide(activeCount, maximumPoolSize);
boolean isSend = divide > threadPoolExecutor.getThreadPoolAlarm().getLivenessAlarm()
&& isSendMessage(threadPoolExecutor, MessageTypeEnum.CAPACITY);
if (isSend) {
SEND_MESSAGE_SERVICE.sendAlarmMessage(threadPoolExecutor);
}
}
/**
* Check thread pool reject policy alarm.
*
* @param threadPoolExecutor
*/
public static void checkPoolRejectAlarm(CustomThreadPoolExecutor threadPoolExecutor) {
if (SEND_MESSAGE_SERVICE == null) {
return;
}
if (isSendMessage(threadPoolExecutor, MessageTypeEnum.REJECT)) {
SEND_MESSAGE_SERVICE.sendAlarmMessage(threadPoolExecutor);
}
}
/**
* Send thread pool configuration change message.
*
* @param parameter
*/
public static void sendPoolConfigChange(PoolParameterInfo parameter) {
if (SEND_MESSAGE_SERVICE == null) {
return;
}
SEND_MESSAGE_SERVICE.sendChangeMessage(parameter);
}
/**
* Is send message.
*
* @param threadPoolExecutor
* @param typeEnum
* @return
*/
private static boolean isSendMessage(CustomThreadPoolExecutor threadPoolExecutor, MessageTypeEnum typeEnum) {
AlarmControlDTO alarmControl = AlarmControlDTO.builder()
.threadPool(threadPoolExecutor.getThreadPoolId())
.typeEnum(typeEnum)
.build();
return ALARM_CONTROL_HANDLER.isSend(alarmControl);
}
}

@ -1,32 +0,0 @@
package com.github.dynamic.threadpool.starter.common;
import com.github.dynamic.threadpool.starter.toolkit.thread.CustomThreadPoolExecutor;
import com.github.dynamic.threadpool.starter.toolkit.thread.QueueTypeEnum;
import com.github.dynamic.threadpool.starter.toolkit.thread.RejectedPolicies;
import com.github.dynamic.threadpool.starter.toolkit.thread.ThreadPoolBuilder;
import java.util.concurrent.TimeUnit;
/**
* Common threadPool.
*
* @author chen.ma
* @date 2021/6/16 22:35
*/
public class CommonThreadPool {
public static CustomThreadPoolExecutor getInstance(String threadPoolId) {
CustomThreadPoolExecutor poolExecutor = (CustomThreadPoolExecutor) ThreadPoolBuilder.builder()
.isCustomPool(true)
.threadPoolId(threadPoolId)
.threadFactory(threadPoolId)
.poolThreadSize(3, 5)
.keepAliveTime(1000L, TimeUnit.SECONDS)
.rejected(RejectedPolicies.runsOldestTaskPolicy())
.alarmConfig(1, 80, 80)
.workQueue(QueueTypeEnum.RESIZABLE_LINKED_BLOCKING_QUEUE, 512)
.build();
return poolExecutor;
}
}

@ -1,55 +0,0 @@
package com.github.dynamic.threadpool.starter.config;
import com.github.dynamic.threadpool.starter.alarm.NotifyConfig;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.List;
/**
* Bootstrap properties.
*
* @author chen.ma
* @date 2021/6/22 09:14
*/
@Slf4j
@Getter
@Setter
@ConfigurationProperties(prefix = BootstrapProperties.PREFIX)
public class BootstrapProperties {
public static final String PREFIX = "spring.dynamic.thread-pool";
/**
* serverAddr
*/
private String serverAddr;
/**
* namespace
*/
private String namespace;
/**
* itemId
*/
private String itemId;
/**
* Enable banner
*/
private boolean banner = true;
/**
* Alarm interval
*/
private Long alarmInterval;
/**
* notifys
*/
private List<NotifyConfig> notifys;
}

@ -1,49 +0,0 @@
package com.github.dynamic.threadpool.starter.config;
import com.github.dynamic.threadpool.common.model.InstanceInfo;
import com.github.dynamic.threadpool.starter.core.DiscoveryClient;
import com.github.dynamic.threadpool.starter.remote.HttpAgent;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.ConfigurableEnvironment;
import java.net.InetAddress;
import static com.github.dynamic.threadpool.starter.toolkit.CloudCommonIdUtil.getDefaultInstanceId;
import static com.github.dynamic.threadpool.starter.toolkit.CloudCommonIdUtil.getIpApplicationName;
/**
* Dynamic threadPool discovery config.
*
* @author chen.ma
* @date 2021/8/6 21:35
*/
@AllArgsConstructor
public class DiscoveryConfig {
private ConfigurableEnvironment environment;
@Bean
@SneakyThrows
public InstanceInfo instanceConfig() {
InstanceInfo instanceInfo = new InstanceInfo();
instanceInfo.setInstanceId(getDefaultInstanceId(environment))
.setIpApplicationName(getIpApplicationName(environment))
.setHostName(InetAddress.getLocalHost().getHostAddress())
.setAppName(environment.getProperty("spring.application.name"))
.setClientBasePath(environment.getProperty("server.servlet.context-path"));
String callBackUrl = new StringBuilder().append(instanceInfo.getHostName()).append(":")
.append(environment.getProperty("server.port")).append(instanceInfo.getClientBasePath())
.toString();
instanceInfo.setCallBackUrl(callBackUrl);
return instanceInfo;
}
@Bean
public DiscoveryClient discoveryClient(HttpAgent httpAgent, InstanceInfo instanceInfo) {
return new DiscoveryClient(httpAgent, instanceInfo);
}
}

@ -1,69 +0,0 @@
package com.github.dynamic.threadpool.starter.config;
import com.github.dynamic.threadpool.common.config.ApplicationContextHolder;
import com.github.dynamic.threadpool.starter.controller.PoolRunStateController;
import com.github.dynamic.threadpool.starter.core.*;
import com.github.dynamic.threadpool.starter.enable.MarkerConfiguration;
import com.github.dynamic.threadpool.starter.handler.DynamicThreadPoolBannerHandler;
import com.github.dynamic.threadpool.starter.remote.HttpAgent;
import lombok.AllArgsConstructor;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
/**
* DynamicTp auto configuration.
*
* @author chen.ma
* @date 2021/6/22 09:20
*/
@Configuration
@AllArgsConstructor
@ConditionalOnBean(MarkerConfiguration.Marker.class)
@EnableConfigurationProperties(BootstrapProperties.class)
@ImportAutoConfiguration({HttpClientConfig.class, DiscoveryConfig.class, MessageAlarmConfig.class})
public class DynamicThreadPoolAutoConfiguration {
private final BootstrapProperties properties;
@Bean
public DynamicThreadPoolBannerHandler threadPoolBannerHandler() {
return new DynamicThreadPoolBannerHandler(properties);
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public ApplicationContextHolder applicationContextHolder() {
return new ApplicationContextHolder();
}
@Bean
@SuppressWarnings("all")
public ConfigService configService(HttpAgent httpAgent) {
return new ThreadPoolConfigService(httpAgent);
}
@Bean
public ThreadPoolOperation threadPoolOperation(ConfigService configService) {
return new ThreadPoolOperation(properties, configService);
}
@Bean
@SuppressWarnings("all")
public DynamicThreadPoolPostProcessor threadPoolBeanPostProcessor(HttpAgent httpAgent, ThreadPoolOperation threadPoolOperation,
ApplicationContextHolder applicationContextHolder) {
return new DynamicThreadPoolPostProcessor(properties, httpAgent, threadPoolOperation);
}
@Bean
public PoolRunStateController poolRunStateController() {
return new PoolRunStateController();
}
}

@ -1,49 +0,0 @@
package com.github.dynamic.threadpool.starter.config;
import com.github.dynamic.threadpool.common.model.InstanceInfo;
import com.github.dynamic.threadpool.starter.alarm.*;
import lombok.AllArgsConstructor;
import org.apache.logging.log4j.util.Strings;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.DependsOn;
import org.springframework.core.env.ConfigurableEnvironment;
import java.util.Optional;
/**
* Message alarm config.
*
* @author chen.ma
* @date 2021/8/15 15:39
*/
@AllArgsConstructor
public class MessageAlarmConfig {
private final BootstrapProperties properties;
private final InstanceInfo instanceInfo;
private ConfigurableEnvironment environment;
public static final String SEND_MESSAGE_BEAN_NAME = "sendMessageService";
@DependsOn("applicationContextHolder")
@Bean(MessageAlarmConfig.SEND_MESSAGE_BEAN_NAME)
public SendMessageService sendMessageService() {
return new BaseSendMessageService(properties.getNotifys());
}
@Bean
public SendMessageHandler dingSendMessageHandler() {
String active = environment.getProperty("spring.profiles.active", Strings.EMPTY);
Long alarmInterval = Optional.ofNullable(properties.getAlarmInterval()).orElse(5L);
return new DingSendMessageHandler(active, alarmInterval, instanceInfo);
}
@Bean
public AlarmControlHandler alarmControlHandler() {
Long alarmInterval = properties.getAlarmInterval();
return new AlarmControlHandler(alarmInterval);
}
}

@ -1,26 +0,0 @@
package com.github.dynamic.threadpool.starter.controller;
import com.github.dynamic.threadpool.starter.handler.ThreadPoolRunStateHandler;
import com.github.dynamic.threadpool.common.model.PoolRunStateInfo;
import com.github.dynamic.threadpool.common.web.base.Result;
import com.github.dynamic.threadpool.common.web.base.Results;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
/**
* Pool run state controller.
*
* @author chen.ma
* @date 2021/7/7 21:34
*/
@RestController
public class PoolRunStateController {
@GetMapping("/run/state/{tpId}")
public Result<PoolRunStateInfo> getPoolRunState(@PathVariable("tpId") String tpId) {
PoolRunStateInfo poolRunState = ThreadPoolRunStateHandler.getPoolRunState(tpId);
return Results.success(poolRunState);
}
}

@ -1,20 +0,0 @@
package com.github.dynamic.threadpool.starter.core;
/**
* Config adapter.
*
* @author chen.ma
* @date 2021/6/22 21:29
*/
public class ConfigAdapter {
/**
* Callback Config.
*
* @param config
*/
public void callbackConfig(String config) {
ThreadPoolDynamicRefresh.refreshDynamicPool(config);
}
}

@ -1,146 +0,0 @@
package com.github.dynamic.threadpool.starter.core;
import com.alibaba.fastjson.JSON;
import com.github.dynamic.threadpool.common.config.ApplicationContextHolder;
import com.github.dynamic.threadpool.common.constant.Constants;
import com.github.dynamic.threadpool.common.model.PoolParameterInfo;
import com.github.dynamic.threadpool.common.web.base.Result;
import com.github.dynamic.threadpool.starter.common.CommonThreadPool;
import com.github.dynamic.threadpool.starter.config.BootstrapProperties;
import com.github.dynamic.threadpool.starter.remote.HttpAgent;
import com.github.dynamic.threadpool.starter.toolkit.thread.CustomThreadPoolExecutor;
import com.github.dynamic.threadpool.starter.toolkit.thread.QueueTypeEnum;
import com.github.dynamic.threadpool.starter.toolkit.thread.RejectedTypeEnum;
import com.github.dynamic.threadpool.starter.toolkit.thread.ThreadPoolBuilder;
import com.github.dynamic.threadpool.starter.wrap.DynamicThreadPoolWrap;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.var;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import static com.github.dynamic.threadpool.common.constant.Constants.*;
/**
* Dynamic threadPool post processor.
*
* @author chen.ma
* @date 2021/8/2 20:40
*/
@Slf4j
@AllArgsConstructor
public final class DynamicThreadPoolPostProcessor implements BeanPostProcessor {
private final BootstrapProperties properties;
private final HttpAgent httpAgent;
private final ThreadPoolOperation threadPoolOperation;
private final ExecutorService executorService = ThreadPoolBuilder.builder()
.poolThreadSize(2, 4)
.keepAliveTime(0L, TimeUnit.MILLISECONDS)
.workQueue(QueueTypeEnum.ARRAY_BLOCKING_QUEUE, 1)
.threadFactory("dynamic-threadPool-config")
.rejected(new ThreadPoolExecutor.DiscardOldestPolicy())
.build();
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof CustomThreadPoolExecutor) {
var dynamicThreadPool = ApplicationContextHolder.findAnnotationOnBean(beanName, DynamicThreadPool.class);
if (Objects.isNull(dynamicThreadPool)) {
return bean;
}
var customExecutor = (CustomThreadPoolExecutor) bean;
var wrap = new DynamicThreadPoolWrap(customExecutor.getThreadPoolId(), customExecutor);
CustomThreadPoolExecutor remoteExecutor = fillPoolAndRegister(wrap);
subscribeConfig(wrap);
return remoteExecutor;
} else if (bean instanceof DynamicThreadPoolWrap) {
var wrap = (DynamicThreadPoolWrap) bean;
registerAndSubscribe(wrap);
}
return bean;
}
/**
* Register and subscribe.
*
* @param dynamicThreadPoolWrap
*/
protected void registerAndSubscribe(DynamicThreadPoolWrap dynamicThreadPoolWrap) {
fillPoolAndRegister(dynamicThreadPoolWrap);
subscribeConfig(dynamicThreadPoolWrap);
}
/**
* Fill the thread pool and register.
*
* @param dynamicThreadPoolWrap
*/
protected CustomThreadPoolExecutor fillPoolAndRegister(DynamicThreadPoolWrap dynamicThreadPoolWrap) {
String tpId = dynamicThreadPoolWrap.getTpId();
Map<String, String> queryStrMap = new HashMap(3);
queryStrMap.put(TP_ID, tpId);
queryStrMap.put(ITEM_ID, properties.getItemId());
queryStrMap.put(NAMESPACE, properties.getNamespace());
Result result;
boolean isSubscribe = false;
CustomThreadPoolExecutor poolExecutor = null;
PoolParameterInfo ppi = new PoolParameterInfo();
try {
result = httpAgent.httpGetByConfig(Constants.CONFIG_CONTROLLER_PATH, null, queryStrMap, 3000L);
if (result.isSuccess() && result.getData() != null && (ppi = JSON.toJavaObject((JSON) result.getData(), PoolParameterInfo.class)) != null) {
// 使用相关参数创建线程池
BlockingQueue workQueue = QueueTypeEnum.createBlockingQueue(ppi.getQueueType(), ppi.getCapacity());
poolExecutor = (CustomThreadPoolExecutor) ThreadPoolBuilder.builder()
.isCustomPool(true)
.workQueue(workQueue)
.threadPoolId(tpId)
.threadFactory(tpId)
.poolThreadSize(ppi.getCoreSize(), ppi.getMaxSize())
.keepAliveTime(ppi.getKeepAliveTime(), TimeUnit.SECONDS)
.rejected(RejectedTypeEnum.createPolicy(ppi.getRejectedType()))
.alarmConfig(ppi.getIsAlarm(), ppi.getCapacityAlarm(), ppi.getLivenessAlarm())
.build();
dynamicThreadPoolWrap.setPool(poolExecutor);
isSubscribe = true;
}
} catch (Exception ex) {
poolExecutor = dynamicThreadPoolWrap.getPool() != null ? dynamicThreadPoolWrap.getPool() : CommonThreadPool.getInstance(tpId);
dynamicThreadPoolWrap.setPool(poolExecutor);
log.error("[Init pool] Failed to initialize thread pool configuration. error message :: {}", ex.getMessage());
} finally {
if (Objects.isNull(dynamicThreadPoolWrap.getPool())) {
dynamicThreadPoolWrap.setPool(CommonThreadPool.getInstance(tpId));
}
// 设置是否订阅远端线程池配置
dynamicThreadPoolWrap.setSubscribeFlag(isSubscribe);
}
GlobalThreadPoolManage.register(dynamicThreadPoolWrap.getTpId(), ppi, dynamicThreadPoolWrap);
return poolExecutor;
}
protected void subscribeConfig(DynamicThreadPoolWrap dynamicThreadPoolWrap) {
if (dynamicThreadPoolWrap.isSubscribeFlag()) {
threadPoolOperation.subscribeConfig(dynamicThreadPoolWrap.getTpId(), executorService, config -> ThreadPoolDynamicRefresh.refreshDynamicPool(config));
}
}
}

@ -1,42 +0,0 @@
package com.github.dynamic.threadpool.starter.core;
import com.github.dynamic.threadpool.starter.wrap.DynamicThreadPoolWrap;
import com.github.dynamic.threadpool.common.model.PoolParameter;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Global threadPool manage.
*
* @author chen.ma
* @date 2021/6/20 15:57
*/
public class GlobalThreadPoolManage {
private static final Map<String, PoolParameter> POOL_PARAMETER = new ConcurrentHashMap();
private static final Map<String, DynamicThreadPoolWrap> EXECUTOR_MAP = new ConcurrentHashMap();
public static DynamicThreadPoolWrap getExecutorService(String tpId) {
return EXECUTOR_MAP.get(tpId);
}
public static PoolParameter getPoolParameter(String tpId) {
return POOL_PARAMETER.get(tpId);
}
public static void register(String tpId, PoolParameter poolParameter, DynamicThreadPoolWrap executor) {
registerPool(tpId, executor);
registerPoolParameter(tpId, poolParameter);
}
public static void registerPool(String tpId, DynamicThreadPoolWrap executor) {
EXECUTOR_MAP.put(tpId, executor);
}
public static void registerPoolParameter(String tpId, PoolParameter poolParameter) {
POOL_PARAMETER.put(tpId, poolParameter);
}
}

@ -1,38 +0,0 @@
package com.github.dynamic.threadpool.starter.core;
import com.github.dynamic.threadpool.starter.remote.HttpAgent;
import java.util.Arrays;
/**
* ThreadPool config service.
*
* @author chen.ma
* @date 2021/6/21 21:50
*/
public class ThreadPoolConfigService implements ConfigService {
private final HttpAgent httpAgent;
private final ClientWorker clientWorker;
public ThreadPoolConfigService(HttpAgent httpAgent) {
this.httpAgent = httpAgent;
clientWorker = new ClientWorker(httpAgent);
}
@Override
public void addListener(String tenantId, String itemId, String tpId, Listener listener) {
clientWorker.addTenantListeners(tenantId, itemId, tpId, Arrays.asList(listener));
}
@Override
public String getServerStatus() {
if (clientWorker.isHealthServer()) {
return "UP";
} else {
return "DOWN";
}
}
}

@ -1,83 +0,0 @@
package com.github.dynamic.threadpool.starter.core;
import com.alibaba.fastjson.JSON;
import com.github.dynamic.threadpool.common.model.PoolParameterInfo;
import com.github.dynamic.threadpool.starter.alarm.ThreadPoolAlarmManage;
import com.github.dynamic.threadpool.starter.toolkit.thread.QueueTypeEnum;
import com.github.dynamic.threadpool.starter.toolkit.thread.RejectedTypeEnum;
import com.github.dynamic.threadpool.starter.toolkit.thread.ResizableCapacityLinkedBlockIngQueue;
import lombok.extern.slf4j.Slf4j;
import java.util.Objects;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* ThreadPool dynamic refresh.
*
* @author chen.ma
* @date 2021/6/20 15:51
*/
@Slf4j
public class ThreadPoolDynamicRefresh {
public static void refreshDynamicPool(String content) {
PoolParameterInfo parameter = JSON.parseObject(content, PoolParameterInfo.class);
ThreadPoolAlarmManage.sendPoolConfigChange(parameter);
ThreadPoolDynamicRefresh.refreshDynamicPool(parameter);
}
public static void refreshDynamicPool(PoolParameterInfo parameter) {
String threadPoolId = parameter.getTpId();
ThreadPoolExecutor executor = GlobalThreadPoolManage.getExecutorService(threadPoolId).getPool();
int originalCoreSize = executor.getCorePoolSize();
int originalMaximumPoolSize = executor.getMaximumPoolSize();
int originalQueryType = parameter.getQueueType();
int originalCapacity = executor.getQueue().remainingCapacity() + executor.getQueue().size();
long originalKeepAliveTime = executor.getKeepAliveTime(TimeUnit.MILLISECONDS);
int originalRejectedType = parameter.getRejectedType();
changePoolInfo(executor, parameter);
ThreadPoolExecutor afterExecutor = GlobalThreadPoolManage.getExecutorService(threadPoolId).getPool();
log.info("[🔥 {}] Changed thread pool. coreSize :: [{}], maxSize :: [{}], queueType :: [{}], capacity :: [{}], keepAliveTime :: [{}], rejectedType :: [{}]",
threadPoolId.toUpperCase(),
String.format("%s=>%s", originalCoreSize, afterExecutor.getCorePoolSize()),
String.format("%s=>%s", originalMaximumPoolSize, afterExecutor.getMaximumPoolSize()),
String.format("%s=>%s", originalQueryType, parameter.getQueueType()),
String.format("%s=>%s", originalCapacity,
(afterExecutor.getQueue().remainingCapacity() + afterExecutor.getQueue().size())),
String.format("%s=>%s", originalKeepAliveTime, afterExecutor.getKeepAliveTime(TimeUnit.MILLISECONDS)),
String.format("%s=>%s", originalRejectedType, parameter.getRejectedType()));
}
public static void changePoolInfo(ThreadPoolExecutor executor, PoolParameterInfo parameter) {
if (parameter.getCoreSize() != null) {
executor.setCorePoolSize(parameter.getCoreSize());
}
if (parameter.getMaxSize() != null) {
executor.setMaximumPoolSize(parameter.getMaxSize());
}
if (parameter.getCapacity() != null
&& Objects.equals(QueueTypeEnum.RESIZABLE_LINKED_BLOCKING_QUEUE.type, parameter.getQueueType())) {
if (executor.getQueue() instanceof ResizableCapacityLinkedBlockIngQueue) {
ResizableCapacityLinkedBlockIngQueue queue = (ResizableCapacityLinkedBlockIngQueue) executor.getQueue();
queue.setCapacity(parameter.getCapacity());
} else {
log.warn("[Pool change] The queue length cannot be modified. Queue type mismatch. Current queue type :: {}", executor.getQueue().getClass().getSimpleName());
}
}
if (parameter.getKeepAliveTime() != null) {
executor.setKeepAliveTime(parameter.getKeepAliveTime(), TimeUnit.SECONDS);
}
if (parameter.getRejectedType() != null) {
executor.setRejectedExecutionHandler(RejectedTypeEnum.createPolicy(parameter.getRejectedType()));
}
}
}

@ -1,89 +0,0 @@
package com.github.dynamic.threadpool.starter.handler;
import com.github.dynamic.threadpool.common.model.PoolRunStateInfo;
import com.github.dynamic.threadpool.starter.core.GlobalThreadPoolManage;
import com.github.dynamic.threadpool.starter.toolkit.CalculateUtil;
import com.github.dynamic.threadpool.starter.toolkit.thread.CustomThreadPoolExecutor;
import com.github.dynamic.threadpool.starter.wrap.DynamicThreadPoolWrap;
import lombok.extern.slf4j.Slf4j;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
/**
* Thread pool run state service.
*
* @author chen.ma
* @date 2021/7/12 21:25
*/
@Slf4j
public class ThreadPoolRunStateHandler {
private static InetAddress INET_ADDRESS;
static {
try {
INET_ADDRESS = InetAddress.getLocalHost();
} catch (UnknownHostException ex) {
log.error("Local IP acquisition failed.", ex);
}
}
public static PoolRunStateInfo getPoolRunState(String tpId) {
DynamicThreadPoolWrap executorService = GlobalThreadPoolManage.getExecutorService(tpId);
ThreadPoolExecutor pool = executorService.getPool();
// 核心线程数
int corePoolSize = pool.getCorePoolSize();
// 最大线程数
int maximumPoolSize = pool.getMaximumPoolSize();
// 线程池当前线程数
int poolSize = pool.getPoolSize();
// 活跃线程数
int activeCount = pool.getActiveCount();
// 同时进入池中的最大线程数
int largestPoolSize = pool.getLargestPoolSize();
// 线程池中执行任务总数量
long completedTaskCount = pool.getCompletedTaskCount();
// 当前负载
String currentLoad = CalculateUtil.divide(activeCount, maximumPoolSize) + "%";
// 峰值负载
String peakLoad = CalculateUtil.divide(largestPoolSize, maximumPoolSize) + "%";
BlockingQueue<Runnable> queue = pool.getQueue();
// 队列类型
String queueType = queue.getClass().getSimpleName();
// 队列元素个数
int queueSize = queue.size();
// 队列剩余容量
int remainingCapacity = queue.remainingCapacity();
// 队列容量
int queueCapacity = queueSize + remainingCapacity;
PoolRunStateInfo stateInfo = new PoolRunStateInfo();
stateInfo.setCoreSize(corePoolSize);
stateInfo.setMaximumSize(maximumPoolSize);
stateInfo.setPoolSize(poolSize);
stateInfo.setActiveSize(activeCount);
stateInfo.setCurrentLoad(currentLoad);
stateInfo.setPeakLoad(peakLoad);
stateInfo.setQueueType(queueType);
stateInfo.setQueueSize(queueSize);
stateInfo.setQueueRemainingCapacity(remainingCapacity);
stateInfo.setQueueCapacity(queueCapacity);
stateInfo.setLargestPoolSize(largestPoolSize);
stateInfo.setCompletedTaskCount(completedTaskCount);
stateInfo.setHost(INET_ADDRESS.getHostAddress());
stateInfo.setTpId(tpId);
int rejectCount = pool instanceof CustomThreadPoolExecutor
? ((CustomThreadPoolExecutor) pool).getRejectCount()
: -1;
stateInfo.setRejectCount(rejectCount);
return stateInfo;
}
}

@ -1,68 +0,0 @@
package com.github.dynamic.threadpool.starter.remote;
import com.github.dynamic.threadpool.common.web.base.Result;
import com.github.dynamic.threadpool.starter.config.BootstrapProperties;
import com.github.dynamic.threadpool.starter.toolkit.HttpClientUtil;
import java.util.Map;
/**
* Server http agent.
*
* @author chen.ma
* @date 2021/6/23 20:50
*/
public class ServerHttpAgent implements HttpAgent {
private final BootstrapProperties dynamicThreadPoolProperties;
private final ServerListManager serverListManager;
private final HttpClientUtil httpClientUtil;
public ServerHttpAgent(BootstrapProperties properties, HttpClientUtil httpClientUtil) {
this.dynamicThreadPoolProperties = properties;
this.httpClientUtil = httpClientUtil;
this.serverListManager = new ServerListManager(dynamicThreadPoolProperties);
}
@Override
public void start() {
}
@Override
public String getTenantId() {
return dynamicThreadPoolProperties.getNamespace();
}
@Override
public String getEncode() {
return null;
}
@Override
public Result httpPostByDiscovery(String path, Object body) {
return httpClientUtil.restApiPost(buildUrl(path), body, Result.class);
}
@Override
public Result httpGetByConfig(String path, Map<String, String> headers, Map<String, String> paramValues, long readTimeoutMs) {
return httpClientUtil.restApiGetByThreadPool(buildUrl(path), headers, paramValues, readTimeoutMs, Result.class);
}
@Override
public Result httpPostByConfig(String path, Map<String, String> headers, Map<String, String> paramValues, long readTimeoutMs) {
return httpClientUtil.restApiPostByThreadPool(buildUrl(path), headers, paramValues, readTimeoutMs, Result.class);
}
@Override
public Result httpDeleteByConfig(String path, Map<String, String> headers, Map<String, String> paramValues, long readTimeoutMs) {
return null;
}
private String buildUrl(String path) {
return serverListManager.getCurrentServerAddr() + path;
}
}

@ -1,174 +0,0 @@
package com.github.dynamic.threadpool.starter.toolkit.thread;
import com.github.dynamic.threadpool.common.toolkit.Assert;
import com.github.dynamic.threadpool.starter.alarm.ThreadPoolAlarm;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.*;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
/**
* Abstract build threadPool template.
*
* @author chen.ma
* @date 2021/7/5 21:45
*/
@Slf4j
public class AbstractBuildThreadPoolTemplate {
/**
* 线
* <p>
* , , abstract
* {@link AbstractQueuedSynchronizer#tryAcquire}
*
* @return
*/
protected static ThreadPoolInitParam initParam() {
throw new UnsupportedOperationException();
}
/**
* 线
*
* @return
*/
public static ThreadPoolExecutor buildPool() {
ThreadPoolInitParam initParam = initParam();
return buildPool(initParam);
}
/**
* 线
*
* @return
*/
public static ThreadPoolExecutor buildPool(ThreadPoolInitParam initParam) {
Assert.notNull(initParam);
ThreadPoolExecutor executorService =
new ThreadPoolExecutorTemplate(initParam.getCorePoolNum(),
initParam.getMaxPoolNum(),
initParam.getKeepAliveTime(),
initParam.getTimeUnit(),
initParam.getWorkQueue(),
initParam.getThreadFactory(),
initParam.rejectedExecutionHandler);
return executorService;
}
/**
* 线
*
* @return
*/
public static ThreadPoolExecutor buildFastPool() {
ThreadPoolInitParam initParam = initParam();
return buildFastPool(initParam);
}
/**
* 线
*
* @return
*/
public static ThreadPoolExecutor buildFastPool(ThreadPoolInitParam initParam) {
TaskQueue<Runnable> taskQueue = new TaskQueue(initParam.getCapacity());
FastThreadPoolExecutor fastThreadPoolExecutor =
new FastThreadPoolExecutor(initParam.getCorePoolNum(),
initParam.getMaxPoolNum(),
initParam.getKeepAliveTime(),
initParam.getTimeUnit(),
taskQueue,
initParam.getThreadFactory(),
initParam.rejectedExecutionHandler);
taskQueue.setExecutor(fastThreadPoolExecutor);
return fastThreadPoolExecutor;
}
/**
* 线
*
* @param initParam
* @return
*/
public static CustomThreadPoolExecutor buildCustomPool(ThreadPoolInitParam initParam) {
Assert.notNull(initParam);
CustomThreadPoolExecutor executorService =
new CustomThreadPoolExecutor(initParam.getCorePoolNum(),
initParam.getMaxPoolNum(),
initParam.getKeepAliveTime(),
initParam.getTimeUnit(),
initParam.getWorkQueue(),
initParam.getThreadPoolId(),
initParam.getThreadFactory(),
initParam.getThreadPoolAlarm(),
initParam.getRejectedExecutionHandler());
return executorService;
}
@Data
@Accessors(chain = true)
public static class ThreadPoolInitParam {
/**
* 线
*/
private Integer corePoolNum;
/**
* 线
*/
private Integer maxPoolNum;
/**
* 线
*/
private Long keepAliveTime;
/**
* 线
*/
private TimeUnit timeUnit;
/**
*
*/
private Integer capacity;
/**
*
*/
private BlockingQueue<Runnable> workQueue;
/**
* 线
*/
private RejectedExecutionHandler rejectedExecutionHandler;
/**
* 线
*/
private ThreadFactory threadFactory;
/**
* 线 ID
*/
private String threadPoolId;
/**
*
*/
private ThreadPoolAlarm threadPoolAlarm;
public ThreadPoolInitParam(String threadNamePrefix, boolean isDaemon) {
this.threadPoolId = threadNamePrefix;
this.threadFactory = ThreadFactoryBuilder.builder()
.prefix(threadNamePrefix)
.daemon(isDaemon)
.build();
}
}
}

@ -1,954 +0,0 @@
package com.github.dynamic.threadpool.starter.toolkit.thread;
import com.github.dynamic.threadpool.starter.alarm.ThreadPoolAlarm;
import com.github.dynamic.threadpool.starter.alarm.ThreadPoolAlarmManage;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import static com.github.dynamic.threadpool.common.constant.Constants.MAP_INITIAL_CAPACITY;
/**
* Custom threadPool wrap.
*
* @author chen.ma
* @date 2021/7/8 21:47
*/
public final class CustomThreadPoolExecutor extends ThreadPoolExecutor {
private final AtomicInteger rejectCount = new AtomicInteger();
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
private final BlockingQueue<Runnable> workQueue;
private final ReentrantLock mainLock = new ReentrantLock();
private final HashSet<Worker> workers = new HashSet();
private final Condition termination = mainLock.newCondition();
private int largestPoolSize;
private long completedTaskCount;
private volatile long keepAliveTime;
private volatile boolean allowCoreThreadTimeOut;
private volatile int corePoolSize;
private volatile int maximumPoolSize;
private String threadPoolId;
private final AccessControlContext acc;
private volatile ThreadPoolAlarm threadPoolAlarm;
private volatile ThreadFactory threadFactory;
private volatile RejectedExecutionHandler handler;
private static final RejectedExecutionHandler DEFAULT_HANDLER = new ThreadPoolExecutor.AbortPolicy();
private static final RuntimePermission SHUTDOWN_PERM = new RuntimePermission("modifyThread");
public CustomThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
@NonNull BlockingQueue<Runnable> workQueue,
@NonNull String threadPoolId,
@NonNull ThreadFactory threadFactory,
@NonNull ThreadPoolAlarm threadPoolAlarm,
@NonNull RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0) {
throw new IllegalArgumentException();
}
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.threadPoolId = threadPoolId;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
this.threadPoolAlarm = threadPoolAlarm;
this.acc = System.getSecurityManager() == null ? null : AccessController.getContext();
}
private static int runStateOf(int c) {
return c & ~CAPACITY;
}
private static int workerCountOf(int c) {
return c & CAPACITY;
}
private static int ctlOf(int rs, int wc) {
return rs | wc;
}
private static boolean runStateLessThan(int c, int s) {
return c < s;
}
private static boolean runStateAtLeast(int c, int s) {
return c >= s;
}
private static boolean isRunning(int c) {
return c < SHUTDOWN;
}
private boolean compareAndIncrementWorkerCount(int expect) {
return ctl.compareAndSet(expect, expect + 1);
}
private boolean compareAndDecrementWorkerCount(int expect) {
return ctl.compareAndSet(expect, expect - 1);
}
private void decrementWorkerCount() {
do {
} while (!compareAndDecrementWorkerCount(ctl.get()));
}
public Integer getRejectCount() {
return rejectCount.get();
}
public ThreadPoolAlarm getThreadPoolAlarm() {
return this.threadPoolAlarm;
}
public void setThreadPoolAlarm(ThreadPoolAlarm threadPoolAlarm) {
this.threadPoolAlarm = threadPoolAlarm;
}
public String getThreadPoolId() {
return this.threadPoolId;
}
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable {
private static final long serialVersionUID = 6138294804551838833L;
final Thread thread;
Runnable firstTask;
volatile long completedTasks;
Worker(Runnable firstTask) {
setState(-1);
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
@Override
public void run() {
runWorker(this);
}
@Override
protected boolean isHeldExclusively() {
return getState() != 0;
}
@Override
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
@Override
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
public void lock() {
acquire(1);
}
public boolean tryLock() {
return tryAcquire(1);
}
public void unlock() {
release(1);
}
public boolean isLocked() {
return isHeldExclusively();
}
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
private void advanceRunState(int targetState) {
for (; ; ) {
int c = ctl.get();
if (runStateAtLeast(c, targetState) ||
ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c)))) {
break;
}
}
}
final void tryTerminate() {
for (; ; ) {
int c = ctl.get();
if (isRunning(c)
|| runStateAtLeast(c, TIDYING)
|| (runStateOf(c) == SHUTDOWN && !workQueue.isEmpty())) {
return;
}
if (workerCountOf(c) != 0) {
interruptIdleWorkers(ONLY_ONE);
return;
}
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
terminated();
} finally {
ctl.set(ctlOf(TERMINATED, 0));
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
}
}
private void checkShutdownAccess() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkPermission(SHUTDOWN_PERM);
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
security.checkAccess(w.thread);
}
} finally {
mainLock.unlock();
}
}
}
private void interruptWorkers() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
w.interruptIfStarted();
}
} finally {
mainLock.unlock();
}
}
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne) {
break;
}
}
} finally {
mainLock.unlock();
}
}
private void interruptIdleWorkers() {
interruptIdleWorkers(false);
}
private static final boolean ONLY_ONE = true;
final void reject(Runnable command) {
rejectCount.incrementAndGet();
ThreadPoolAlarmManage.checkPoolRejectAlarm(this);
handler.rejectedExecution(command, this);
}
void onShutdown() {
}
final boolean isRunningOrShutdown(boolean shutdownOk) {
int rs = runStateOf(ctl.get());
return rs == RUNNING || (rs == SHUTDOWN && shutdownOk);
}
private List<Runnable> drainQueue() {
BlockingQueue<Runnable> q = workQueue;
ArrayList<Runnable> taskList = new ArrayList<Runnable>();
q.drainTo(taskList);
if (!q.isEmpty()) {
for (Runnable r : q.toArray(new Runnable[0])) {
if (q.remove(r)) {
taskList.add(r);
}
}
}
return taskList;
}
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (; ; ) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && !(rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty())) {
return false;
}
for (; ; ) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize)) {
return false;
}
if (compareAndIncrementWorkerCount(c)) {
break retry;
}
c = ctl.get();
if (runStateOf(c) != rs) {
continue retry;
}
}
}
ThreadPoolAlarmManage.checkPoolLivenessAlarm(core, this);
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) {
throw new IllegalThreadStateException();
}
workers.add(w);
int s = workers.size();
if (s > largestPoolSize) {
largestPoolSize = s;
}
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (!workerStarted) {
addWorkerFailed(w);
}
}
return workerStarted;
}
private void addWorkerFailed(Worker w) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (w != null) {
workers.remove(w);
}
decrementWorkerCount();
tryTerminate();
} finally {
mainLock.unlock();
}
}
private void processWorkerExit(Worker w, boolean completedAbruptly) {
if (completedAbruptly) {
decrementWorkerCount();
}
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
completedTaskCount += w.completedTasks;
workers.remove(w);
} finally {
mainLock.unlock();
}
tryTerminate();
int c = ctl.get();
if (runStateLessThan(c, STOP)) {
if (!completedAbruptly) {
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && !workQueue.isEmpty()) {
min = 1;
}
if (workerCountOf(c) >= min) {
return;
}
}
addWorker(null, false);
}
}
private Runnable getTask() {
boolean timedOut = false;
for (; ; ) {
int c = ctl.get();
int rs = runStateOf(c);
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c)) {
return null;
}
continue;
}
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null) {
return r;
}
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock();
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
if ((runStateAtLeast(ctl.get(), STOP)
|| (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP)))
&& !wt.isInterrupted()) {
wt.interrupt();
}
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run();
} catch (RuntimeException x) {
thrown = x;
throw x;
} catch (Error x) {
thrown = x;
throw x;
} catch (Throwable x) {
thrown = x;
throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
@Override
public void execute(@NonNull Runnable command) {
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true)) {
return;
}
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
ThreadPoolAlarmManage.checkPoolCapacityAlarm(this);
int recheck = ctl.get();
if (!isRunning(recheck) && remove(command)) {
reject(command);
} else if (workerCountOf(recheck) == 0) {
addWorker(null, false);
}
} else if (!addWorker(command, false)) {
reject(command);
}
}
@Override
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(SHUTDOWN);
interruptIdleWorkers();
onShutdown();
} finally {
mainLock.unlock();
}
tryTerminate();
}
@Override
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(STOP);
interruptWorkers();
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
@Override
public boolean isShutdown() {
return !isRunning(ctl.get());
}
@Override
public boolean isTerminating() {
int c = ctl.get();
return !isRunning(c) && runStateLessThan(c, TERMINATED);
}
@Override
public boolean isTerminated() {
return runStateAtLeast(ctl.get(), TERMINATED);
}
@Override
public boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (; ; ) {
if (runStateAtLeast(ctl.get(), TERMINATED)) {
return true;
}
if (nanos <= 0) {
return false;
}
nanos = termination.awaitNanos(nanos);
}
} finally {
mainLock.unlock();
}
}
@Override
protected void finalize() {
SecurityManager sm = System.getSecurityManager();
if (sm == null || acc == null) {
shutdown();
} else {
PrivilegedAction<Void> pa = () -> {
shutdown();
return null;
};
AccessController.doPrivileged(pa, acc);
}
}
@Override
public void setThreadFactory(@NonNull ThreadFactory threadFactory) {
this.threadFactory = threadFactory;
}
@Override
public ThreadFactory getThreadFactory() {
return threadFactory;
}
@Override
public void setRejectedExecutionHandler(@NonNull RejectedExecutionHandler handler) {
this.handler = handler;
}
@Override
public RejectedExecutionHandler getRejectedExecutionHandler() {
return handler;
}
@Override
public void setCorePoolSize(int corePoolSize) {
if (corePoolSize < 0) {
throw new IllegalArgumentException();
}
int delta = corePoolSize - this.corePoolSize;
this.corePoolSize = corePoolSize;
if (workerCountOf(ctl.get()) > corePoolSize) {
interruptIdleWorkers();
} else if (delta > 0) {
int k = Math.min(delta, workQueue.size());
while (k-- > 0 && addWorker(null, true)) {
if (workQueue.isEmpty()) {
break;
}
}
}
}
@Override
public int getCorePoolSize() {
return corePoolSize;
}
@Override
public boolean prestartCoreThread() {
return workerCountOf(ctl.get()) < corePoolSize &&
addWorker(null, true);
}
void ensurePrestart() {
int wc = workerCountOf(ctl.get());
if (wc < corePoolSize) {
addWorker(null, true);
} else if (wc == 0) {
addWorker(null, false);
}
}
@Override
public int prestartAllCoreThreads() {
int n = 0;
while (addWorker(null, true)) {
++n;
}
return n;
}
@Override
public boolean allowsCoreThreadTimeOut() {
return allowCoreThreadTimeOut;
}
@Override
public void allowCoreThreadTimeOut(boolean value) {
if (value && keepAliveTime <= 0) {
throw new IllegalArgumentException("Core threads must have nonzero keep alive times");
}
if (value != allowCoreThreadTimeOut) {
allowCoreThreadTimeOut = value;
if (value) {
interruptIdleWorkers();
}
}
}
@Override
public void setMaximumPoolSize(int maximumPoolSize) {
if (maximumPoolSize <= 0 || maximumPoolSize < corePoolSize) {
throw new IllegalArgumentException();
}
this.maximumPoolSize = maximumPoolSize;
if (workerCountOf(ctl.get()) > maximumPoolSize) {
interruptIdleWorkers();
}
}
@Override
public int getMaximumPoolSize() {
return maximumPoolSize;
}
@Override
public void setKeepAliveTime(long time, TimeUnit unit) {
if (time < 0) {
throw new IllegalArgumentException();
}
if (time == 0 && allowsCoreThreadTimeOut()) {
throw new IllegalArgumentException("Core threads must have nonzero keep alive times");
}
long keepAliveTime = unit.toNanos(time);
long delta = keepAliveTime - this.keepAliveTime;
this.keepAliveTime = keepAliveTime;
if (delta < 0) {
interruptIdleWorkers();
}
}
@Override
public long getKeepAliveTime(TimeUnit unit) {
return unit.convert(keepAliveTime, TimeUnit.NANOSECONDS);
}
@Override
public BlockingQueue<Runnable> getQueue() {
return workQueue;
}
@Override
public boolean remove(Runnable task) {
boolean removed = workQueue.remove(task);
tryTerminate();
return removed;
}
@Override
public void purge() {
final BlockingQueue<Runnable> q = workQueue;
try {
Iterator<Runnable> it = q.iterator();
while (it.hasNext()) {
Runnable r = it.next();
if (r instanceof Future<?> && ((Future<?>) r).isCancelled()) {
it.remove();
}
}
} catch (ConcurrentModificationException fallThrough) {
for (Object r : q.toArray()) {
if (r instanceof Future<?> && ((Future<?>) r).isCancelled()) {
q.remove(r);
}
}
}
tryTerminate();
}
@Override
public int getPoolSize() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
return runStateAtLeast(ctl.get(), TIDYING) ? 0 : workers.size();
} finally {
mainLock.unlock();
}
}
@Override
public int getActiveCount() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
int n = 0;
for (Worker w : workers) {
if (w.isLocked()) {
++n;
}
}
return n;
} finally {
mainLock.unlock();
}
}
@Override
public int getLargestPoolSize() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
return largestPoolSize;
} finally {
mainLock.unlock();
}
}
@Override
public long getTaskCount() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
long n = completedTaskCount;
for (Worker w : workers) {
n += w.completedTasks;
if (w.isLocked()) {
++n;
}
}
return n + workQueue.size();
} finally {
mainLock.unlock();
}
}
@Override
public long getCompletedTaskCount() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
long n = completedTaskCount;
for (Worker w : workers) {
n += w.completedTasks;
}
return n;
} finally {
mainLock.unlock();
}
}
@Override
public String toString() {
long ncompleted;
int nworkers, nactive;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
ncompleted = completedTaskCount;
nactive = 0;
nworkers = workers.size();
for (Worker w : workers) {
ncompleted += w.completedTasks;
if (w.isLocked()) {
++nactive;
}
}
} finally {
mainLock.unlock();
}
int c = ctl.get();
String rs = (runStateLessThan(c, SHUTDOWN) ? "Running" :
(runStateAtLeast(c, TERMINATED) ? "Terminated" :
"Shutting down"));
return super.toString() +
"[" + rs +
", pool size = " + nworkers +
", active threads = " + nactive +
", queued tasks = " + workQueue.size() +
", completed tasks = " + ncompleted +
"]";
}
private ConcurrentHashMap<String, Date> statisticsTime = new ConcurrentHashMap(MAP_INITIAL_CAPACITY);
@Override
protected void beforeExecute(Thread t, Runnable r) {
statisticsTime.put(String.valueOf(r.hashCode()), new Date());
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
Date startDate = statisticsTime.remove(String.valueOf(r.hashCode()));
Date finishDate = new Date();
long diff = finishDate.getTime() - startDate.getTime();
}
@Override
protected void terminated() {
}
@NoArgsConstructor
public static class CallerRunsPolicy implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
@NoArgsConstructor
public static class AbortPolicy implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
@NoArgsConstructor
public static class DiscardPolicy implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
@NoArgsConstructor
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}
}

@ -1,99 +0,0 @@
package com.github.dynamic.threadpool.starter.toolkit.thread;
import com.github.dynamic.threadpool.starter.spi.CustomRejectedExecutionHandler;
import com.github.dynamic.threadpool.starter.spi.DynamicTpServiceLoader;
import java.util.Arrays;
import java.util.Collection;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Stream;
/**
* Reject policy type Enum.
*
* @author chen.ma
* @date 2021/7/10 23:16
*/
public enum RejectedTypeEnum {
/**
* 线
*/
CALLER_RUNS_POLICY(1, new ThreadPoolExecutor.CallerRunsPolicy()),
/**
* ,
*/
ABORT_POLICY(2, new ThreadPoolExecutor.AbortPolicy()),
/**
* ,
*/
DISCARD_POLICY(3, new ThreadPoolExecutor.DiscardPolicy()),
/**
* , ,
*/
DISCARD_OLDEST_POLICY(4, new ThreadPoolExecutor.DiscardOldestPolicy()),
/**
* ,
*/
RUNS_OLDEST_TASK_POLICY(5, RejectedPolicies.runsOldestTaskPolicy()),
/**
* 使,
*/
SYNC_PUT_QUEUE_POLICY(6, RejectedPolicies.syncPutQueuePolicy());
/**
*
*/
public Integer type;
/**
* 线
*/
public RejectedExecutionHandler rejectedHandler;
RejectedTypeEnum(Integer type, RejectedExecutionHandler rejectedHandler) {
this.type = type;
this.rejectedHandler = rejectedHandler;
}
static {
DynamicTpServiceLoader.register(CustomRejectedExecutionHandler.class);
}
public static RejectedExecutionHandler createPolicy(int type) {
Optional<RejectedExecutionHandler> rejectedTypeEnum = Stream.of(RejectedTypeEnum.values())
.filter(each -> Objects.equals(type, each.type))
.map(each -> each.rejectedHandler)
.findFirst();
// 使用 SPI 匹配拒绝策略
RejectedExecutionHandler resultRejected = rejectedTypeEnum.orElseGet(() -> {
Collection<CustomRejectedExecutionHandler> customRejectedExecutionHandlers = DynamicTpServiceLoader
.getSingletonServiceInstances(CustomRejectedExecutionHandler.class);
Optional<RejectedExecutionHandler> customRejected = customRejectedExecutionHandlers.stream()
.filter(each -> Objects.equals(type, each.getType()))
.map(each -> each.generateRejected())
.findFirst();
return customRejected.orElse(ABORT_POLICY.rejectedHandler);
});
return resultRejected;
}
public static String getRejectedNameByType(int type) {
Optional<RejectedTypeEnum> rejectedTypeEnum = Arrays.stream(RejectedTypeEnum.values())
.filter(each -> each.type == type).findFirst();
return rejectedTypeEnum.map(each -> each.rejectedHandler.getClass().getSimpleName()).orElse("");
}
}

@ -1,79 +0,0 @@
package com.github.dynamic.threadpool.starter.wrap;
import com.github.dynamic.threadpool.starter.common.CommonThreadPool;
import com.github.dynamic.threadpool.starter.toolkit.thread.CustomThreadPoolExecutor;
import lombok.Data;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
/**
* Dynamic threadPool wrap.
*
* @author chen.ma
* @date 2021/6/20 16:55
*/
@Data
public class DynamicThreadPoolWrap {
private String tenantId;
private String itemId;
private String tpId;
private boolean subscribeFlag;
private CustomThreadPoolExecutor pool;
/**
* 线, 使线 {@link CommonThreadPool#getInstance(String)}
*
* @param threadPoolId
*/
public DynamicThreadPoolWrap(String threadPoolId) {
this(threadPoolId, CommonThreadPool.getInstance(threadPoolId));
}
/**
* 线, 使 threadPoolExecutor
*
* @param threadPoolId
* @param threadPoolExecutor
*/
public DynamicThreadPoolWrap(String threadPoolId, CustomThreadPoolExecutor threadPoolExecutor) {
this.tpId = threadPoolId;
this.pool = threadPoolExecutor;
}
/**
*
*
* @param command
*/
public void execute(Runnable command) {
pool.execute(command);
}
/**
*
*
* @param task
* @return
*/
public Future<?> submit(Runnable task) {
return pool.submit(task);
}
/**
*
*
* @param task
* @param <T>
* @return
*/
public <T> Future<T> submit(Callable<T> task) {
return pool.submit(task);
}
}

@ -1,26 +0,0 @@
package com.github.dynamic.threadpool.starter.wrap;
import com.github.dynamic.threadpool.starter.core.Listener;
import lombok.Getter;
import lombok.Setter;
/**
* Manager listener wrap.
*
* @author chen.ma
* @date 2021/6/22 17:47
*/
@Getter
@Setter
public class ManagerListenerWrap {
private String lastCallMd5;
final Listener listener;
public ManagerListenerWrap(String md5, Listener listener) {
this.lastCallMd5 = md5;
this.listener = listener;
}
}

@ -1,2 +0,0 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.github.dynamic.threadpool.starter.config.DynamicThreadPoolAutoConfiguration

@ -1,15 +0,0 @@
package com.github.dynamic.threadpool.example;
import com.github.dynamic.threadpool.starter.enable.EnableDynamicThreadPool;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@EnableDynamicThreadPool
public class ExampleApplication {
public static void main(String[] args) {
SpringApplication.run(ExampleApplication.class, args);
}
}

@ -1,48 +0,0 @@
package com.github.dynamic.threadpool.example.config;
import com.github.dynamic.threadpool.starter.core.DynamicThreadPool;
import com.github.dynamic.threadpool.starter.toolkit.thread.CustomThreadPoolExecutor;
import com.github.dynamic.threadpool.starter.toolkit.thread.ThreadPoolBuilder;
import com.github.dynamic.threadpool.starter.wrap.DynamicThreadPoolWrap;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.ThreadPoolExecutor;
import static com.github.dynamic.threadpool.example.constant.GlobalTestConstant.*;
/**
* Thread pool config.
*
* @author chen.ma
* @date 2021/6/20 17:16
*/
@Slf4j
@Configuration
public class ThreadPoolConfig {
/**
* {@link DynamicThreadPoolWrap} Server .
*
* @return
*/
@Bean
public DynamicThreadPoolWrap messageCenterConsumeThreadPool() {
return new DynamicThreadPoolWrap(MESSAGE_CONSUME);
}
/**
* {@link DynamicThreadPool} {@link CustomThreadPoolExecutor} Server .
* <p>
* 线, IOC {@link CustomThreadPoolExecutor}
*
* @return
*/
@Bean
@DynamicThreadPool
public ThreadPoolExecutor customThreadPoolExecutor() {
return ThreadPoolBuilder.builder().threadFactory(MESSAGE_PRODUCE).isCustomPool(true).build();
}
}

@ -1,59 +0,0 @@
package com.github.dynamic.threadpool.example.inittest;
import com.github.dynamic.threadpool.starter.core.GlobalThreadPoolManage;
import com.github.dynamic.threadpool.starter.wrap.DynamicThreadPoolWrap;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import static com.github.dynamic.threadpool.example.constant.GlobalTestConstant.MESSAGE_PRODUCE;
/**
* Test run time metrics.
*
* @author chen.ma
* @date 2021/8/15 21:00
*/
@Slf4j
@Component
public class RunStateHandlerTest {
// @PostConstruct
@SuppressWarnings("all")
public void runStateHandlerTest() {
log.info("Test thread pool runtime state interface, The rejection policy will be triggered after 30s...");
ScheduledExecutorService scheduledThreadPool = Executors.newSingleThreadScheduledExecutor();
scheduledThreadPool.scheduleAtFixedRate(() -> {
DynamicThreadPoolWrap executorService = GlobalThreadPoolManage.getExecutorService(MESSAGE_PRODUCE);
ThreadPoolExecutor pool = executorService.getPool();
try {
pool.execute(() -> {
log.info("Thread pool name :: {}, Executing incoming blocking...", Thread.currentThread().getName());
try {
int maxRandom = 10;
int temp = 2;
Random random = new Random();
// Assignment thread pool completedTaskCount
if (random.nextInt(maxRandom) % temp == 0) {
Thread.sleep(10241024);
} else {
Thread.sleep(3000);
}
} catch (InterruptedException e) {
// ignore
}
});
} catch (Exception ex) {
// ignore
}
}, 5, 2, TimeUnit.SECONDS);
}
}

@ -1,21 +0,0 @@
server:
port: 8088
servlet:
context-path: /example
spring:
profiles:
active: dev
application:
name: dynamic-threadpool-example
dynamic:
thread-pool:
notifys:
- type: DING
url: https://oapi.dingtalk.com/robot/send?access_token=
token: 4a582a588a161d6e3a1bd1de7eea9ee9f562cdfcbe56b6e72029e7fd512b2eae
receives: '15601166691'
alarm-interval: 5
server-addr: http://localhost:6691
namespace: prescription
item-id: ${spring.application.name}

@ -4,14 +4,18 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.github.dynamic-threadpool</groupId>
<artifactId>parent</artifactId>
<groupId>cn.hippo4j</groupId>
<artifactId>hippo4j-all</artifactId>
<version>${revision}</version>
</parent>
<artifactId>auth</artifactId>
<artifactId>hippo4j-auth</artifactId>
<packaging>jar</packaging>
<properties>
<maven.deploy.skip>true</maven.deploy.skip>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
@ -49,6 +53,22 @@
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>cn.hippo4j</groupId>
<artifactId>hippo4j-common</artifactId>
</dependency>
</dependencies>
</project>

@ -0,0 +1,95 @@
package cn.hippo4j.auth.config;
import cn.hippo4j.auth.constant.Constants;
import cn.hippo4j.auth.filter.JWTAuthenticationFilter;
import cn.hippo4j.auth.filter.JWTAuthorizationFilter;
import cn.hippo4j.auth.security.JwtTokenManager;
import cn.hippo4j.auth.service.impl.UserDetailsServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import javax.annotation.Resource;
import java.util.stream.Stream;
/**
* .
*
* @author chen.ma
* @date 2021/11/9 21:10
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class GlobalSecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private UserDetailsService userDetailsService;
@Resource
private JwtTokenManager tokenManager;
@Bean
public UserDetailsService customUserService() {
return new UserDetailsServiceImpl();
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean(name = BeanIds.AUTHENTICATION_MANAGER)
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.addAllowedMethod(Constants.SPLIT_STAR);
config.applyPermitDefaultValues();
source.registerCorsConfiguration("/**", config);
return source;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.authorizeRequests()
.antMatchers("/static/**", "/index.html", "/favicon.ico", "/avatar.jpg").permitAll()
.antMatchers("/doc.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs").anonymous()
.anyRequest().authenticated()
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(tokenManager, authenticationManager()))
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
@Override
public void configure(WebSecurity web) throws Exception {
String[] ignores = Stream.of("/hippo4j/v1/cs/auth/users/apply/token/**", "/hippo4j/v1/cs/configs/**").toArray(String[]::new);
web.ignoring().antMatchers(ignores);
}
}

@ -0,0 +1,17 @@
package cn.hippo4j.auth.constant;
/**
* Constants.
*
* @author chen.ma
* @date 2021/11/9 22:24
*/
public class Constants {
public static final String SPLIT_STAR = "*";
public static final String SPLIT_COMMA = ",";
public static final long TOKEN_VALIDITY_IN_SECONDS = 18000L;
}

@ -0,0 +1,99 @@
package cn.hippo4j.auth.filter;
import cn.hippo4j.auth.model.biz.user.JwtUser;
import cn.hippo4j.auth.model.biz.user.LoginUser;
import cn.hippo4j.auth.toolkit.JwtTokenUtil;
import cn.hippo4j.auth.toolkit.ReturnT;
import cn.hippo4j.common.web.base.Results;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import static cn.hippo4j.auth.constant.Constants.SPLIT_COMMA;
import static cn.hippo4j.common.constant.Constants.BASE_PATH;
import static cn.hippo4j.common.constant.Constants.MAP_INITIAL_CAPACITY;
/**
* JWT authentication filter.
*
* @author chen.ma
* @date 2021/11/9 22:21
*/
@Slf4j
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
private final ThreadLocal<Integer> rememberMe = new ThreadLocal();
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
super.setFilterProcessesUrl(BASE_PATH + "/auth/login");
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
// 从输入流中获取到登录的信息
try {
LoginUser loginUser = new ObjectMapper().readValue(request.getInputStream(), LoginUser.class);
rememberMe.set(loginUser.getRememberMe());
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginUser.getUsername(), loginUser.getPassword(), new ArrayList())
);
} catch (IOException e) {
logger.error("attemptAuthentication error :{}", e);
return null;
}
}
@Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication authResult) throws IOException {
try {
JwtUser jwtUser = (JwtUser) authResult.getPrincipal();
boolean isRemember = rememberMe.get() == 1;
String role = "";
Collection<? extends GrantedAuthority> authorities = jwtUser.getAuthorities();
for (GrantedAuthority authority : authorities) {
role = authority.getAuthority();
}
String token = JwtTokenUtil.createToken(jwtUser.getId(), jwtUser.getUsername(), role, isRemember);
response.setHeader("token", JwtTokenUtil.TOKEN_PREFIX + token);
response.setCharacterEncoding("UTF-8");
Map<String, Object> maps = new HashMap(MAP_INITIAL_CAPACITY);
maps.put("data", JwtTokenUtil.TOKEN_PREFIX + token);
maps.put("roles", role.split(SPLIT_COMMA));
response.getWriter().write(JSONUtil.toJsonStr(Results.success(maps)));
} finally {
rememberMe.remove();
}
}
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
response.setCharacterEncoding("UTF-8");
response.getWriter().write(JSONUtil.toJsonStr(new ReturnT(-1, "Server Error")));
}
}

@ -0,0 +1,118 @@
package cn.hippo4j.auth.filter;
import cn.hippo4j.auth.security.JwtTokenManager;
import cn.hippo4j.auth.toolkit.JwtTokenUtil;
import cn.hippo4j.common.toolkit.JSONUtil;
import cn.hippo4j.common.toolkit.UserContext;
import cn.hippo4j.common.web.base.Results;
import cn.hippo4j.common.web.exception.ServiceException;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collections;
import static cn.hippo4j.common.constant.Constants.ACCESS_TOKEN;
import static cn.hippo4j.common.web.exception.ErrorCodeEnum.LOGIN_TIMEOUT;
/**
* JWT authorization filter.
*
* @author chen.ma
* @date 2021/11/9 22:21
*/
@Slf4j
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
private final JwtTokenManager tokenManager;
public JWTAuthorizationFilter(JwtTokenManager tokenManager, AuthenticationManager authenticationManager) {
super(authenticationManager);
this.tokenManager = tokenManager;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 验证客户端交互时 Token
String accessToken = request.getParameter(ACCESS_TOKEN);
if (StrUtil.isNotBlank(accessToken)) {
tokenManager.validateToken(accessToken);
Authentication authentication = this.tokenManager.getAuthentication(accessToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
return;
}
// 如果请求头中没有 Authorization 信息则直接放行
String tokenHeader = request.getHeader(JwtTokenUtil.TOKEN_HEADER);
if (tokenHeader == null || !tokenHeader.startsWith(JwtTokenUtil.TOKEN_PREFIX)) {
chain.doFilter(request, response);
return;
}
// 如果请求头中有 Token, 则进行解析, 并且设置认证信息
try {
SecurityContextHolder.getContext().setAuthentication(getAuthentication(tokenHeader));
} catch (Exception ex) {
// 返回 Json 形式的错误信息
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
String resultStatus = "-1";
if (ex instanceof ServiceException) {
ServiceException serviceException = (ServiceException) ex;
resultStatus = serviceException.errorCode.getCode();
}
response.getWriter().write(JSONUtil.toJSONString(Results.failure(resultStatus, ex.getMessage())));
response.getWriter().flush();
return;
}
try {
super.doFilterInternal(request, response, chain);
} finally {
UserContext.clear();
}
}
/**
* Token Token.
*
* @param tokenHeader
* @return
*/
private UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader) {
String token = tokenHeader.replace(JwtTokenUtil.TOKEN_PREFIX, "");
boolean expiration = JwtTokenUtil.isExpiration(token);
if (expiration) {
throw new ServiceException(LOGIN_TIMEOUT);
}
String username = JwtTokenUtil.getUsername(token);
String userRole = JwtTokenUtil.getUserRole(token);
UserContext.setUserInfo(username, userRole);
String role = JwtTokenUtil.getUserRole(token);
if (username != null) {
return new UsernamePasswordAuthenticationToken(username, null,
Collections.singleton(new SimpleGrantedAuthority(role))
);
}
return null;
}
}

@ -1,7 +1,7 @@
package com.github.dynamic.threadpool.auth.mapper;
package cn.hippo4j.auth.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.github.dynamic.threadpool.auth.model.PermissionInfo;
import cn.hippo4j.auth.model.PermissionInfo;
import org.apache.ibatis.annotations.Mapper;
/**

@ -1,7 +1,7 @@
package com.github.dynamic.threadpool.auth.mapper;
package cn.hippo4j.auth.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.github.dynamic.threadpool.auth.model.RoleInfo;
import cn.hippo4j.auth.model.RoleInfo;
import org.apache.ibatis.annotations.Mapper;
/**

@ -1,7 +1,7 @@
package com.github.dynamic.threadpool.auth.mapper;
package cn.hippo4j.auth.mapper;
import cn.hippo4j.auth.model.UserInfo;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.github.dynamic.threadpool.auth.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
/**

@ -1,4 +1,4 @@
package com.github.dynamic.threadpool.auth.model;
package cn.hippo4j.auth.model;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
@ -18,7 +18,7 @@ public class PermissionInfo {
/**
* id
*/
@TableId
@TableId(type = IdType.AUTO)
private Long id;
/**

@ -1,4 +1,4 @@
package com.github.dynamic.threadpool.auth.model;
package cn.hippo4j.auth.model;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
@ -18,7 +18,7 @@ public class RoleInfo {
/**
* id
*/
@TableId
@TableId(type = IdType.AUTO)
private Long id;
/**

@ -1,4 +1,4 @@
package com.github.dynamic.threadpool.auth.model;
package cn.hippo4j.auth.model;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
@ -18,7 +18,7 @@ public class UserInfo {
/**
* id
*/
@TableId
@TableId(type = IdType.AUTO)
private Long id;
/**
@ -31,6 +31,11 @@ public class UserInfo {
*/
private String password;
/**
* role
*/
private String role;
/**
* gmtCreate
*/

@ -1,4 +1,4 @@
package com.github.dynamic.threadpool.auth.model.biz.permission;
package cn.hippo4j.auth.model.biz.permission;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.Data;

@ -1,4 +1,4 @@
package com.github.dynamic.threadpool.auth.model.biz.permission;
package cn.hippo4j.auth.model.biz.permission;
import lombok.Data;

@ -1,4 +1,4 @@
package com.github.dynamic.threadpool.auth.model.biz.role;
package cn.hippo4j.auth.model.biz.role;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.Data;

@ -1,4 +1,4 @@
package com.github.dynamic.threadpool.auth.model.biz.role;
package cn.hippo4j.auth.model.biz.role;
import lombok.Data;

@ -0,0 +1,58 @@
package cn.hippo4j.auth.model.biz.user;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
/**
* Jwt user.
*
* @author chen.ma
* @date 2021/11/9 22:34
*/
@Data
public class JwtUser implements UserDetails {
/**
* id
*/
private Long id;
/**
* userName
*/
private String username;
/**
* password
*/
private String password;
/**
* authorities
*/
private Collection<? extends GrantedAuthority> authorities;
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}

@ -0,0 +1,29 @@
package cn.hippo4j.auth.model.biz.user;
import lombok.Data;
/**
* Login user.
*
* @author chen.ma
* @date 2021/11/9 22:41
*/
@Data
public class LoginUser {
/**
* username
*/
private String username;
/**
* password
*/
private String password;
/**
* rememberMe
*/
private Integer rememberMe;
}

@ -1,7 +1,8 @@
package com.github.dynamic.threadpool.auth.model.biz.user;
package cn.hippo4j.auth.model.biz.user;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* User query page.
@ -10,10 +11,12 @@ import lombok.Data;
* @date 2021/10/30 21:47
*/
@Data
@Accessors(chain = true)
public class UserQueryPageReqDTO extends Page {
public UserQueryPageReqDTO(long current, long size) {
super(current, size);
}
/**
* userName
*/
private String userName;
}

@ -0,0 +1,32 @@
package cn.hippo4j.auth.model.biz.user;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* User req dto.
*
* @author chen.ma
* @date 2021/11/11 20:30
*/
@Data
@Accessors(chain = true)
public class UserReqDTO extends Page {
/**
* userName
*/
private String userName;
/**
* password
*/
private String password;
/**
* role
*/
private String role;
}

@ -1,4 +1,4 @@
package com.github.dynamic.threadpool.auth.model.biz.user;
package cn.hippo4j.auth.model.biz.user;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
@ -20,9 +20,9 @@ public class UserRespDTO {
private String userName;
/**
* password
* role
*/
private String password;
private String role;
/**
* gmtCreate

@ -0,0 +1,46 @@
package cn.hippo4j.auth.security;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.expression.AccessException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;
/**
* Auth manager
*
* @author chen.ma
* @date 2021/12/20 20:34
*/
@Component
@AllArgsConstructor
public class AuthManager {
private final JwtTokenManager jwtTokenManager;
private final AuthenticationManager authenticationManager;
/**
* Resolve token from user.
*
* @param userName
* @param rawPassword
* @return
* @throws AccessException
*/
@SneakyThrows
public String resolveTokenFromUser(String userName, String rawPassword) {
try {
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(userName, rawPassword);
authenticationManager.authenticate(authenticationToken);
} catch (AuthenticationException e) {
throw new AccessException("Unknown user.");
}
return jwtTokenManager.createToken(userName);
}
}

@ -0,0 +1,61 @@
package cn.hippo4j.auth.security;
import cn.hutool.core.util.StrUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.List;
import static cn.hippo4j.auth.constant.Constants.TOKEN_VALIDITY_IN_SECONDS;
import static cn.hippo4j.auth.toolkit.JwtTokenUtil.SECRET;
import static cn.hippo4j.common.constant.Constants.AUTHORITIES_KEY;
/**
* Jwt token manager.
*
* @author chen.ma
* @date 2021/12/20 20:36
*/
@Component
public class JwtTokenManager {
public String createToken(String userName) {
long now = System.currentTimeMillis();
Date validity;
validity = new Date(now + TOKEN_VALIDITY_IN_SECONDS * 1000L);
Claims claims = Jwts.claims().setSubject(userName);
return Jwts.builder().setClaims(claims).setExpiration(validity)
.signWith(SignatureAlgorithm.HS512, SECRET).compact();
}
public void validateToken(String token) {
Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token);
}
/**
* Get auth Info.
*
* @param token token
* @return auth info
*/
public Authentication getAuthentication(String token) {
Claims claims = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();
List<GrantedAuthority> authorities = AuthorityUtils
.commaSeparatedStringToAuthorityList((String) claims.get(AUTHORITIES_KEY));
User principal = new User(claims.getSubject(), StrUtil.EMPTY, authorities);
return new UsernamePasswordAuthenticationToken(principal, StrUtil.EMPTY, authorities);
}
}

@ -1,7 +1,7 @@
package com.github.dynamic.threadpool.auth.service;
package cn.hippo4j.auth.service;
import cn.hippo4j.auth.model.biz.permission.PermissionRespDTO;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.github.dynamic.threadpool.auth.model.biz.permission.PermissionRespDTO;
/**
* Permission service.

@ -1,7 +1,7 @@
package com.github.dynamic.threadpool.auth.service;
package cn.hippo4j.auth.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.github.dynamic.threadpool.auth.model.biz.role.RoleRespDTO;
import cn.hippo4j.auth.model.biz.role.RoleRespDTO;
import java.util.List;

@ -1,7 +1,9 @@
package com.github.dynamic.threadpool.auth.service;
package cn.hippo4j.auth.service;
import cn.hippo4j.auth.model.biz.user.UserQueryPageReqDTO;
import cn.hippo4j.auth.model.biz.user.UserRespDTO;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.github.dynamic.threadpool.auth.model.biz.user.UserRespDTO;
import cn.hippo4j.auth.model.biz.user.UserReqDTO;
import java.util.List;
@ -16,27 +18,24 @@ public interface UserService {
/**
* .
*
* @param pageNo
* @param pageSize
* @param reqDTO
* @return
*/
IPage<UserRespDTO> listUser(int pageNo, int pageSize);
IPage<UserRespDTO> listUser(UserQueryPageReqDTO reqDTO);
/**
* .
*
* @param userName
* @param password
* @param reqDTO
*/
void addUser(String userName, String password);
void addUser(UserReqDTO reqDTO);
/**
* .
*
* @param userName
* @param password
* @param reqDTO
*/
void updateUser(String userName, String password);
void updateUser(UserReqDTO reqDTO);
/**
* .
@ -53,4 +52,12 @@ public interface UserService {
*/
List<String> getUserLikeUsername(String userName);
/**
* .
*
* @param reqDTO
* @return
*/
UserRespDTO getUser(UserReqDTO reqDTO);
}

@ -1,15 +1,16 @@
package com.github.dynamic.threadpool.auth.service.impl;
package cn.hippo4j.auth.service.impl;
import cn.hippo4j.auth.mapper.PermissionMapper;
import cn.hippo4j.auth.model.biz.permission.PermissionQueryPageReqDTO;
import cn.hippo4j.auth.model.biz.permission.PermissionRespDTO;
import cn.hippo4j.auth.service.PermissionService;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.github.dynamic.threadpool.auth.mapper.PermissionMapper;
import com.github.dynamic.threadpool.auth.model.PermissionInfo;
import com.github.dynamic.threadpool.auth.model.biz.permission.PermissionQueryPageReqDTO;
import com.github.dynamic.threadpool.auth.model.biz.permission.PermissionRespDTO;
import com.github.dynamic.threadpool.auth.service.PermissionService;
import cn.hippo4j.auth.model.PermissionInfo;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
@ -54,9 +55,9 @@ public class PermissionServiceImpl implements PermissionService {
@Override
public void deletePermission(String role, String resource, String action) {
LambdaUpdateWrapper<PermissionInfo> updateWrapper = Wrappers.lambdaUpdate(PermissionInfo.class)
.eq(PermissionInfo::getRole, role)
.eq(PermissionInfo::getResource, resource)
.eq(PermissionInfo::getAction, action);
.eq(StrUtil.isNotBlank(role), PermissionInfo::getRole, role)
.eq(StrUtil.isNotBlank(resource), PermissionInfo::getResource, resource)
.eq(StrUtil.isNotBlank(action), PermissionInfo::getAction, action);
permissionMapper.delete(updateWrapper);
}

@ -1,15 +1,18 @@
package com.github.dynamic.threadpool.auth.service.impl;
package cn.hippo4j.auth.service.impl;
import cn.hippo4j.auth.mapper.RoleMapper;
import cn.hippo4j.auth.model.biz.role.RoleQueryPageReqDTO;
import cn.hippo4j.auth.model.biz.role.RoleRespDTO;
import cn.hippo4j.auth.service.PermissionService;
import cn.hippo4j.auth.service.RoleService;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.github.dynamic.threadpool.auth.mapper.RoleMapper;
import com.github.dynamic.threadpool.auth.model.RoleInfo;
import com.github.dynamic.threadpool.auth.model.biz.role.RoleQueryPageReqDTO;
import com.github.dynamic.threadpool.auth.model.biz.role.RoleRespDTO;
import com.github.dynamic.threadpool.auth.service.RoleService;
import cn.hippo4j.auth.model.RoleInfo;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
@ -28,6 +31,8 @@ public class RoleServiceImpl implements RoleService {
private final RoleMapper roleMapper;
private final PermissionService permissionService;
@Override
public IPage<RoleRespDTO> listRole(int pageNo, int pageSize) {
RoleQueryPageReqDTO queryPage = new RoleQueryPageReqDTO(pageNo, pageSize);
@ -53,10 +58,18 @@ public class RoleServiceImpl implements RoleService {
@Override
public void deleteRole(String role, String userName) {
List<String> roleStrList = CollUtil.toList(role);
if (StrUtil.isBlank(role)) {
LambdaQueryWrapper<RoleInfo> queryWrapper = Wrappers.lambdaQuery(RoleInfo.class).eq(RoleInfo::getUserName, userName);
roleStrList = roleMapper.selectList(queryWrapper).stream().map(RoleInfo::getRole).collect(Collectors.toList());
}
LambdaUpdateWrapper<RoleInfo> updateWrapper = Wrappers.lambdaUpdate(RoleInfo.class)
.eq(RoleInfo::getRole, role)
.eq(RoleInfo::getUserName, userName);
.eq(StrUtil.isNotBlank(role), RoleInfo::getRole, role)
.eq(StrUtil.isNotBlank(userName), RoleInfo::getUserName, userName);
roleMapper.delete(updateWrapper);
roleStrList.forEach(each -> permissionService.deletePermission(each, "", ""));
}
@Override

@ -0,0 +1,42 @@
package cn.hippo4j.auth.service.impl;
import cn.hippo4j.auth.mapper.UserMapper;
import cn.hippo4j.auth.model.UserInfo;
import cn.hippo4j.auth.model.biz.user.JwtUser;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.Set;
/**
* User details service impl.
*
* @author chen.ma
* @date 2021/11/9 22:26
*/
public class UserDetailsServiceImpl implements UserDetailsService {
@Resource
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
UserInfo userInfo = userMapper.selectOne(Wrappers.lambdaQuery(UserInfo.class).eq(UserInfo::getUserName, userName));
JwtUser jwtUser = new JwtUser();
jwtUser.setId(userInfo.getId());
jwtUser.setUsername(userName);
jwtUser.setPassword(userInfo.getPassword());
Set<SimpleGrantedAuthority> authorities = Collections.singleton(new SimpleGrantedAuthority(userInfo.getRole() + ""));
jwtUser.setAuthorities(authorities);
return jwtUser;
}
}

@ -0,0 +1,109 @@
package cn.hippo4j.auth.service.impl;
import cn.hippo4j.auth.mapper.UserMapper;
import cn.hippo4j.auth.model.UserInfo;
import cn.hippo4j.auth.model.biz.user.UserQueryPageReqDTO;
import cn.hippo4j.auth.model.biz.user.UserReqDTO;
import cn.hippo4j.auth.model.biz.user.UserRespDTO;
import cn.hippo4j.auth.service.RoleService;
import cn.hippo4j.auth.service.UserService;
import cn.hippo4j.common.toolkit.StringUtil;
import cn.hippo4j.common.web.exception.ServiceException;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.AllArgsConstructor;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* User service impl.
*
* @author chen.ma
* @date 2021/10/30 21:40
*/
@Service
@AllArgsConstructor
public class UserServiceImpl implements UserService {
private final UserMapper userMapper;
private final RoleService roleService;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
@Override
public IPage<UserRespDTO> listUser(UserQueryPageReqDTO reqDTO) {
LambdaQueryWrapper<UserInfo> queryWrapper = Wrappers.lambdaQuery(UserInfo.class)
.eq(StringUtil.isNotBlank(reqDTO.getUserName()), UserInfo::getUserName, reqDTO.getUserName());
IPage<UserInfo> selectPage = userMapper.selectPage(reqDTO, queryWrapper);
return selectPage.convert(each -> BeanUtil.toBean(each, UserRespDTO.class));
}
@Override
public void addUser(UserReqDTO reqDTO) {
LambdaQueryWrapper<UserInfo> queryWrapper = Wrappers.lambdaQuery(UserInfo.class)
.eq(UserInfo::getUserName, reqDTO.getUserName());
UserInfo existUserInfo = userMapper.selectOne(queryWrapper);
if (existUserInfo != null) {
throw new RuntimeException("用户名重复");
}
reqDTO.setPassword(bCryptPasswordEncoder.encode(reqDTO.getPassword()));
UserInfo insertUser = BeanUtil.toBean(reqDTO, UserInfo.class);
userMapper.insert(insertUser);
}
@Override
public void updateUser(UserReqDTO reqDTO) {
if (StrUtil.isNotBlank(reqDTO.getPassword())) {
reqDTO.setPassword(bCryptPasswordEncoder.encode(reqDTO.getPassword()));
}
UserInfo updateUser = BeanUtil.toBean(reqDTO, UserInfo.class);
LambdaUpdateWrapper<UserInfo> updateWrapper = Wrappers.lambdaUpdate(UserInfo.class)
.eq(UserInfo::getUserName, reqDTO.getUserName());
userMapper.update(updateUser, updateWrapper);
}
@Override
public void deleteUser(String userName) {
LambdaUpdateWrapper<UserInfo> updateWrapper = Wrappers.lambdaUpdate(UserInfo.class)
.eq(UserInfo::getUserName, userName);
userMapper.delete(updateWrapper);
// roleService.deleteRole("", userName);
}
@Override
public List<String> getUserLikeUsername(String userName) {
LambdaQueryWrapper<UserInfo> queryWrapper = Wrappers.lambdaQuery(UserInfo.class)
.like(UserInfo::getUserName, userName)
.select(UserInfo::getUserName);
List<UserInfo> userInfos = userMapper.selectList(queryWrapper);
List<String> userNames = userInfos.stream().map(UserInfo::getUserName).collect(Collectors.toList());
return userNames;
}
@Override
public UserRespDTO getUser(UserReqDTO reqDTO) {
Wrapper queryWrapper = Wrappers.lambdaQuery(UserInfo.class).eq(UserInfo::getUserName, reqDTO.getUserName());
UserInfo userInfo = userMapper.selectOne(queryWrapper);
UserRespDTO respUser = Optional.ofNullable(userInfo)
.map(each -> BeanUtil.toBean(each, UserRespDTO.class))
.orElseThrow(() -> new ServiceException("查询无此用户, 可以尝试清空缓存或退出登录."));
return respUser;
}
}

@ -0,0 +1,118 @@
package cn.hippo4j.auth.toolkit;
import cn.hippo4j.auth.constant.Constants;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import static cn.hippo4j.common.constant.Constants.MAP_INITIAL_CAPACITY;
/**
* Jwt token util.
*
* @author chen.ma
* @date 2021/11/9 22:43
*/
public class JwtTokenUtil {
public static final String TOKEN_HEADER = "Authorization";
public static final String TOKEN_PREFIX = "Bearer ";
public static final String SECRET = "SecretKey039245678901232039487623456783092349288901402967890140939827";
public static final String ISS = "admin";
/**
* Key
*/
private static final String ROLE_CLAIMS = "rol";
/**
* 3600 , 24
*/
private static final long EXPIRATION = 86400L;
/**
* 7
*/
private static final long EXPIRATION_REMEMBER = 7 * EXPIRATION;
/**
* Token.
*
* @param id
* @param username
* @param role
* @param isRememberMe
* @return
*/
public static String createToken(Long id, String username, String role, boolean isRememberMe) {
long expiration = isRememberMe ? EXPIRATION_REMEMBER : EXPIRATION;
HashMap<String, Object> map = new HashMap(MAP_INITIAL_CAPACITY);
map.put(ROLE_CLAIMS, role);
return Jwts.builder()
.signWith(SignatureAlgorithm.HS512, SECRET)
.setClaims(map)
.setIssuer(ISS)
.setSubject(id + Constants.SPLIT_COMMA + username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
.compact();
}
/**
* Token .
*
* @param token
* @return
*/
public static String getUsername(String token) {
List<String> userInfo = Arrays.asList(getTokenBody(token).getSubject().split(Constants.SPLIT_COMMA));
return userInfo.get(1);
}
/**
* Token .
*
* @param token
* @return
*/
public static Integer getUserId(String token) {
List<String> userInfo = Arrays.asList(getTokenBody(token).getSubject().split(Constants.SPLIT_COMMA));
return Integer.parseInt(userInfo.get(0));
}
/**
* .
*
* @param token
* @return
*/
public static String getUserRole(String token) {
return (String) getTokenBody(token).get(ROLE_CLAIMS);
}
/**
* .
*
* @param token
* @return
*/
public static boolean isExpiration(String token) {
try {
return getTokenBody(token).getExpiration().before(new Date());
} catch (ExpiredJwtException e) {
return true;
}
}
private static Claims getTokenBody(String token) {
return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();
}
}

@ -0,0 +1,40 @@
package cn.hippo4j.auth.toolkit;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* ReturnT.
*
* @author chen.ma
* @date 2021/11/10 00:00
*/
@Data
@NoArgsConstructor
public class ReturnT<T> implements Serializable {
public static final long serialVersionUID = 42L;
public static final int SUCCESS_CODE = 200;
public static final int FAIL_CODE = 500;
public static final ReturnT<String> SUCCESS = new ReturnT<>(null);
public static final ReturnT<String> FAIL = new ReturnT<>(FAIL_CODE, null);
private int code;
private String msg;
private T content;
public ReturnT(int code, String msg) {
this.code = code;
this.msg = msg;
}
public ReturnT(T content) {
this.code = SUCCESS_CODE;
this.content = content;
}
}

@ -4,16 +4,16 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.github.dynamic-threadpool</groupId>
<artifactId>parent</artifactId>
<groupId>cn.hippo4j</groupId>
<artifactId>hippo4j-all</artifactId>
<version>${revision}</version>
</parent>
<artifactId>dynamic-threadpool-spring-boot-starter</artifactId>
<artifactId>hippo4j-common</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>${project.artifactId}</description>
<description>HIPPO4J、HIPPO4J-CORE 公共代码库.</description>
<dependencies>
<dependency>
@ -22,49 +22,36 @@
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>logging-interceptor</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.github.dynamic-threadpool</groupId>
<artifactId>common</artifactId>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>alibaba-dingtalk-service-sdk</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<!-- 用户反馈其中 javax.jms 无法下载, 未发现 log4j 用处, 暂且排除 -->
<exclusions>
<exclusion>
<artifactId>log4j</artifactId>
<groupId>log4j</groupId>
</exclusion>
</exclusions>
<optional>true</optional>
</dependency>
</dependencies>
@ -84,6 +71,19 @@
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.10.3</version>
<executions>
<execution>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

@ -0,0 +1,42 @@
package cn.hippo4j.common.api;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* Client close hook execute.
*
* @author chen.ma
* @date 2022/1/6 22:14
*/
public interface ClientCloseHookExecute {
/**
* Client close hook function execution.
*
* @param req
*/
void closeHook(ClientCloseHookReq req);
@Data
@Accessors(chain = true)
class ClientCloseHookReq {
/**
* appName
*/
private String appName;
/**
* instanceId
*/
private String instanceId;
/**
* groupKey
*/
private String groupKey;
}
}

@ -0,0 +1,41 @@
package cn.hippo4j.common.api;
import java.util.List;
/**
* Json facade.
*
* @author chen.ma
* @date 2021/12/13 20:01
*/
public interface JsonFacade {
/**
* To JSON string.
*
* @param object
* @return
*/
String toJSONString(Object object);
/**
* Parse object.
*
* @param text
* @param clazz
* @param <T>
* @return
*/
<T> T parseObject(String text, Class<T> clazz);
/**
* Parse array.
*
* @param text
* @param clazz
* @param <T>
* @return
*/
<T> List<T> parseArray(String text, Class<T> clazz);
}

@ -0,0 +1,23 @@
package cn.hippo4j.common.api;
import cn.hippo4j.common.notify.NotifyConfigDTO;
import java.util.List;
import java.util.Map;
/**
* Notify config builder.
*
* @author chen.ma
* @date 2022/2/24 19:50
*/
public interface NotifyConfigBuilder {
/**
* Build notify.
*
* @return
*/
Map<String, List<NotifyConfigDTO>> buildNotify();
}

@ -0,0 +1,32 @@
package cn.hippo4j.common.api;
import cn.hippo4j.common.model.ThreadDetailStateInfo;
import java.util.List;
import java.util.concurrent.ThreadPoolExecutor;
/**
* Get thread status in thread pool.
*
* @author chen.ma
* @date 2022/1/9 12:47
*/
public interface ThreadDetailState {
/**
* Get thread status in thread pool.
*
* @param threadPoolId
* @return
*/
List<ThreadDetailStateInfo> getThreadDetailStateInfo(String threadPoolId);
/**
* Get thread status in thread pool.
*
* @param threadPoolExecutor
* @return
*/
List<ThreadDetailStateInfo> getThreadDetailStateInfo(ThreadPoolExecutor threadPoolExecutor);
}

@ -0,0 +1,18 @@
package cn.hippo4j.common.api;
/**
* Thread pool dynamic refresh.
*
* @author chen.ma
* @date 2022/2/26 12:26
*/
public interface ThreadPoolDynamicRefresh {
/**
* Dynamic refresh.
*
* @param content
*/
void dynamicRefresh(String content);
}

@ -1,4 +1,4 @@
package com.github.dynamic.threadpool.common.config;
package cn.hippo4j.common.config;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;

@ -1,4 +1,4 @@
package com.github.dynamic.threadpool.common.constant;
package cn.hippo4j.common.constant;
/**
* Constants.
@ -14,10 +14,20 @@ public class Constants {
public static final String NAMESPACE = "namespace";
public static final String GROUP_KEY = "groupKey";
public static final String AUTHORITIES_KEY = "auth";
public static final String ACCESS_TOKEN = "accessToken";
public static final String TOKEN_TTL = "tokenTtl";
public static final String DEFAULT_NAMESPACE_ID = "public";
public static final String NULL = "";
public static final String UP = "UP";
public static final String ENCODE = "UTF-8";
public static final int CONFIG_LONG_POLL_TIMEOUT = 30000;
@ -28,28 +38,52 @@ public class Constants {
public static final String GENERAL_SPLIT_SYMBOL = ",";
public static final String IDENTIFY_SLICER_SYMBOL = "_";
public static final String LONG_POLLING_LINE_SEPARATOR = "\r\n";
public static final String BASE_PATH = "/v1/cs";
public static final String BASE_PATH = "/hippo4j/v1/cs";
public static final String CONFIG_CONTROLLER_PATH = BASE_PATH + "/configs";
public static final String LISTENER_PATH = CONFIG_CONTROLLER_PATH + "/listener";
public static final String MONITOR_PATH = BASE_PATH + "/monitor";
public static final String HEALTH_CHECK_PATH = BASE_PATH + "/health/check";
public static final String PROBE_MODIFY_REQUEST = "Listening-Configs";
public static final String LONG_PULLING_TIMEOUT = "Long-Pulling-Timeout";
public static final String LONG_PULLING_TIMEOUT_NO_HANGUP = "Long-Pulling-Timeout-No-Hangup";
public static final String LONG_PULLING_CLIENT_IDENTIFICATION = "Long-Pulling-Client-Identification";
public static final String LISTENING_CONFIGS = "Listening-Configs";
public static final String WEIGHT_CONFIGS = "Weight-Configs";
public static final String GROUP_KEY_DELIMITER = "+";
public static final String GROUP_KEY_DELIMITER_TRANSLATION = "\\+";
public static final long EVICTION_INTERVAL_TIMER_IN_MS = 60 * 1000;
public static final int SCHEDULED_THREAD_CORE_NUM = 1;
public static final int MAP_INITIAL_CAPACITY = 16;
public static final int HEALTH_CHECK_INTERVAL = 5;
public static final int AVAILABLE_PROCESSORS = Runtime.getRuntime().availableProcessors();
public static final String DEFAULT_GROUP = "default group";
public static final String UNKNOWN = "unknown";
public static final String EXECUTE_TIMEOUT_TRACE = "executeTimeoutTrace";
public static final int HTTP_EXECUTE_TIMEOUT = 5000;
}

@ -1,4 +1,4 @@
package com.github.dynamic.threadpool.starter.toolkit.thread;
package cn.hippo4j.common.design.builder;
import java.io.Serializable;

@ -0,0 +1,134 @@
package cn.hippo4j.common.design.observer;
import cn.hippo4j.common.toolkit.CollectionUtil;
import cn.hippo4j.common.toolkit.StringUtil;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Send observer notification.
*
* @author chen.ma
* @date 2021/12/25 19:47
*/
@Slf4j
public class AbstractSubjectCenter {
private static final Map<String, List<Observer>> OBSERVERS_MAP = new ConcurrentHashMap();
/**
* Register observer.
*
* @param observer
*/
public static void register(Observer observer) {
register(SubjectType.SPRING_CONTENT_REFRESHED.name(), observer);
}
/**
* Register observer.
*
* @param subjectType
* @param observer
*/
public static void register(SubjectType subjectType, Observer observer) {
register(subjectType.name(), observer);
}
/**
* Register observer.
*
* @param subject
* @param observer
*/
public static void register(String subject, Observer observer) {
if (StringUtil.isBlank(subject) || observer == null) {
log.warn("Register observer. A string whose subject or observer is empty or empty.");
return;
}
List<Observer> observers = OBSERVERS_MAP.get(subject);
if (CollectionUtil.isEmpty(observers)) {
observers = new ArrayList();
}
observers.add(observer);
OBSERVERS_MAP.put(subject, observers);
}
/**
* Remove observer.
*
* @param observer
*/
public static void remove(Observer observer) {
remove(SubjectType.SPRING_CONTENT_REFRESHED.name(), observer);
}
/**
* Remove observer.
*
* @param subject
* @param observer
*/
public static void remove(String subject, Observer observer) {
List<Observer> observers;
if (StringUtil.isBlank(subject) || CollectionUtil.isEmpty((observers = OBSERVERS_MAP.get(subject))) || observer == null) {
log.warn("Remove observer. A string whose subject or observer is empty or empty.");
return;
}
observers.remove(observer);
}
/**
* Notify.
*
* @param subjectType
* @param observerMessage
*/
public static void notify(SubjectType subjectType, ObserverMessage observerMessage) {
notify(subjectType.name(), observerMessage);
}
/**
* Notify.
*
* @param subject
* @param observerMessage
*/
public static void notify(String subject, ObserverMessage observerMessage) {
List<Observer> observers = OBSERVERS_MAP.get(subject);
if (CollectionUtil.isEmpty(observers)) {
log.warn("Under the subject, there is no observer group.");
return;
}
observers.parallelStream().forEach(each -> {
try {
each.accept(observerMessage);
} catch (Exception ex) {
log.error("Notification subject :: {} observer exception", subject);
}
});
}
public enum SubjectType {
/**
* Spring content refreshed.
*/
SPRING_CONTENT_REFRESHED,
/**
* Clear config cache.
*/
CLEAR_CONFIG_CACHE
}
}

@ -0,0 +1,18 @@
package cn.hippo4j.common.design.observer;
/**
* Observer.
*
* @author chen.ma
* @date 2021/12/25 19:46
*/
public interface Observer<T> {
/**
* Receive notification.
*
* @param observerMessage
*/
void accept(ObserverMessage<T> observerMessage);
}

@ -0,0 +1,18 @@
package cn.hippo4j.common.design.observer;
/**
* Message notifying observer.
*
* @author chen.ma
* @date 2021/12/25 19:54
*/
public interface ObserverMessage<T> {
/**
* Message.
*
* @return
*/
T message();
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save