Compare commits

..

53 Commits

Author SHA1 Message Date
李佳航
8d46e1d44d Merge pull request #133 from dromara/dev
🔨 修改 tsc.
2025-09-11 20:44:36 +08:00
lijiahangmax
b3c045aa46 🔨 修改 tsc. 2025-09-11 20:44:03 +08:00
李佳航
ca4ec20e49 Merge pull request #132 from dromara/dev
Dev
2025-09-11 20:38:27 +08:00
lijiahangmax
8db2986dfc 🔖 升级版本. 2025-09-11 01:09:37 +08:00
lijiahangmax
3156ae1dff ✏️ 添加切图. 2025-09-10 21:53:53 +08:00
lijiahangmax
697d29c6f2 🔨 修改配置. 2025-09-10 18:11:54 +08:00
lijiahangmax
bf4b1f9702 🔖 修改版本. 2025-09-10 01:44:58 +08:00
lijiahangmax
d2703661c8 🐳 修改 docker 配置. 2025-09-10 01:43:11 +08:00
lijiahangmax
df78fc5977 🔨 修改配置文件. 2025-09-09 23:46:02 +08:00
lijiahangmax
5e03810295 🔨 sql 脚本. 2025-09-09 23:10:03 +08:00
lijiahangmax
6b44e193f5 🔨 修改文档. 2025-09-09 22:45:23 +08:00
lijiahangmax
919e8383bf 🔨 修改插件包名. 2025-09-09 22:27:51 +08:00
lijiahangmax
0b7faa038a 🔨 监控逻辑. 2025-09-09 21:25:44 +08:00
lijiahangmax
3c75aedcec 🔨 优化锁逻辑. 2025-08-23 15:05:03 +08:00
lijiahangmax
393286d309 🔨 修改项目加密模块. 2025-08-23 14:11:15 +08:00
lijiahangmax
8501e900c7 🔨 修改代码生成器模板. 2025-08-13 00:00:45 +08:00
lijiahangmax
c53042a4b5 🔨 修改代码生成器模板. 2025-08-12 23:28:30 +08:00
lijiahangmax
c661d34a79 🔨 修改主机配置字段. 2025-08-10 19:29:57 +08:00
lijiahangmax
a3476596dd 🔨 修改额外配置字段名称. 2025-08-10 19:26:33 +08:00
lijiahangmax
8a4176bc9e Merge remote-tracking branch 'origin/dev' into dev 2025-08-02 14:46:57 +08:00
lijiahangmax
a0a7240191 Merge remote-tracking branch 'origin/main' 2025-08-01 12:29:14 +08:00
lijiahangmax
2b52697cdc merge dev into main
merge

Created-by: lijiahangmax
Commit-by: lijiahangmax;2022521971;autoscope;qq_23961285;hailan1024;muzi_teacher;haobo96
Merged-by: lijiahangmax
Description: update: 更新文件 README.md
update: 更新文件 README.md
merge dev into dev
update: 更新文件 README.md
merge dev into dev
...

See merge request: dromara/orion-visor!24
2025-08-01 12:25:53 +08:00
lijiahangmax
63d82b5a19 Merge remote-tracking branch 'origin/dev' into dev 2025-08-01 11:24:49 +08:00
lijiahangmax
af23e56f03 merge dev into dev
✏️ 修改文档.

Created-by: 2022521971
Commit-by: 2022521971
Merged-by: lijiahangmax
Description: ✏️ 修改文档.

See merge request: dromara/orion-visor!23
2025-08-01 11:19:02 +08:00
2022521971
cecd71c42a ✏️ 修改文档.
Signed-off-by: 2022521971 <202252197@qq.com>
2025-08-01 11:15:33 +08:00
lijiahangmax
3ed9df8788 Merge remote-tracking branch 'origin/dev' into dev 2025-08-01 11:04:40 +08:00
lijiahangmax
7a89858790 merge dev into dev
🐳 添加 docker 参数.

Created-by: autoscope
Commit-by: autoscope
Merged-by: lijiahangmax
Description: 🐳 添加 docker 参数.

See merge request: dromara/orion-visor!22
2025-07-30 18:09:06 +08:00
autoscope
a548413358 🐳 添加 docker 参数.
Signed-off-by: autoscope <autoscope@noreply.gitcode.com>
2025-07-30 18:04:19 +08:00
lijiahangmax
c859225908 merge dev into dev
✏️ 修改文档.

Created-by: qq_23961285
Commit-by: qq_23961285
Merged-by: lijiahangmax
Description: ✏️ 修改文档.

See merge request: dromara/orion-visor!21
2025-07-30 17:50:38 +08:00
qq_23961285
b097836ec8 ✏️ 修改文档.
Signed-off-by: qq_23961285 <qq_23961285@noreply.gitcode.com>
2025-07-30 17:49:11 +08:00
lijiahangmax
9e0cfef4da merge dev into dev
🔨 更新文件 git-pull.sh

Created-by: hailan1024
Commit-by: hailan1024
Merged-by: lijiahangmax
Description: 🔨 更新文件 git-pull.sh

See merge request: dromara/orion-visor!20
2025-07-30 17:04:09 +08:00
hailan1024
a76a7d4150 🔨 更新文件 git-pull.sh
Signed-off-by: hailan1024 <hailan1024@noreply.gitcode.com>
2025-07-30 17:00:52 +08:00
lijiahangmax
2920504023 merge dev into dev
update: 更新文件 README.md

Created-by: muzi_teacher
Commit-by: muzi_teacher
Merged-by: lijiahangmax
Description: update: 更新文件 README.md

See merge request: dromara/orion-visor!19
2025-07-30 16:37:16 +08:00
muzi_teacher
c32b590bb4 update: 更新文件 README.md
Signed-off-by: muzi_teacher <muzi_teacher@noreply.gitcode.com>
2025-07-30 16:36:34 +08:00
lijiahangmax
612d7f1166 merge dev into dev
update: 更新文件 README.md

Created-by: haobo96
Commit-by: haobo96
Merged-by: lijiahangmax
Description: update: 更新文件 README.md

See merge request: dromara/orion-visor!18
2025-07-30 16:35:45 +08:00
haobo96
b7c4fbcab8 update: 更新文件 README.md 2025-07-30 16:34:17 +08:00
haobo96
a3f84e799c update: 更新文件 README.md 2025-07-30 16:29:47 +08:00
李佳航
5ab3f168d8 Merge pull request #129 from dromara/dev
✏️ 修改文档.
2025-07-19 18:52:13 +08:00
lijiahangmax
402e183d2f ✏️ 修改文档. 2025-07-14 19:06:36 +08:00
lijiahangmax
d3e5e08f6c ✏️ 修改文档. 2025-07-14 19:02:16 +08:00
李佳航
e214fbde5c Merge pull request #128 from dromara/dev
Dev
2025-07-13 18:32:17 +08:00
lijiahangmax
2f7b4bd5ea ✏️ 修改文档. 2025-07-13 18:24:02 +08:00
lijiahangmax
8bba423ff3 ✏️ 修改文档. 2025-07-13 18:06:07 +08:00
李佳航
0d0eadc3bf Merge pull request #127 from dromara/dev
🐳 修改 docker 配置.
2025-07-10 15:17:18 +08:00
lijiahangmax
ab430d8934 🐳 修改 docker 配置. 2025-07-10 15:16:29 +08:00
李佳航
edcc2cf0c8 Merge pull request #126 from dromara/dev
🐳 修改 docker 配置.
2025-07-10 14:51:48 +08:00
lijiahangmax
2913ddb2e0 🐳 修改 docker 配置. 2025-07-10 14:50:36 +08:00
李佳航
7d35f839df Merge pull request #125 from dromara/dev
🐳 修改 docker 配置.
2025-07-10 14:30:21 +08:00
lijiahangmax
ef9c34f7d9 🐳 修改 docker 配置. 2025-07-10 14:28:45 +08:00
李佳航
f1a4e049ca Merge pull request #124 from dromara/dev
Dev
2025-07-10 03:50:05 +08:00
lijiahangmax
8ee1e6acf1 🐳 修改 docker 配置. 2025-07-10 03:43:56 +08:00
lijiahangmax
d0eddf1e15 升级版本. 2025-07-10 03:34:23 +08:00
lijiahangmax
b42645b0ce 🐛 修复会话打开黑屏. 2025-07-10 03:32:45 +08:00
314 changed files with 14447 additions and 636 deletions

View File

@@ -1,10 +1,13 @@
VOLUME_BASE=/data/orion-visor-space/docker-volumes
DEMO_MODE=false
SERVICE_PORT=1081
SPRING_PROFILES_ACTIVE=prod
DEMO_MODE=false
API_CORS=true
SECRET_KEY=uQeacXV8b3isvKLK
API_EXPOSE_TOKEN=pmqeHOyZaumHm0Wt
MYSQL_HOST=mysql
MYSQL_PORT=3306
@@ -20,4 +23,16 @@ REDIS_DATA_VERSION=1
GUACD_HOST=guacd
GUACD_PORT=4822
GUACD_SSH_PORT=22
GUACD_SSH_USERNAME=guacd
GUACD_SSH_PASSWORD=guacd
GUACD_DRIVE_PATH=/drive
INFLUXDB_ENABLED=true
INFLUXDB_HOST=influxdb
INFLUXDB_PORT=8086
INFLUXDB_ORG=orion-visor
INFLUXDB_BUCKET=metrics
INFLUXDB_TOKEN=Data@123456
INFLUXDB_ADMIN_USERNAME=admin
INFLUXDB_ADMIN_PASSWORD=Data@123456

View File

@@ -0,0 +1,10 @@
### *当前使用版本 (必填)
### 问题描述
### 该问题是如何引起的
### 重现步骤
### 报错信息

View File

@@ -0,0 +1,8 @@
### 修改描述
### 关联的 Issue
### 测试用例
### 修复效果的截屏

58
.github/ISSUE_TEMPLATE/bug_report.yaml vendored Normal file
View File

