Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
B
basic-api-boot
概览
概览
详情
活动
周期分析
版本库
存储库
文件
提交
分支
标签
贡献者
分支图
比较
统计图
问题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程表
图表
维基
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
Basic
basic-api-boot
Commits
022df2b1
提交
022df2b1
authored
5月 11, 2024
作者:
方治民
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
feat: 新增 @UptimePush 注解,实现自动上报监控结果到 Uptime Kuma 并支持重试功能
上级
4f29817d
隐藏空白字符变更
内嵌
并排
正在显示
10 个修改的文件
包含
407 行增加
和
70 行删除
+407
-70
TestJob.java
app/src/main/java/com/yiring/app/job/TestJob.java
+59
-0
RateLimiter.java
...c/main/java/com/yiring/common/annotation/RateLimiter.java
+2
-2
UptimePush.java
...rc/main/java/com/yiring/common/annotation/UptimePush.java
+54
-0
UptimePushAspect.java
.../main/java/com/yiring/common/aspect/UptimePushAspect.java
+143
-0
Result.java
...mon/core/src/main/java/com/yiring/common/core/Result.java
+3
-3
UptimeNotice.java
...re/src/main/java/com/yiring/common/core/UptimeNotice.java
+79
-0
UptimeException.java
...ain/java/com/yiring/common/exception/UptimeException.java
+33
-0
XxlJobAspect.java
.../src/main/java/com/yiring/common/aspect/XxlJobAspect.java
+0
-37
Retriever.java
.../util/src/main/java/com/yiring/common/core/Retriever.java
+6
-1
Uptime.java
...mon/util/src/main/java/com/yiring/common/util/Uptime.java
+28
-27
没有找到文件。
app/src/main/java/com/yiring/app/job/TestJob.java
0 → 100644
浏览文件 @
022df2b1
/* (C) 2024 YiRing, Inc. */
package
com
.
yiring
.
app
.
job
;
import
com.yiring.common.annotation.UptimePush
;
import
com.yiring.common.constant.DateFormatter
;
import
com.yiring.common.core.Retriever
;
import
com.yiring.common.exception.UptimeException
;
import
com.yiring.common.util.Uptime
;
import
java.time.LocalDateTime
;
import
java.util.Random
;
import
java.util.concurrent.TimeUnit
;
import
lombok.RequiredArgsConstructor
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.scheduling.annotation.EnableScheduling
;
import
org.springframework.scheduling.annotation.Scheduled
;
import
org.springframework.stereotype.Component
;
/**
* @author Jim
*/
@Slf4j
@Component
@EnableScheduling
@RequiredArgsConstructor
public
class
TestJob
{
final
Retriever
retriever
;
/**
* 一个使用 Retriever + @UptimePush 双链路重试的定时任务示例
*/
@UptimePush
(
key
=
"BiCC4Jgoa5"
,
group
=
"Test"
,
name
=
"测试任务调度"
,
retryCount
=
3
,
retryStatus
=
Uptime
.
Status
.
UP
)
@Scheduled
(
fixedDelay
=
5
,
timeUnit
=
TimeUnit
.
SECONDS
)
public
void
test
()
{
String
time
=
LocalDateTime
.
now
().
format
(
DateFormatter
.
DATE_TIME
);
log
.
info
(
"TestJobHandler: {}"
,
time
);
Random
random
=
new
Random
();
int
randomCount
=
random
.
nextInt
(
3
);
String
result
=
retriever
.
execute
(
ctx
->
{
if
(
ctx
.
getRetryCount
()
>
randomCount
)
{
return
"OK"
;
}
throw
new
UptimeException
(
"[TestJobHandler] test err: "
+
time
);
},
Retriever:
:
defaultPolicy
);
log
.
info
(
"[Test] result: {}"
,
result
);
}
}
basic-common/core/src/main/java/com/yiring/common/annotation/RateLimiter.java
浏览文件 @
022df2b1
...
...
@@ -18,12 +18,12 @@ import java.lang.annotation.Target;
@Retention
(
RetentionPolicy
.
RUNTIME
)
@Target
(
ElementType
.
METHOD
)
public
@interface
RateLimiter
{
String
RATE_LIMIT
_KEY
=
"rate_limit:"
;
String
DEFAULT_CACHE
_KEY
=
"rate_limit:"
;
/**
* 限流key
*/
String
key
()
default
RATE_LIMIT
_KEY
;
String
key
()
default
DEFAULT_CACHE
_KEY
;
/**
* 限流时间,单位秒
...
...
basic-common/core/src/main/java/com/yiring/common/annotation/UptimePush.java
0 → 100644
浏览文件 @
022df2b1
/* (C) 2023 YiRing, Inc. */
package
com
.
yiring
.
common
.
annotation
;
import
com.yiring.common.util.Uptime
;
import
java.lang.annotation.ElementType
;
import
java.lang.annotation.Retention
;
import
java.lang.annotation.RetentionPolicy
;
import
java.lang.annotation.Target
;
/**
* Uptime Kuma 服务健康检查注解, 用于标记需要 Push 类型监控的方法
*
* @author Jim
* @version 0.1
* 2024/12/19 15:20
*/
@SuppressWarnings
({
"unused"
})
@Retention
(
RetentionPolicy
.
RUNTIME
)
@Target
(
ElementType
.
METHOD
)
public
@interface
UptimePush
{
String
CACHE_PREFIX
=
"uptime_health_push:"
;
/**
* Push 类型的监控项唯一标识
* eg: <a href="https://uptime.yiring.com/api/push/BiCC4Jgoa5?status=up&msg=OK&ping=">BiCC4Jgoa5</a> 其中的 BiCC4Jgoa5 就是 key
*/
String
key
();
/**
* 监控项名称
*/
String
name
()
default
""
;
/**
* 分组名名称
*/
String
group
()
default
""
;
/**
* 重试次数
*/
int
retryCount
()
default
0
;
/**
* 重试时的状态标记,默认为 DOWN
*/
Uptime
.
Status
retryStatus
()
default
Uptime
.
Status
.
DOWN
;
/**
* 重试循环,例如设置重试次数为 5,第五次会真正触发异常上报,然后后续继续出现异常触发重试又会积累 5 次后再次上报
* 默认不开启,代表当达到重试次数后,不会再走重试逻辑,按照默认的失败机制处理,直到成功一次后再重置
*/
boolean
retryLoop
()
default
false
;
}
basic-common/core/src/main/java/com/yiring/common/aspect/UptimePushAspect.java
0 → 100644
浏览文件 @
022df2b1
/* (C) 2023 YiRing, Inc. */
package
com
.
yiring
.
common
.
aspect
;
import
cn.hutool.core.util.StrUtil
;
import
com.yiring.common.annotation.UptimePush
;
import
com.yiring.common.core.Redis
;
import
com.yiring.common.core.UptimeNotice
;
import
com.yiring.common.exception.UptimeException
;
import
com.yiring.common.util.Uptime
;
import
java.util.ArrayList
;
import
java.util.List
;
import
java.util.Objects
;
import
java.util.Optional
;
import
lombok.RequiredArgsConstructor
;
import
lombok.extern.slf4j.Slf4j
;
import
org.aspectj.lang.ProceedingJoinPoint
;
import
org.aspectj.lang.annotation.Around
;
import
org.aspectj.lang.annotation.Aspect
;
import
org.springframework.stereotype.Component
;
/**
* Uptime Push 监控项切面
*
* @author Jim
* @version 0.1
* 2023/12/19 15:17
*/
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public
class
UptimePushAspect
{
final
Redis
redis
;
@Around
(
"@annotation(annotation)"
)
public
Object
around
(
ProceedingJoinPoint
point
,
UptimePush
annotation
)
throws
Throwable
{
// 获取 Push 监控项的 key 唯一标识
String
key
=
annotation
.
key
();
String
redisKey
=
UptimePush
.
CACHE_PREFIX
+
key
;
int
retryCount
=
annotation
.
retryCount
();
// 获取当前的重试次数
Integer
currentRetryCount
=
redis
.
get
(
redisKey
,
Integer
.
class
);
Object
result
=
null
;
String
err
=
null
;
long
start
=
System
.
currentTimeMillis
();
try
{
result
=
point
.
proceed
();
// 针对有返回值的 Job 判断是否为 UptimeNotice 类实例
if
(
result
instanceof
UptimeNotice
value
)
{
result
=
value
.
getMsg
();
throw
new
UptimeException
(
value
.
getMsg
());
}
// 开始重复计数
redis
.
del
(
redisKey
);
}
catch
(
Exception
e
)
{
err
=
e
.
getMessage
();
// 非指定 UptimeException 异常情况下才抛出
if
(!(
e
instanceof
UptimeException
)
||
retryCount
<=
0
)
{
throw
e
;
}
}
finally
{
// 获取重试时的状态标记
Uptime
.
Status
status
=
Uptime
.
Status
.
UP
;
// 构建消息内容集合
List
<
String
>
texts
=
new
ArrayList
<>();
if
(
StrUtil
.
isNotBlank
(
annotation
.
group
()))
{
texts
.
add
(
"【"
+
annotation
.
group
()
+
"】"
);
}
if
(
StrUtil
.
isNotBlank
(
annotation
.
name
()))
{
texts
.
add
(
annotation
.
name
()
+
(
retryCount
>
0
?
"("
+
Optional
.
ofNullable
(
currentRetryCount
).
orElse
(
0
)
+
"/"
+
retryCount
+
")"
:
""
)
+
","
);
}
// 判断是否有异常消息
if
(
StrUtil
.
isNotBlank
(
err
))
{
status
=
Uptime
.
Status
.
DOWN
;
if
(
retryCount
>
0
)
{
// 判断重试次数是否符合条件,设置重试时的上报状态
if
(
Objects
.
isNull
(
currentRetryCount
)
||
currentRetryCount
<
retryCount
)
{
status
=
annotation
.
retryStatus
();
// 重试次数递增 1
Long
count
=
redis
.
incr
(
redisKey
,
1
);
// 追加重试次数
texts
.
add
(
StrUtil
.
format
(
"已启用重试模式,预计最多重试 {} 次 \n\n"
,
retryCount
));
log
.
info
(
"[Uptime Push] key: {}, group: {}, name: {}, retry count: {}"
,
key
,
annotation
.
group
(),
annotation
.
name
(),
count
);
}
else
{
if
(
annotation
.
retryLoop
())
{
// 开始重复计数
redis
.
del
(
redisKey
);
}
else
{
// 追加重试衰退提示
texts
.
add
(
StrUtil
.
format
(
"重试超过 {} 次仍然未恢复,由于未启用循环重试模式,衰退至默认机制,请及时检查并处理 \n\n"
,
retryCount
)
);
}
}
}
// 追加异常消息
texts
.
add
(
err
);
}
else
{
if
(
result
instanceof
String
text
)
{
texts
.
add
(
text
);
}
else
{
texts
.
add
(
"OK"
);
}
}
// 构建上报的消息内容
String
msg
=
StrUtil
.
join
(
" "
,
texts
);
long
ping
=
System
.
currentTimeMillis
()
-
start
;
// 上报监控结果
Uptime
.
notice
(
key
,
status
,
msg
,
ping
);
}
return
result
;
}
}
basic-common/core/src/main/java/com/yiring/common/core/Result.java
浏览文件 @
022df2b1
...
...
@@ -35,10 +35,10 @@ public class Result<T> implements Serializable {
/**
* 注入 I18n
*/
protected
static
final
I18n
i18n
;
protected
static
final
I18n
I18N
;
static
{
i18n
=
SpringUtil
.
getBean
(
I18n
.
class
);
I18N
=
SpringUtil
.
getBean
(
I18n
.
class
);
}
/**
...
...
@@ -228,6 +228,6 @@ public class Result<T> implements Serializable {
return
null
;
}
return
i18n
.
get
(
message
,
message
);
return
I18N
.
get
(
message
,
message
);
}
}
basic-common/core/src/main/java/com/yiring/common/core/UptimeNotice.java
0 → 100644
浏览文件 @
022df2b1
/* (C) 2021 YiRing, Inc. */
package
com
.
yiring
.
common
.
core
;
import
com.fasterxml.jackson.annotation.JsonInclude
;
import
com.yiring.common.util.Uptime
;
import
java.io.Serial
;
import
java.io.Serializable
;
import
lombok.AccessLevel
;
import
lombok.Builder
;
import
lombok.Data
;
import
lombok.experimental.FieldDefaults
;
/**
* 标准的响应对象(所有的接口响应内容格式都应该是一致的)
*
* @author ifzm
* @version 1.1
* 2018/9/4 11:05
*/
@JsonInclude
(
JsonInclude
.
Include
.
NON_NULL
)
@Data
@Builder
@FieldDefaults
(
level
=
AccessLevel
.
PRIVATE
)
public
class
UptimeNotice
implements
Serializable
{
@Serial
private
static
final
long
serialVersionUID
=
-
4250534970217176081L
;
/**
* 状态
*/
Uptime
.
Status
status
;
/**
* 消息
*/
String
msg
;
/**
* 返回成功响应内容(默认)
*
* @return UptimeNotice
* @see Uptime.Status#UP
*/
public
static
UptimeNotice
ok
()
{
return
ok
(
"OK"
);
}
/**
* 返回自定义成功响应内容
*
* @return UptimeNotice
* @see Uptime.Status
*/
public
static
UptimeNotice
ok
(
String
msg
)
{
return
UptimeNotice
.
builder
().
status
(
Uptime
.
Status
.
UP
).
msg
(
msg
).
build
();
}
/**
* 返回默认的 400 错误响应
*
* @return UptimeNotice
* @see Uptime.Status#DOWN
*/
public
static
UptimeNotice
no
()
{
return
no
(
"Fail"
);
}
/**
* 返回自定义成功响应内容
*
* @return UptimeNotice
* @see Uptime.Status
*/
public
static
UptimeNotice
no
(
String
msg
)
{
return
UptimeNotice
.
builder
().
status
(
Uptime
.
Status
.
DOWN
).
msg
(
msg
).
build
();
}
}
basic-common/core/src/main/java/com/yiring/common/exception/UptimeException.java
0 → 100644
浏览文件 @
022df2b1
/* (C) 2022 YiRing, Inc. */
package
com
.
yiring
.
common
.
exception
;
import
java.io.Serial
;
import
lombok.AccessLevel
;
import
lombok.AllArgsConstructor
;
import
lombok.Data
;
import
lombok.EqualsAndHashCode
;
import
lombok.experimental.FieldDefaults
;
/**
* Uptime 异常
* 搭配 @UptimePush 注解可实现快速上报监控结果
*
* @author Jim
* @version 0.1
* 2022/3/28 11:36
*/
@EqualsAndHashCode
(
callSuper
=
true
)
@Data
@AllArgsConstructor
@FieldDefaults
(
level
=
AccessLevel
.
PRIVATE
)
public
class
UptimeException
extends
RuntimeException
{
@Serial
private
static
final
long
serialVersionUID
=
-
4226669531686389671L
;
/**
* 异常消息
*/
String
message
;
}
basic-common/util/src/main/java/com/yiring/common/aspect/XxlJobAspect.java
deleted
100644 → 0
浏览文件 @
4f29817d
/* (C) 2021 YiRing, Inc. */
package
com
.
yiring
.
common
.
aspect
;
import
lombok.extern.slf4j.Slf4j
;
import
org.aspectj.lang.ProceedingJoinPoint
;
import
org.aspectj.lang.annotation.Around
;
import
org.aspectj.lang.annotation.Aspect
;
import
org.aspectj.lang.annotation.Pointcut
;
import
org.springframework.boot.autoconfigure.condition.ConditionalOnClass
;
import
org.springframework.stereotype.Component
;
/**
* XxlJob 注解切面,修复无法输出异常的问题
*
* @author ifzm
* @version 0.1
*/
@Slf4j
@Aspect
@Component
@ConditionalOnClass
(
name
=
"com.xxl.job.core.handler.annotation.XxlJob"
)
public
class
XxlJobAspect
{
@Pointcut
(
"@annotation(com.xxl.job.core.handler.annotation.XxlJob)"
)
public
void
log
()
{}
@Around
(
"log()"
)
public
Object
around
(
ProceedingJoinPoint
point
)
throws
Throwable
{
try
{
return
point
.
proceed
();
}
catch
(
Exception
e
)
{
log
.
error
(
"XxlJob Execute Error: "
+
e
.
getMessage
(),
e
);
throw
e
;
}
}
}
basic-common/util/src/main/java/com/yiring/common/core/Retriever.java
浏览文件 @
022df2b1
...
...
@@ -87,7 +87,12 @@ public class Retriever {
ctx
.
getRetryCount
(),
throwable
.
getMessage
()
);
throw
new
RuntimeException
(
throwable
);
if
(
throwable
instanceof
Exception
e
)
{
throw
e
;
}
else
{
throw
new
RuntimeException
(
throwable
.
getMessage
(),
throwable
);
}
}
);
}
...
...
basic-common/util/src/main/java/com/yiring/common/util/Uptime.java
浏览文件 @
022df2b1
...
...
@@ -20,36 +20,30 @@ public class Uptime {
static
String
UPTIME_DOMAIN
;
static
{
UPTIME_DOMAIN
=
SpringUtil
.
getProperty
(
"uptime.domain"
);
}
String
defaultUptimeDomain
=
"https://uptime.yiring.com"
;
/**
* 内网请求 监测请求
* eg: <a href="http://uptime.health.yiring.com">uptime.health.yiring.com</a>
*
* @param key key
* @param status 状态
* @param msg 信息
*/
public
void
noticeInternal
(
String
key
,
Status
status
,
String
msg
)
{
// 服务器不具备公网访问权限
// http://192.168.173.150
String
domain
=
"http"
+
"://uptime.health.yiring.com"
;
notice
(
domain
,
key
,
status
,
msg
);
String
domain
=
SpringUtil
.
getProperty
(
"uptime.domain"
);
if
(
StrUtil
.
isBlank
(
domain
))
{
domain
=
defaultUptimeDomain
;
log
.
warn
(
"[Uptime Config] No `uptime.domain` configuration detected, using default domain: {}"
,
defaultUptimeDomain
);
}
UPTIME_DOMAIN
=
domain
;
}
/**
*
公网请求 监测请求
* eg: <a href="https://uptime.yiring.com">
uptime.yiring.com
</a>
*
默认的通知方法
* eg: <a href="https://uptime.yiring.com">
${uptime.domain}
</a>
*
* @param key key
* @param status 状态
* @param msg 信息
*/
public
void
noticeExternal
(
String
key
,
Status
status
,
String
msg
)
{
// 服务器具备公网访问权限
String
domain
=
"https"
+
"://uptime.yiring.com"
;
notice
(
domain
,
key
,
status
,
msg
);
public
void
notice
(
String
key
,
Status
status
,
String
msg
)
{
notice
(
UPTIME_DOMAIN
,
key
,
status
,
msg
,
null
);
}
/**
...
...
@@ -59,13 +53,14 @@ public class Uptime {
* @param key key
* @param status 状态
* @param msg 信息
* @param ping Push ping
*/
public
void
notice
(
String
key
,
Status
status
,
String
msg
)
{
public
void
notice
(
String
key
,
Status
status
,
String
msg
,
Long
ping
)
{
if
(
StrUtil
.
isBlank
(
UPTIME_DOMAIN
))
{
throw
new
RuntimeException
(
"please set ${uptime.domain} value in application config file."
);
}
notice
(
UPTIME_DOMAIN
,
key
,
status
,
msg
);
notice
(
UPTIME_DOMAIN
,
key
,
status
,
msg
,
ping
);
}
/**
...
...
@@ -75,18 +70,24 @@ public class Uptime {
* @param key Push key
* @param status Push status
* @param msg Push message
* @param ping Push ping
*/
private
void
notice
(
String
domain
,
String
key
,
Status
status
,
String
msg
)
{
String
url
=
"{}/api/push/{}?status={}&msg={}&ping="
;
private
void
notice
(
String
domain
,
String
key
,
Status
status
,
String
msg
,
Long
ping
)
{
String
url
=
"{}/api/push/{}?status={}&ping={}&msg={}"
;
String
message
=
url
;
try
{
// 读取配置文件中的 uptime.domain 覆盖默认配置
if
(!
Objects
.
equals
(
domain
,
UPTIME_DOMAIN
)
&&
StrUtil
.
isNotEmpty
(
UPTIME_DOMAIN
))
{
domain
=
UPTIME_DOMAIN
;
}
HttpUtil
.
get
(
StrUtil
.
format
(
url
,
domain
,
key
,
status
.
getValue
(),
msg
));
message
=
StrUtil
.
format
(
message
,
domain
,
key
,
status
.
getValue
(),
ping
,
"***"
);
HttpUtil
.
get
(
StrUtil
.
format
(
url
,
domain
,
key
,
status
.
getValue
(),
ping
,
msg
));
}
catch
(
Exception
e
)
{
log
.
error
(
"[Uptime Kuma] network connection failure: {}"
,
e
.
getMessage
(),
e
);
log
.
error
(
"[Uptime] notice failure: {}"
,
e
.
getMessage
(),
e
);
}
finally
{
log
.
info
(
"[Uptime] notice URL: {}"
,
message
);
}
}
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论