Merge pull request #137 from dromara/dev

Dev
This commit is contained in:
李佳航
2025-09-25 00:59:39 +08:00
committed by GitHub
305 changed files with 16787 additions and 885 deletions

View File

@@ -6,8 +6,11 @@ SPRING_PROFILES_ACTIVE=prod
DEMO_MODE=false
API_CORS=true
SECRET_KEY=uQeacXV8b3isvKLK
API_EXPOSE_TOKEN=pmqeHOyZaumHm0Wt
SECRET_KEY=uQeacXV8b3isvKLK
NGINX_SERVICE_HOST=service
NGINX_SERVICE_PORT=9200
MYSQL_HOST=mysql
MYSQL_PORT=3306

3
.gitignore vendored
View File

@@ -16,7 +16,8 @@ target/
.sts4-cache
### IntelliJ IDEA ###
.idea
**/.idea/*
!**/.idea/icon.png
*.iws
*.iml
*.ipr

BIN
.idea/icon.png generated Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -113,7 +113,9 @@ docker compose up -d
#### 主机监控
![主机监控](docs/assets/screenshot/monitor-list.png?time=20250627 "主机监控")
![监控详情](docs/assets/screenshot/monitor-detail.png?time=20250627 "监控详情")
![监控概览](docs/assets/screenshot/monitor-override.png?time=20250627 "监控概览")
![监控详情](docs/assets/screenshot/monitor-chart.png?time=20250627 "监控表格")
![告警通知](docs/assets/screenshot/monitor-alarm.png?time=20250627 "告警通知")
#### 批量执行

View File

@@ -1,6 +1,6 @@
version: '3.3'
# latest = 2.5.0
# latest = 2.5.1
# 支持以下源
# lijiahangmax/*
@@ -12,6 +12,9 @@ services:
image: registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-ui:latest
ports:
- ${SERVICE_PORT:-1081}:80
environment:
NGINX_SERVICE_HOST: ${NGINX_SERVICE_HOST:-service}
NGINX_SERVICE_PORT: ${NGINX_SERVICE_PORT:-9200}
restart: unless-stopped
depends_on:
service:

View File

@@ -7,7 +7,7 @@ set -e
source ./project-build.sh "$@"
# 版本号
version=2.5.0
version=2.5.1
# 是否推送镜像
push_image=false
# 是否构建 latest

View File

@@ -4,7 +4,7 @@ set -e
# DockerContext: orion-visor
# 版本号
version=2.5.0
version=2.5.1
# 是否构建 service
export build_service=false
# 是否构建 ui

View File

@@ -11,13 +11,20 @@ RUN \
ln -sf /usr/share/zoneinfo/${TZ} /etc/localtime && \
echo "${TZ}" > /etc/timezone && \
rm -rf /var/cache/apk/* && \
rm -rf /etc/nginx/nginx.conf && \
rm -rf /etc/nginx/conf.d/*
# 复制
# 复制前端静态文件
COPY ./ui/dist /usr/share/nginx/html
# 复制配置
COPY ./ui/nginx.conf /etc/nginx/conf.d
COPY ./ui/nginx.conf /etc/nginx
COPY ./ui/service.conf /etc/nginx/conf.d
# 复制启动脚本
COPY ./ui/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
# 启动
ENTRYPOINT ["/entrypoint.sh"]
CMD ["nginx", "-g", "daemon off;"]

11
docker/ui/entrypoint.sh Normal file
View File

@@ -0,0 +1,11 @@
#!/bin/sh
# 设置环境变量
NGINX_SERVICE_HOST="${NGINX_SERVICE_HOST:-service}"
NGINX_SERVICE_PORT="${NGINX_SERVICE_PORT:-9200}"
# 替换环境变量
sed -i "s|\${NGINX_SERVICE_HOST}|${NGINX_SERVICE_HOST}|g" /etc/nginx/conf.d/service.conf
sed -i "s|\${NGINX_SERVICE_PORT}|${NGINX_SERVICE_PORT}|g" /etc/nginx/conf.d/service.conf
exec "$@"

View File

@@ -1,56 +1,30 @@
server {
listen 80;
server_name localhost;
client_max_body_size 1024m;
user nginx;
worker_processes auto;
# 是否启动 gzip 压缩
gzip on;
# 需要压缩的常见静态资源
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
# 如果文件大于 1k 就启动压缩
gzip_min_length 1k;
# 缓冲区
gzip_buffers 4 16k;
# 压缩的等级
gzip_comp_level 2;
# access_log /var/log/nginx/host.access.log main;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# web history 模式 404
try_files $uri $uri/ /index.html;
}
location /orion-visor/api {
proxy_pass http://service:9200/orion-visor/api;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /orion-visor/keep-alive {
proxy_pass http://service:9200/orion-visor/keep-alive;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
error_log /var/log/nginx/error.log notice;
pid /run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
server_tokens off;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
include /etc/nginx/conf.d/*.conf;
}

56
docker/ui/service.conf Normal file
View File

@@ -0,0 +1,56 @@
server {
listen 80;
server_name localhost;
client_max_body_size 1024m;
# 是否启动 gzip 压缩
gzip on;
# 需要压缩的常见静态资源
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
# 如果文件大于 1k 就启动压缩
gzip_min_length 1k;
# 缓冲区
gzip_buffers 4 16k;
# 压缩的等级
gzip_comp_level 2;
# access_log /var/log/nginx/host.access.log main;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# web history 模式 404
try_files $uri $uri/ /index.html;
}
location /orion-visor/api {
proxy_pass http://${NGINX_SERVICE_HOST}:${NGINX_SERVICE_PORT}/orion-visor/api;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /orion-visor/keep-alive {
proxy_pass http://${NGINX_SERVICE_HOST}:${NGINX_SERVICE_PORT}/orion-visor/keep-alive;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

Before

Width:  |  Height:  |  Size: 232 KiB

After

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

View File

@@ -36,7 +36,7 @@ public interface AppConst extends OrionConst {
/**
* 同 ${orion.version} 迭代时候需要手动更改
*/
String VERSION = "2.5.0";
String VERSION = "2.5.1";
/**
* 同 ${spring.application.name}

View File

@@ -63,13 +63,15 @@ public interface AutoConfigureOrderConst {
int FRAMEWORK_JOB = Integer.MIN_VALUE + 2600;
int FRAMEWORK_JOB_QUARTZ = Integer.MIN_VALUE + 2700;
int FRAMEWORK_JOB_QUARTZ = Integer.MIN_VALUE + 2610;
int FRAMEWORK_JOB_ASYNC = Integer.MIN_VALUE + 2800;
int FRAMEWORK_JOB_ASYNC = Integer.MIN_VALUE + 2620;
int FRAMEWORK_MONITOR = Integer.MIN_VALUE + 2900;
int FRAMEWORK_BIZ_PUSH = Integer.MIN_VALUE + 2700;
int FRAMEWORK_BIZ_OPERATOR_LOG = Integer.MIN_VALUE + 3000;
int FRAMEWORK_BIZ_OPERATOR_LOG = Integer.MIN_VALUE + 7000;
int FRAMEWORK_MONITOR = Integer.MIN_VALUE + 9000;
int FRAMEWORK_BANNER = Integer.MIN_VALUE + 10000;

View File

@@ -100,6 +100,12 @@ public interface ErrorMessage {
String GROUP_ABSENT = "分组不存在";
String METRICS_ABSENT = "指标不存在";
String RULE_ABSENT = "规则不存在";
String ALARM_POLICY_ABSENT = "告警策略不存在";
String HOST_TYPE_ERROR = "主机类型错误";
String HOST_NOT_ENABLED = "{} 主机未启用";
@@ -132,6 +138,8 @@ public interface ErrorMessage {
String CURRENT_USER_UNSUPPORTED_OPT = "当前" + USER_UNSUPPORTED_OPT;
String PUSH_USER_NOT_EMPTY = "推送用户不能为空";
String PATH_NOT_NORMALIZE = "路径不合法";
String OPERATE_ERROR = "操作失败";
@@ -140,6 +148,8 @@ public interface ErrorMessage {
String DECRYPT_ERROR = "数据解密失败";
String GET_REQUEST_URL_ERROR = "获取请求路径失败";
String UNKNOWN_TYPE = "未知类型";
String ERROR_TYPE = "错误的类型";

View File

@@ -0,0 +1,62 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.common.entity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
/**
* 推送用户
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/9/18 21:46
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "PushUser", description = "推送用户")
public class PushUser implements Serializable {
@NotNull
@Schema(description = "用户id")
private Long id;
@NotNull
@Schema(description = "用户名")
private String username;
@Schema(description = "花名")
private String nickname;
@Schema(description = "手机号")
private String mobile;
}

View File

@@ -14,11 +14,11 @@
<url>https://github.com/dromara/orion-visor</url>
<properties>
<revision>2.5.0</revision>
<revision>2.5.1</revision>
<spring.boot.version>2.7.17</spring.boot.version>
<spring.boot.admin.version>2.7.15</spring.boot.admin.version>
<flatten.maven.plugin.version>1.5.0</flatten.maven.plugin.version>
<orion.kit.version>2.0.2</orion.kit.version>
<orion.kit.version>2.0.3</orion.kit.version>
<aspectj.version>1.9.7</aspectj.version>
<lombok.version>1.18.26</lombok.version>
<springdoc.version>1.6.15</springdoc.version>
@@ -156,6 +156,11 @@
<artifactId>orion-visor-spring-boot-starter-influxdb</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.dromara.visor</groupId>
<artifactId>orion-visor-spring-boot-starter-biz-push</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.dromara.visor</groupId>
<artifactId>orion-visor-spring-boot-starter-biz-operator-log</artifactId>

View File

@@ -23,8 +23,11 @@
package org.dromara.visor.framework.biz.operator.log.core.factory;
import cn.orionsec.kit.lang.utils.Arrays1;
import cn.orionsec.kit.spring.SpringHolder;
import org.dromara.visor.framework.biz.operator.log.core.annotation.Module;
import org.dromara.visor.framework.biz.operator.log.core.model.OperatorType;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import javax.annotation.PostConstruct;
@@ -35,7 +38,14 @@ import javax.annotation.PostConstruct;
* @version 1.0.0
* @since 2023/10/13 17:45
*/
public abstract class InitializingOperatorTypes implements OperatorTypeDefinition {
public abstract class InitializingOperatorTypes implements OperatorTypeDefinition, BeanNameAware {
private String beanName;
@Override
public void setBeanName(String name) {
this.beanName = name;
}
@PostConstruct
public void init() {
@@ -55,6 +65,8 @@ public abstract class InitializingOperatorTypes implements OperatorTypeDefinitio
type.setModule(module);
OperatorTypeHolder.set(type);
}
// 自动销毁
((BeanDefinitionRegistry) SpringHolder.getBeanFactory()).removeBeanDefinition(beanName);
}
}

View File