@@ -0,0 +1,58 @@
name: 错误报告
description: File a bug report.
title: "[错误报告]: "
labels: [ "" ]
body:
- type: markdown
attributes:
value: |
在提交前请确认:
- 使用的是[最新版本](https://github.com/dromara/orion-visor/releases)
- 参考了[安装文档](https://visor.orionsec.cn/quickstart/docker.html)
- 查阅了[常见问题](https://visor.orionsec.cn/support/faq.html)
- 搜索了[已有 issue](https://github.com/dromara/orion-visor/issues)
- type: checkboxes
id: confirm
attributes:
label: 确认
description: 在提交 issue 之前, 请确认你已经阅读并确认以下内容
options:
- label: 我使用的是最新版本 [最新版](https://github.com/dromara/orion-visor/releases)
required: true
- label: 我使用官方文档进行部署 [安装文档](https://visor.orionsec.cn/quickstart/docker.html)
required: true
- label: 我已检查了 [常见问题](https://visor.orionsec.cn/support/faq.html) 并没有找到解决方法
required: true
- label: 我已搜索 [issue](https://github.com/dromara/orion-visor/issues) 并没有找到相关问题
required: true
- type: input
id: version
attributes:
label: 当前程序版本
description: 遇到问题时程序所在的版本号
validations:
required: true
- type: dropdown
id: install
attributes:
label: 安装方式
options:
- Docker
- 普通安装
- 其他
validations:
required: true
- type: textarea
id: what-happened
attributes:
label: 问题描述
description: 请详细描述你碰到的问题
placeholder: "问题描述"
validations:
required: true
- type: textarea
id: logs
attributes:
label: 详细日志
description: 问题出现时的程序日志
render: bash

5
.github/ISSUE_TEMPLATE/config.yaml vendored Normal file
View File

@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: 官网
url: https://visor.orionsec.cn/
about: document.

View File

@@ -0,0 +1,35 @@
name: 功能改进
description: 提出新功能建议 (请提交到需求收集帖)
title: "[功能建议]: "
labels: [ "" ]
body:
- type: markdown
attributes:
value: |
所有功能建议请统一提交到需求收集帖: 🔗 [#83 需求收集](https://github.com/dromara/orion-visor/issues/83)
在提交前请确认:
- ✅ 使用的是[最新版本](https://github.com/dromara/orion-visor/releases)
- ✅ 已搜索[已有 issue](https://github.com/dromara/orion-visor/issues) 和 需求收集帖避免重复
- ✅ 定制化需求请联系作者
---
### 如何提交高质量建议?
1. **功能描述**: 你希望增加什么?
2. **使用场景**: 你在什么情况下需要它?
4. **参考实现**: 开源项目中的类似功能(**禁止引用商业闭源软件**
- type: textarea
id: feature
attributes:
label: 功能改进
description: 请详细描述需要改进或者添加的功能。
placeholder: "功能改进"
validations:
required: true
- type: textarea
id: references
attributes:
label: 参考资料
description: 可以列举一些参考资料, 但是不要引用同类但商业化软件的任何内容。
placeholder: "参考资料"

View File

@@ -41,6 +41,10 @@ jobs:
pnpm install
pnpm build
- name: 📦️ Download instance-agent
working-directory: ./docker/service
run: wget https://github.com/lijiahangmax/orion-visor-agent/releases/latest/download/instance-agent-release.tar.gz -O instance-agent-release.tar.gz
- name: 📁 Prepare build context
run: |
cp -r ./sql ./docker/mysql/sql
@@ -62,7 +66,7 @@ jobs:
strategy:
matrix:
service: [ adminer, guacd, mysql, redis, service, ui ]
service: [ adminer, guacd, mysql, redis, influxdb, service, ui ]
env:
GITHUB_REGISTRY: ghcr.io
@@ -117,11 +121,15 @@ jobs:
type=semver,pattern={{major}}
- name: 🛠️ Build and push Docker image for orion-visor-${{ matrix.service }}
uses: docker/build-push-action@v5
uses: docker/build-push-action@v6
with:
context: ./docker
file: ./docker/${{ matrix.service }}/Dockerfile
push: true
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64,linux/arm64
labels: ${{ steps.meta.outputs.labels }}
tags: |
${{ env.DOCKERHUB_NAMESPACE }}/orion-visor-${{ matrix.service }}:${{ steps.meta.outputs.version }}
${{ env.DOCKERHUB_NAMESPACE }}/orion-visor-${{ matrix.service }}:latest
@@ -129,5 +137,3 @@ jobs:
${{ env.GITHUB_REGISTRY }}/${{ github.repository_owner }}/orion-visor-${{ matrix.service }}:latest
${{ env.ALIYUN_REGISTRY }}/${{ env.ALIYUN_NAMESPACE }}/orion-visor-${{ matrix.service }}:${{ steps.meta.outputs.version }}
${{ env.ALIYUN_REGISTRY }}/${{ env.ALIYUN_NAMESPACE }}/orion-visor-${{ matrix.service }}:latest
labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64,linux/arm64

View File

@@ -18,4 +18,4 @@ jobs:
run: |
sudo curl -L https://github.com/docker/compose/releases/download/v2.23.0/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose
sudo chmod u+x /usr/local/bin/docker-compose
docker compose -f docker-compose-testing.yml up --build testing --exit-code-from testing --remove-orphans
docker compose -f docker-compose-testing.yaml up --build testing --exit-code-from testing --remove-orphans

2
NOTICE
View File

@@ -5,5 +5,5 @@
1. 禁止修改或删除 LICENSE 文件。
2. 禁止修改或删除源码头部的版权声明。
3. 本项目可免费商业使用,商业使用请保留项目源码、出处、描述文件和作者声明等。
4. 分发源码时候,请注明软件出处 https://visor.dromara.org/
4. 分发源码时候,请注明软件出处 https://visor.orionsec.cn/
5. 不可二次开发或参与同类竞品的开发。

View File

@@ -52,6 +52,7 @@
* **文件管理**:支持远程主机 SFTP 大文件的批量上传、下载和在线编辑等操作。
* **批量操作**:支持批量执行主机命令、多主机文件分发等功能。
* **计划任务**:支持配置 cron 表达式,定时执行主机命令。
* **系统监控**:支持对主机 CPU、内存、磁盘、网络等系统指标的监控和告警。
* **安全可靠**:动态配置权限,记录用户操作日志,提供简单的审计功能。
## 演示环境
@@ -62,9 +63,6 @@
这对我很重要! [github](https://github.com/dromara/orion-visor) [gitee](https://gitee.com/dromara/orion-visor) [gitcode](https://gitcode.com/dromara/orion-visor)
* 🌈 如果本项目对你有帮助请帮忙推广一下 让更多的人知道此项目!
* 🎭 演示环境部分功能不可用, 完整功能请本地部署!
* 📛 演示环境请不要随便删除数据!
* 📧 如果演示环境不可用请联系我!
* 📨 **作者随缘寻java高级/资深内推 望京/5号/10号线 有坑位的联系我哦** 微信: `ljh1553488`
## 快速开始
@@ -79,18 +77,19 @@ docker compose up -d
## 项目文档
* [文档地址](https://visor.dromara.org/)
* [安装文档](https://visor.dromara.org/quickstart/docker.html)
* [更新日志](https://visor.dromara.org/update/change-log.html)
* [操作手册](https://visor.dromara.org/operator/asset.html)
* [常见问题](https://visor.dromara.org/support/faq.html)
* [文档地址](https://visor.orionsec.cn/)
* [安装文档](https://visor.orionsec.cn/quickstart/docker.html)
* [更新日志](https://visor.orionsec.cn/update/change-log.html)
* [操作手册](https://visor.orionsec.cn/operator/asset.html)
* [常见问题](https://visor.orionsec.cn/support/faq.html)
## 技术栈
* SpringBoot 2.7.17
* SpringBoot 2.7+
* Mysql 8.0+
* Redis 6.0+
* Vue3 3.5+
* InfluxDB 2.7+
* Vue 3.5+
* Arco Design 2.56+
## 主要功能预览
@@ -111,6 +110,11 @@ docker compose up -d
![主机列表](docs/assets/screenshot/host-list.png?time=20250627 "主机列表")
#### 主机监控
![主机监控](docs/assets/screenshot/monitor-list.png?time=20250627 "主机监控")
![监控详情](docs/assets/screenshot/monitor-detail.png?time=20250627 "监控详情")
#### 批量执行
![批量执行](docs/assets/screenshot/exec-command.png?time=20250627 "批量执行")
@@ -130,7 +134,8 @@ docker compose up -d
## 关于我
本人专注于使用 Java 和 Vue 进行全栈开发, 并在系统自动化运维方面拥有丰富开发的经验。如果您在这些领域有需求或遇到痛点, 请随时联系我, 并备注“合作”。
本人专注于使用 Java 和 Vue 进行全栈开发, 并在系统自动化运维方面拥有丰富开发的经验, 并提供企业级的解决方案。如果您在这些领域有需求或遇到痛点, 请随时联系我,
并备注“合作”。
## 联系我
@@ -156,6 +161,10 @@ QQ群: 755242157
本项目遵循 [Apache-2.0](https://github.com/dromara/orion-visor/blob/main/LICENSE) 开源许可证。
## 贡献者
[![Contributors](https://contri.buzz/api/wall?repo=dromara/orion-visor)](https://github.com/dromara/orion-visor, "Contributors")
## Gitee 最有价值的开源项目 GVP
![GVP](docs/assets/gvp.jpg?time=20250627 "GVP")

View File

@@ -5,7 +5,7 @@ services:
image: registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-service:latest
privileged: true
ports:
- 9200:9200
- "9200:9200"
environment:
SPRING_PROFILES_ACTIVE: prod
MYSQL_HOST: mysql
@@ -37,7 +37,7 @@ services:
image: registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-mysql:latest
privileged: true
ports:
- 3307:3306
- "3307:3306"
environment:
MYSQL_DATABASE: orion_visor
MYSQL_USER: orion
@@ -59,7 +59,7 @@ services:
image: registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-redis:latest
privileged: true
ports:
- 6380:6379
- "6380:6379"
environment:
REDIS_PASSWORD: Data@123456
volumes:

View File

@@ -1,6 +1,6 @@
version: '3.3'
# latest = 2.4.2
# latest = 2.5.0
# 支持以下源
# lijiahangmax/*
@@ -23,7 +23,7 @@ services:
image: registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-service:latest
privileged: true
ports:
- 9200:9200
- "9200:9200"
environment:
SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE:-prod}
MYSQL_HOST: ${MYSQL_HOST:-mysql}
@@ -35,10 +35,18 @@ services:
REDIS_PASSWORD: ${REDIS_PASSWORD:-Data@123456}
REDIS_DATABASE: ${REDIS_DATABASE:-0}
REDIS_DATA_VERSION: ${REDIS_DATA_VERSION:-1}
INFLUXDB_ENABLED: ${INFLUXDB_ENABLED:-true}
INFLUXDB_HOST: ${INFLUXDB_HOST:-influxdb}
INFLUXDB_PORT: ${INFLUXDB_PORT:-8086}
INFLUXDB_ORG: ${INFLUXDB_ORG:-orion-visor}
INFLUXDB_BUCKET: ${INFLUXDB_BUCKET:-metrics}
INFLUXDB_TOKEN: ${INFLUXDB_TOKEN:-Data@123456}
GUACD_HOST: ${GUACD_HOST:-guacd}
GUACD_PORT: ${GUACD_PORT:-4822}
GUACD_DRIVE_PATH: ${GUACD_DRIVE_PATH:-/drive}
SECRET_KEY: ${SECRET_KEY:-uQeacXV8b3isvKLK}
SECRET_KEY: ${SECRET_KEY:-pmqeHOyZaumHm0Wt}
API_EXPOSE_TOKEN: ${API_EXPOSE_TOKEN:-uQeacXV8b3isvKLK}
API_CORS: ${API_CORS:-true}
DEMO_MODE: ${DEMO_MODE:-false}
volumes:
- ${VOLUME_BASE:-/data/orion-visor-space/docker-volumes}/service/root-orion:/root/orion
@@ -54,6 +62,8 @@ services:
condition: service_healthy
redis:
condition: service_healthy
influxdb:
condition: service_healthy
networks:
- orion-visor-net
@@ -61,7 +71,7 @@ services:
image: registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-mysql:latest
privileged: true
ports:
- 3307:3306
- "3307:3306"
environment:
MYSQL_DATABASE: ${MYSQL_DATABASE:-orion_visor}
MYSQL_USER: ${MYSQL_USER:-orion}
@@ -84,7 +94,7 @@ services:
image: registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-redis:latest
privileged: true
ports:
- 6380:6379
- "6380:6379"
environment:
REDIS_PASSWORD: ${REDIS_PASSWORD:-Data@123456}
volumes:
@@ -100,10 +110,35 @@ services:
networks:
- orion-visor-net
influxdb:
image: registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-influxdb:latest
privileged: true
ports:
- "8086:8086"
environment:
DOCKER_INFLUXDB_INIT_MODE: setup
DOCKER_INFLUXDB_INIT_USERNAME: ${INFLUXDB_ADMIN_USERNAME:-admin}
DOCKER_INFLUXDB_INIT_PASSWORD: ${INFLUXDB_ADMIN_PASSWORD:-Data@123456}
DOCKER_INFLUXDB_INIT_ADMIN_TOKEN: ${INFLUXDB_TOKEN:-Data@123456}
DOCKER_INFLUXDB_INIT_ORG: ${INFLUXDB_ORG:-orion-visor}
DOCKER_INFLUXDB_INIT_BUCKET: ${INFLUXDB_BUCKET:-metrics}
volumes:
- ${VOLUME_BASE:-/data/orion-visor-space/docker-volumes}/influxdb/data:/var/lib/influxdb2
- ${VOLUME_BASE:-/data/orion-visor-space/docker-volumes}/influxdb/config:/etc/influxdb2
restart: unless-stopped
healthcheck:
test: [ "CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/8086" ]
interval: 15s
timeout: 5s
retries: 10
start_period: 10s
networks:
- orion-visor-net
guacd:
image: registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-guacd:latest
ports:
- 4822:4822
- "4822:4822"
environment:
GUACD_LOG_LEVEL: info
GUACD_LOG_FILE: /var/log/guacd.log
@@ -125,7 +160,7 @@ services:
adminer:
image: registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-adminer:latest
ports:
- 8081:8080
- "8081:8080"
environment:
ADMINER_DEFAULT_SERVER: ${MYSQL_HOST:-mysql}
depends_on:

View File

@@ -1 +1 @@
FROM --platform=$BUILDPLATFORM adminer:latest
FROM --platform=$TARGETPLATFORM adminer:latest

View File

@@ -1,4 +1,5 @@
FROM maven:3.9.10-eclipse-temurin-8-alpine AS builder
#FROM --platform=$BUILDPLATFORM maven:3.9-amazoncorretto-8 AS builder
FROM maven:3.9-amazoncorretto-8 AS builder
# 设置阿里云镜像加速
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories

View File

@@ -1,3 +1,4 @@
#FROM --platform=$BUILDPLATFORM node:18-alpine AS builder
FROM node:18-alpine AS builder
# 设置阿里云镜像加速

View File

@@ -7,7 +7,7 @@ set -e
source ./project-build.sh "$@"
# 版本号
version=2.4.2
version=2.5.0
# 是否推送镜像
push_image=false
# 是否构建 latest
@@ -46,6 +46,7 @@ declare -A images=(
["./service/Dockerfile"]="orion-visor-service"
["./mysql/Dockerfile"]="orion-visor-mysql"
["./redis/Dockerfile"]="orion-visor-redis"
["./influxdb/Dockerfile"]="orion-visor-influxdb"
["./adminer/Dockerfile"]="orion-visor-adminer"
["./guacd/Dockerfile"]="orion-visor-guacd"
)
@@ -68,6 +69,30 @@ function prepare_app_jar() {
fi
}
# 准备 instance-agent
function prepare_instance_agent() {
local target_file="./service/instance-agent-release.tar.gz"
if [ ! -f "$target_file" ]; then
echo "警告: $target_file 不存在, 正在尝试从 Github Release 下载..."
# 尝试从 GitHub Release 下载
if curl -L --fail \
--connect-timeout 30 --max-time 30 \
https://github.com/lijiahangmax/orion-visor-agent/releases/latest/download/instance-agent-release.tar.gz \
-o "$target_file"; then
echo "已成功下载到 $target_file"
fi
# 如果下载失败, 提示用户手动下载
echo "错误: 无法从 Release 获取 instance-agent-release.tar.gz"
echo "请手动从以下地址下载, 并放置到 $target_file"
echo " 1) https://github.com/lijiahangmax/orion-visor-agent/raw/main/instance-agent-release.tar.gz"
echo " 2) https://gitee.com/lijiahangmax/orion-visor-agent/raw/main/instance-agent-release.tar.gz"
exit 1
else
echo "$target_file 已存在, 无需下载."
fi
}
# 准备前端 dist 目录
function prepare_dist_directory() {
local source_dir="../orion-visor-ui/dist"
@@ -115,7 +140,7 @@ function modify_dockerfiles() {
if [ -f "$file" ]; then
echo "备份并修改: $file"
cp "$file" "$file$backup_suffix"
sed -i 's/--platform=\$BUILDPLATFORM//g' "$file"
sed -i 's/--platform=\TARGETPLATFORM//g' "$file"
else
echo "文件不存在 -> $file"
fi
@@ -185,6 +210,7 @@ fi
# 检查资源
echo "正在检查并准备必要的构建资源..."
prepare_app_jar
prepare_instance_agent
prepare_dist_directory
prepare_sql_directory
echo "所有前置资源已准备完毕"

View File

@@ -1,4 +1,4 @@
FROM --platform=$BUILDPLATFORM guacamole/guacd:1.6.0
FROM --platform=$TARGETPLATFORM guacamole/guacd:1.6.0
USER root

View File

@@ -0,0 +1,8 @@
FROM --platform=$TARGETPLATFORM influxdb:2
# 系统时区
ARG TZ=Asia/Shanghai
# 设置时区
RUN ln -sf /usr/share/zoneinfo/${TZ} /etc/localtime && \
echo "${TZ}" > /etc/timezone

View File

@@ -1,4 +1,4 @@
FROM --platform=$BUILDPLATFORM mysql:8.0.28
FROM --platform=$TARGETPLATFORM mysql:8.0.39
# 系统时区
ARG TZ=Asia/Shanghai
@@ -12,3 +12,7 @@ COPY ./mysql/my.cnf /etc/mysql/conf.d/my.cnf
# 复制初始化脚本
COPY ./mysql/sql/init-*.sql /docker-entrypoint-initdb.d/
# 心跳检测
HEALTHCHECK --interval=10s --timeout=3s --start-period=3s --retries=3 \
CMD mysqladmin ping -h localhost -u root --password=${MYSQL_ROOT_PASSWORD} || exit 1

View File

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

View File

@@ -1,4 +1,4 @@
FROM --platform=$BUILDPLATFORM redis:6.0.16-alpine
FROM --platform=$TARGETPLATFORM redis:6.0.16-alpine
WORKDIR /data

View File

@@ -1,4 +1,4 @@
FROM --platform=$BUILDPLATFORM openjdk:8-jdk-alpine
FROM --platform=$TARGETPLATFORM openjdk:8-jdk-alpine
USER root
@@ -17,8 +17,19 @@ RUN \
ln -sf /usr/share/zoneinfo/${TZ} /etc/localtime && \
echo "${TZ}" > /etc/timezone
# 复制启动脚本
COPY ./service/entrypoint.sh /app/entrypoint.sh
RUN chmod +x /app/entrypoint.sh
# 复制 jar 包
COPY ./service/orion-visor-launch.jar /app/app.jar
# 复制探针包
ADD ./service/instance-agent-release.tar.gz /app/instance-agent-release
# 启动检测
HEALTHCHECK --interval=15s --timeout=5s --retries=5 --start-period=10s \
CMD wget -T5 -qO- http://127.0.0.1:9200/orion-visor/api/server/bootstrap/health | grep ok || exit 1
# 启动
ENTRYPOINT ["/app/entrypoint.sh"]
CMD ["java", "-jar", "/app/app.jar"]

View File

@@ -0,0 +1,23 @@
#!/bin/sh
AGENT_RELEASE_DIR="/root/orion/orion-visor/instance-agent-release"
DEFAULT_AGENT_DIR="/app/instance-agent-release"
# 确保父目录存在
mkdir -p "$(dirname "$AGENT_RELEASE_DIR")"
# 加载探针
if [ -d "$AGENT_RELEASE_DIR" ] && [ -n "$(ls -A "$AGENT_RELEASE_DIR" 2>/dev/null)" ]; then
echo "Using mounted agent release: $AGENT_RELEASE_DIR"
else
echo "Using default agent release: $DEFAULT_AGENT_DIR"
# 复制探针
cp -rf "$DEFAULT_AGENT_DIR" "$AGENT_RELEASE_DIR"
fi
# 打印探针版本信息
if [ -f "$AGENT_RELEASE_DIR/.version" ]; then
echo "Agent version: $(cat "$AGENT_RELEASE_DIR/.version")"
fi
exec "$@"

View File

@@ -1,4 +1,4 @@
FROM --platform=$BUILDPLATFORM nginx:alpine
FROM --platform=$TARGETPLATFORM nginx:alpine
# 系统时区
ARG TZ=Asia/Shanghai

View File

@@ -1,4 +1,4 @@
## 文档已迁移至网页端
## 文档已迁移至在线文档
* https://visor.dromara.org
* https://visor.dromara.org.cn

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

View File

@@ -1,5 +1,9 @@
#!/bin/bash
# 清空
git clean -df
# 切换到 HEAD
git reset --hard HEAD
# 拉取远程
git pull
# 查看日志
git log -n 1

View File

@@ -20,7 +20,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.common.interfaces;
package org.dromara.visor.common.cipher;
import cn.orionsec.kit.lang.utils.codec.Base62s;
import cn.orionsec.kit.lang.utils.crypto.symmetric.SymmetricCrypto;

View File

@@ -20,7 +20,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.common.interfaces;
package org.dromara.visor.common.cipher;
/**
* rsa 解密器

View File

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

View File

@@ -53,21 +53,23 @@ public interface AutoConfigureOrderConst {
int FRAMEWORK_REDIS_CACHE = Integer.MIN_VALUE + 2000;
int FRAMEWORK_CONFIG = Integer.MIN_VALUE + 2100;
int FRAMEWORK_INFLUXDB = Integer.MIN_VALUE + 2100;
int FRAMEWORK_ENCRYPT = Integer.MIN_VALUE + 2200;
int FRAMEWORK_CONFIG = Integer.MIN_VALUE + 2300;
int FRAMEWORK_STORAGE = Integer.MIN_VALUE + 2300;
int FRAMEWORK_CYPHER = Integer.MIN_VALUE + 2400;
int FRAMEWORK_JOB = Integer.MIN_VALUE + 2400;
int FRAMEWORK_STORAGE = Integer.MIN_VALUE + 2500;
int FRAMEWORK_JOB_QUARTZ = Integer.MIN_VALUE + 2500;
int FRAMEWORK_JOB = Integer.MIN_VALUE + 2600;
int FRAMEWORK_JOB_ASYNC = Integer.MIN_VALUE + 2600;
int FRAMEWORK_JOB_QUARTZ = Integer.MIN_VALUE + 2700;
int FRAMEWORK_MONITOR = Integer.MIN_VALUE + 2700;
int FRAMEWORK_JOB_ASYNC = Integer.MIN_VALUE + 2800;
int FRAMEWORK_BIZ_OPERATOR_LOG = Integer.MIN_VALUE + 2800;
int FRAMEWORK_MONITOR = Integer.MIN_VALUE + 2900;
int FRAMEWORK_BIZ_OPERATOR_LOG = Integer.MIN_VALUE + 3000;
int FRAMEWORK_BANNER = Integer.MIN_VALUE + 10000;

View File

@@ -31,24 +31,29 @@ package org.dromara.visor.common.constant;
*/
public interface BeanOrderConst {
/**
* 公共返回值包装处理器
*/
int RESPONSE_ADVICE_WRAPPER = Integer.MIN_VALUE + 1000;
/**
* 演示模式切面
*/
int DEMO_DISABLE_API_ASPECT = Integer.MIN_VALUE + 10;
int DEMO_DISABLE_API_ASPECT = Integer.MIN_VALUE + 100;
/**
* 全局日志打印
*/
int LOG_PRINT_ASPECT = Integer.MIN_VALUE + 20;
int LOG_PRINT_ASPECT = Integer.MIN_VALUE + 200;
/**
* 暴露接口切面
*/
int EXPOSE_API_ASPECT = Integer.MIN_VALUE + 300;
/**
* 操作日志切面
*/
int OPERATOR_LOG_ASPECT = Integer.MIN_VALUE + 30;
int OPERATOR_LOG_ASPECT = Integer.MIN_VALUE + 400;
/**
* 公共返回值包装处理器
*/
int RESPONSE_ADVICE_WRAPPER = Integer.MIN_VALUE + 1000;
}

View File

@@ -33,4 +33,8 @@ public interface CustomHeaderConst {
String APP_VERSION = "X-App-Version";
String AGENT_KEY_HEADER = "X-Agent-Key";
String AGENT_VERSION_HEADER = "X-Agent-Version";
}

View File

@@ -48,6 +48,8 @@ public enum ErrorCode implements CodeInfo {
UNAUTHORIZED(401, "当前认证信息已失效, 请重新登录"),
EXPOSE_UNAUTHORIZED(401, "当前认证信息错误, 请检查后重试"),
FORBIDDEN(403, "无操作权限"),
NOT_FOUND(404, "未找到该资源"),

View File

@@ -102,7 +102,7 @@ public interface ErrorMessage {
String HOST_TYPE_ERROR = "主机类型错误";
String HOST_NOT_ENABLED = "主机未启用";
String HOST_NOT_ENABLED = "{} 主机未启用";
String CONFIG_NOT_ENABLED = "配置未启用";
@@ -146,6 +146,8 @@ public interface ErrorMessage {
String FILE_ABSENT = "文件不存在";
String FILE_EXTENSION_TYPE = "文件类型不正确";
String FILE_ABSENT_CLEAR = "文件不存在 (可能已被清理)";
String LOG_ABSENT = "日志不存在";
@@ -158,6 +160,8 @@ public interface ErrorMessage {
String FILE_UPLOAD_ERROR = "文件上传失败";
String CALC_SIGN_FAILED = "计算签名失败";
String SCRIPT_UPLOAD_ERROR = "脚本上传失败";
String EXEC_ERROR = "执行失败";
@@ -182,6 +186,8 @@ public interface ErrorMessage {
String COMPRESS_FILE_ABSENT = "压缩文件不存在";
String DECOMPRESS_FILE_ABSENT = "压缩文件不存在";
String UNABLE_DOWNLOAD_FOLDER = "无法下载文件夹";
String VALID_ERROR = "验证失败";
@@ -209,6 +215,27 @@ public interface ErrorMessage {
|| ex instanceof ApplicationException;
}
/**
* 获取错误信息
*
* @param ex ex
* @return message
*/
static String getErrorMessage(Exception ex) {
return getErrorMessage(ex, ErrorMessage.EXEC_ERROR, 0);
}
/**
* 获取错误信息
*
* @param ex ex
* @param len len
* @return message
*/
static String getErrorMessage(Exception ex, int len) {
return getErrorMessage(ex, ErrorMessage.EXEC_ERROR, len);
}
/**
* 获取错误信息
*
@@ -217,6 +244,18 @@ public interface ErrorMessage {
* @return message
*/
static String getErrorMessage(Exception ex, String defaultMsg) {
return getErrorMessage(ex, defaultMsg, 0);
}
/**
* 获取错误信息
*
* @param ex ex
* @param defaultMsg defaultMsg
* @param len len
* @return message
*/
static String getErrorMessage(Exception ex, String defaultMsg, int len) {
if (ex == null) {
return null;
}
@@ -226,7 +265,11 @@ public interface ErrorMessage {
}
// 业务异常
if (isBizException(ex)) {
return message;
if (len > 0) {
return Strings.retain(message, len);
} else {
return message;
}
}
return defaultMsg;
}

View File

@@ -35,6 +35,8 @@ public interface ExtraFieldConst extends FieldConst {
String TRACE_ID = "traceId";
String TASK_ID = "taskId";
String IDENTITY = "identity";
String GROUP_NAME = "groupName";
@@ -69,4 +71,6 @@ public interface ExtraFieldConst extends FieldConst {
String DARK = "dark";
String AGENT_KEY = "agentKey";
}

View File

@@ -45,12 +45,18 @@ public interface FieldConst {
String LABEL = "label";
String FIELD = "field";
String TYPE = "type";
String COLOR = "color";
String LOADING = "loading";
String STATUS = "status";
String SWITCH = "switch";
String INFO = "info";
String EXTRA = "extra";
@@ -71,6 +77,8 @@ public interface FieldConst {
String SEQ = "seq";
String START = "start";
String PATH = "path";
String ADDRESS = "address";
@@ -119,4 +127,12 @@ public interface FieldConst {
String CONFIG = "config";
String VERSION = "version";
String SYNCED = "synced";
String SIGN = "sign";
String SIGN_SHORT = "signShort";
}

View File

@@ -37,4 +37,20 @@ public interface FileConst {
String SCRIPT = "script";
String INSTANCE_AGENT_PATH = "instance-agent";
String INSTANCE_AGENT_NAME = "instance_agent";
String INSTANCE_AGENT_FILE_FORMAT = "instance_agent_{}_{}{}";
String INSTANCE_AGENT_RELEASE = "instance-agent-release";
String INSTANCE_AGENT_RELEASE_TEMP = "instance-agent-release-temp";
String INSTANCE_AGENT_RELEASE_TAR_GZ = "instance-agent-release.tar.gz";
String VERSION = ".version";
String CONFIG_YAML = "config.yaml";
}

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.common.entity.chart;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.Map;
/**
* 时序图系列
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/9/3 21:08
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "TimeChartSeries", description = "时序图系列")
public class TimeChartSeries {
@Schema(description = "name")
private String name;
@Schema(description = "颜色")
private String color;
@Schema(description = "tags")
private Map<String, Object> tags;
@Schema(description = "数据 [0]timestampMills [1]value")
private List<List<Object>> data;
}

View File

@@ -20,7 +20,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.common.interfaces;
package org.dromara.visor.common.file;
import java.io.InputStream;
import java.io.OutputStream;

View File

@@ -0,0 +1,80 @@
/*
* 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.lock;
import cn.orionsec.kit.lang.able.Executable;
import java.util.function.Supplier;
/**
* 空实现的锁
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/8/23 13:59
*/
public class EmptyLocker implements Locker {
@Override
public boolean tryLockExecute(String key, Executable executable) {
executable.exec();
return true;
}
@Override
public boolean tryLockExecute(String key, long timeout, Executable executable) {
executable.exec();
return true;
}
@Override
public <T> T tryLockExecute(String key, Supplier<T> callable) {
return callable.get();
}
@Override
public <T> T tryLockExecute(String key, long timeout, Supplier<T> callable) {
return callable.get();
}
@Override
public void lockExecute(String key, Executable executable) {
executable.exec();
}
@Override
public void lockExecute(String key, long timeout, Executable executable) {
executable.exec();
}
@Override
public <T> T lockExecute(String key, Supplier<T> callable) {
return callable.get();
}
@Override
public <T> T lockExecute(String key, long timeout, Supplier<T> callable) {
return callable.get();
}
}

View File

@@ -0,0 +1,116 @@
/*
* 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.lock;
import cn.orionsec.kit.lang.able.Executable;
import java.util.function.Supplier;
/**
* 分布式锁
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/5/16 12:24
*/
public interface Locker {
/**
* 尝试获取锁并执行
*
* @param key key
* @param executable exec
* @return 是否获取到锁
*/
boolean tryLockExecute(String key, Executable executable);
/**
* 尝试获取锁并执行
*
* @param key key
* @param timeout timeout
* @param executable exec
* @return 是否获取到锁
*/
boolean tryLockExecute(String key, long timeout, Executable executable);
/**
* 尝试获取锁并执行 未获取到锁则抛出异常
*
* @param key key
* @param callable callable
* @param <T> T
* @return 执行结果
*/
<T> T tryLockExecute(String key, Supplier<T> callable);
/**
* 尝试获取锁并执行 未获取到锁则抛出异常
*
* @param key key
* @param timeout timeout
* @param callable callable
* @param <T> T
* @return 执行结果
*/
<T> T tryLockExecute(String key, long timeout, Supplier<T> callable);
/**
* 阻塞获取锁并执行
*
* @param key key
* @param executable exec
*/
void lockExecute(String key, Executable executable);
/**
* 阻塞获取锁并执行
*
* @param key key
* @param timeout timeout
* @param executable exec
*/
void lockExecute(String key, long timeout, Executable executable);
/**
* 阻塞获取锁并执行
*
* @param key key
* @param callable callable
* @param <T> T
* @return 执行结果
*/
<T> T lockExecute(String key, Supplier<T> callable);
/**
* 阻塞获取锁并执行
*
* @param key key
* @param timeout timeout
* @param callable callable
* @param <T> T
* @return 执行结果
*/
<T> T lockExecute(String key, long timeout, Supplier<T> callable);
}

View File

@@ -63,6 +63,9 @@ public class BaseConnectConfig implements IBaseConnectConfig {
@Schema(description = "主机端口")
private Integer hostPort;
@Schema(description = "agentKey")
private String agentKey;
@Schema(description = "用户名")
private String username;

View File

@@ -61,6 +61,10 @@ public interface IBaseConnectConfig {
void setHostPort(Integer hostPort);
String getAgentKey();
void setAgentKey(String agentKey);
String getUsername();
void setUsername(String username);

View File

@@ -23,7 +23,7 @@
package org.dromara.visor.common.utils;
import cn.orionsec.kit.lang.utils.Exceptions;
import org.dromara.visor.common.interfaces.AesEncryptor;
import org.dromara.visor.common.cipher.AesEncryptor;
/**
* aes 数据加密工具类

View File

@@ -22,9 +22,10 @@
*/
package org.dromara.visor.common.utils;
import cn.orionsec.kit.lang.able.Executable;
import cn.orionsec.kit.lang.utils.Exceptions;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.common.interfaces.Locker;
import org.dromara.visor.common.lock.Locker;
import java.util.function.Supplier;
@@ -44,26 +45,97 @@ public class LockerUtils {
}
/**
* 尝试获取锁
* 尝试获取锁并执行
*
* @param key key
* @param run run
* @param key key
* @param executable exec
* @return 是否获取到锁
*/
public static boolean tryLock(String key, Runnable run) {
return delegate.tryLock(key, run);
public static boolean tryLockExecute(String key, Executable executable) {
return delegate.tryLockExecute(key, executable);
}
/**
* 尝试获取锁
* 尝试获取锁并执行
*
* @param key key
* @param call call
* @param <T> T
* @param key key
* @param timeout timeout
* @param executable exec
* @return 是否获取到锁
*/
public static boolean tryLockExecute(String key, long timeout, Executable executable) {
return delegate.tryLockExecute(key, timeout, executable);
}
/**
* 尝试获取锁并执行 未获取到锁则抛出异常
*
* @param key key
* @param callable callable
* @param <T> T
* @return 执行结果
*/
public static <T> T tryLock(String key, Supplier<T> call) {
return delegate.tryLock(key, call);
public static <T> T tryLockExecute(String key, Supplier<T> callable) {
return delegate.tryLockExecute(key, callable);
}
/**
* 尝试获取锁并执行 未获取到锁则抛出异常
*
* @param key key
* @param timeout timeout
* @param callable callable
* @param <T> T
* @return 执行结果
*/
public static <T> T tryLockExecute(String key, long timeout, Supplier<T> callable) {
return delegate.tryLockExecute(key, timeout, callable);
}
/**
* 阻塞获取锁并执行
*
* @param key key
* @param executable exec
*/
public static void lockExecute(String key, Executable executable) {
delegate.lockExecute(key, executable);
}
/**
* 阻塞获取锁并执行
*
* @param key key
* @param timeout timeout
* @param executable exec
*/
public static void lockExecute(String key, long timeout, Executable executable) {
delegate.lockExecute(key, timeout, executable);
}
/**
* 阻塞获取锁并执行
*
* @param key key
* @param callable callable
* @param <T> T
* @return 执行结果
*/
public static <T> T lockExecute(String key, Supplier<T> callable) {
return delegate.lockExecute(key, callable);
}
/**
* 阻塞获取锁并执行
*
* @param key key
* @param timeout timeout
* @param callable callable
* @param <T> T
* @return 执行结果
*/
public static <T> T lockExecute(String key, long timeout, Supplier<T> callable) {
return delegate.lockExecute(key, timeout, callable);
}
public static void setDelegate(Locker delegate) {

View File

@@ -23,7 +23,7 @@
package org.dromara.visor.common.utils;
import cn.orionsec.kit.lang.utils.Exceptions;
import org.dromara.visor.common.interfaces.RsaDecryptor;
import org.dromara.visor.common.cipher.RsaDecryptor;
/**
* rsa 参数解密工具类

View File

@@ -23,7 +23,7 @@
package org.dromara.visor.common.validator.group;
/**
* 分页验证分组
* id 验证分组
*
* @author Jiahang Li
* @version 1.0.0

View File

@@ -0,0 +1,33 @@
/*
* 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.validator.group;
/**
* key 验证分组
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/9/1 19:13
*/
public interface Key {
}

View File

@@ -14,7 +14,7 @@
<url>https://github.com/dromara/orion-visor</url>
<properties>
<revision>2.4.2</revision>
<revision>2.5.0</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>
@@ -31,6 +31,7 @@
<druid.version>1.2.16</druid.version>
<redisson.version>3.18.0</redisson.version>
<transmittable.thread.local.version>2.14.2</transmittable.thread.local.version>
<influxdb.client.version>6.6.0</influxdb.client.version>
<mockito.inline.version>4.11.0</mockito.inline.version>
<jedis.mock.version>1.0.7</jedis.mock.version>
<podam.version>7.2.11.RELEASE</podam.version>
@@ -54,8 +55,12 @@
<version>${orion.kit.version}</version>
<exclusions>
<exclusion>
<artifactId>orion-log</artifactId>
<groupId>cn.orionsec.kit</groupId>
<artifactId>orion-log</artifactId>
</exclusion>
<exclusion>
<groupId>cn.orionsec.kit</groupId>
<artifactId>orion-generator</artifactId>
</exclusion>
</exclusions>
</dependency>
@@ -118,7 +123,7 @@
</dependency>
<dependency>
<groupId>org.dromara.visor</groupId>
<artifactId>orion-visor-spring-boot-starter-encrypt</artifactId>
<artifactId>orion-visor-spring-boot-starter-cipher</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
@@ -146,6 +151,11 @@
<artifactId>orion-visor-spring-boot-starter-test</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.dromara.visor</groupId>
<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-operator-log</artifactId>
@@ -272,6 +282,13 @@
<version>${transmittable.thread.local.version}</version>
</dependency>
<!-- influxdb -->
<dependency>
<groupId>com.influxdb</groupId>
<artifactId>influxdb-client-java</artifactId>
<version>${influxdb.client.version}</version>
</dependency>
<!-- test -->
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@@ -73,7 +73,6 @@ public class OperatorLogAspect {
.maxPoolSize(1)
.useLinkedBlockingQueue()
.allowCoreThreadTimeout()
.useLinkedBlockingQueue()
.build();
private final OperatorLogFrameworkService operatorLogFrameworkService;

View File

@@ -9,7 +9,7 @@
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>orion-visor-spring-boot-starter-encrypt</artifactId>
<artifactId>orion-visor-spring-boot-starter-cipher</artifactId>
<name>${project.artifactId}</name>
<packaging>jar</packaging>

View File

@@ -22,10 +22,10 @@
*/
package org.dromara.visor.framework.encrypt.configuration;
import org.dromara.visor.common.cipher.AesEncryptor;
import org.dromara.visor.common.cipher.RsaDecryptor;
import org.dromara.visor.common.config.ConfigStore;
import org.dromara.visor.common.constant.AutoConfigureOrderConst;
import org.dromara.visor.common.interfaces.AesEncryptor;
import org.dromara.visor.common.interfaces.RsaDecryptor;
import org.dromara.visor.common.utils.AesEncryptUtils;
import org.dromara.visor.common.utils.RsaParamDecryptUtils;
import org.dromara.visor.framework.encrypt.configuration.config.AesEncryptConfig;
@@ -45,7 +45,7 @@ import org.springframework.context.annotation.Bean;
*/
@AutoConfiguration
@EnableConfigurationProperties({AesEncryptConfig.class})
@AutoConfigureOrder(AutoConfigureOrderConst.FRAMEWORK_ENCRYPT)
@AutoConfigureOrder(AutoConfigureOrderConst.FRAMEWORK_CYPHER)
public class OrionEncryptAutoConfiguration {
/**

View File

@@ -22,7 +22,7 @@
*/
package org.dromara.visor.framework.encrypt.core;
import org.dromara.visor.common.interfaces.AesEncryptor;
import org.dromara.visor.common.cipher.AesEncryptor;
/**
* 数据加密器

View File

@@ -26,7 +26,7 @@ import cn.orionsec.kit.lang.utils.crypto.RSA;
import org.dromara.visor.common.config.ConfigRef;
import org.dromara.visor.common.config.ConfigStore;
import org.dromara.visor.common.constant.ConfigKeys;
import org.dromara.visor.common.interfaces.RsaDecryptor;
import org.dromara.visor.common.cipher.RsaDecryptor;
import java.security.interfaces.RSAPrivateKey;
import java.util.Arrays;

View File

@@ -0,0 +1,39 @@
<?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-influxdb</artifactId>
<name>${project.artifactId}</name>
<packaging>jar</packaging>
<description>项目 influxdb 配置包</description>
<url>https://github.com/dromara/orion-visor</url>
<dependencies>
<!-- common -->
<dependency>
<groupId>org.dromara.visor</groupId>
<artifactId>orion-visor-common</artifactId>
</dependency>
<!-- influxdb -->
<dependency>
<groupId>com.influxdb</groupId>
<artifactId>influxdb-client-java</artifactId>
</dependency>
<!-- web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,84 @@
/*
* 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.influxdb.configuration;
import cn.orionsec.kit.lang.utils.Strings;
import com.influxdb.LogLevel;
import com.influxdb.client.InfluxDBClient;
import com.influxdb.client.InfluxDBClientFactory;
import com.influxdb.client.InfluxDBClientOptions;
import org.dromara.visor.common.constant.AutoConfigureOrderConst;
import org.dromara.visor.framework.influxdb.configuration.config.InfluxdbConfig;
import org.dromara.visor.framework.influxdb.core.utils.InfluxdbUtils;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Lazy;
import java.net.ConnectException;
/**
* influxdb 配置类
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/8/10 20:35
*/
@Lazy(false)
@AutoConfiguration
@AutoConfigureOrder(AutoConfigureOrderConst.FRAMEWORK_INFLUXDB)
@ConditionalOnProperty(value = "spring.influxdb.enabled", havingValue = "true")
@EnableConfigurationProperties(InfluxdbConfig.class)
public class OrionInfluxdbAutoConfiguration {
/**
* TODO 重连
*
* @param config config
* @return influxdb 客户端
*/
@Bean(name = "influxDBClient")
public InfluxDBClient influxDBClient(InfluxdbConfig config) throws ConnectException {
// 参数
InfluxDBClientOptions options = InfluxDBClientOptions.builder()
.url(config.getUrl())
.authenticateToken(config.getToken().toCharArray())
.org(config.getOrg())
.bucket(config.getBucket())
.logLevel(LogLevel.NONE)
.build();
// 客户端
InfluxDBClient client = InfluxDBClientFactory.create(options);
// 尝试连接
Boolean ping = client.ping();
if (!ping) {
throw new ConnectException(Strings.format("connect to influxdb failed. url: {}, org: {}", config.getUrl(), config.getOrg()));
}
// 设置工具类
InfluxdbUtils.setInfluxClient(config.getBucket(), client);
return client;
}
}

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.influxdb.configuration.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* influxdb 配置
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/8/10 20:36
*/
@Data
@ConfigurationProperties("spring.influxdb")
public class InfluxdbConfig {
/**
* url
*/
private String url;
/**
* org
*/
private String org;
/**
* bucket
*/
private String bucket;
/**
* apiToken
*/
private String token;
}

View File

@@ -0,0 +1,349 @@
/*
* 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.influxdb.core.query;
import cn.orionsec.kit.lang.utils.collect.Collections;
import cn.orionsec.kit.lang.utils.collect.Lists;
import org.dromara.visor.common.constant.Const;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* flux 查询构建器
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/9/3 16:08
*/
public class FluxQueryBuilder {
private final StringBuilder query;
private boolean hasFilter;
private boolean pretty;
private FluxQueryBuilder(String bucket) {
this.query = new StringBuilder();
this.query.append(String.format("from(bucket: \"%s\")", bucket));
}
/**
* 创建构建器
*
* @param bucket bucket
* @return builder
*/
public static FluxQueryBuilder from(String bucket) {
return new FluxQueryBuilder(bucket);
}
/**
* 时间范围
*
* @param start 开始时间
* @param end 结束时间
* @return this
*/
public FluxQueryBuilder range(long start, long end) {
query.append(String.format(" |> range(start: %s, stop: %s)", Instant.ofEpochMilli(start), Instant.ofEpochMilli(end)));
return this;
}
/**
* 时间范围
*
* @param range range
* @return this
*/
public FluxQueryBuilder range(String range) {
query.append(String.format(" |> range(start: %s)", range));
return this;
}
/**
* 过滤 measurement
*
* @param measurement measurement
* @return this
*/
public FluxQueryBuilder measurement(String measurement) {
this.appendFilter(String.format("r[\"_measurement\"] == \"%s\"", measurement));
this.closeFilter();
return this;
}
/**
* 过滤单个 field
*
* @param field field
* @return this
*/
public FluxQueryBuilder field(String field) {
this.appendFilter(String.format("r[\"_field\"] == \"%s\"", field));
this.closeFilter();
return this;
}
/**
* 过滤多个 field
*
* @param fields fields
* @return this
*/
public FluxQueryBuilder fields(Collection<String> fields) {
if (Collections.isEmpty(fields)) {
return this;
}
List<String> conditions = new ArrayList<>();
for (String field : fields) {
conditions.add(String.format("r[\"_field\"] == \"%s\"", field));
}
this.appendFilter(String.join(" or ", conditions));
this.closeFilter();
return this;
}
/**
* 过滤 tag key
*
* @param value value
* @return this
*/
public FluxQueryBuilder key(String value) {
return this.tag(Const.KEY, value);
}
/**
* 过滤 tag key
*
* @param values values
* @return this
*/
public FluxQueryBuilder key(Collection<String> values) {
return this.tag(Const.KEY, values);
}
/**
* 过滤 tag name
*
* @param value value
* @return this
*/
public FluxQueryBuilder name(String value) {
return this.tag(Const.NAME, value);
}
/**
* 过滤 tag name
*
* @param values values
* @return this
*/
public FluxQueryBuilder name(Collection<String> values) {
return this.tag(Const.NAME, values);
}
/**
* 过滤 tag
*
* @return this
*/
public FluxQueryBuilder tag(String key, String value) {
this.appendFilter(String.format("r[\"%s\"] == \"%s\"", key, value));
this.closeFilter();
return this;
}
/**
* 过滤 tag
*
* @param key key
* @param values values
* @return this
*/
public FluxQueryBuilder tag(String key, Collection<String> values) {
if (values == null || values.isEmpty()) {
return this;
}
if (values.size() == 1) {
return this.tag(key, Collections.first(values));
}
//
Collection<String> conditions = values.stream()
.map(value -> String.format("r[\"%s\"] == \"%s\"", key, value))
.collect(Collectors.toList());
this.appendFilter(String.join(" or ", conditions));
this.closeFilter();
return this;
}
/**
* 过滤多个 tag
* tag 使用 and
* value 使用 or
*
* @param tags tags
* @return this
*/
public FluxQueryBuilder tags(Map<String, ? extends Collection<String>> tags) {
for (Map.Entry<String, ? extends Collection<String>> entry : tags.entrySet()) {
String key = entry.getKey();
Collection<String> values = entry.getValue();
if (Collections.isEmpty(values)) {
continue;
}
if (values.size() == 1) {
// 单值直接用等号
String singleValue = values.iterator().next();
this.appendFilter(String.format("r[\"%s\"] == \"%s\"", key, singleValue));
} else {
// 多值用 OR
Collection<String> conditions = values.stream()
.map(v -> String.format("r[\"%s\"] == \"%s\"", key, v))
.collect(Collectors.toList());
this.appendFilter("(" + String.join(" or ", conditions) + ")");
}
}
this.closeFilter();
return this;
}
/**
* 聚合窗口
*/
public FluxQueryBuilder aggregateWindow(String every, String fn) {
query.append(String.format(" |> aggregateWindow(every: %s, fn: %s)", every, fn));
return this;
}
/**
* 聚合窗口
*/
public FluxQueryBuilder aggregateWindow(String every, String fn, boolean createEmpty) {
query.append(String.format(" |> aggregateWindow(every: %s, fn: %s, createEmpty: %b)", every, fn, createEmpty));
return this;
}
/**
* 排序
*
* @param columns columns
* @return this
*/
public FluxQueryBuilder sort(List<String> columns) {
StringBuilder cols = new StringBuilder();
for (int i = 0; i < columns.size(); i++) {
cols.append("\"").append(columns.get(i)).append("\"");
if (i < columns.size() - 1) cols.append(", ");
}
query.append(String.format(" |> sort(columns: [%s])", cols));
return this;
}
/**
* 降序
*
* @param column column
* @return this
*/
public FluxQueryBuilder sortDesc(String column) {
return this.sort(Lists.singleton("-" + column));
}
/**
* 升序
*
* @param column column
* @return this
*/
public FluxQueryBuilder sortAsc(String column) {
return this.sort(Lists.singleton(column));
}
/**
* 限制条数
*
* @param n limit
* @return this
*/
public FluxQueryBuilder limit(int n) {
query.append(String.format(" |> limit(n: %d)", n));
return this;
}
/**
* 基础过滤拼接
*/
private void appendFilter(String condition) {
if (!hasFilter) {
query.append(" |> filter(fn: (r) => ");
this.hasFilter = true;
} else {
query.append(" and ");
}
query.append(condition);
}
/**
* 结束 filter 并闭合括号
*/
private void closeFilter() {
if (hasFilter) {
query.append(")");
this.hasFilter = false;
}
}
/**
* 设置美观输出
*
* @return this
*/
public FluxQueryBuilder pretty() {
this.pretty = true;
return this;
}
/**
* 构建查询
*/
public String build() {
if (this.pretty) {
return query.toString().replaceAll("\\|>", "\n |>");
} else {
return query.toString();
}
}
@Override
public String toString() {
return this.build();
}
}

View File

@@ -0,0 +1,183 @@
/*
* 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.influxdb.core.utils;
import cn.orionsec.kit.lang.utils.Exceptions;
import cn.orionsec.kit.lang.utils.collect.Lists;
import com.influxdb.client.InfluxDBClient;
import com.influxdb.client.WriteApi;
import com.influxdb.client.write.Point;
import com.influxdb.query.FluxRecord;
import com.influxdb.query.FluxTable;
import org.dromara.visor.common.constant.Const;
import org.dromara.visor.common.entity.chart.TimeChartSeries;
import org.dromara.visor.framework.influxdb.core.query.FluxQueryBuilder;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* influxdb 工具类
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/8/10 20:47
*/
public class InfluxdbUtils {
private static final String FIELD_KEY = "_field";
private static final List<String> SKIP_EXTRA_KEY = Lists.of("result", "table", "_measurement", "_start", "_stop", "_time", "_value");
private static InfluxDBClient client;
private static String bucket;
private InfluxdbUtils() {
}
/**
* 写入指标
*
* @param points points
*/
public static void writePoints(List<Point> points) {
try (WriteApi api = client.makeWriteApi()) {
// 写入指标
api.writePoints(points);
}
}
/**
* 查询数据点
*
* @param query query
* @return points
*/
public static List<FluxTable> queryTable(String query) {
return client.getQueryApi().query(query);
}
/**
* 查询数据点
*
* @param query query
* @return points
*/
public static FluxTable querySingleTable(String query) {
return Lists.first(queryTable(query));
}
/**
* 查询时序系列
*
* @param query query
* @return points
*/
public static List<TimeChartSeries> querySeries(String query) {
return toSeries(queryTable(query));
}
/**
* 查询时序系列
*
* @param query query
* @return points
*/
public static TimeChartSeries querySingleSeries(String query) {
return toSeries(querySingleTable(query));
}
/**
* 转为时序系列
*
* @param table table
* @return series
*/
public static TimeChartSeries toSeries(FluxTable table) {
// 数据
Map<String, Object> tags = new HashMap<>();
List<List<Object>> dataList = new ArrayList<>();
for (FluxRecord record : table.getRecords()) {
Instant time = record.getTime();
if (time == null) {
continue;
}
// 设置数据
List<Object> data = new ArrayList<>(2);
data.add(time.toEpochMilli());
data.add(record.getValue());
dataList.add(data);
// 设置额外值
record.getValues().forEach((k, v) -> {
if (SKIP_EXTRA_KEY.contains(k)) {
return;
}
tags.put(k, v);
});
}
// 设置 field
tags.put(Const.FIELD, tags.get(FIELD_KEY));
tags.remove(FIELD_KEY);
// 创建 series
return TimeChartSeries.builder()
.data(dataList)
.tags(tags)
.build();
}
/**
* 转为时序系列
*
* @param tables tables
* @return series
*/
public static List<TimeChartSeries> toSeries(List<FluxTable> tables) {
return tables.stream()
.map(InfluxdbUtils::toSeries)
.collect(Collectors.toList());
}
/**
* 获取查询构建器
*
* @return builder
*/
public static FluxQueryBuilder query() {
return FluxQueryBuilder.from(bucket);
}
public static void setInfluxClient(String bucket, InfluxDBClient client) {
if (InfluxdbUtils.client != null) {
// unmodified
throw Exceptions.state();
}
InfluxdbUtils.client = client;
InfluxdbUtils.bucket = bucket;
}
}

View File

@@ -0,0 +1,37 @@
{
"groups": [
{
"name": "spring.influxdb",
"type": "org.dromara.visor.framework.influxdb.configuration.config.InfluxdbConfig",
"sourceType": "org.dromara.visor.framework.influxdb.configuration.config.InfluxdbConfig"
}
],
"properties": [
{
"name": "spring.influxdb.enabled",
"type": "java.lang.Boolean",
"description": "是否启用 influxdb.",
"defaultValue": "false"
},
{
"name": "spring.influxdb.url",
"type": "java.lang.String",
"description": "influxdb 地址."
},
{
"name": "spring.influxdb.org",
"type": "java.lang.String",
"description": "influxdb org."
},
{
"name": "spring.influxdb.bucket",
"type": "java.lang.String",
"description": "influxdb bucket."
},
{
"name": "spring.influxdb.token",
"type": "java.lang.String",
"description": "influxdb token."
}
]
}

View File

@@ -0,0 +1 @@
org.dromara.visor.framework.influxdb.configuration.OrionInfluxdbAutoConfiguration

View File

@@ -23,13 +23,13 @@
package org.dromara.visor.framework.job.configuration;
import org.dromara.visor.common.constant.AutoConfigureOrderConst;
import org.dromara.visor.common.constant.Const;
import org.dromara.visor.common.thread.ThreadPoolMdcTaskExecutor;
import org.dromara.visor.framework.job.configuration.config.AsyncExecutorConfig;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.annotation.EnableAsync;
@@ -49,13 +49,10 @@ import java.util.concurrent.ThreadPoolExecutor;
public class OrionAsyncAutoConfiguration {
/**
* 支持 MDC 的异步线程池
* <p>
* {@code @Async("asyncExecutor")}
*
* @return 异步线程池
* @return 支持 MDC 的异步线程池
*/
@Primary
@Bean(name = "asyncExecutor")
public TaskExecutor asyncExecutor(AsyncExecutorConfig config) {
ThreadPoolMdcTaskExecutor executor = new ThreadPoolMdcTaskExecutor();
@@ -75,4 +72,25 @@ public class OrionAsyncAutoConfiguration {
return executor;
}
/**
* {@code @Async("metricsExecutor")}
*
* @return 指标线程池
*/
@Bean(name = "metricsExecutor")
public TaskExecutor metricsExecutor() {
ThreadPoolMdcTaskExecutor executor = new ThreadPoolMdcTaskExecutor();
executor.setCorePoolSize(4);
executor.setMaxPoolSize(8);
executor.setQueueCapacity(1000);
executor.setKeepAliveSeconds(Const.MS_S_60);
executor.setAllowCoreThreadTimeOut(true);
executor.setThreadNamePrefix("metrics-task-");
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}

View File

@@ -29,8 +29,8 @@ import java.lang.annotation.*;
/**
* 不执行统一日志打印
* <p>
* 如果设置在方法上则忽略该方法的日志打印
* 如果设置到参数上则忽略该参数的日志打印
* 如果设置在方法上, 则忽略该方法的日志打印
* 如果设置到参数上, 则忽略该参数的日志打印
*
* @author Jiahang Li
* @version 1.0.0

View File

@@ -22,6 +22,7 @@
*/
package org.dromara.visor.framework.log.core.interceptor;
import cn.orionsec.kit.lang.utils.Strings;
import cn.orionsec.kit.lang.utils.collect.Maps;
import cn.orionsec.kit.lang.utils.reflect.Classes;
import com.alibaba.fastjson.JSON;
@@ -31,8 +32,8 @@ import com.alibaba.fastjson.serializer.ValueFilter;
import org.aopalliance.intercept.MethodInvocation;
import org.dromara.visor.common.json.FieldDesensitizeFilter;
import org.dromara.visor.common.json.FieldIgnoreFilter;
import org.dromara.visor.common.trace.TraceIdHolder;
import org.dromara.visor.common.security.SecurityHolder;
import org.dromara.visor.common.trace.TraceIdHolder;
import org.dromara.visor.framework.log.configuration.config.LogPrinterConfig;
import org.dromara.visor.framework.log.core.annotation.IgnoreLog;
import org.dromara.visor.framework.log.core.enums.IgnoreLogMode;
@@ -42,12 +43,13 @@ import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Predicate;
/**
* 日志打印拦截器 基类
@@ -60,11 +62,6 @@ public abstract class AbstractLogPrinterInterceptor implements LogPrinterInterce
private static final ThreadLocal<IgnoreLogMode> IGNORE_LOG_MODE = new ThreadLocal<>();
/**
* 请求头过滤器
*/
protected Predicate<String> headerFilter;
/**
* 字段过滤器
*/
@@ -93,8 +90,6 @@ public abstract class AbstractLogPrinterInterceptor implements LogPrinterInterce
@Override
public void init() {
// 请求头过滤器
this.headerFilter = header -> config.getHeaders().contains(header);
// 参数过滤器
this.serializeFilters = new SerializeFilter[]{
// 忽略字段过滤器
@@ -136,6 +131,24 @@ public abstract class AbstractLogPrinterInterceptor implements LogPrinterInterce
}
}
/**
* 获取请求头
*
* @param request request
* @return headers
*/
protected Map<String, String> getHeaderMap(HttpServletRequest request) {
Map<String, String> headers = new LinkedHashMap<>();
for (String headerName : config.getHeaders()) {
String headerValue = request.getHeader(headerName);
if (Strings.isBlank(headerValue)) {
continue;
}
headers.put(headerName, headerValue);
}
return headers;
}
/**
* 打印请求信息
*

View File

@@ -84,13 +84,11 @@ public class PrettyLogPrinterInterceptor extends AbstractLogPrinterInterceptor {
if (request != null) {
// remoteAddr
requestLog.append("\tremoteAddr: ").append(IpUtils.getRemoteAddr(request)).append('\n');
// header
Servlets.getHeaderMap(request).forEach((hk, hv) -> {
if (headerFilter.test(hk.toLowerCase())) {
requestLog.append('\t')
.append(hk).append(": ")
.append(hv).append('\n');
}
// headers
this.getHeaderMap(request).forEach((hk, hv) -> {
requestLog.append('\t')
.append(hk).append(": ")
.append(hv).append('\n');
});
}
Method method = invocation.getMethod();

View File

@@ -85,14 +85,8 @@ public class RowLogPrinterInterceptor extends AbstractLogPrinterInterceptor impl
if (request != null) {
// remoteAddr
fields.put(REMOTE_ADDR, IpUtils.getRemoteAddr(request));
// header
Map<String, Object> headers = new LinkedHashMap<>();
Servlets.getHeaderMap(request).forEach((hk, hv) -> {
if (headerFilter.test(hk.toLowerCase())) {
headers.put(hk, hv);
}
});
fields.put(HEADERS, headers);
// headers
fields.put(HEADERS, this.getHeaderMap(request));
}
Method method = invocation.getMethod();
// 方法签名

View File

@@ -66,7 +66,7 @@ public class BaseDO implements Serializable {
private String creator;
@Schema(description = "修改人")
@TableField(fill = FieldFill.INSERT_UPDATE, jdbcType = JdbcType.VARCHAR)
@TableField(fill = FieldFill.INSERT_UPDATE, update = "IFNULL(#{et.updater}, updater)", jdbcType = JdbcType.VARCHAR)
private String updater;
/**
@@ -78,4 +78,4 @@ public class BaseDO implements Serializable {
@TableField(fill = FieldFill.INSERT, jdbcType = JdbcType.TINYINT)
private Boolean deleted;
}
}

View File

@@ -54,12 +54,13 @@ public class CodeGenerators {
// 作者
String author = Const.ORION_AUTHOR;
// 模块
String module = "infra";
String module = "asset";
// 生成的表
Table[] tables = {
// Template.create("dict_key", "字典配置项", "dict")
// .enableProviderApi()
// .disableUnitTest()
// .enableDeleteUseBatch()
// .cache("dict:keys", "字典配置项")
// .expire(8, TimeUnit.HOURS)
// .vue("system", "dict-key")
@@ -73,23 +74,28 @@ public class CodeGenerators {
// .color("blue", "gray", "red", "green", "white")
// .valueUseFields()
// .build(),
// Template.create("exec_template_host", "执行模板主机", "exec")
// .enableProviderApi()
// .cache("sl", "22")
// .vue("exec", "exec-template-host")
// .build(),
Template.create("system_message", "系统消息", "message")
Template.create("host_agent_log", "主机探针日志", "agent")
.disableUnitTest()
.enableProviderApi()
.vue("system", "message")
.dict("messageType", "type", "messageType")
.comment("消息类型")
.fields("EXEC_FAILED", "UPLOAD_FAILED")
.labels("执行失败", "上传失败")
.extra("tagLabel", "执行失败", "上传失败")
.extra("tagVisible", true, true)
.extra("tagColor", "red", "red")
.vue("monitor", "monitor-host")
.disableRowSelection()
.enableCardView()
.enableDrawerForm()
.dict("agentLogType", "type")
.comment("探针日志类型")
.fields("OFFLINE", "ONLINE", "INSTALL", "START", "STOP")
.labels("下线", "上线", "安装", "启动", "停止")
.valueUseFields()
.dict("agentLogStatus", "status")
.comment("探针日志状态")
.fields("WAIT", "RUNNING", "SUCCESS", "FAILED")
.labels("等待中", "运行中", "成功", "失败")
.color("green", "green", "arcoblue", "red")
.loading(true, true, false, false)
.valueUseFields()
.build(),
};
// jdbc 配置 - 使用配置文件
@@ -98,7 +104,6 @@ public class CodeGenerators {
String url = resolveConfigValue(yaml.getValue("spring.datasource.druid.url"));
String username = resolveConfigValue(yaml.getValue("spring.datasource.druid.username"));
String password = resolveConfigValue(yaml.getValue("spring.datasource.druid.password"));
// 执行
runGenerator(outputDir, author,
tables, module,

View File

@@ -23,10 +23,12 @@
package org.dromara.visor.framework.mybatis.core.generator.core;
import cn.orionsec.kit.lang.able.Executable;
import cn.orionsec.kit.lang.constant.Const;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.builder.CustomFile;
import com.baomidou.mybatisplus.generator.config.builder.Entity;
import com.baomidou.mybatisplus.generator.config.querys.MySqlQuery;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.DbColumnType;
@@ -135,6 +137,10 @@ public class CodeGenerator implements Executable {
// 整合注入配置
.injection(injectionConfig);
// 提前解析父类 并且排除父类的 id 字段
Entity entity = strategyConfig.entity();
entity.getSuperEntityColumns().remove(Const.ID);
// 执行
ag.execute(engine);
}
@@ -188,6 +194,8 @@ public class CodeGenerator implements Executable {
case Types.BIT:
case Types.TINYINT:
return DbColumnType.INTEGER;
case Types.DOUBLE:
return DbColumnType.DOUBLE;
default:
return typeRegistry.getColumnType(metaInfo);
}
@@ -321,9 +329,14 @@ public class CodeGenerator implements Executable {
*/
private InjectionConfig getInjectionConfig() {
String[][] customFileDefineArr = new String[][]{
// -------------------- 后端 - module --------------------
// -------------------- 后端 --------------------
// http 文件
new String[]{"/templates/orion-server-module-controller.http.vm", "${type}Controller.http", "controller"},
// operator log define 文件
new String[]{"/templates/orion-server-module-operator-key-define.java.vm", "${type}OperatorType.java", "define.operator"},
// convert 文件
new String[]{"/templates/orion-server-module-convert.java.vm", "${type}Convert.java", "convert"},
// -------------------- 后端 - 实体 --------------------
// vo 文件
new String[]{"/templates/orion-server-module-entity-vo.java.vm", "${type}VO.java", "entity.vo"},
// create request 文件
@@ -332,19 +345,19 @@ public class CodeGenerator implements Executable {
new String[]{"/templates/orion-server-module-entity-request-update.java.vm", "${type}UpdateRequest.java", "entity.request.${bizPackage}"},
// query request 文件
new String[]{"/templates/orion-server-module-entity-request-query.java.vm", "${type}QueryRequest.java", "entity.request.${bizPackage}"},
// convert 文件
new String[]{"/templates/orion-server-module-convert.java.vm", "${type}Convert.java", "convert"},
// -------------------- 后端 - 缓存 --------------------
// cache dto 文件
new String[]{"/templates/orion-server-module-cache-dto.java.vm", "${type}CacheDTO.java", "entity.dto"},
// cache key define 文件
new String[]{"/templates/orion-server-module-cache-key-define.java.vm", "${type}CacheKeyDefine.java", "define.cache"},
// operator log define 文件
new String[]{"/templates/orion-server-module-operator-key-define.java.vm", "${type}OperatorType.java", "define.operator"},
// -------------------- 后端 - provider --------------------
// api 文件
new String[]{"/templates/orion-server-provider-api.java.vm", "${type}Api.java", "api"},
// api impl 文件
new String[]{"/templates/orion-server-provider-api-impl.java.vm", "${type}ApiImpl.java", "api.impl"},
// convert 文件
new String[]{"/templates/orion-server-provider-convert.java.vm", "${type}ProviderConvert.java", "convert"},
// -------------------- 后端 - provider 实体 --------------------
// dto 文件
new String[]{"/templates/orion-server-provider-entity-dto.java.vm", "${type}DTO.java", "entity.dto.${bizPackage}"},
// create dto 文件
@@ -353,8 +366,6 @@ public class CodeGenerator implements Executable {
new String[]{"/templates/orion-server-provider-entity-dto-update.java.vm", "${type}UpdateDTO.java", "entity.dto.${bizPackage}"},
// query dto 文件
new String[]{"/templates/orion-server-provider-entity-dto-query.java.vm", "${type}QueryDTO.java", "entity.dto.${bizPackage}"},
// convert 文件
new String[]{"/templates/orion-server-provider-convert.java.vm", "${type}ProviderConvert.java", "convert"},
// -------------------- 后端 - test --------------------
// service unit test 文件
new String[]{"/templates/orion-server-test-service-impl-tests.java.vm", "${type}ServiceImplTests.java", "service.impl"},
@@ -369,22 +380,26 @@ public class CodeGenerator implements Executable {
new String[]{"/templates/orion-vue-router.ts.vm", "${feature}.ts", "vue/router/routes/modules"},
// views index.ts 文件
new String[]{"/templates/orion-vue-views-index.vue.vm", "index.vue", "vue/views/${module}/${feature}"},
// const.ts 文件
new String[]{"/templates/orion-vue-views-types-const.ts.vm", "const.ts", "vue/views/${module}/${feature}/types"},
// -------------------- 前端 - form --------------------
// form-modal.vue 文件
new String[]{"/templates/orion-vue-views-components-form-modal.vue.vm", "${feature}-form-modal.vue", "vue/views/${module}/${feature}/components"},
// form-drawer.vue 文件
new String[]{"/templates/orion-vue-views-components-form-drawer.vue.vm", "${feature}-form-drawer.vue", "vue/views/${module}/${feature}/components"},
// table.vue 文件
new String[]{"/templates/orion-vue-views-components-table.vue.vm", "${feature}-table.vue", "vue/views/${module}/${feature}/components"},
// card-list.vue 文件
new String[]{"/templates/orion-vue-views-components-card-list.vue.vm", "${feature}-card-list.vue", "vue/views/${module}/${feature}/components"},
// const.ts 文件
new String[]{"/templates/orion-vue-views-types-const.ts.vm", "const.ts", "vue/views/${module}/${feature}/types"},
// form.rules.ts 文件
new String[]{"/templates/orion-vue-views-types-form.rules.ts.vm", "form.rules.ts", "vue/views/${module}/${feature}/types"},
// -------------------- 前端 - table --------------------
// table.vue 文件
new String[]{"/templates/orion-vue-views-components-table.vue.vm", "${feature}-table.vue", "vue/views/${module}/${feature}/components"},
// table.columns.ts 文件
new String[]{"/templates/orion-vue-views-types-table.columns.ts.vm", "table.columns.ts", "vue/views/${module}/${feature}/types"},
// -------------------- 前端 - card --------------------
// card-list.vue 文件
new String[]{"/templates/orion-vue-views-components-card-list.vue.vm", "${feature}-card-list.vue", "vue/views/${module}/${feature}/components"},
// card.fields.ts 文件
new String[]{"/templates/orion-vue-views-types-card.fields.ts.vm", "card.fields.ts", "vue/views/${module}/${feature}/types"},
// -------------------- sql --------------------
// menu.sql 文件
new String[]{"/templates/orion-sql-menu.sql.vm", "${tableName}-menu.sql", "sql"},
// dict.sql 文件

View File

@@ -81,7 +81,7 @@ public class DictParser {
meta.setComment(Strings.def(tableField.getComment(), meta.getField()));
}
// 设置额外参数 schema
if (meta.getExtraValues().size() > 0) {
if (!meta.getExtraValues().isEmpty()) {
List<Map<String, String>> extraSchema = meta.getExtraValues().get(0)
.keySet()
.stream()

View File

@@ -165,6 +165,16 @@ public class DictTemplate extends Template {
return this.extra(Const.COLOR, colors);
}
/**
* 添加 loading
*
* @param loading loading
* @return this
*/
public DictTemplate loading(Object... loading) {
return this.extra(Const.LOADING, loading);
}
/**
* 添加额外值
*

View File

@@ -60,6 +60,7 @@ public class ${entity} {
#end
## ---------- BEGIN 字段循环遍历 ----------
#foreach($field in ${table.fields})
#if("$!field.propertyName" != "id")
#if(${field.keyFlag})
#set($keyPropertyName=${field.propertyName})
@@ -88,5 +89,6 @@ public class ${entity} {
#end
private ${field.propertyType} ${field.propertyName};
#end
#end
}

View File

@@ -133,14 +133,14 @@
const cardColLayout = useCardColLayout();
const pagination = useCardPagination();
const { loading, setLoading } = useLoading();
const queryOrder = useQueryOrder(TableName, ASC);
const { cardFieldConfig, fieldsHook } = useCardFieldConfig(TableName, fieldConfig);
const { loading, setLoading } = useLoading();
#if($dictMap.entrySet().size() > 0)
const { toOptions, getDictValue } = useDictStore();
#end
const list = ref<${vue.featureEntity}QueryResponse[]>([]);
const list = ref<Array<${vue.featureEntity}QueryResponse>>([]);
const formRef = ref();
const formModel = reactive<${vue.featureEntity}QueryRequest>({
searchValue: undefined,

View File

@@ -172,9 +172,9 @@
const rowSelection = useRowSelection();
#end
const pagination = useTablePagination();
const { loading, setLoading } = useLoading();
const queryOrder = useQueryOrder(TableName, ASC);
const { tableColumns, columnsHook } = useTableColumns(TableName, columns);
const { loading, setLoading } = useLoading();
#if($dictMap.entrySet().size() > 0)
const { toOptions, getDictValue } = useDictStore();
#end

View File

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

View File

@@ -1,4 +1,4 @@
export const TABLE_NAME = '$table.name';
export const TableName = '$table.name';
#if($dictMap.entrySet().size() > 0)
#foreach($enumEntity in $dictMap.entrySet())

View File

@@ -1,4 +1,5 @@
import type { FieldRule } from '@arco-design/web-vue';
#foreach($field in ${table.fields})
#if("$!field.propertyName" != "id")
#if(${field.propertyType} == 'String' && "$field.metaInfo.jdbcType" != "LONGVARCHAR")

View File

@@ -19,6 +19,8 @@ const columns = [
minWidth: 238,
ellipsis: true,
tooltip: true,
#elseif(${field.propertyType} == 'Integer' || ${field.propertyType} == 'Long')
width: 120,
#elseif(${field.propertyType} == 'Date')
width: 180,
render: ({ record }) => {
@@ -35,6 +37,7 @@ const columns = [
render: ({ record }) => {
return dateFormat(new Date(record.createTime));
},
default: true,
}, {
title: '修改时间',
dataIndex: 'updateTime',
@@ -48,6 +51,7 @@ const columns = [
title: '创建人',
dataIndex: 'creator',
slotName: 'creator',
default: true,
}, {
title: '修改人',
dataIndex: 'updater',
@@ -58,6 +62,7 @@ const columns = [
width: 130,
align: 'center',
fixed: 'right',
default: true,
},
] as TableColumnData[];

View File

@@ -24,7 +24,8 @@ package org.dromara.visor.framework.redis.configuration;
import com.github.fppt.jedismock.RedisServer;
import org.dromara.visor.common.constant.AutoConfigureOrderConst;
import org.dromara.visor.common.interfaces.Locker;
import org.dromara.visor.common.lock.EmptyLocker;
import org.dromara.visor.common.lock.Locker;
import org.dromara.visor.common.utils.LockerUtils;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
@@ -35,7 +36,6 @@ import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import java.net.InetAddress;
import java.util.function.Supplier;
/**
* MockRedis
@@ -79,18 +79,7 @@ public class OrionMockRedisAutoConfiguration {
*/
@Bean
public Locker redisLocker() {
Locker locker = new Locker() {
@Override
public boolean tryLock(String key, Runnable run) {
run.run();
return true;
}
@Override
public <T> T tryLock(String key, Supplier<T> call) {
return call.get();
}
};
EmptyLocker locker = new EmptyLocker();
LockerUtils.setDelegate(locker);
return locker;
}

View File

@@ -24,7 +24,7 @@ package org.dromara.visor.framework.redis.configuration;
import cn.orionsec.kit.lang.define.cache.key.CacheKeyDefine;
import org.dromara.visor.common.constant.AutoConfigureOrderConst;
import org.dromara.visor.common.interfaces.Locker;
import org.dromara.visor.common.lock.Locker;
import org.dromara.visor.common.utils.LockerUtils;
import org.dromara.visor.framework.redis.configuration.config.RedissonConfig;
import org.dromara.visor.framework.redis.core.lock.RedisLocker;

View File

@@ -22,12 +22,14 @@
*/
package org.dromara.visor.framework.redis.core.lock;
import cn.orionsec.kit.lang.able.Executable;
import cn.orionsec.kit.lang.utils.Exceptions;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.common.interfaces.Locker;
import org.dromara.visor.common.lock.Locker;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
/**
@@ -40,6 +42,8 @@ import java.util.function.Supplier;
@Slf4j
public class RedisLocker implements Locker {
private static final String LOCK_KEY_PREFIX = "lock:";
private final RedissonClient redissonClient;
public RedisLocker(RedissonClient redissonClient) {
@@ -47,37 +51,154 @@ public class RedisLocker implements Locker {
}
@Override
public boolean tryLock(String key, Runnable run) {
public boolean tryLockExecute(String key, Executable executable) {
return this.tryLockExecute(key, 0, executable);
}
@Override
public boolean tryLockExecute(String key, long timeout, Executable executable) {
// 获取锁
RLock lock = redissonClient.getLock(key);
RLock lock = this.getLock(key);
// 未获取到直接返回
if (!lock.tryLock()) {
log.info("RedisLocker.tryLock failed {}", key);
if (this.tryLock(lock, timeout)) {
return false;
}
// 执行
try {
run.run();
executable.exec();
} finally {
lock.unlock();
this.unlockSafe(lock);
}
return true;
}
@Override
public <T> T tryLock(String key, Supplier<T> call) {
public <T> T tryLockExecute(String key, Supplier<T> callable) {
return this.tryLockExecute(key, 0, callable);
}
@Override
public <T> T tryLockExecute(String key, long timeout, Supplier<T> callable) {
// 获取锁
RLock lock = redissonClient.getLock(key);
RLock lock = this.getLock(key);
// 未获取到直接返回
if (!lock.tryLock()) {
log.info("RedisLocker.tryLock failed {}", key);
if (this.tryLock(lock, timeout)) {
throw Exceptions.lock();
}
// 执行
try {
return call.get();
return callable.get();
} finally {
this.unlockSafe(lock);
}
}
@Override
public void lockExecute(String key, Executable executable) {
this.lockExecute(key, 0, executable);
}
@Override
public void lockExecute(String key, long timeout, Executable executable) {
// 获取锁
RLock lock = this.getLock(key);
this.lock(lock, timeout);
// 执行
try {
executable.exec();
} finally {
this.unlockSafe(lock);
}
}
@Override
public <T> T lockExecute(String key, Supplier<T> callable) {
return this.lockExecute(key, 0, callable);
}
@Override
public <T> T lockExecute(String key, long timeout, Supplier<T> callable) {
// 获取锁
RLock lock = this.getLock(key);
this.lock(lock, timeout);
// 执行
try {
return callable.get();
} finally {
this.unlockSafe(lock);
}
}
/**
* 获取锁
*
* @param key key
* @return lock
*/
private RLock getLock(String key) {
return redissonClient.getLock(LOCK_KEY_PREFIX + key);
}
/**
* 尝试上锁
*
* @param lock lock
* @param timeout timeout
* @return locked
*/
private boolean tryLock(RLock lock, long timeout) {
boolean result;
try {
if (timeout == 0) {
result = lock.tryLock();
} else {
result = lock.tryLock(timeout, TimeUnit.MILLISECONDS);
}
if (!result) {
log.warn("RedisLocker.tryLock failed {}", lock.getName());
}
} catch (InterruptedException e) {
log.error("RedisLocker.tryLock timed out {}", lock.getName(), e);
throw Exceptions.lock(e);
} catch (Exception e) {
log.error("RedisLocker.tryLock error {}", lock.getName(), e);
throw Exceptions.lock(e);
}
return result;
}
/**
* 上锁
*
* @param lock lock
* @param timeout timeout
*/
private void lock(RLock lock, long timeout) {
try {
if (timeout == 0) {
lock.lock();
} else {
lock.lock(timeout, TimeUnit.MILLISECONDS);
}
} catch (Exception e) {
log.error("RedisLocker.lock lock error {}", lock.getName(), e);
throw Exceptions.lock(e);
}
}
/**
* 安全的释放锁
*
* @param lock lock
*/
private void unlockSafe(RLock lock) {
if (!lock.isHeldByCurrentThread()) {
return;
}
try {
lock.unlock();
} catch (Exception e) {
log.warn("RedisLocker.unlock failed {}", lock.getName(), e);
}
}

View File

@@ -32,6 +32,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
@@ -49,6 +50,42 @@ public class RedisStrings extends RedisUtils {
private RedisStrings() {
}
/**
* 获取值
*
* @param key key
* @return value
*/
public static String get(String key) {
return redisTemplate.opsForValue().get(key);
}
/**
* 获取值
*
* @param define define
* @return value
*/
public static String get(CacheKeyDefine define) {
return get(define.getKey());
}
/**
* 获取值
*
* @param key key
* @param mapper mapper
* @param <T> T
* @return value
*/
public static <T> T get(String key, Function<String, T> mapper) {
String value = redisTemplate.opsForValue().get(key);
if (value == null) {
return null;
}
return mapper.apply(value);
}
/**
* 获取 json
*
@@ -56,11 +93,7 @@ public class RedisStrings extends RedisUtils {
* @return JSONObject
*/
public static JSONObject getJson(String key) {
String value = redisTemplate.opsForValue().get(key);
if (value == null) {
return null;
}
return JSON.parseObject(value);
return get(key, JSON::parseObject);
}
/**
@@ -95,57 +128,7 @@ public class RedisStrings extends RedisUtils {
* @return T
*/
public static <T> T getJson(String key, Class<T> type) {
String value = redisTemplate.opsForValue().get(key);
if (value == null) {
return null;
}
return (T) JSON.parseObject(value, type);
}
/**
* 获取 json 列表
*
* @param keys keys
* @return cache
*/
public static List<JSONObject> getJsonList(Collection<String> keys) {
List<String> values = redisTemplate.opsForValue().multiGet(keys);
if (values == null) {
return new ArrayList<>();
}
return values.stream()
.map(JSON::parseObject)
.collect(Collectors.toList());
}
/**
* 获取 json 列表
*
* @param keys keys
* @param define define
* @param <T> T
* @return cache
*/
public static <T> List<T> getJsonList(Collection<String> keys, CacheKeyDefine define) {
return getJsonList(keys, (Class<T>) define.getType());
}
/**
* 获取 json 列表
*
* @param keys keys
* @param type type
* @param <T> T
* @return cache
*/
public static <T> List<T> getJsonList(Collection<String> keys, Class<T> type) {
List<String> values = redisTemplate.opsForValue().multiGet(keys);
if (values == null) {
return new ArrayList<>();
}
return values.stream()
.map(s -> JSON.parseObject(s, type))
.collect(Collectors.toList());
return get(key, s -> JSON.parseObject(s, type));
}
/**
@@ -155,11 +138,7 @@ public class RedisStrings extends RedisUtils {
* @return JSONArray
*/
public static JSONArray getJsonArray(String key) {
String value = redisTemplate.opsForValue().get(key);
if (value == null) {
return null;
}
return JSON.parseArray(value);
return get(key, JSON::parseArray);
}
/**
@@ -194,11 +173,69 @@ public class RedisStrings extends RedisUtils {
* @return T
*/
public static <T> List<T> getJsonArray(String key, Class<T> type) {
String value = redisTemplate.opsForValue().get(key);
if (value == null) {
return null;
return get(key, s -> JSON.parseArray(s, type));
}
/**
* 获取 json 列表
*
* @param keys keys
* @return cache
*/
public static List<String> getList(Collection<String> keys) {
return getList(keys, Function.identity());
}
/**
* 获取 json 列表
*
* @param keys keys
* @param mapper mapper
* @param <T> T
* @return cache
*/
public static <T> List<T> getList(Collection<String> keys, Function<String, T> mapper) {
List<String> values = redisTemplate.opsForValue().multiGet(keys);
if (values == null) {
return new ArrayList<>();
}
return JSON.parseArray(value, type);
return values.stream()
.map(mapper)
.collect(Collectors.toList());
}
/**
* 获取 json 列表
*
* @param keys keys
* @return cache
*/
public static List<JSONObject> getJsonList(Collection<String> keys) {
return getList(keys, JSON::parseObject);
}
/**
* 获取 json 列表
*
* @param keys keys
* @param define define
* @param <T> T
* @return cache
*/
public static <T> List<T> getJsonList(Collection<String> keys, CacheKeyDefine define) {
return getList(keys, s -> JSON.parseObject(s, (Class<T>) define.getType()));
}
/**
* 获取 json 列表
*
* @param keys keys
* @param type type
* @param <T> T
* @return cache
*/
public static <T> List<T> getJsonList(Collection<String> keys, Class<T> type) {
return getList(keys, s -> JSON.parseObject(s, type));
}
/**
@@ -208,13 +245,7 @@ public class RedisStrings extends RedisUtils {
* @return cache
*/
public static List<JSONArray> getJsonArrayList(Collection<String> keys) {
List<String> values = redisTemplate.opsForValue().multiGet(keys);
if (values == null) {
return new ArrayList<>();
}
return values.stream()
.map(JSON::parseArray)
.collect(Collectors.toList());
return getList(keys, JSON::parseArray);
}
/**
@@ -226,7 +257,7 @@ public class RedisStrings extends RedisUtils {
* @return cache
*/
public static <T> List<List<T>> getJsonArrayList(Collection<String> keys, CacheKeyDefine define) {
return getJsonArrayList(keys, (Class<T>) define.getType());
return getList(keys, s -> JSON.parseArray(s, (Class<T>) define.getType()));
}
/**
@@ -238,13 +269,7 @@ public class RedisStrings extends RedisUtils {
* @return cache
*/
public static <T> List<List<T>> getJsonArrayList(Collection<String> keys, Class<T> type) {
List<String> values = redisTemplate.opsForValue().multiGet(keys);
if (values == null) {
return new ArrayList<>();
}
return values.stream()
.map(s -> JSON.parseArray(s, type))
.collect(Collectors.toList());
return getList(keys, s -> JSON.parseArray(s, type));
}
/**

View File

@@ -46,6 +46,7 @@ public class AuthenticationEntryPointHandler implements AuthenticationEntryPoint
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException {
log.warn("AuthenticationEntryPoint-commence-401 {}", request.getRequestURI());
log.debug("AuthenticationEntryPoint-commence-unauthorized {}", request.getRequestURI(), e);
Servlets.writeHttpWrapper(response, ErrorCode.UNAUTHORIZED.getWrapper());
}

View File

@@ -23,7 +23,7 @@
package org.dromara.visor.framework.storage.configuration;
import org.dromara.visor.common.constant.AutoConfigureOrderConst;
import org.dromara.visor.common.interfaces.FileClient;
import org.dromara.visor.common.file.FileClient;
import org.dromara.visor.framework.storage.configuration.config.LocalStorageConfig;
import org.dromara.visor.framework.storage.configuration.config.LogsStorageConfig;
import org.dromara.visor.framework.storage.core.client.local.LocalFileClient;

View File

@@ -26,7 +26,7 @@ import cn.orionsec.kit.lang.utils.io.Files1;
import cn.orionsec.kit.lang.utils.io.Streams;
import cn.orionsec.kit.lang.utils.time.Dates;
import org.dromara.visor.common.constant.Const;
import org.dromara.visor.common.interfaces.FileClient;
import org.dromara.visor.common.file.FileClient;
import java.io.InputStream;
import java.io.OutputStream;

View File

@@ -23,7 +23,8 @@
package org.dromara.visor.framework.test.configuration;
import com.github.fppt.jedismock.RedisServer;
import org.dromara.visor.common.interfaces.Locker;
import org.dromara.visor.common.lock.EmptyLocker;
import org.dromara.visor.common.lock.Locker;
import org.dromara.visor.common.utils.LockerUtils;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -33,7 +34,6 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Profile;
import java.net.InetAddress;
import java.util.function.Supplier;
/**
* 单元测试 redis mock server 初始化
@@ -66,18 +66,7 @@ public class OrionMockRedisTestConfiguration {
*/
@Bean
public Locker unitTestLocker() {
Locker locker = new Locker() {
@Override
public boolean tryLock(String key, Runnable run) {
run.run();
return true;
}
@Override
public <T> T tryLock(String key, Supplier<T> call) {
return call.get();
}
};
EmptyLocker locker = new EmptyLocker();
LockerUtils.setDelegate(locker);
return locker;
}

View File

@@ -28,7 +28,9 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import org.dromara.visor.common.constant.AutoConfigureOrderConst;
import org.dromara.visor.common.constant.FilterOrderConst;
import org.dromara.visor.common.web.WebFilterCreator;
import org.dromara.visor.framework.web.configuration.config.ExposeApiConfig;
import org.dromara.visor.framework.web.core.aspect.DemoDisableApiAspect;
import org.dromara.visor.framework.web.core.aspect.ExposeApiAspect;
import org.dromara.visor.framework.web.core.filter.TraceIdFilter;
import org.dromara.visor.framework.web.core.handler.GlobalExceptionHandler;
import org.dromara.visor.framework.web.core.handler.WrapperResultHandler;
@@ -37,6 +39,7 @@ import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.http.MediaType;
@@ -50,6 +53,7 @@ import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.nio.charset.StandardCharsets;
@@ -67,8 +71,12 @@ import java.util.List;
*/
@AutoConfiguration
@AutoConfigureOrder(AutoConfigureOrderConst.FRAMEWORK_WEB)
@EnableConfigurationProperties(ExposeApiConfig.class)
public class OrionWebAutoConfiguration implements WebMvcConfigurer {
@Value("${orion.prefix}")
private String orionPrefix;
@Value("${orion.api.prefix}")
private String orionApiPrefix;
@@ -77,7 +85,14 @@ public class OrionWebAutoConfiguration implements WebMvcConfigurer {
// 公共 api 前缀
AntPathMatcher antPathMatcher = new AntPathMatcher(".");
configurer.addPathPrefix(orionApiPrefix, clazz -> clazz.isAnnotationPresent(RestController.class)
&& antPathMatcher.match("org.dromara.visor.**.controller.**", clazz.getPackage().getName()));
&& antPathMatcher.match("org.dromara.visor.module.**.controller.**", clazz.getPackage().getName()));
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 公共模板前缀
registry.addResourceHandler(orionPrefix + "/template/**")
.addResourceLocations("classpath:/public/template/");
}
/**
@@ -171,4 +186,13 @@ public class OrionWebAutoConfiguration implements WebMvcConfigurer {
return new DemoDisableApiAspect();
}
/**
* @param config config
* @return 对外服务 api 切面
*/
@Bean
public ExposeApiAspect exposeApiAspect(ExposeApiConfig config) {
return new ExposeApiAspect(config);
}
}

View File

@@ -0,0 +1,49 @@
/*
* 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.web.configuration.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 对外服务配置属性
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/8/22 19:57
*/
@Data
@ConfigurationProperties("orion.api.expose")
public class ExposeApiConfig {
/**
* 对外服务请求头
*/
private String header;
/**
* 对外服务请求值
*/
private String token;
}

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.framework.web.core.annotation;
import javax.annotation.security.PermitAll;
import java.lang.annotation.*;
/**
* 对外服务 api 注解
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/8/22 9:59
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@PermitAll
public @interface ExposeApi {
/**
* @return 请求来源
*/
String source() default "";
}

View File

@@ -22,7 +22,6 @@
*/
package org.dromara.visor.framework.web.core.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@@ -39,7 +38,6 @@ import org.springframework.core.annotation.Order;
* @since 2024/5/21 16:52
*/
@Aspect
@Slf4j
@Order(BeanOrderConst.DEMO_DISABLE_API_ASPECT)
public class DemoDisableApiAspect {

View File

@@ -0,0 +1,79 @@
/*
* 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.web.core.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.dromara.visor.common.constant.BeanOrderConst;
import org.dromara.visor.common.constant.ErrorCode;
import org.dromara.visor.framework.web.configuration.config.ExposeApiConfig;
import org.dromara.visor.framework.web.core.annotation.ExposeApi;
import org.springframework.core.annotation.Order;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Optional;
/**
* 对外服务 api 切面
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/8/22 16:52
*/
@Slf4j
@Aspect
@Order(BeanOrderConst.EXPOSE_API_ASPECT)
public class ExposeApiAspect {
private final ExposeApiConfig config;
public ExposeApiAspect(ExposeApiConfig config) {
this.config = config;
}
@Pointcut("@annotation(e)")
public void exposeApi(ExposeApi e) {
}
@Before(value = "exposeApi(e)", argNames = "e")
public void beforeExposeApi(ExposeApi e) {
// 获取请求
HttpServletRequest request = Optional.ofNullable(RequestContextHolder.getRequestAttributes())
.map(s -> (ServletRequestAttributes) s)
.map(ServletRequestAttributes::getRequest)
.orElse(null);
if (request == null) {
throw ErrorCode.EXPOSE_UNAUTHORIZED.exception();
}
// 验证对外服务参数
if (!config.getToken().equals(request.getHeader(config.getHeader()))) {
log.warn("expose api unauthorized, url: {}", request.getRequestURI());
throw ErrorCode.EXPOSE_UNAUTHORIZED.exception();
}
}
}

View File

@@ -149,6 +149,7 @@ public class GlobalExceptionHandler {
}
@ExceptionHandler(value = {
LockException.class,
TimeoutException.class,
java.util.concurrent.TimeoutException.class
})

View File

@@ -1,4 +1,11 @@
{
"groups": [
{
"name": "orion.api.expose",
"type": "org.dromara.visor.framework.web.configuration.config.ExposeApiConfig",
"sourceType": "org.dromara.visor.framework.web.configuration.config.ExposeApiConfig"
}
],
"properties": [
{
"name": "orion.version",
@@ -25,6 +32,16 @@
"name": "orion.api.cors",
"type": "java.lang.Boolean",
"description": "是否开启 cors 过滤器."
},
{
"name": "orion.api.expose.header",
"type": "java.lang.String",
"description": "对外服务请求头."
},
{
"name": "orion.api.expose.token",
"type": "java.lang.String",
"description": "对外服务请求值."
}
]
}

View File

@@ -21,17 +21,18 @@
<module>orion-visor-spring-boot-starter-swagger</module>
<module>orion-visor-spring-boot-starter-datasource</module>
<module>orion-visor-spring-boot-starter-mybatis</module>
<module>orion-visor-spring-boot-starter-cipher</module>
<module>orion-visor-spring-boot-starter-config</module>
<module>orion-visor-spring-boot-starter-job</module>
<module>orion-visor-spring-boot-starter-websocket</module>
<module>orion-visor-spring-boot-starter-redis</module>
<module>orion-visor-spring-boot-starter-desensitize</module>
<module>orion-visor-spring-boot-starter-encrypt</module>
<module>orion-visor-spring-boot-starter-log</module>
<module>orion-visor-spring-boot-starter-storage</module>
<module>orion-visor-spring-boot-starter-security</module>
<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-operator-log</module>
</modules>

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