@@ -0,0 +1,37 @@
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.dromara.visor</groupId>
<artifactId>orion-visor-framework</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>orion-visor-spring-boot-starter-biz-push</artifactId>
<name>${project.artifactId}</name>
<packaging>jar</packaging>
<description>项目业务推送包</description>
<url>https://github.com/dromara/orion-visor</url>
<dependencies>
<dependency>
<groupId>org.dromara.visor</groupId>
<artifactId>orion-visor-common</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,121 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.framework.biz.push.configuration;
import cn.orionsec.kit.lang.function.Functions;
import org.dromara.visor.common.constant.AutoConfigureOrderConst;
import org.dromara.visor.framework.biz.push.core.enums.PushChannelEnum;
import org.dromara.visor.framework.biz.push.core.framework.service.PushTemplateFrameworkService;
import org.dromara.visor.framework.biz.push.core.framework.service.PushTemplateFrameworkServiceDelegate;
import org.dromara.visor.framework.biz.push.core.framework.service.WebsiteMessageFrameworkService;
import org.dromara.visor.framework.biz.push.core.listener.PushMessageEventListener;
import org.dromara.visor.framework.biz.push.core.message.PushMessage;
import org.dromara.visor.framework.biz.push.core.service.*;
import org.dromara.visor.framework.biz.push.core.utils.MessageChannelUtils;
import org.dromara.visor.framework.biz.push.core.utils.PushUtils;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 推送自动配置类
*
* @author Shihao Lv
* @version 1.0.0
* @since 2025/9/17 23:04
*/
@AutoConfiguration
@AutoConfigureOrder(AutoConfigureOrderConst.FRAMEWORK_BIZ_PUSH)
public class OrionPushAutoConfiguration {
/**
* @return 钉钉推送服务
*/
@Bean
public DingPushService dingPushService() {
return new DingPushService();
}
/**
* @return 飞书推送服务
*/
@Bean
public FeiShuPushService feiShuPushService() {
return new FeiShuPushService();
}
/**
* @return 企业微信推送服务
*/
@Bean
public WeComPushService weComPushService() {
return new WeComPushService();
}
/**
* @param websiteMessageFrameworkService websiteMessageFrameworkService
* @return 站内信推送服务
*/
@Bean
public WebsitePushService websitePushService(WebsiteMessageFrameworkService websiteMessageFrameworkService) {
return new WebsitePushService(websiteMessageFrameworkService);
}
/**
* @param impl impl
* @return 推送模板服务
*/
@Bean
@Primary
@ConditionalOnBean(PushTemplateFrameworkService.class)
public PushTemplateFrameworkService pushTemplateFrameworkService(PushTemplateFrameworkService impl) {
PushTemplateFrameworkServiceDelegate delegate = new PushTemplateFrameworkServiceDelegate(impl);
// 设置到工具类
PushUtils.setPushTemplateFrameworkService(delegate);
return delegate;
}
/**
* @param pushServices 推送服务
* @return 消息推送事件监听器
*/
@Bean
public PushMessageEventListener pushMessageEventListener(List<IPushService<? extends PushMessage>> pushServices) {
// 服务列表
Map<PushChannelEnum, IPushService<? extends PushMessage>> serviceMap = pushServices.stream()
.collect(Collectors.toMap(
MessageChannelUtils::getPushChannel,
Function.identity(),
Functions.right()));
// 创建监听器
return new PushMessageEventListener(serviceMap);
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.framework.biz.push.core.annotation;
import org.dromara.visor.framework.biz.push.core.enums.PushChannelEnum;
import java.lang.annotation.*;
/**
* 消息渠道
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/9/18 21:58
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MessageChannel {
/**
* 消息渠道
*
* @return channel
*/
PushChannelEnum value();
}

View File

@@ -0,0 +1,104 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.framework.biz.push.core.entity;
import cn.orionsec.kit.lang.able.IJsonObject;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.List;
/**
* 钉钉请求体
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/9/18 18:41
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DingRequestBody implements Serializable, IJsonObject {
/**
* 消息类型
*/
@JSONField(name = "msgtype")
private String msgType;
/**
* markdown 内容
*/
private DingRequestBody.MarkdownPayload markdown;
/**
* at 配置
*/
private DingRequestBody.AtPayload at;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class MarkdownPayload implements Serializable {
/**
* 标题
*/
private String title;
/**
* 内容
*/
private String text;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class AtPayload implements Serializable {
/**
* 被 at 的手机号
*/
private List<String> atMobiles;
/**
* 被 at 的 userId
*/
private List<String> atUserIds;
/**
* 是否 at 所有人
*/
private Boolean isAtAll;
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.framework.biz.push.core.entity;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 钉钉响应体
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/9/18 18:41
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DingResponseBody implements Serializable {
/**
* 错误码值
*/
@JSONField(name = "errcode")
private Integer errCode;
/**
* 错误码描述
*/
@JSONField(name = "errmsg")
private String errMsg;
}

View File

@@ -0,0 +1,81 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.framework.biz.push.core.entity;
import cn.orionsec.kit.lang.able.IJsonObject;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 飞书请求体
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/9/18 18:41
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class FeiShuRequestBody implements Serializable, IJsonObject {
/**
* 时间戳
*/
private Long timestamp;
/**
* 签名
*/
private String sign;
/**
* 消息类型
*/
@JSONField(name = "msg_type")
private String msgType;
/**
* text 内容
*/
private TextPayload content;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class TextPayload implements Serializable {
/**
* 内容
*/
private String text;
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.framework.biz.push.core.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 飞书响应体
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/9/18 18:41
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class FeiShuResponseBody implements Serializable {
/**
* code
*/
private Integer code;
/**
* msg
*/
private String msg;
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.framework.biz.push.core.entity;
import cn.orionsec.kit.lang.able.IJsonObject;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.List;
/**
* 企业微信请求体
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/9/18 18:41
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class WeComRequestBody implements Serializable, IJsonObject {
/**
* 消息类型
*/
@JSONField(name = "msgtype")
private String msgType;
/**
* markdown 内容
*/
private MarkdownPayload markdown;
/**
* 被 at 的 userId
*/
@JSONField(name = "mentioned_list")
private List<String> mentionedList;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class MarkdownPayload implements Serializable {
/**
* 内容
*/
private String content;
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.framework.biz.push.core.entity;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 企业微信响应体
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/9/18 18:41
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class WeComResponseBody implements Serializable {
/**
* 错误码值
*/
@JSONField(name = "errcode")
private Integer errCode;
/**
* 错误码描述
*/
@JSONField(name = "errmsg")
private String errMsg;
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.framework.biz.push.core.enums;
import com.alibaba.fastjson.JSON;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.dromara.visor.framework.biz.push.core.message.*;
/**
* 通知模板类型枚举
*
* @author Shihao Lv
* @version 1.0.0
* @since 2025/9/15 23:20
*/
@Getter
@AllArgsConstructor
public enum PushChannelEnum {
/**
* 站内信
*/
WEBSITE(WebsiteMessage.class),
/**
* 钉钉
*/
DING(DingPushMessage.class),
/**
* 飞书
*/
FEI_SHU(FeiShuPushMessage.class),
/**
* 企业微信
*/
WE_COM(WeComPushMessage.class),
;
public final Class<?> messageClass;
@SuppressWarnings("unchecked")
public <T extends PushMessage> T createMessage(String config) {
try {
return (T) JSON.parseObject(config, messageClass);
} catch (Exception e) {
return null;
}
}
/**
* 根据渠道名称获取枚举
*
* @param channel 渠道名称
* @return 枚举
*/
public static PushChannelEnum of(String channel) {
for (PushChannelEnum value : values()) {
if (value.name().equalsIgnoreCase(channel)) {
return value;
}
}
return WEBSITE;
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.framework.biz.push.core.event;
import org.dromara.visor.framework.biz.push.core.message.PushMessage;
import org.springframework.context.ApplicationEvent;
/**
* 消息推送事件
*
* @author Shihao Lv
* @version 1.0.0
* @since 2025/1/15
*/
public class PushMessageEvent extends ApplicationEvent {
public PushMessageEvent(PushMessage message) {
super(message);
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.framework.biz.push.core.framework.service;
import org.dromara.visor.framework.biz.push.core.message.PushMessage;
/**
* 推送消息模板框架服务
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/9/21 16:26
*/
public interface PushTemplateFrameworkService {
/**
* 根据 templateId 获取消息
*
* @param templateId templateId
* @return message
*/
PushMessage getPushMessageByTemplateId(Long templateId);
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.framework.biz.push.core.framework.service;
import org.dromara.visor.framework.biz.push.core.message.PushMessage;
/**
* 推送消息模板框架服务委托类
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/9/21 16:26
*/
public class PushTemplateFrameworkServiceDelegate implements PushTemplateFrameworkService {
private final PushTemplateFrameworkService delegate;
public PushTemplateFrameworkServiceDelegate(PushTemplateFrameworkService delegate) {
this.delegate = delegate;
}
@Override
public PushMessage getPushMessageByTemplateId(Long templateId) {
return delegate.getPushMessageByTemplateId(templateId);
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.framework.biz.push.core.framework.service;
import org.dromara.visor.framework.biz.push.core.message.WebsiteMessage;
/**
* 发送站内信框架服务
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/9/18 22:09
*/
public interface WebsiteMessageFrameworkService {
/**
* 发送站内信
*
* @param message message
*/
void push(WebsiteMessage message);
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.framework.biz.push.core.listener;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.framework.biz.push.core.enums.PushChannelEnum;
import org.dromara.visor.framework.biz.push.core.event.PushMessageEvent;
import org.dromara.visor.framework.biz.push.core.message.PushMessage;
import org.dromara.visor.framework.biz.push.core.service.IPushService;
import org.springframework.context.ApplicationListener;
import org.springframework.scheduling.annotation.Async;
import java.util.Map;
/**
* 消息推送事件监听器
*
* @author Shihao Lv
* @version 1.0.0
* @since 2025/1/15
*/
@Slf4j
public class PushMessageEventListener implements ApplicationListener<PushMessageEvent> {
private final Map<PushChannelEnum, IPushService<? extends PushMessage>> pushServiceMap;
public PushMessageEventListener(Map<PushChannelEnum, IPushService<? extends PushMessage>> pushServiceMap) {
this.pushServiceMap = pushServiceMap;
}
// FIXME
@Async("asyncExecutor")
@Override
public void onApplicationEvent(PushMessageEvent event) {
try {
// 获取消息
PushMessage message = (PushMessage) event.getSource();
// 发送消息
this.getPushService(message).push(message);
log.info("PushMessageEventListener push success");
} catch (Exception e) {
log.error("PushMessageEventListener push error", e);
}
}
@SuppressWarnings("unchecked")
private <T extends PushMessage> IPushService<T> getPushService(PushMessage message) {
return (IPushService<T>) pushServiceMap.get(message.getChannel());
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.framework.biz.push.core.message;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import org.dromara.visor.common.entity.PushUser;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
/**
* 推送消息基类
*
* @author Shihao Lv
* @version 1.0.0
* @since 2025/9/15 23:28
*/
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "BasePushMessage", description = "推送消息基类")
public abstract class BasePushMessage implements PushMessage, Serializable {
@NotBlank
@Schema(description = "消息模板")
private String template;
@Schema(description = "消息参数")
private Map<String, Object> params;
@Valid
@Schema(description = "推送用户")
private List<PushUser> pushUsers;
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.framework.biz.push.core.message;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import org.dromara.visor.framework.biz.push.core.enums.PushChannelEnum;
import javax.validation.constraints.NotBlank;
/**
* 钉钉推送消息
*
* @author Shihao Lv
* @version 1.0.0
* @since 2025/9/15 23:29
*/
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@Schema(name = "DingPushMessage", description = "钉钉推送消息")
public class DingPushMessage extends BasePushMessage {
@NotBlank
@Schema(description = "webhook")
private String webhook;
@Schema(description = "密钥")
private String secret;
@NotBlank
@Schema(description = "推送标题")
private String title;
@Override
public PushChannelEnum getChannel() {
return PushChannelEnum.DING;
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.framework.biz.push.core.message;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import org.dromara.visor.framework.biz.push.core.enums.PushChannelEnum;
import javax.validation.constraints.NotBlank;
/**
* 飞书推送消息
*
* @author Shihao Lv
* @version 1.0.0
* @since 2025/9/16 00:26
*/
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@Schema(name = "FeiShuPushMessage", description = "飞书推送消息")
public class FeiShuPushMessage extends BasePushMessage {
@NotBlank
@Schema(description = "webhook")
private String webhook;
@Schema(description = "密钥")
private String secret;
@Override
public PushChannelEnum getChannel() {
return PushChannelEnum.FEI_SHU;
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.framework.biz.push.core.message;
import org.dromara.visor.common.entity.PushUser;
import org.dromara.visor.framework.biz.push.core.enums.PushChannelEnum;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
/**
* 推送消息接口
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/9/18 18:42
*/
public interface PushMessage extends Serializable {
/**
* 获取模板
*
* @return 模板
*/
String getTemplate();
/**
* 设置模板
*
* @param template template
*/
void setTemplate(String template);
/**
* 获取参数
*
* @return 参数
*/
Map<String, Object> getParams();
/**
* 设置参数
*
* @param params params
*/
void setParams(Map<String, Object> params);
/**
* 获取推送用户
*
* @return 推送用户
*/
List<PushUser> getPushUsers();
/**
* 设置推送用户列表
*
* @param pushUsers pushUsers
*/
void setPushUsers(List<PushUser> pushUsers);
/**
* 获取推送渠道
*
* @return 推送渠道
*/
PushChannelEnum getChannel();
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.framework.biz.push.core.message;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import org.dromara.visor.framework.biz.push.core.enums.PushChannelEnum;
import javax.validation.constraints.NotBlank;
/**
* 企业微信推送消息
*
* @author Shihao Lv
* @version 1.0.0
* @since 2025/9/15 23:30
*/
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@Schema(name = "WeComPushMessage", description = "企业微信推送消息")
public class WeComPushMessage extends BasePushMessage {
@NotBlank
@Schema(description = "webhook")
private String webhook;
@Override
public PushChannelEnum getChannel() {
return PushChannelEnum.WE_COM;
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.framework.biz.push.core.message;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import org.dromara.visor.framework.biz.push.core.enums.PushChannelEnum;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
/**
* 站内信推送
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/9/18 19:56
*/
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@Schema(name = "WebsiteMessage", description = "站内信消息")
public class WebsiteMessage extends BasePushMessage {
@NotBlank
@Size(max = 10)
@Schema(description = "消息分类")
private String messageClassify;
@NotBlank
@Size(max = 32)
@Schema(description = "消息类型")
private String messageType;
@Schema(description = "消息关联")
private String relKey;
@NotBlank
@Size(max = 128)
@Schema(description = "标题")
private String title;
@Override
public PushChannelEnum getChannel() {
return PushChannelEnum.WEBSITE;
}
}

View File

@@ -0,0 +1,126 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.framework.biz.push.core.service;
import cn.orionsec.kit.lang.utils.Strings;
import cn.orionsec.kit.lang.utils.collect.Lists;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.common.constant.Const;
import org.dromara.visor.common.entity.PushUser;
import org.dromara.visor.common.utils.Valid;
import org.dromara.visor.framework.biz.push.core.message.PushMessage;
import java.util.stream.Collectors;
/**
* 消息推送抽象服务
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/9/18 18:51
*/
@Slf4j
public abstract class BasePushService<Message extends PushMessage> implements IPushService<Message> {
@Override
public void push(Message message) {
try {
// 验证消息
this.validateMessage(message);
} catch (Exception e) {
log.error("BasePushService validateMessage message: {}", JSON.toJSONString(message), e);
return;
}
// 请求路径
String url = this.buildRequestUrl(message);
// 构建请求体
String body = this.buildRequestBody(message);
// 发送请求
this.sendRequest(message, url, body);
}
/**
* 验证消息
*
* @param message message
*/
protected void validateMessage(Message message) {
// 验证消息
Valid.valid(message);
}
/**
* 构建请求 url
*
* @param message message
* @return url
*/
protected abstract String buildRequestUrl(Message message);
/**
* 构建请求体
*
* @param message message
* @return body
*/
protected abstract String buildRequestBody(Message message);
/**
* 发送请求
*
* @param message message
* @param url url
* @param body body
*/
protected abstract void sendRequest(Message message, String url, String body);
/**
* 追加 at 用户
*
* @param message message
* @param content content
* @return content
*/
protected String appendAtUsers(Message message, String content) {
if (Lists.isEmpty(message.getPushUsers())) {
return content;
}
return content + Const.LF + this.getAtUsers(message);
}
/**
* 获取 @ 的用户
*
* @param message message
* @return users
*/
protected String getAtUsers(Message message) {
return Lists.stream(message.getPushUsers())
.map(PushUser::getNickname)
.filter(Strings::isNotBlank)
.map(s -> Const.AT + s)
.collect(Collectors.joining(Const.SPACE));
}
}

View File

@@ -0,0 +1,114 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.framework.biz.push.core.service;
import cn.orionsec.kit.http.ok.OkRequests;
import cn.orionsec.kit.http.ok.OkResponse;
import cn.orionsec.kit.lang.constant.StandardContentType;
import cn.orionsec.kit.lang.utils.Exceptions;
import cn.orionsec.kit.lang.utils.Strings;
import cn.orionsec.kit.lang.utils.Valid;
import cn.orionsec.kit.lang.utils.codec.Base64s;
import cn.orionsec.kit.lang.utils.crypto.Signatures;
import cn.orionsec.kit.lang.utils.math.Hex;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.common.constant.ErrorMessage;
import org.dromara.visor.framework.biz.push.core.annotation.MessageChannel;
import org.dromara.visor.framework.biz.push.core.entity.DingRequestBody;
import org.dromara.visor.framework.biz.push.core.entity.DingResponseBody;
import org.dromara.visor.framework.biz.push.core.enums.PushChannelEnum;
import org.dromara.visor.framework.biz.push.core.message.DingPushMessage;
import org.dromara.visor.framework.biz.push.core.utils.MessageUtils;
/**
* 钉钉推送服务类
* <p>
* <a href="https://open.dingtalk.com/document/dingstart/obtain-the-webhook-address-of-a-custom-robot">doc</a>
*
* @author Shihao Lv
* @version 1.0.0
* @since 2025/9/17 23:13
*/
@Slf4j
@MessageChannel(value = PushChannelEnum.DING)
public class DingPushService extends BasePushService<DingPushMessage> {
private static final String MARKDOWN = "markdown";
private static final Integer SUCCESS_CODE = 0;
@Override
protected String buildRequestUrl(DingPushMessage message) {
String secret = message.getSecret();
if (Strings.isBlank(secret)) {
return message.getWebhook();
}
// 加签
try {
long timestamp = System.currentTimeMillis();
String plainText = timestamp + "\n" + secret;
String hexSign = Signatures.hmacSha256(plainText, secret);
if (hexSign == null) {
log.error("DingPushService-sign error plain: {}", plainText);
throw Exceptions.argument(ErrorMessage.GET_REQUEST_URL_ERROR);
}
byte[] signData = Hex.hexToBytes(hexSign);
String sign = Base64s.encodeToString(signData);
// 实际请求地址
return message.getWebhook() + "&timestamp=" + timestamp + "&sign=" + sign;
} catch (Exception e) {
log.error("DingPushService-buildRequestUrl error", e);
throw e;
}
}
@Override
protected String buildRequestBody(DingPushMessage message) {
// 格式化内容
String formattedContent = MessageUtils.format(message.getTemplate(), message.getParams());
String formattedTitle = MessageUtils.format(message.getTitle(), message.getParams());
// at 用户
formattedContent = this.appendAtUsers(message, formattedContent);
// 构建请求体
return DingRequestBody.builder()
.msgType(MARKDOWN)
.markdown(DingRequestBody.MarkdownPayload.builder()
.title(formattedTitle)
.text(formattedContent)
.build())
.build()
.toJsonString();
}
@Override
protected void sendRequest(DingPushMessage message, String url, String body) {
// 发送请求
OkResponse response = OkRequests.post(url, StandardContentType.APPLICATION_JSON_UTF8, body);
DingResponseBody responseBody = JSON.parseObject(response.getBodyString(), DingResponseBody.class);
// 验证发送结果
Valid.eq(responseBody.getErrCode(), SUCCESS_CODE, responseBody.getErrMsg());
}
}

View File

@@ -0,0 +1,118 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.framework.biz.push.core.service;
import cn.orionsec.kit.http.ok.OkRequests;
import cn.orionsec.kit.http.ok.OkResponse;
import cn.orionsec.kit.lang.constant.Const;
import cn.orionsec.kit.lang.constant.StandardContentType;
import cn.orionsec.kit.lang.utils.Strings;
import cn.orionsec.kit.lang.utils.Valid;
import cn.orionsec.kit.lang.utils.codec.Base64s;
import cn.orionsec.kit.lang.utils.crypto.Signatures;
import cn.orionsec.kit.lang.utils.crypto.enums.SecretKeySpecMode;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.framework.biz.push.core.annotation.MessageChannel;
import org.dromara.visor.framework.biz.push.core.entity.FeiShuRequestBody;
import org.dromara.visor.framework.biz.push.core.entity.FeiShuResponseBody;
import org.dromara.visor.framework.biz.push.core.enums.PushChannelEnum;
import org.dromara.visor.framework.biz.push.core.message.FeiShuPushMessage;
import org.dromara.visor.framework.biz.push.core.utils.MessageUtils;
/**
* 飞书推送服务类
* <p>
* <a href="https://open.feishu.cn/document/client-docs/bot-v3/add-custom-bot?lang=zh-CN">doc</a>
*
* @author Shihao Lv
* @version 1.0.0
* @since 2025/9/17 23:13
*/
@Slf4j
@MessageChannel(value = PushChannelEnum.FEI_SHU)
public class FeiShuPushService extends BasePushService<FeiShuPushMessage> {
private static final String TEXT = "text";
private static final Integer SUCCESS_CODE = 0;
@Override
protected String buildRequestUrl(FeiShuPushMessage message) {
return message.getWebhook();
}
/**
* 生成签名
*
* @param message message
* @param timestamp timestamp
* @return sign
*/
protected String buildSign(FeiShuPushMessage message, long timestamp) {
String secret = message.getSecret();
if (Strings.isBlank(secret)) {
return null;
}
// 加签
try {
String stringToSign = timestamp + "\n" + secret;
byte[] signData = Signatures.hmacHashSignBytes(new byte[]{}, Strings.bytes(stringToSign), SecretKeySpecMode.HMAC_SHA256);
return Base64s.encodeToString(signData);
} catch (Exception e) {
log.error("FeiShuPushService-buildSign error", e);
return null;
}
}
@Override
protected String buildRequestBody(FeiShuPushMessage message) {
long timestamp = System.currentTimeMillis() / Const.MS_S_1;
// 加签
String sign = this.buildSign(message, timestamp);
// 格式化内容
String formattedContent = MessageUtils.format(message.getTemplate(), message.getParams());
// at 的用户
formattedContent = this.appendAtUsers(message, formattedContent);
// 构建请求体
return FeiShuRequestBody.builder()
.timestamp(timestamp)
.sign(sign)
.msgType(TEXT)
.content(FeiShuRequestBody.TextPayload.builder()
.text(formattedContent)
.build())
.build()
.toJsonString();
}
@Override
protected void sendRequest(FeiShuPushMessage message, String url, String body) {
// 发送请求
OkResponse response = OkRequests.post(url, StandardContentType.APPLICATION_JSON_UTF8, body);
FeiShuResponseBody responseBody = JSON.parseObject(response.getBodyString(), FeiShuResponseBody.class);
// 验证发送结果
Valid.eq(responseBody.getCode(), SUCCESS_CODE, responseBody.getMsg());
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.framework.biz.push.core.service;
import org.dromara.visor.framework.biz.push.core.message.PushMessage;
/**
* 消息推送服务
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/9/17 17:17
*/
public interface IPushService<Message extends PushMessage> {
/**
* 推送消息
*
* @param message message
*/
void push(Message message);
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.framework.biz.push.core.service;
import cn.orionsec.kit.http.ok.OkRequests;
import cn.orionsec.kit.http.ok.OkResponse;
import cn.orionsec.kit.lang.constant.StandardContentType;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.common.utils.Valid;
import org.dromara.visor.framework.biz.push.core.annotation.MessageChannel;
import org.dromara.visor.framework.biz.push.core.entity.WeComRequestBody;
import org.dromara.visor.framework.biz.push.core.entity.WeComResponseBody;
import org.dromara.visor.framework.biz.push.core.enums.PushChannelEnum;
import org.dromara.visor.framework.biz.push.core.message.WeComPushMessage;
import org.dromara.visor.framework.biz.push.core.utils.MessageUtils;
/**
* 企业微信推送服务
* <p>
* <a href="https://developer.work.weixin.qq.com/document/path/99110">docs</a>
*
* @author Shihao Lv
* @version 1.0.0
* @since 2025/9/17 23:13
*/
@Slf4j
@MessageChannel(value = PushChannelEnum.WE_COM)
public class WeComPushService extends BasePushService<WeComPushMessage> {
private static final String MAKRDOWN = "markdown";
private static final Integer SUCCESS_CODE = 0;
@Override
protected String buildRequestUrl(WeComPushMessage message) {
return message.getWebhook();
}
@Override
protected String buildRequestBody(WeComPushMessage message) {
// 格式化内容
String formattedContent = MessageUtils.format(message.getTemplate(), message.getParams());
// at 用户
formattedContent = this.appendAtUsers(message, formattedContent);
// 构建请求体
WeComRequestBody.WeComRequestBodyBuilder builder = WeComRequestBody.builder()
.msgType(MAKRDOWN)
.markdown(WeComRequestBody.MarkdownPayload.builder()
.content(formattedContent)
.build());
return builder.build().toJsonString();
}
@Override
protected void sendRequest(WeComPushMessage message, String url, String body) {
// 发送请求
OkResponse response = OkRequests.post(url, StandardContentType.APPLICATION_JSON_UTF8, body);
WeComResponseBody responseBody = JSON.parseObject(response.getBodyString(), WeComResponseBody.class);
// 验证发送结果
Valid.eq(responseBody.getErrCode(), SUCCESS_CODE, responseBody.getErrMsg());
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.framework.biz.push.core.service;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.framework.biz.push.core.annotation.MessageChannel;
import org.dromara.visor.framework.biz.push.core.enums.PushChannelEnum;
import org.dromara.visor.framework.biz.push.core.framework.service.WebsiteMessageFrameworkService;
import org.dromara.visor.framework.biz.push.core.message.WebsiteMessage;
import org.dromara.visor.framework.biz.push.core.utils.MessageUtils;
/**
* 站内信推送服务
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/9/18 22:13
*/
@Slf4j
@MessageChannel(value = PushChannelEnum.WEBSITE)
public class WebsitePushService extends BasePushService<WebsiteMessage> {
private final WebsiteMessageFrameworkService websiteMessageFrameworkService;
public WebsitePushService(WebsiteMessageFrameworkService websiteMessageFrameworkService) {
this.websiteMessageFrameworkService = websiteMessageFrameworkService;
}
@Override
protected String buildRequestUrl(WebsiteMessage message) {
return null;
}
@Override
protected String buildRequestBody(WebsiteMessage message) {
// 格式化内容
message.setTemplate(MessageUtils.format(message.getTemplate(), message.getParams()));
// 格式化标题
message.setTitle(MessageUtils.format(message.getTitle(), message.getParams()));
return null;
}
@Override
protected void sendRequest(WebsiteMessage message, String url, String body) {
websiteMessageFrameworkService.push(message);
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.framework.biz.push.core.utils;
import cn.orionsec.kit.lang.utils.Strings;
import org.dromara.visor.framework.biz.push.core.annotation.MessageChannel;
import org.dromara.visor.framework.biz.push.core.enums.PushChannelEnum;
import org.dromara.visor.framework.biz.push.core.message.PushMessage;
import org.dromara.visor.framework.biz.push.core.service.IPushService;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.core.annotation.AnnotationUtils;
/**
* 消息渠道工具类
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/9/18 22:05
*/
public class MessageChannelUtils {
private MessageChannelUtils() {
}
/**
* 获取推送渠道
*
* @param service service
* @return channel
*/
public static PushChannelEnum getPushChannel(IPushService<? extends PushMessage> service) {
// 获取类型注解
MessageChannel messageChannel = AnnotationUtils.findAnnotation(service.getClass(), MessageChannel.class);
if (messageChannel == null) {
throw new BeanInitializationException(Strings.format("Push service [{}] not found @MessageChannel annotation", service.getClass().getName()));
}
return messageChannel.value();
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.framework.biz.push.core.utils;
import cn.orionsec.kit.lang.utils.json.matcher.NoMatchStrategy;
import cn.orionsec.kit.lang.utils.json.matcher.ReplacementFormatter;
import cn.orionsec.kit.lang.utils.json.matcher.ReplacementFormatters;
import java.util.Map;
/**
* 消息工具类
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/1/16 17:28
*/
public class MessageUtils {
private static final ReplacementFormatter FORMATTER = ReplacementFormatters.create("@{{ ", " }}")
.noMatchStrategy(NoMatchStrategy.KEEP);
private MessageUtils() {
}
/**
* 替换模板
*
* @param template template
* @param params params
* @return template
*/
public static String format(String template, Map<String, Object> params) {
if (params == null) {
return template;
}
return FORMATTER.format(template, params);
}
/**
* 替换模板
*
* @param template template
* @param json json
* @return template
*/
public static String format(String template, String json) {
return FORMATTER.format(template, json);
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.framework.biz.push.core.utils;
import cn.orionsec.kit.lang.utils.Exceptions;
import cn.orionsec.kit.spring.SpringHolder;
import org.dromara.visor.common.entity.PushUser;
import org.dromara.visor.framework.biz.push.core.event.PushMessageEvent;
import org.dromara.visor.framework.biz.push.core.framework.service.PushTemplateFrameworkService;
import org.dromara.visor.framework.biz.push.core.message.PushMessage;
import java.util.List;
import java.util.Map;
/**
* 消息推送工具类
*
* @author Shihao Lv
* @version 1.0.0
* @since 2025/9/17 23:13
*/
public class PushUtils {
private static PushTemplateFrameworkService pushTemplateFrameworkService;
/**
* 发布消息推送事件
*
* @param pushMessage 推送消息配置
*/
public static void push(PushMessage pushMessage) {
SpringHolder.publishEvent(new PushMessageEvent(pushMessage));
}
/**
* 根据模板推送消息
*
* @param templateId 模板ID
* @param params 模板参数
*/
public static void pushTemplate(Long templateId, Map<String, Object> params) {
pushTemplate(templateId, params, null);
}
/**
* 根据模板推送消息
*
* @param templateId 模板ID
* @param params 模板参数
* @param pushUsers 推送用户
*/
public static void pushTemplate(Long templateId, Map<String, Object> params, List<PushUser> pushUsers) {
PushMessage pushMessage = pushTemplateFrameworkService.getPushMessageByTemplateId(templateId);
if (pushMessage == null) {
return;
}
// 设置模板参数
pushMessage.setParams(params);
pushMessage.setPushUsers(pushUsers);
// 发布消息
push(pushMessage);
}
public static void setPushTemplateFrameworkService(PushTemplateFrameworkService pushTemplateFrameworkService) {
if (PushUtils.pushTemplateFrameworkService != null) {
// unmodified
throw Exceptions.state();
}
PushUtils.pushTemplateFrameworkService = pushTemplateFrameworkService;
}
}

View File

@@ -0,0 +1 @@
org.dromara.visor.framework.biz.push.configuration.OrionPushAutoConfiguration

View File

@@ -54,8 +54,6 @@ import java.net.ConnectException;
public class OrionInfluxdbAutoConfiguration {
/**
* TODO 重连
*
* @param config config
* @return influxdb 客户端
*/

View File

@@ -54,19 +54,19 @@ public class BaseDO implements Serializable {
private Long id;
@Schema(description = "创建时间")
@TableField(fill = FieldFill.INSERT)
@TableField(value = "create_time", fill = FieldFill.INSERT)
private Date createTime;
@Schema(description = "修改时间")
@TableField(fill = FieldFill.INSERT_UPDATE)
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
@Schema(description = "创建人")
@TableField(fill = FieldFill.INSERT, jdbcType = JdbcType.VARCHAR)
@TableField(value = "creator", fill = FieldFill.INSERT, jdbcType = JdbcType.VARCHAR)
private String creator;
@Schema(description = "修改人")
@TableField(fill = FieldFill.INSERT_UPDATE, update = "IFNULL(#{et.updater}, updater)", jdbcType = JdbcType.VARCHAR)
@TableField(value = "updater", fill = FieldFill.INSERT_UPDATE, update = "IFNULL(#{et.updater}, updater)", jdbcType = JdbcType.VARCHAR)
private String updater;
/**
@@ -75,7 +75,7 @@ public class BaseDO implements Serializable {
*/
@Schema(description = "是否删除 0未删除 1已删除")
@TableLogic
@TableField(fill = FieldFill.INSERT, jdbcType = JdbcType.TINYINT)
@TableField(value = "deleted", fill = FieldFill.INSERT, jdbcType = JdbcType.TINYINT)
private Boolean deleted;
}

View File

@@ -90,6 +90,20 @@ public class DomainFillUtils {
}
}
/**
* 清空基础字段
*
* @param baseDO baseDO
*/
public static void clearBaseFields(BaseDO baseDO) {
baseDO.setId(null);
baseDO.setCreateTime(null);
baseDO.setUpdateTime(null);
baseDO.setCreator(null);
baseDO.setUpdater(null);
baseDO.setDeleted(null);
}
public static void setSecurityHolder(SecurityHolder securityHolder) {
if (DomainFillUtils.securityHolder != null) {
// unmodified

View File

@@ -4,7 +4,7 @@
:create-card-position="false"
:loading="loading"
:field-config="cardFieldConfig"
:list="list"
:list="renderList"
:pagination="pagination"
:card-layout-cols="cardColLayout"
:filter-count="filterCount"
@@ -140,7 +140,7 @@
const { toOptions, getDictValue } = useDictStore();
#end
const list = ref<Array<${vue.featureEntity}QueryResponse>>([]);
const renderList = ref<Array<${vue.featureEntity}QueryResponse>>([]);
const formRef = ref();
const formModel = reactive<${vue.featureEntity}QueryRequest>({
searchValue: undefined,
@@ -196,7 +196,7 @@
try {
setLoading(true);
const { data } = await get${vue.featureEntity}Page(queryOrder.markOrderly(request));
list.value = data.rows;
renderList.value = data.rows;
pagination.total = data.total;
pagination.current = request.page;
pagination.pageSize = request.limit;

View File

@@ -57,10 +57,12 @@
<script lang="ts" setup>
import type { ${vue.featureEntity}UpdateRequest } from '@/api/${vue.module}/${vue.feature}';
import type { FormHandle } from '@/types/form';
import { ref } from 'vue';
import useLoading from '@/hooks/loading';
import useVisible from '@/hooks/visible';
import formRules from '../types/form.rules';
import { assignOmitRecord } from '@/utils';
#if($dictMap.entrySet().size() > 0)
import { #foreach($entry in ${dictMap.entrySet()})${entry.value.keyField}#if($foreach.hasNext), #end#end } from '../types/const';
#else
@@ -81,14 +83,14 @@
#end
const title = ref<string>();
const isAddHandle = ref<boolean>(true);
const formHandle = ref<FormHandle>('add');
const formRef = ref<any>();
const formModel = ref<${vue.featureEntity}UpdateRequest>({});
const defaultForm = (): ${vue.featureEntity}UpdateRequest => {
return {
#foreach($field in ${table.fields})
${field.propertyName}: undefined,
${field.propertyName}: undefined,
#end
};
};
@@ -96,24 +98,19 @@
// 打开新增
const openAdd = () => {
title.value = '添加${table.comment}';
isAddHandle.value = true;
renderForm({ ...defaultForm() });
formHandle.value = 'add';
formModel.value = assignOmitRecord({ ...defaultForm() });
setVisible(true);
};
// 打开修改
const openUpdate = (record: any) => {
title.value = '修改${table.comment}';
isAddHandle.value = false;
renderForm({ ...defaultForm(), ...record });
formHandle.value = 'update';
formModel.value = assignOmitRecord({ ...defaultForm(), ...record });
setVisible(true);
};
// 渲染表单
const renderForm = (record: any) => {
formModel.value = Object.assign({}, record);
};
defineExpose({ openAdd, openUpdate });
// 确定
@@ -125,7 +122,7 @@
if (error) {
return false;
}
if (isAddHandle.value) {
if (formHandle.value === 'add') {
// 新增
await create${vue.featureEntity}(formModel.value);
Message.success('创建成功');

View File

@@ -61,10 +61,12 @@
<script lang="ts" setup>
import type { ${vue.featureEntity}UpdateRequest } from '@/api/${vue.module}/${vue.feature}';
import type { FormHandle } from '@/types/form';
import { ref } from 'vue';
import useLoading from '@/hooks/loading';
import useVisible from '@/hooks/visible';
import formRules from '../types/form.rules';
import { assignOmitRecord } from '@/utils';
#if($dictMap.entrySet().size() > 0)
import { #foreach($entry in ${dictMap.entrySet()})${entry.value.keyField}#if($foreach.hasNext), #end#end } from '../types/const';
#else
@@ -85,7 +87,7 @@
#end
const title = ref<string>();
const isAddHandle = ref<boolean>(true);
const formHandle = ref<FormHandle>('add');
const formRef = ref<any>();
const formModel = ref<${vue.featureEntity}UpdateRequest>({});
@@ -100,24 +102,19 @@
// 打开新增
const openAdd = () => {
title.value = '添加${table.comment}';
isAddHandle.value = true;
renderForm({ ...defaultForm() });
formHandle.value = 'add';
formModel.value = assignOmitRecord({ ...defaultForm() });
setVisible(true);
};
// 打开修改
const openUpdate = (record: any) => {
title.value = '修改${table.comment}';
isAddHandle.value = false;
renderForm({ ...defaultForm(), ...record });
formHandle.value = 'update';
formModel.value = assignOmitRecord({ ...defaultForm(), ...record });
setVisible(true);
};
// 渲染表单
const renderForm = (record: any) => {
formModel.value = Object.assign({}, record);
};
defineExpose({ openAdd, openUpdate });
// 确定
@@ -129,7 +126,7 @@
if (error) {
return false;
}
if (isAddHandle.value) {
if (formHandle.value === 'add') {
// 新增
await create${vue.featureEntity}(formModel.value);
Message.success('创建成功');

View File

@@ -146,7 +146,7 @@
<script lang="ts" setup>
import type { ${vue.featureEntity}QueryRequest, ${vue.featureEntity}QueryResponse } from '@/api/${vue.module}/${vue.feature}';
import { reactive, ref, onMounted } from 'vue';
import { batchDelete${vue.featureEntity}, delete${vue.featureEntity}, get${vue.featureEntity}Page } from '@/api/${vue.module}/${vue.feature}';
import {#if($vue.enableRowSelection) batchDelete${vue.featureEntity},#end delete${vue.featureEntity}, get${vue.featureEntity}Page } from '@/api/${vue.module}/${vue.feature}';
import { Message } from '@arco-design/web-vue';
import useLoading from '@/hooks/loading';
import columns from '../types/table.columns';

View File

@@ -5,17 +5,17 @@
<${vue.feature}-table v-if="renderTable"
ref="table"
@open-add="() =>#if($vue.enableDrawerForm) drawer#else modal#end.openAdd()"
@open-update="(e) =>#if($vue.enableDrawerForm) drawer#else modal#end.openUpdate(e)" />
@open-update="(e: any) =>#if($vue.enableDrawerForm) drawer#else modal#end.openUpdate(e)" />
<!-- 列表-卡片 -->
<${vue.feature}-card-list v-else
ref="card"
@open-add="() =>#if($vue.enableDrawerForm) drawer#else modal#end.openAdd()"
@open-update="(e) =>#if($vue.enableDrawerForm) drawer#else modal#end.openUpdate(e)" />
@open-update="(e: any) =>#if($vue.enableDrawerForm) drawer#else modal#end.openUpdate(e)" />
#else
<!-- 列表-表格 -->
<${vue.feature}-table ref="table"
@open-add="() =>#if($vue.enableDrawerForm) drawer#else modal#end.openAdd()"
@open-update="(e) =>#if($vue.enableDrawerForm) drawer#else modal#end.openUpdate(e)" />
@open-update="(e: any) =>#if($vue.enableDrawerForm) drawer#else modal#end.openUpdate(e)" />
#end
#if($vue.enableDrawerForm)
<!-- 添加修改抽屉 -->

View File

@@ -30,7 +30,6 @@ const fieldConfig = {
render: ({ record }) => {
return dateFormat(new Date(record.createTime));
},
default: true,
}, {
label: '修改时间',
dataIndex: 'updateTime',
@@ -38,6 +37,7 @@ const fieldConfig = {
render: ({ record }) => {
return dateFormat(new Date(record.updateTime));
},
default: true,
}, {
label: '创建人',
dataIndex: 'creator',
@@ -45,7 +45,6 @@ const fieldConfig = {
width: 148,
ellipsis: true,
tooltip: true,
default: true,
}, {
label: '修改人',
dataIndex: 'updater',
@@ -53,6 +52,7 @@ const fieldConfig = {
width: 148,
ellipsis: true,
tooltip: true,
default: true,
}
] as CardField[]
} as CardFieldConfig;

View File

@@ -1,28 +1,17 @@
import type { FieldRule } from '@arco-design/web-vue';
const rules = {
#foreach($field in ${table.fields})
#if("$!field.propertyName" != "id")
#if(${field.propertyType} == 'String' && "$field.metaInfo.jdbcType" != "LONGVARCHAR")
export const ${field.propertyName} = [{
required: true,
message: '请输入${field.comment}'
}, {
maxLength: $field.metaInfo.length,
message: '${field.comment}长度不能大于$field.metaInfo.length位'
}] as FieldRule[];
#else
export const ${field.propertyName} = [{
required: true,
message: '请输入${field.comment}'
}] as FieldRule[];
#end
#end
#end
export default {
#foreach($field in ${table.fields})
#if("$!field.propertyName" != "id")
${field.propertyName},
${field.propertyName}: [{
required: true,
message: '请输入${field.comment}'
}#if(${field.propertyType} == 'String' && "$field.metaInfo.jdbcType" != "LONGVARCHAR"), {
maxLength: $field.metaInfo.length,
message: '${field.comment}长度不能大于$field.metaInfo.length位'
}#end],
#end
#end
} as Record<string, FieldRule | FieldRule[]>;
export default rules;

View File

@@ -37,7 +37,6 @@ const columns = [
render: ({ record }) => {
return dateFormat(new Date(record.createTime));
},
default: true,
}, {
title: '修改时间',
dataIndex: 'updateTime',
@@ -47,15 +46,18 @@ const columns = [
render: ({ record }) => {
return dateFormat(new Date(record.updateTime));
},
default: true,
}, {
title: '创建人',
dataIndex: 'creator',
slotName: 'creator',
default: true,
width: 148,
}, {
title: '修改人',
dataIndex: 'updater',
slotName: 'updater',
width: 148,
default: true,
}, {
title: '操作',
slotName: 'handle',

View File

@@ -357,8 +357,6 @@ public class RedisMaps extends RedisUtils {
*
* @param key key
* @param hashKeys hashKeys
* @param keyMapper keyMapper
* @param <K> K
* @param <V> V
* @return values
*/
@@ -371,9 +369,7 @@ public class RedisMaps extends RedisUtils {
*
* @param key key
* @param hashKeys hashKeys
* @param keyMapper keyMapper
* @param clazz clazz
* @param <K> K
* @param <V> V
* @return values
*/

View File

@@ -272,6 +272,16 @@ public class RedisStrings extends RedisUtils {
return getList(keys, s -> JSON.parseArray(s, type));
}
/**
* 设置值
*
* @param key key
* @param value value
*/
public static void set(String key, Object value) {
set(key, null, value);
}
/**
* 设置值
*

View File

@@ -49,6 +49,26 @@ public class RedisUtils {
protected RedisUtils() {
}
/**
* 是否包含 key
*
* @param define define
* @return has
*/
public static boolean hasKey(CacheKeyDefine define) {
return hasKey(define.getKey());
}
/**
* 是否包含 key
*
* @param key key
* @return has
*/
public static boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}
/**
* 扫描 key
*

View File

@@ -25,6 +25,7 @@ package org.dromara.visor.framework.security.core.utils;
import cn.orionsec.kit.lang.constant.StandardHttpHeader;
import cn.orionsec.kit.lang.utils.Strings;
import org.dromara.visor.common.constant.Const;
import org.dromara.visor.common.constant.ErrorCode;
import org.dromara.visor.common.security.LoginUser;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
@@ -90,6 +91,19 @@ public class SecurityUtils {
return authentication.getPrincipal() instanceof LoginUser ? (LoginUser) authentication.getPrincipal() : null;
}
/**
* 获取当前用户 不为空
*
* @return 当前用户
*/
public static LoginUser getLoginUserNotNull() {
LoginUser user = getLoginUser();
if (user == null) {
throw ErrorCode.UNAUTHORIZED.exception();
}
return user;
}
/**
* 获取当前 userId
*

View File

@@ -39,8 +39,6 @@ import org.springframework.context.annotation.Bean;
* @author Jiahang Li
* @version 1.0.0
* @since 2023/6/30 16:21
* <p>
* TODO 后续添加 FAST MINIO OSS 等
*/
@AutoConfiguration
@AutoConfigureOrder(AutoConfigureOrderConst.FRAMEWORK_STORAGE)

View File

@@ -62,8 +62,6 @@ import java.util.List;
/**
* web 配置类
* <p>
* TODO XSS 后续选择性的配置
*
* @author Jiahang Li
* @version 1.0.0
@@ -85,7 +83,7 @@ public class OrionWebAutoConfiguration implements WebMvcConfigurer {
// 公共 api 前缀
AntPathMatcher antPathMatcher = new AntPathMatcher(".");
configurer.addPathPrefix(orionApiPrefix, clazz -> clazz.isAnnotationPresent(RestController.class)
&& antPathMatcher.match("org.dromara.visor.module.**.controller.**", clazz.getPackage().getName()));
&& antPathMatcher.match("org.dromara.visor.**.controller.**", clazz.getPackage().getName()));
}
@Override

View File

@@ -33,6 +33,7 @@
<module>orion-visor-spring-boot-starter-monitor</module>
<module>orion-visor-spring-boot-starter-test</module>
<module>orion-visor-spring-boot-starter-influxdb</module>
<module>orion-visor-spring-boot-starter-biz-push</module>
<module>orion-visor-spring-boot-starter-biz-operator-log</module>
</modules>

View File

@@ -134,6 +134,10 @@
<groupId>org.dromara.visor</groupId>
<artifactId>orion-visor-spring-boot-starter-influxdb</artifactId>
</dependency>
<dependency>
<groupId>org.dromara.visor</groupId>
<artifactId>orion-visor-spring-boot-starter-biz-push</artifactId>
</dependency>
<dependency>
<groupId>org.dromara.visor</groupId>
<artifactId>orion-visor-spring-boot-starter-biz-operator-log</artifactId>

View File

@@ -39,9 +39,9 @@ import java.util.function.Function;
*/
public class ReplaceVersion {
private static final String TARGET_VERSION = "2.4.3";
private static final String TARGET_VERSION = "2.5.0";
private static final String REPLACE_VERSION = "2.5.0";
private static final String REPLACE_VERSION = "2.5.1";
private static final String PATH = new File("").getAbsolutePath();

View File

@@ -23,6 +23,7 @@
package org.dromara.visor.module.asset.api;
import org.dromara.visor.module.asset.entity.dto.host.HostAgentLogDTO;
import org.dromara.visor.module.asset.entity.dto.host.HostBaseDTO;
import java.util.List;
import java.util.Map;
@@ -50,7 +51,15 @@ public interface HostAgentApi {
* @param agentKeyList agentKeyList
* @return nameMap
*/
Map<String, String> getCacheNameByAgentKey(List<String> agentKeyList);
Map<String, String> getNameCacheByAgentKey(List<String> agentKeyList);
/**
* 获取缓存名称
*
* @param agentKey agentKey
* @return nameMap
*/
HostBaseDTO getHostCacheByAgentKey(String agentKey);
/**
* 获取探针版本

View File

@@ -0,0 +1,47 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.entity.event;
import cn.orionsec.kit.lang.utils.collect.Lists;
import org.springframework.context.ApplicationEvent;
import java.util.List;
/**
* agent 下线事件
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/9/19 20:57
*/
public class AgentOfflineEvent extends ApplicationEvent {
public AgentOfflineEvent(String agentKey) {
this(Lists.singleton(agentKey));
}
public AgentOfflineEvent(List<String> agentKeys) {
super(agentKeys);
}
}

View File

@@ -56,10 +56,6 @@
<groupId>org.dromara.visor</groupId>
<artifactId>orion-visor-spring-boot-starter-log</artifactId>
</dependency>
<dependency>
<groupId>org.dromara.visor</groupId>
<artifactId>orion-visor-spring-boot-starter-biz-operator-log</artifactId>
</dependency>
<dependency>
<groupId>org.dromara.visor</groupId>
<artifactId>orion-visor-spring-boot-starter-desensitize</artifactId>
@@ -96,6 +92,14 @@
<groupId>org.dromara.visor</groupId>
<artifactId>orion-visor-spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.dromara.visor</groupId>
<artifactId>orion-visor-spring-boot-starter-biz-push</artifactId>
</dependency>
<dependency>
<groupId>org.dromara.visor</groupId>
<artifactId>orion-visor-spring-boot-starter-biz-operator-log</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -26,18 +26,22 @@ import cn.orionsec.kit.lang.define.cache.TimedCache;
import cn.orionsec.kit.lang.define.cache.TimedCacheBuilder;
import cn.orionsec.kit.lang.utils.Strings;
import cn.orionsec.kit.lang.utils.collect.Lists;
import cn.orionsec.kit.lang.utils.io.Streams;
import org.dromara.visor.common.constant.Const;
import org.dromara.visor.framework.redis.core.utils.RedisStrings;
import org.dromara.visor.module.asset.api.HostAgentApi;
import org.dromara.visor.module.asset.convert.HostAgentLogProviderConvert;
import org.dromara.visor.module.asset.convert.HostProviderConvert;
import org.dromara.visor.module.asset.dao.HostAgentLogDAO;
import org.dromara.visor.module.asset.dao.HostDAO;
import org.dromara.visor.module.asset.define.cache.HostCacheKeyDefine;
import org.dromara.visor.module.asset.entity.domain.HostDO;
import org.dromara.visor.module.asset.entity.dto.host.HostAgentLogDTO;
import org.dromara.visor.module.asset.entity.dto.host.HostBaseDTO;
import org.dromara.visor.module.asset.service.HostAgentService;
import org.springframework.stereotype.Service;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
@@ -55,9 +59,9 @@ import java.util.stream.Collectors;
@Service
public class HostAgentApiImpl implements HostAgentApi {
private static final TimedCache AGENT_NAME_CACHE = TimedCacheBuilder.create()
.expiredDelay(Const.MS_S_60 * 30)
.checkDelay(Const.MS_S_60)
private static final TimedCache<HostBaseDTO> AGENT_HOST_CACHE = TimedCacheBuilder.<HostBaseDTO>create()
.expireAfter(30 * Const.MS_S_60)
.checkInterval(Const.MS_S_60)
.build();
@Resource
@@ -69,6 +73,11 @@ public class HostAgentApiImpl implements HostAgentApi {
@Resource
private HostAgentService hostAgentService;
@PreDestroy
public void destroyTimedCache() {
Streams.close(AGENT_HOST_CACHE);
}
@Override
public List<HostAgentLogDTO> selectAgentInstallLog(List<Long> hostIdList) {
if (Lists.isEmpty(hostIdList)) {
@@ -94,39 +103,67 @@ public class HostAgentApiImpl implements HostAgentApi {
}
@Override
public Map<String, String> getCacheNameByAgentKey(List<String> agentKeyList) {
public Map<String, String> getNameCacheByAgentKey(List<String> agentKeyList) {
Map<String, String> result = new HashMap<>();
List<String> queryList = new ArrayList<>();
// 查询缓存
for (String agentKey : agentKeyList) {
String name = AGENT_NAME_CACHE.get(agentKey);
if (name != null) {
result.put(agentKey, name);
HostBaseDTO host = AGENT_HOST_CACHE.get(agentKey);
if (host != null) {
result.put(agentKey, host.getName());
} else {
queryList.add(agentKey);
}
}
// 查询数据库
if (!queryList.isEmpty()) {
// 查询数据
hostDAO.of()
.createWrapper()
.select(HostDO::getName, HostDO::getAgentKey)
.in(HostDO::getAgentKey, queryList)
.then()
.list()
.forEach(s -> result.put(s.getAgentKey(), s.getName()));
for (String agentKey : queryList) {
result.putIfAbsent(agentKey, Const.EMPTY);
AGENT_NAME_CACHE.put(agentKey, result.get(agentKey));
}
// 加载缓存
this.loadHostCache(queryList);
// 重新设置返回结果
queryList.forEach(agentKey -> {
HostBaseDTO host = AGENT_HOST_CACHE.get(agentKey);
if (host != null) {
result.put(agentKey, host.getName());
}
});
}
return result;
}
@Override
public HostBaseDTO getHostCacheByAgentKey(String agentKey) {
HostBaseDTO host = AGENT_HOST_CACHE.get(agentKey);
// 加载缓存
if (host == null) {
this.loadHostCache(Lists.singleton(agentKey));
host = AGENT_HOST_CACHE.get(agentKey);
}
return host;
}
@Override
public String getAgentVersion() {
return hostAgentService.getAgentVersion();
}
/**
* 加载主机缓存
*
* @param agentKeys agentKeys
*/
private void loadHostCache(List<String> agentKeys) {
// 查询数据并设置缓存
hostDAO.of()
.createWrapper()
.select(HostDO::getId,
HostDO::getName,
HostDO::getCode,
HostDO::getAddress,
HostDO::getAgentKey)
.in(HostDO::getAgentKey, agentKeys)
.then()
.list(HostProviderConvert.MAPPER::toBase)
.forEach(s -> AGENT_HOST_CACHE.put(s.getAgentKey(), s));
}
}

View File

@@ -33,6 +33,7 @@ import org.dromara.visor.module.asset.entity.dto.host.HostDTO;
import org.dromara.visor.module.asset.entity.dto.host.HostQueryDTO;
import org.dromara.visor.module.asset.entity.request.host.HostQueryRequest;
import org.dromara.visor.module.asset.service.HostService;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@@ -53,6 +54,7 @@ public class HostApiImpl implements HostApi {
@Resource
private HostDAO hostDAO;
@Lazy
@Resource
private HostService hostService;

View File

@@ -25,6 +25,7 @@ package org.dromara.visor.module.asset.service.impl;
import cn.orionsec.kit.lang.utils.Booleans;
import cn.orionsec.kit.lang.utils.Valid;
import cn.orionsec.kit.lang.utils.collect.Lists;
import cn.orionsec.kit.spring.SpringHolder;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.common.constant.Const;
import org.dromara.visor.common.constant.ErrorMessage;
@@ -32,6 +33,7 @@ import org.dromara.visor.module.asset.dao.HostAgentLogDAO;
import org.dromara.visor.module.asset.dao.HostDAO;
import org.dromara.visor.module.asset.entity.domain.HostAgentLogDO;
import org.dromara.visor.module.asset.entity.domain.HostDO;
import org.dromara.visor.module.asset.entity.event.AgentOfflineEvent;
import org.dromara.visor.module.asset.entity.vo.HostOnlineAgentConfigVO;
import org.dromara.visor.module.asset.enums.AgentInstallStatusEnum;
import org.dromara.visor.module.asset.enums.AgentLogStatusEnum;
@@ -41,7 +43,6 @@ import org.dromara.visor.module.asset.handler.host.extra.HostExtraItemEnum;
import org.dromara.visor.module.asset.handler.host.extra.model.HostSpecExtraModel;
import org.dromara.visor.module.asset.service.HostAgentEndpointService;
import org.dromara.visor.module.asset.service.HostExtraService;
import org.dromara.visor.module.monitor.api.MonitorHostApi;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
@@ -83,14 +84,12 @@ public class HostAgentEndpointServiceImpl implements HostAgentEndpointService {
@Resource
private HostExtraService hostExtraService;
@Resource
private MonitorHostApi monitorHostApi;
/**
* 初始化主机在线状态
*/
@PostConstruct
public void initHostOnlineStatus() {
log.info("HostAgentEndpointService-initHostOnlineStatus start.");
List<HostDO> hosts = hostDAO.selectList(null);
for (HostDO host : hosts) {
Integer agentOnlineStatus = host.getAgentOnlineStatus();
@@ -98,6 +97,7 @@ public class HostAgentEndpointServiceImpl implements HostAgentEndpointService {
ONLINE_STATUS_CACHE.put(host.getAgentKey(), agentOnlineStatus);
}
}
log.info("HostAgentEndpointService-initHostOnlineStatus end.");
}
@Override
@@ -167,8 +167,8 @@ public class HostAgentEndpointServiceImpl implements HostAgentEndpointService {
.status(AgentLogStatusEnum.SUCCESS.name())
.build();
hostAgentLogDAO.insert(agentLog);
// 设置监控上下文为已下线
monitorHostApi.setAgentOffline(Lists.singleton(agentKey));
// 发送已下线事件
SpringHolder.publishEvent(new AgentOfflineEvent(agentKey));
}
@Override
@@ -248,9 +248,9 @@ public class HostAgentEndpointServiceImpl implements HostAgentEndpointService {
if (!logList.isEmpty()) {
hostAgentLogDAO.insertBatch(logList);
}
// 设置监控上下文为已下线
// 发送已下线事件
if (AgentOnlineStatusEnum.OFFLINE.equals(status)) {
monitorHostApi.setAgentOffline(agentKeyList);
SpringHolder.publishEvent(new AgentOfflineEvent(agentKeyList));
}
}

View File

@@ -164,7 +164,7 @@ public class ExecCommandLogController {
@OperatorLog(ExecCommandLogOperatorType.CLEAR)
@PostMapping("/clear")
@Operation(summary = "批量执行日志")
@Operation(summary = "批量执行日志")
@PreAuthorize("@ss.hasPermission('exec:exec-command-log:management:clear')")
public Integer clearExecCommandLog(@Validated @RequestBody ExecLogClearRequest request) {
request.setSource(SOURCE);

View File

@@ -153,7 +153,7 @@ public class ExecJobLogController {
@OperatorLog(ExecJobLogOperatorType.CLEAR)
@PostMapping("/clear")
@Operation(summary = "计划任务日志")
@Operation(summary = "计划任务日志")
@PreAuthorize("@ss.hasPermission('exec:exec-job-log:management:clear')")
public Integer clearExecJobLog(@Validated @RequestBody ExecLogClearRequest request) {
request.setSource(SOURCE);

View File

@@ -145,7 +145,7 @@ public class UploadTaskController {
@OperatorLog(UploadTaskOperatorType.CLEAR)
@PostMapping("/clear")
@Operation(summary = "上传任务")
@Operation(summary = "上传任务")
@PreAuthorize("@ss.hasPermission('exec:upload-task:management:clear')")
public Integer clearUploadTask(@Validated @RequestBody UploadTaskClearRequest request) {
return uploadTaskService.clearUploadTask(request);

View File

@@ -90,7 +90,7 @@ public class ExecCommandServiceImpl implements ExecCommandService {
@Override
public ExecLogVO execCommand(ExecCommandRequest request) {
log.info("ExecService.execCommand start params: {}", JSON.toJSONString(request));
LoginUser user = Valid.notNull(SecurityUtils.getLoginUser());
LoginUser user = SecurityUtils.getLoginUserNotNull();
Long userId = user.getId();
List<Long> hostIdList = request.getHostIdList();
// 检查主机权限

View File

@@ -87,7 +87,7 @@ public class ExecHostLogServiceImpl implements ExecHostLogService {
// 分批次删除
List<List<Long>> partitions = Lists.partition(logIdList, 500);
for (List<Long> batch : partitions) {
LambdaQueryWrapper<ExecHostLogDO> wrapper = execHostLogDAO.wrapper()
LambdaQueryWrapper<ExecHostLogDO> wrapper = execHostLogDAO.lambda()
.in(ExecHostLogDO::getLogId, batch);
effect += execHostLogDAO.delete(wrapper);
}

View File

@@ -108,7 +108,7 @@ public class ExecJobServiceImpl implements ExecJobService {
@Transactional(rollbackFor = Exception.class)
public Long createExecJob(ExecJobCreateRequest request) {
log.info("ExecJobService-createExecJob request: {}", JSON.toJSONString(request));
LoginUser loginUser = SecurityUtils.getLoginUser();
LoginUser loginUser = SecurityUtils.getLoginUserNotNull();
// 验证表达式是否正确
Cron.of(request.getExpression());
// 转换
@@ -119,10 +119,8 @@ public class ExecJobServiceImpl implements ExecJobService {
this.checkHostPermission(request.getHostIdList());
// 插入任务
record.setStatus(ExecJobStatusEnum.DISABLED.getStatus());
if (loginUser != null) {
record.setExecUserId(loginUser.getId());
record.setExecUsername(loginUser.getUsername());
}
record.setExecUserId(loginUser.getId());
record.setExecUsername(loginUser.getUsername());
int effect = execJobDAO.insert(record);
Long id = record.getId();
// 设置任务主机
@@ -340,11 +338,9 @@ public class ExecJobServiceImpl implements ExecJobService {
request.setId(id);
request.setExecMode(ExecModeEnum.MANUAL.name());
// 设置执行用户
LoginUser user = SecurityUtils.getLoginUser();
if (user != null) {
request.setUserId(user.getId());
request.setUsername(user.getUsername());
}
LoginUser user = SecurityUtils.getLoginUserNotNull();
request.setUserId(user.getId());
request.setUsername(user.getUsername());
// 上下文触发任务
SpringHolder.getBean(ExecJobService.class).triggerExecJob(request, job);
}

View File

@@ -79,7 +79,7 @@ public class ExecTemplateHostServiceImpl implements ExecTemplateHostService {
@Override
public void setTemplateHost(Long templateId, List<Long> hostList) {
LambdaQueryWrapper<ExecTemplateHostDO> wrapper = execTemplateHostDAO.wrapper()
LambdaQueryWrapper<ExecTemplateHostDO> wrapper = execTemplateHostDAO.lambda()
.eq(ExecTemplateHostDO::getTemplateId, templateId);
if (Lists.isEmpty(hostList)) {
// 为空移除

View File

@@ -61,7 +61,7 @@ public class UploadTaskFileServiceImpl implements UploadTaskFileService {
@Override
public Integer deleteFileByTaskId(Long taskId) {
log.info("UploadTaskFileService-deleteFileByTaskId id: {}", taskId);
LambdaQueryWrapper<UploadTaskFileDO> wrapper = uploadTaskFileDAO.wrapper()
LambdaQueryWrapper<UploadTaskFileDO> wrapper = uploadTaskFileDAO.lambda()
.eq(UploadTaskFileDO::getTaskId, taskId);
int effect = uploadTaskFileDAO.delete(wrapper);
log.info("UploadTaskFileService-deleteFileByTaskId id: {}, effect: {}", taskId, effect);
@@ -71,7 +71,7 @@ public class UploadTaskFileServiceImpl implements UploadTaskFileService {
@Override
public Integer deleteFileByTaskIdList(List<Long> taskIdList) {
log.info("UploadTaskFileService-deleteFileByTaskIdList idList: {}", taskIdList);
LambdaQueryWrapper<UploadTaskFileDO> wrapper = uploadTaskFileDAO.wrapper()
LambdaQueryWrapper<UploadTaskFileDO> wrapper = uploadTaskFileDAO.lambda()
.in(UploadTaskFileDO::getTaskId, taskIdList);
int effect = uploadTaskFileDAO.delete(wrapper);
log.info("UploadTaskFileService-deleteFileByTaskIdList effect: {}", effect);

View File

@@ -115,7 +115,7 @@ public class UploadTaskServiceImpl implements UploadTaskService {
@Transactional(rollbackFor = Exception.class)
@Override
public UploadTaskCreateVO createUploadTask(UploadTaskCreateRequest request) {
LoginUser user = Valid.notNull(SecurityUtils.getLoginUser());
LoginUser user = SecurityUtils.getLoginUserNotNull();
List<Long> hostIdList = request.getHostIdList();
List<UploadTaskFileRequest> files = request.getFiles();
log.info("UploadTaskService-createUploadTask request: {}", JSON.toJSONString(request));

View File

@@ -0,0 +1,46 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.infra.api;
import org.dromara.visor.module.infra.entity.dto.notify.NotifyTemplateDTO;
import java.util.List;
/**
* 通知模板 对外服务类
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025-9-13 21:05
*/
public interface NotifyTemplateApi {
/**
* 批量查询通知模板
*
* @param idList idList
* @return rows
*/
List<NotifyTemplateDTO> getNotifyTemplateByIdList(List<Long> idList);
}

View File

@@ -22,8 +22,11 @@
*/
package org.dromara.visor.module.infra.api;
import org.dromara.visor.common.entity.PushUser;
import org.dromara.visor.module.infra.entity.dto.user.SystemUserDTO;
import java.util.List;
/**
* 用户服务
*
@@ -65,4 +68,28 @@ public interface SystemUserApi {
*/
SystemUserDTO getUserById(Long id);
/**
* 通过 id 查询用户
*
* @param idList idList
* @return user
*/
List<SystemUserDTO> getUserByIdList(List<Long> idList);
/**
* 通过 id 查询推送用户
*
* @param id id
* @return user
*/
PushUser getNotifyUserById(Long id);
/**
* 通过 id 查询推送用户
*
* @param idList idList
* @return user
*/
List<PushUser> getNotifyUserByIdList(List<Long> idList);
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.infra.entity.dto.notify;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
/**
* 通知模板 业务对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025-9-13 21:05
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "NotifyTemplatelDTO", description = "通知模板 业务对象")
public class NotifyTemplateDTO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "id")
private Long id;
@Schema(description = "通知名称")
private String name;
@Schema(description = "业务类型")
private String bizType;
@Schema(description = "渠道类型")
private String channelType;
@Schema(description = "渠道配置")
private String channelConfig;
@Schema(description = "消息模板")
private String messageTemplate;
@Schema(description = "描述")
private String description;
@Schema(description = "创建时间")
private Date createTime;
@Schema(description = "修改时间")
private Date updateTime;
@Schema(description = "创建人")
private String creator;
@Schema(description = "修改人")
private String updater;
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.infra.enums;
/**
* 通知业务类型
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/9/13 21:19
*/
public enum NotifyBizTypeEnum {
/**
* 告警
*/
ALARM,
;
public static NotifyBizTypeEnum of(String value) {
if (value == null) {
return null;
}
for (NotifyBizTypeEnum type : values()) {
if (type.name().equalsIgnoreCase(value)) {
return type;
}
}
return null;
}
}

View File

@@ -45,6 +45,11 @@
<artifactId>orion-visor-module-terminal-provider</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.dromara.visor</groupId>
<artifactId>orion-visor-module-monitor-provider</artifactId>
<version>${revision}</version>
</dependency>
<!-- framework starter -->
<dependency>
@@ -59,10 +64,6 @@
<groupId>org.dromara.visor</groupId>
<artifactId>orion-visor-spring-boot-starter-log</artifactId>
</dependency>
<dependency>
<groupId>org.dromara.visor</groupId>
<artifactId>orion-visor-spring-boot-starter-biz-operator-log</artifactId>
</dependency>
<dependency>
<groupId>org.dromara.visor</groupId>
<artifactId>orion-visor-spring-boot-starter-desensitize</artifactId>
@@ -91,6 +92,14 @@
<groupId>org.dromara.visor</groupId>
<artifactId>orion-visor-spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.dromara.visor</groupId>
<artifactId>orion-visor-spring-boot-starter-biz-push</artifactId>
</dependency>
<dependency>
<groupId>org.dromara.visor</groupId>
<artifactId>orion-visor-spring-boot-starter-biz-operator-log</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,63 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.infra.api.impl;
import cn.orionsec.kit.lang.utils.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.module.infra.api.NotifyTemplateApi;
import org.dromara.visor.module.infra.convert.NotifyTemplateProviderConvert;
import org.dromara.visor.module.infra.dao.NotifyTemplateDAO;
import org.dromara.visor.module.infra.entity.domain.NotifyTemplateDO;
import org.dromara.visor.module.infra.entity.dto.notify.NotifyTemplateDTO;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
/**
* 通知模板 对外服务实现类
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025-9-13 21:05
*/
@Slf4j
@Service
public class NotifyTemplateApiImpl implements NotifyTemplateApi {
@Resource
private NotifyTemplateDAO notifyTemplateDAO;
@Override
public List<NotifyTemplateDTO> getNotifyTemplateByIdList(List<Long> idList) {
log.info("NotifyTemplateApi.getNotifyTemplateByIdList idList: {}", idList);
if (Lists.isEmpty(idList)) {
return Lists.empty();
}
// 查询
List<NotifyTemplateDO> rows = notifyTemplateDAO.selectBatchIds(idList);
// 转换
return NotifyTemplateProviderConvert.MAPPER.toList(rows);
}
}

View File

@@ -22,6 +22,8 @@
*/
package org.dromara.visor.module.infra.api.impl;
import cn.orionsec.kit.lang.utils.collect.Lists;
import org.dromara.visor.common.entity.PushUser;
import org.dromara.visor.module.infra.api.SystemUserApi;
import org.dromara.visor.module.infra.convert.SystemUserProviderConvert;
import org.dromara.visor.module.infra.dao.SystemUserDAO;
@@ -30,6 +32,9 @@ import org.dromara.visor.module.infra.entity.dto.user.SystemUserDTO;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
/**
* 用户服务实现
@@ -83,4 +88,35 @@ public class SystemUserApiImpl implements SystemUserApi {
return SystemUserProviderConvert.MAPPER.to(user);
}
@Override
public List<SystemUserDTO> getUserByIdList(List<Long> idList) {
if (Lists.isEmpty(idList)) {
return Collections.emptyList();
}
return systemUserDAO.selectBatchIds(idList)
.stream()
.map(SystemUserProviderConvert.MAPPER::to)
.collect(Collectors.toList());
}
@Override
public PushUser getNotifyUserById(Long idList) {
SystemUserDO user = systemUserDAO.selectById(idList);
if (user == null) {
return null;
}
return SystemUserProviderConvert.MAPPER.toPush(user);
}
@Override
public List<PushUser> getNotifyUserByIdList(List<Long> idList) {
if (Lists.isEmpty(idList)) {
return Collections.emptyList();
}
return systemUserDAO.selectBatchIds(idList)
.stream()
.map(SystemUserProviderConvert.MAPPER::toPush)
.collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,122 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.infra.controller;
import cn.orionsec.kit.lang.define.wrapper.DataGrid;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.common.validator.group.Page;
import org.dromara.visor.framework.biz.operator.log.core.annotation.OperatorLog;
import org.dromara.visor.framework.log.core.annotation.IgnoreLog;
import org.dromara.visor.framework.log.core.enums.IgnoreLogMode;
import org.dromara.visor.framework.web.core.annotation.DemoDisableApi;
import org.dromara.visor.framework.web.core.annotation.RestWrapper;
import org.dromara.visor.module.infra.define.operator.NotifyTemplateOperatorType;
import org.dromara.visor.module.infra.entity.request.notify.NotifyTemplateCreateRequest;
import org.dromara.visor.module.infra.entity.request.notify.NotifyTemplateQueryRequest;
import org.dromara.visor.module.infra.entity.request.notify.NotifyTemplateUpdateRequest;
import org.dromara.visor.module.infra.entity.vo.NotifyTemplateVO;
import org.dromara.visor.module.infra.service.NotifyTemplateService;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
/**
* 通知模板 api
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025-9-13 21:05
*/
@Tag(name = "infra - 通知模板服务")
@Slf4j
@Validated
@RestWrapper
@RestController
@RequestMapping("/infra/notify-template")
public class NotifyTemplateController {
@Resource
private NotifyTemplateService notifyTemplateService;
@DemoDisableApi
@OperatorLog(NotifyTemplateOperatorType.CREATE)
@PostMapping("/create")
@Operation(summary = "创建通知模板")
@PreAuthorize("@ss.hasPermission('infra:notify-template:create')")
public Long createNotifyTemplate(@Validated @RequestBody NotifyTemplateCreateRequest request) {
return notifyTemplateService.createNotifyTemplate(request);
}
@DemoDisableApi
@OperatorLog(NotifyTemplateOperatorType.UPDATE)
@PutMapping("/update")
@Operation(summary = "更新通知模板")
@PreAuthorize("@ss.hasPermission('infra:notify-template:update')")
public Integer updateNotifyTemplate(@Validated @RequestBody NotifyTemplateUpdateRequest request) {
return notifyTemplateService.updateNotifyTemplateById(request);
}
@IgnoreLog(IgnoreLogMode.RET)
@GetMapping("/get")
@Operation(summary = "查询通知模板")
@Parameter(name = "id", description = "id", required = true)
@PreAuthorize("@ss.hasPermission('infra:notify-template:query')")
public NotifyTemplateVO getNotifyTemplate(@RequestParam("id") Long id) {
return notifyTemplateService.getNotifyTemplateById(id);
}
@IgnoreLog(IgnoreLogMode.RET)
@GetMapping("/list")
@Operation(summary = "查询全部通知模板")
@Parameter(name = "bizType", description = "bizType", required = true)
@PreAuthorize("@ss.hasPermission('infra:notify-template:query')")
public List<NotifyTemplateVO> getNotifyTemplateList(@Validated @RequestParam("bizType") String bizType) {
return notifyTemplateService.getNotifyTemplateListByCache(bizType);
}
@IgnoreLog(IgnoreLogMode.RET)
@PostMapping("/query")
@Operation(summary = "分页查询通知模板")
@PreAuthorize("@ss.hasPermission('infra:notify-template:query')")
public DataGrid<NotifyTemplateVO> getNotifyTemplatePage(@Validated(Page.class) @RequestBody NotifyTemplateQueryRequest request) {
return notifyTemplateService.getNotifyTemplatePage(request);
}
@DemoDisableApi
@OperatorLog(NotifyTemplateOperatorType.DELETE)
@DeleteMapping("/delete")
@Operation(summary = "删除通知模板")
@Parameter(name = "id", description = "id", required = true)
@PreAuthorize("@ss.hasPermission('infra:notify-template:delete')")
public Integer deleteNotifyTemplate(@RequestParam("id") Long id) {
return notifyTemplateService.deleteNotifyTemplateById(id);
}
}

View File

@@ -88,7 +88,7 @@ public class OperatorLogController {
@OperatorLog(OperatorLogOperatorType.CLEAR)
@PostMapping("/clear")
@Operation(summary = "操作日志")
@Operation(summary = "操作日志")
@PreAuthorize("@ss.hasPermission('infra:operator-log:management:clear')")
public Integer clearOperatorLog(@Validated @RequestBody OperatorLogClearRequest request) {
return operatorLogService.clearOperatorLog(request);

View File

@@ -0,0 +1,65 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.infra.convert;
import org.dromara.visor.module.infra.entity.domain.NotifyTemplateDO;
import org.dromara.visor.module.infra.entity.dto.NotifyTemplateCacheDTO;
import org.dromara.visor.module.infra.entity.dto.NotifyTemplateDetailCacheDTO;
import org.dromara.visor.module.infra.entity.request.notify.NotifyTemplateCreateRequest;
import org.dromara.visor.module.infra.entity.request.notify.NotifyTemplateQueryRequest;
import org.dromara.visor.module.infra.entity.request.notify.NotifyTemplateUpdateRequest;
import org.dromara.visor.module.infra.entity.vo.NotifyTemplateVO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
/**
* 通知模板 内部对象转换器
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025-9-13 21:05
*/
@Mapper
public interface NotifyTemplateConvert {
NotifyTemplateConvert MAPPER = Mappers.getMapper(NotifyTemplateConvert.class);
NotifyTemplateDO to(NotifyTemplateCreateRequest request);
NotifyTemplateDO to(NotifyTemplateUpdateRequest request);
NotifyTemplateDO to(NotifyTemplateQueryRequest request);
NotifyTemplateVO to(NotifyTemplateDO domain);
List<NotifyTemplateVO> to(List<NotifyTemplateDO> list);
NotifyTemplateVO to(NotifyTemplateCacheDTO cache);
NotifyTemplateCacheDTO toCache(NotifyTemplateDO domain);
NotifyTemplateDetailCacheDTO toDetailCache(NotifyTemplateDO domain);
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.infra.convert;
import org.dromara.visor.module.infra.entity.domain.NotifyTemplateDO;
import org.dromara.visor.module.infra.entity.dto.notify.NotifyTemplateDTO;
import org.dromara.visor.module.infra.entity.vo.NotifyTemplateVO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
/**
* 通知模板 对外服务对象转换器
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025-9-13 21:05
*/
@Mapper
public interface NotifyTemplateProviderConvert {
NotifyTemplateProviderConvert MAPPER = Mappers.getMapper(NotifyTemplateProviderConvert.class);
NotifyTemplateDTO to(NotifyTemplateVO dto);
NotifyTemplateDO to(NotifyTemplateDTO dto);
NotifyTemplateDTO to(NotifyTemplateDO domain);
List<NotifyTemplateDTO> toList(List<NotifyTemplateDO> list);
}

View File

@@ -22,6 +22,7 @@
*/
package org.dromara.visor.module.infra.convert;
import org.dromara.visor.common.entity.PushUser;
import org.dromara.visor.module.infra.entity.domain.SystemUserDO;
import org.dromara.visor.module.infra.entity.dto.user.SystemUserDTO;
import org.mapstruct.Mapper;
@@ -41,4 +42,6 @@ public interface SystemUserProviderConvert {
SystemUserDTO to(SystemUserDO domain);
PushUser toPush(SystemUserDO user);
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.infra.dao;
import org.apache.ibatis.annotations.Mapper;
import org.dromara.visor.framework.mybatis.core.mapper.IMapper;
import org.dromara.visor.module.infra.entity.domain.NotifyTemplateDO;
/**
* 通知模板 Mapper 接口
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025-9-13 21:05
*/
@Mapper
public interface NotifyTemplateDAO extends IMapper<NotifyTemplateDO> {
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.infra.define.cache;
import cn.orionsec.kit.lang.define.cache.key.CacheKeyBuilder;
import cn.orionsec.kit.lang.define.cache.key.CacheKeyDefine;
import cn.orionsec.kit.lang.define.cache.key.struct.RedisCacheStruct;
import org.dromara.visor.module.infra.entity.dto.NotifyTemplateCacheDTO;
import org.dromara.visor.module.infra.entity.dto.NotifyTemplateDetailCacheDTO;
import java.util.concurrent.TimeUnit;
/**
* 通知模板缓存 key
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025-9-13 21:05
*/
public interface NotifyTemplateCacheKeyDefine {
CacheKeyDefine NOTIFY_TEMPLATE_LIST = new CacheKeyBuilder()
.key("notify:template:list:{}")
.desc("通知模板列表 ${bizType}")
.type(NotifyTemplateCacheDTO.class)
.struct(RedisCacheStruct.HASH)
.timeout(8, TimeUnit.HOURS)
.build();
CacheKeyDefine NOTIFY_TEMPLATE_DETAIL = new CacheKeyBuilder()
.key("notify:template:{}")
.desc("通知模板详情 ${bizType}")
.type(NotifyTemplateDetailCacheDTO.class)
.struct(RedisCacheStruct.STRING)
.timeout(8, TimeUnit.HOURS)
.build();
}

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