Compare commits

..

29 Commits

Author SHA1 Message Date
李佳航
120eb1ee69 Merge pull request #54 from dromara/dev
Dev
2024-08-22 12:27:58 +08:00
lijiahang
252c538571 🔖 升级版本. 2024-08-22 11:45:24 +08:00
lijiahang
1eec373b7e demo 禁用定时任务. 2024-08-21 13:23:31 +08:00
lijiahang
aa9b96a9c1 修改表格显示字段. 2024-08-21 12:56:07 +08:00
lijiahang
059fb30aa4 优化权限逻辑. 2024-08-20 10:20:35 +08:00
lijiahang
2afaf7ad34 Merge remote-tracking branch 'origin/dev' into dev 2024-08-19 09:53:15 +08:00
lijiahang
076a0956c5 修改心跳检测时间. 2024-08-19 09:49:41 +08:00
lijiahangmax
4a91ec47bf 添加主题. 2024-08-13 20:56:00 +08:00
lijiahangmax
1066b43b3d 全屏模式. 2024-08-12 00:07:54 +08:00
lijiahangmax
3f78125c43 Merge remote-tracking branch 'origin/dev' into dev 2024-08-11 23:07:14 +08:00
lijiahangmax
144a44673b 🐛 修复更新菜单后查询条件错误. 2024-08-11 23:06:59 +08:00
李佳航
777f7b3758 Merge pull request #52 from dromara/dev
Dev
2024-08-08 10:52:09 +08:00
lijiahang
947fa0fea3 🐳 添加 push 脚本. 2024-08-08 10:17:31 +08:00
lijiahang
7109e89fb4 🔖 升级版本. 2024-08-08 10:14:39 +08:00
lijiahang
70e7b1d544 修改终端标签颜色显示. 2024-08-07 10:18:39 +08:00
lijiahangmax
613f86155c 💄 修改终端样式. 2024-08-06 00:01:27 +08:00
lijiahang
8d0b58e48f ⬆️ 升级 orion kit 版本. 2024-08-05 13:49:52 +08:00
lijiahang
8cea9dc977 优化终端代码. 2024-08-05 09:11:54 +08:00
lijiahangmax
471acfdf00 优化自动聚焦逻辑. 2024-08-04 17:01:44 +08:00
lijiahangmax
8ed42131d0 优化命令片段处理逻辑. 2024-08-02 01:52:29 +08:00
lijiahangmax
18c605354a 🔨 修改终端逻辑. 2024-07-31 00:42:02 +08:00
李佳航
8c04411458 Merge pull request #48 from MemoryShadow/dev
perf: Use the.env file instead to modify docker-compose.yml
2024-07-30 17:53:31 +08:00
lijiahang
9a8d1d05cd 💄 修改终端样式. 2024-07-30 14:40:15 +08:00
MemoryShadow
1cbaf9c424 docs: Clarify behavior semantics and accelerate deployment efficiency 2024-07-29 07:02:32 +00:00
MemoryShadow
537c2fc108 perf: Use the.env file instead to modify docker-compose.yml 2024-07-29 07:02:21 +00:00
李佳航
122b568cf5 Merge pull request #46 from dromara/dev
Dev
2024-07-29 11:39:00 +08:00
lijiahang
8b97c02d15 🐳 修改 docker 配置. 2024-07-29 11:15:44 +08:00
lijiahang
dcfb016ce5 🔖 升级版本. 2024-07-29 10:33:59 +08:00
lijiahang
c842de9e23 修改 hook 位置. 2024-07-29 10:25:11 +08:00
175 changed files with 2318 additions and 1412 deletions

15
.env.example Normal file
View File

@@ -0,0 +1,15 @@
SERVICE_PORT=1081
VOLUME_BASE=/data/orion-visor-space/docker-volumes
MYSQL_HOST=mysql
MYSQL_PORT=3306
MYSQL_DATABASE=orion_visor
MYSQL_USER=orion
MYSQL_PASSWORD=Data@123456
MYSQL_ROOT_PASSWORD=Data@123456
REDIS_HOST=redis
REDIS_PASSWORD=Data@123456
SECRET_KEY=uQeacXV8b3isvKLK
DEMO_MODE=false

1
.gitignore vendored
View File

@@ -33,3 +33,4 @@ build/
### VS Code ### ### VS Code ###
.vscode/ .vscode/
.env

View File

@@ -19,12 +19,12 @@
<a target="_blank" <a target="_blank"
style="text-decoration: none !important;" style="text-decoration: none !important;"
href="https://gitee.com/dromara/orion-visor/stargazers"> href="https://gitee.com/dromara/orion-visor/stargazers">
<img src="https://gitee.com/dromara/orion-visor/badge/star.svg?theme=dark" alt="star" /> <img src="https://gitee.com/dromara/orion-visor/badge/star.svg?theme=gvp" alt="star" />
</a> </a>
<a target="_blank" <a target="_blank"
style="text-decoration: none !important;" style="text-decoration: none !important;"
href="https://gitee.com/dromara/orion-visor/members"> href="https://gitee.com/dromara/orion-visor/members">
<img src="https://gitee.com/dromara/orion-visor/badge/fork.svg?theme=dark" alt="fork" /> <img src="https://gitee.com/dromara/orion-visor/badge/fork.svg?theme=gvp" alt="fork" />
</a> </a>
<a target="_blank" <a target="_blank"
style="text-decoration: none !important;" style="text-decoration: none !important;"
@@ -63,7 +63,7 @@
```bash ```bash
# clone # clone
git clone https://github.com/dromara/orion-visor git clone --depth=1 https://github.com/dromara/orion-visor
cd orion-visor cd orion-visor
# 启动 # 启动
docker compose up -d docker compose up -d

View File

@@ -1,17 +1,17 @@
version: '3.3' version: '3.3'
services: services:
orion-visor-service: service:
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-service:2.1.1 image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-service:2.1.4
privileged: true privileged: true
ports: ports:
- 1081:80 - 1081:80
environment: environment:
- MYSQL_HOST=orion-visor-mysql - MYSQL_HOST=mysql
- MYSQL_PORT=3306 - MYSQL_PORT=3306
- MYSQL_DATABASE=orion_visor - MYSQL_DATABASE=orion_visor
- MYSQL_USER=root - MYSQL_USER=root
- MYSQL_PASSWORD=Data@123456 - MYSQL_PASSWORD=Data@123456
- REDIS_HOST=orion-visor-redis - REDIS_HOST=redis
- REDIS_PASSWORD=Data@123456 - REDIS_PASSWORD=Data@123456
- SECRET_KEY=uQeacXV8b3isvKLK - SECRET_KEY=uQeacXV8b3isvKLK
- DEMO_MODE=false - DEMO_MODE=false
@@ -24,15 +24,15 @@ services:
retries: 200 retries: 200
start_period: 3s start_period: 3s
depends_on: depends_on:
orion-visor-mysql: mysql:
condition: service_healthy condition: service_healthy
orion-visor-redis: redis:
condition: service_healthy condition: service_healthy
links: links:
- orion-visor-mysql - mysql
- orion-visor-redis - redis
orion-visor-mysql: mysql:
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-mysql:2.1.1 image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-mysql:2.1.4
privileged: true privileged: true
ports: ports:
- 3307:3306 - 3307:3306
@@ -51,8 +51,8 @@ services:
timeout: 60s timeout: 60s
retries: 10 retries: 10
start_period: 3s start_period: 3s
orion-visor-redis: redis:
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-redis:2.1.1 image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-redis:2.1.4
privileged: true privileged: true
ports: ports:
- 6380:6379 - 6380:6379
@@ -71,9 +71,9 @@ services:
build: build:
context: ./docker/e2e context: ./docker/e2e
environment: environment:
SERVER: http://orion-visor-service:80 SERVER: http://service:80
depends_on: depends_on:
orion-visor-service: service:
condition: service_healthy condition: service_healthy
links: links:
- orion-visor-service - service

View File

@@ -1,73 +1,78 @@
version: '3.3' version: '3.3'
services: services:
orion-visor-service: service:
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-service:2.1.1 image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-service:2.1.4
privileged: true privileged: true
ports: ports:
- 1081:80 - ${SERVICE_PORT:-1081}:80
environment: environment:
- MYSQL_HOST=orion-visor-mysql - MYSQL_HOST=${MYSQL_HOST:-mysql}
- MYSQL_PORT=3306 - MYSQL_PORT=${MYSQL_PORT:-3306}
- MYSQL_DATABASE=orion_visor - MYSQL_DATABASE=${MYSQL_DATABASE:-orion_visor}
- MYSQL_USER=root - MYSQL_USER=${MYSQL_USER:-root}
- MYSQL_PASSWORD=Data@123456 - MYSQL_PASSWORD=${MYSQL_PASSWORD:-Data@123456}
- REDIS_HOST=orion-visor-redis - REDIS_HOST=${REDIS_HOST:-redis}
- REDIS_PASSWORD=Data@123456 - REDIS_PASSWORD=${REDIS_PASSWORD:-Data@123456}
- SECRET_KEY=uQeacXV8b3isvKLK - SECRET_KEY=${SECRET_KEY:-uQeacXV8b3isvKLK}
- DEMO_MODE=false - DEMO_MODE=${DEMO_MODE:-false}
volumes: volumes:
- /data/orion-visor-space/docker-volumes/service/root-orion:/root/orion - ${VOLUME_BASE:-/data/orion-visor-space/docker-volumes}/service/root-orion:/root/orion
healthcheck: healthcheck:
test: [ "CMD", "curl", "http://127.0.0.1:9200/orion-visor/api/server/bootstrap/health" ] test: [ "CMD", "curl", "http://127.0.0.1:9200/orion-visor/api/server/bootstrap/health" ]
interval: 3s interval: 15s
timeout: 300s timeout: 300s
retries: 200 retries: 15
start_period: 3s start_period: 3s
depends_on: depends_on:
orion-visor-mysql: mysql:
condition: service_healthy condition: service_healthy
orion-visor-redis: redis:
condition: service_healthy condition: service_healthy
links: links:
- orion-visor-mysql - mysql
- orion-visor-redis - redis
orion-visor-mysql: mysql:
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-mysql:2.1.1 image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-mysql:2.1.4
privileged: true privileged: true
ports: ports:
- 3307:3306 - 3307:3306
environment: environment:
- MYSQL_DATABASE=orion_visor - MYSQL_DATABASE=${MYSQL_DATABASE:-orion_visor}
- MYSQL_USER=orion - MYSQL_USER=${MYSQL_USER:-orion}
- MYSQL_PASSWORD=Data@123456 - MYSQL_PASSWORD=${MYSQL_PASSWORD:-Data@123456}
- MYSQL_ROOT_PASSWORD=Data@123456 - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD:-Data@123456}
volumes: volumes:
- /data/orion-visor-space/docker-volumes/mysql/var-lib-mysql:/var/lib/mysql - ${VOLUME_BASE:-/data/orion-visor-space/docker-volumes}/mysql/var-lib-mysql:/var/lib/mysql
- /data/orion-visor-space/docker-volumes/mysql/var-lib-mysql-files:/var/lib/mysql-files - ${VOLUME_BASE:-/data/orion-visor-space/docker-volumes}/mysql/var-lib-mysql-files:/var/lib/mysql-files
- /data/orion-visor-space/docker-volumes/mysql/etc-mysql:/etc/mysql - ${VOLUME_BASE:-/data/orion-visor-space/docker-volumes}/mysql/etc-mysql:/etc/mysql
healthcheck: healthcheck:
test: [ "CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/3306" ] test: [ "CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/3306" ]
interval: 3s interval: 15s
timeout: 60s timeout: 60s
retries: 10 retries: 15
start_period: 3s start_period: 3s
orion-visor-redis: redis:
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-redis:2.1.1 image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-redis:2.1.4
privileged: true privileged: true
ports: ports:
- 6380:6379 - 6380:6379
environment: environment:
- REDIS_PASSWORD=Data@123456 - REDIS_PASSWORD=${REDIS_PASSWORD:-Data@123456}
volumes: volumes:
- /data/orion-visor-space/docker-volumes/redis/data:/data - ${VOLUME_BASE:-/data/orion-visor-space/docker-volumes}/redis/data:/data
command: sh -c "redis-server /usr/local/redis.conf --requirepass $${REDIS_PASSWORD}" command: sh -c "redis-server /usr/local/redis.conf --requirepass $${REDIS_PASSWORD}"
healthcheck: healthcheck:
test: [ "CMD", "redis-cli", "--raw", "incr", "ping" ] test: [ "CMD", "redis-cli", "--raw", "incr", "ping" ]
interval: 3s interval: 15s
timeout: 60s timeout: 60s
retries: 10 retries: 15
start_period: 3s start_period: 3s
orion-visor-adminer: adminer:
image: adminer image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-adminer:2.1.4
ports: ports:
- 8081:8080 - 8081:8080
depends_on:
mysql:
condition: service_healthy
links:
- mysql

View File

@@ -2,6 +2,6 @@
docker compose down docker compose down
# demo 启动 # demo 启动
if [ "$1" == "demo" ]; then if [ "$1" == "demo" ]; then
sed -i 's/DEMO_MODE=false/DEMO_MODE=true/g' docker-compose.yml sed -i 's/\${DEMO_MODE:-false}/true/g' docker-compose.yml
fi fi
docker compose up -d --remove-orphans docker compose up -d --remove-orphans

View File

@@ -0,0 +1 @@
FROM adminer:latest

4
docker/adminer/build.sh Normal file
View File

@@ -0,0 +1,4 @@
#/bin/bash
version=2.1.4
docker build -t orion-visor-adminer:${version} .
docker tag orion-visor-adminer:${version} registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-adminer:${version}

View File

@@ -1,7 +1,6 @@
#/bin/bash #/bin/bash
version=2.1.1 version=2.1.4
cp -r ../../sql ./sql cp -r ../../sql ./sql
docker build -t orion-visor-mysql:${version} . docker build -t orion-visor-mysql:${version} .
rm -rf ./sql rm -rf ./sql
docker tag orion-visor-mysql:${version} registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-mysql:${version} docker tag orion-visor-mysql:${version} registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-mysql:${version}
docker push registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-mysql:${version}

6
docker/push.sh Normal file
View File

@@ -0,0 +1,6 @@
#/bin/bash
version=2.1.4
docker push registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-adminer:${version}
docker push registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-mysql:${version}
docker push registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-redis:${version}
docker push registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-service:${version}

View File

@@ -1,5 +1,4 @@
#/bin/bash #/bin/bash
version=2.1.1 version=2.1.4
docker build -t orion-visor-redis:${version} . docker build -t orion-visor-redis:${version} .
docker tag orion-visor-redis:${version} registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-redis:${version} docker tag orion-visor-redis:${version} registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-redis:${version}
docker push registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-redis:${version}

View File

@@ -1,9 +1,8 @@
#/bin/bash #/bin/bash
version=2.1.1 version=2.1.4
mv ../../orion-visor-launch/target/orion-visor-launch.jar ./orion-visor-launch.jar mv ../../orion-visor-launch/target/orion-visor-launch.jar ./orion-visor-launch.jar
mv ../../orion-visor-ui/dist ./dist mv ../../orion-visor-ui/dist ./dist
docker build -t orion-visor-service:${version} . docker build -t orion-visor-service:${version} .
rm -rf ./orion-visor-launch.jar rm -rf ./orion-visor-launch.jar
rm -rf ./dist rm -rf ./dist
docker tag orion-visor-service:${version} registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-service:${version} docker tag orion-visor-service:${version} registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-service:${version}
docker push registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-service:${version}

View File

@@ -29,7 +29,7 @@ server {
location /orion-visor/api { location /orion-visor/api {
proxy_pass http://localhost:9200/orion-visor/api; proxy_pass http://localhost:9200/orion-visor/api;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
} }

View File

@@ -19,12 +19,12 @@
<a target="_blank" <a target="_blank"
style="text-decoration: none !important;" style="text-decoration: none !important;"
href="https://gitee.com/lijiahangmax/orion-visor/stargazers"> href="https://gitee.com/lijiahangmax/orion-visor/stargazers">
<img src="https://gitee.com/lijiahangmax/orion-visor/badge/star.svg?theme=dark" alt="star" /> <img src="https://gitee.com/lijiahangmax/orion-visor/badge/star.svg?theme=gvp" alt="star" />
</a> </a>
<a target="_blank" <a target="_blank"
style="text-decoration: none !important;" style="text-decoration: none !important;"
href="https://gitee.com/lijiahangmax/orion-visor/members"> href="https://gitee.com/lijiahangmax/orion-visor/members">
<img src="https://gitee.com/lijiahangmax/orion-visor/badge/fork.svg?theme=dark" alt="fork" /> <img src="https://gitee.com/lijiahangmax/orion-visor/badge/fork.svg?theme=gvp" alt="fork" />
</a> </a>
<a target="_blank" <a target="_blank"
style="text-decoration: none !important;" style="text-decoration: none !important;"

View File

@@ -21,22 +21,35 @@ Dashboard 修改)
```shell ```shell
# github # github
git clone https://github.com/lijiahangmax/orion-visor git clone --depth=1 https://github.com/lijiahangmax/orion-visor
# gitee # gitee
git clone https://gitee.com/lijiahangmax/orion-visor git clone --depth=1 https://gitee.com/lijiahangmax/orion-visor
``` ```
### 构建镜像 ### 拉取镜像
``` ```
# 进入仓库目录 # 进入仓库目录
cd orion-visor cd orion-visor
# 修改 docker-compose.yml (建议修改) # 创建名为 .env 的 .env.example 副本
# MYSQL_USER mysql 用户名 cp .env.example .env
# MYSQL_PASSWORD mysql 用户密码 # 将其中的值删除以保持默认或将其修改为你喜欢的值
# MYSQL_ROOT_PASSWORD mysql root 密码 # SERVICE_PORT 你希望服务监听的端口
# REDIS_PASSWORD redis 密码 # VOLUME_BASE 你希望数据持久化保存的目录, 如果不提前创建将以 docker 进程宿主身份创建(通常是 root)
# SECRET_KEY 加密密钥
# MYSQL_HOST mysql 服务所在的主机, 如果你没有现有的 MySQL 请保持值为 mysql, 如果你有自部署的请在 docker-compose.yml 中移除 services.mysql 以节约性能
# MYSQL_PORT mysql 监听的端口
# MYSQL_DATABASE mysql 数据库
# MYSQL_USER mysql 用户名
# MYSQL_PASSWORD mysql 用户密码
# MYSQL_ROOT_PASSWORD mysql root 密码
# REDIS_HOST redis 服务所在的主机, 如果你没有现有的 Redis 请保持值为 redis, 如果你有自部署的请在 docker-compose.yml 中移除 services.redis 以节约性能
# REDIS_PASSWORD redis 密码
# SECRET_KEY 加密密钥
# 拉取远程镜像
docker compose pull
``` ```
### 启动 ### 启动
@@ -45,7 +58,7 @@ cd orion-visor
docker compose up -d docker compose up -d
``` ```
### 修改加密方式 ### 修改 MySQL 账户的加密方式
``` ```
访问 adminer: http://localhost:8081 访问 adminer: http://localhost:8081

View File

@@ -14,11 +14,11 @@
<url>https://github.com/dromara/orion-visor</url> <url>https://github.com/dromara/orion-visor</url>
<properties> <properties>
<revision>2.1.1</revision> <revision>2.1.4</revision>
<spring.boot.version>2.7.17</spring.boot.version> <spring.boot.version>2.7.17</spring.boot.version>
<spring.boot.admin.version>2.7.15</spring.boot.admin.version> <spring.boot.admin.version>2.7.15</spring.boot.admin.version>
<flatten.maven.plugin.version>1.5.0</flatten.maven.plugin.version> <flatten.maven.plugin.version>1.5.0</flatten.maven.plugin.version>
<orion.kit.version>1.0.7</orion.kit.version> <orion.kit.version>1.0.8</orion.kit.version>
<aspectj.version>1.9.7</aspectj.version> <aspectj.version>1.9.7</aspectj.version>
<lombok.version>1.18.26</lombok.version> <lombok.version>1.18.26</lombok.version>
<springdoc.version>1.6.15</springdoc.version> <springdoc.version>1.6.15</springdoc.version>

View File

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

View File

@@ -109,4 +109,6 @@ public interface ErrorMessage {
String CLIENT_ABORT = "手动中断"; String CLIENT_ABORT = "手动中断";
String UNABLE_DOWNLOAD_FOLDER = "无法下载文件夹";
} }

View File

@@ -21,7 +21,7 @@ public class PageRequest implements IPageRequest {
@Schema(description = "页码") @Schema(description = "页码")
private int page; private int page;
@Range(min = 1, max = 100, groups = Page.class) @Range(min = 1, max = 200, groups = Page.class)
@Schema(description = "大小") @Schema(description = "大小")
private int limit; private int limit;

View File

@@ -1,6 +1,8 @@
package com.orion.visor.framework.common.meta; package com.orion.visor.framework.common.meta;
import com.alibaba.ttl.TransmittableThreadLocal; import com.alibaba.ttl.TransmittableThreadLocal;
import com.orion.lang.id.UUIds;
import org.slf4j.MDC;
/** /**
* traceId 持有者 * traceId 持有者
@@ -23,16 +25,74 @@ public class TraceIdHolder {
*/ */
private static final ThreadLocal<String> HOLDER = new TransmittableThreadLocal<>(); private static final ThreadLocal<String> HOLDER = new TransmittableThreadLocal<>();
/**
* 获取 traceId
*
* @return traceId
*/
public static String get() { public static String get() {
return HOLDER.get(); return HOLDER.get();
} }
public static void set(String traceId) { /**
HOLDER.set(traceId); * 设置 traceId
*/
public static void set() {
set(createTraceId());
} }
/**
* 设置 traceId
*
* @param traceId traceId
*/
public static void set(String traceId) {
// 设置应用上下文
HOLDER.set(traceId);
// 设置日志上下文
setMdc(traceId);
}
/**
* 删除 traceId
*/
public static void remove() { public static void remove() {
// 移除应用上下文
HOLDER.remove(); HOLDER.remove();
// 移除日志上下文
removeMdc();
}
/**
* 从应用上下文 设置到日志上下文
*/
public static void setMdc() {
setMdc(HOLDER.get());
}
/**
* 设置到日志上下文
*
* @param traceId traceId
*/
public static void setMdc(String traceId) {
MDC.put(TRACE_ID_MDC, traceId);
}
/**
* 移除日志上下文
*/
public static void removeMdc() {
MDC.remove(TRACE_ID_MDC);
}
/**
* 创建 traceId
*
* @return traceId
*/
public static String createTraceId() {
return UUIds.random32();
} }
} }

View File

@@ -3,6 +3,7 @@ package com.orion.visor.framework.mybatis.core.generator.core;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.generator.config.po.TableField; import com.baomidou.mybatisplus.generator.config.po.TableField;
import com.baomidou.mybatisplus.generator.config.po.TableInfo; import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.orion.lang.utils.Exceptions;
import com.orion.lang.utils.Strings; import com.orion.lang.utils.Strings;
import com.orion.visor.framework.common.constant.Const; import com.orion.visor.framework.common.constant.Const;
import com.orion.visor.framework.common.constant.FieldConst; import com.orion.visor.framework.common.constant.FieldConst;
@@ -47,7 +48,7 @@ public class DictParser {
.stream() .stream()
.filter(s -> variable.equals(s.getName()) || variable.equals(s.getPropertyName())) .filter(s -> variable.equals(s.getName()) || variable.equals(s.getPropertyName()))
.findFirst() .findFirst()
.orElseThrow(() -> new RuntimeException("未查询到字典映射字段 " + variable)); .orElseThrow(() -> Exceptions.runtime("未查询到字典映射字段 " + variable));
// 设置字段名称 // 设置字段名称
if (meta.getField() == null) { if (meta.getField() == null) {
meta.setField(Strings.firstUpper(tableField.getPropertyName())); meta.setField(Strings.firstUpper(tableField.getPropertyName()));

View File

@@ -68,7 +68,7 @@
<template #extra="{ record }"> <template #extra="{ record }">
<a-space> <a-space>
<!-- 更多操作 --> <!-- 更多操作 -->
<a-dropdown trigger="hover"> <a-dropdown trigger="hover" :popup-max-height="false">
<icon-more class="card-extra-icon" /> <icon-more class="card-extra-icon" />
<template #content> <template #content>
<!-- 修改 --> <!-- 修改 -->
@@ -118,7 +118,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { ${vue.featureEntity}QueryRequest, ${vue.featureEntity}QueryResponse } from '@/api/${vue.module}/${vue.feature}'; import type { ${vue.featureEntity}QueryRequest, ${vue.featureEntity}QueryResponse } from '@/api/${vue.module}/${vue.feature}';
import { usePagination, useColLayout } from '@/types/card'; import { useCardPagination, useCardColLayout } from '@/hooks/card';
import { computed, reactive, ref, onMounted } from 'vue'; import { computed, reactive, ref, onMounted } from 'vue';
import useLoading from '@/hooks/loading'; import useLoading from '@/hooks/loading';
import { objectTruthKeyCount, resetObject } from '@/utils'; import { objectTruthKeyCount, resetObject } from '@/utils';
@@ -136,8 +136,8 @@
const emits = defineEmits(['openAdd', 'openUpdate']); const emits = defineEmits(['openAdd', 'openUpdate']);
const cardColLayout = useColLayout(); const cardColLayout = useCardColLayout();
const pagination = usePagination(); const pagination = useCardPagination();
const { loading, setLoading } = useLoading(); const { loading, setLoading } = useLoading();
#if($dictMap.entrySet().size() > 0) #if($dictMap.entrySet().size() > 0)
const { toOptions, getDictValue } = useDictStore(); const { toOptions, getDictValue } = useDictStore();

View File

@@ -151,9 +151,9 @@
import {} from '../types/const'; import {} from '../types/const';
#end #end
#if($vue.enableRowSelection) #if($vue.enableRowSelection)
import { usePagination, useRowSelection } from '@/types/table'; import { useTablePagination, useRowSelection } from '@/hooks/table';
#else #else
import { usePagination } from '@/types/table'; import { useTablePagination } from '@/hooks/table';
#end #end
#if($dictMap.entrySet().size() > 0) #if($dictMap.entrySet().size() > 0)
import { useDictStore } from '@/store'; import { useDictStore } from '@/store';
@@ -161,7 +161,7 @@
const emits = defineEmits(['openAdd', 'openUpdate']); const emits = defineEmits(['openAdd', 'openUpdate']);
const pagination = usePagination(); const pagination = useTablePagination();
#if($vue.enableRowSelection) #if($vue.enableRowSelection)
const rowSelection = useRowSelection(); const rowSelection = useRowSelection();
#end #end

View File

@@ -98,6 +98,13 @@ public class SecurityUtils {
return loginUser != null ? loginUser.getTimestamp() : null; return loginUser != null ? loginUser.getTimestamp() : null;
} }
/**
* 清空用户上下文
*/
public static void clearAuthentication() {
SecurityContextHolder.getContext().setAuthentication(null);
}
/** /**
* 设置当前用户 * 设置当前用户
* *
@@ -107,7 +114,9 @@ public class SecurityUtils {
public static void setLoginUser(LoginUser loginUser, HttpServletRequest request) { public static void setLoginUser(LoginUser loginUser, HttpServletRequest request) {
// 创建 authentication // 创建 authentication
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(loginUser, null, Collections.emptyList()); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(loginUser, null, Collections.emptyList());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); if (request != null) {
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
}
// 设置上下文 // 设置上下文
SecurityContextHolder.getContext().setAuthentication(authentication); SecurityContextHolder.getContext().setAuthentication(authentication);
} }

View File

@@ -1,8 +1,6 @@
package com.orion.visor.framework.web.core.filter; package com.orion.visor.framework.web.core.filter;
import com.orion.lang.id.UUIds;
import com.orion.visor.framework.common.meta.TraceIdHolder; import com.orion.visor.framework.common.meta.TraceIdHolder;
import org.slf4j.MDC;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain; import javax.servlet.FilterChain;
@@ -23,21 +21,17 @@ public class TraceIdFilter extends OncePerRequestFilter {
@Override @Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try { try {
// 获 traceId // 获 traceId
String traceId = UUIds.random32(); String traceId = TraceIdHolder.createTraceId();
// 设置应用上下文 // 设置 traceId 上下文
TraceIdHolder.set(traceId); TraceIdHolder.set(traceId);
// 设置日志上下文
MDC.put(TraceIdHolder.TRACE_ID_MDC, traceId);
// 设置响应头 // 设置响应头
response.setHeader(TraceIdHolder.TRACE_ID_HEADER, traceId); response.setHeader(TraceIdHolder.TRACE_ID_HEADER, traceId);
// 执行请求 // 执行请求
filterChain.doFilter(request, response); filterChain.doFilter(request, response);
} finally { } finally {
// 清理应用上下文 // 清空 traceId 上下文
TraceIdHolder.remove(); TraceIdHolder.remove();
// 清理日志上下文
MDC.clear();
} }
} }

View File

@@ -167,6 +167,8 @@ app:
allow-refresh: true allow-refresh: true
# 凭证续签最大次数 # 凭证续签最大次数
max-refresh-count: 3 max-refresh-count: 3
# 登录失败发送站内信阈值
login-failed-send-threshold: 3
# 登录失败锁定次数 # 登录失败锁定次数
login-failed-lock-count: 5 login-failed-lock-count: 5
# 登录失败锁定时间 (分) # 登录失败锁定时间 (分)

View File

@@ -5,6 +5,7 @@ import com.orion.visor.module.asset.handler.host.terminal.TerminalMessageDispatc
import com.orion.visor.module.asset.handler.host.transfer.TransferMessageDispatcher; import com.orion.visor.module.asset.handler.host.transfer.TransferMessageDispatcher;
import com.orion.visor.module.asset.interceptor.ExecLogTailInterceptor; import com.orion.visor.module.asset.interceptor.ExecLogTailInterceptor;
import com.orion.visor.module.asset.interceptor.TerminalAccessInterceptor; import com.orion.visor.module.asset.interceptor.TerminalAccessInterceptor;
import com.orion.visor.module.asset.interceptor.TerminalTransferInterceptor;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer; import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
@@ -28,6 +29,9 @@ public class AssetWebSocketConfiguration implements WebSocketConfigurer {
@Resource @Resource
private TerminalAccessInterceptor terminalAccessInterceptor; private TerminalAccessInterceptor terminalAccessInterceptor;
@Resource
private TerminalTransferInterceptor terminalTransferInterceptor;
@Resource @Resource
private ExecLogTailInterceptor execLogTailInterceptor; private ExecLogTailInterceptor execLogTailInterceptor;
@@ -42,13 +46,13 @@ public class AssetWebSocketConfiguration implements WebSocketConfigurer {
@Override @Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// 终端 // 终端会话
registry.addHandler(terminalMessageDispatcher, prefix + "/host/terminal/{accessToken}") registry.addHandler(terminalMessageDispatcher, prefix + "/host/terminal/{accessToken}")
.addInterceptors(terminalAccessInterceptor) .addInterceptors(terminalAccessInterceptor)
.setAllowedOrigins("*"); .setAllowedOrigins("*");
// 文件传输 // 文件传输
registry.addHandler(transferMessageDispatcher, prefix + "/host/transfer/{accessToken}") registry.addHandler(transferMessageDispatcher, prefix + "/host/transfer/{transferToken}")
.addInterceptors(terminalAccessInterceptor) .addInterceptors(terminalTransferInterceptor)
.setAllowedOrigins("*"); .setAllowedOrigins("*");
// 执行日志 // 执行日志
registry.addHandler(execLogTailHandler, prefix + "/exec/log/{token}") registry.addHandler(execLogTailHandler, prefix + "/exec/log/{token}")

View File

@@ -5,6 +5,7 @@ import com.orion.visor.framework.biz.operator.log.core.annotation.OperatorLog;
import com.orion.visor.framework.common.validator.group.Page; import com.orion.visor.framework.common.validator.group.Page;
import com.orion.visor.framework.log.core.annotation.IgnoreLog; import com.orion.visor.framework.log.core.annotation.IgnoreLog;
import com.orion.visor.framework.log.core.enums.IgnoreLogMode; import com.orion.visor.framework.log.core.enums.IgnoreLogMode;
import com.orion.visor.framework.web.core.annotation.DemoDisableApi;
import com.orion.visor.framework.web.core.annotation.RestWrapper; import com.orion.visor.framework.web.core.annotation.RestWrapper;
import com.orion.visor.module.asset.define.operator.ExecJobOperatorType; import com.orion.visor.module.asset.define.operator.ExecJobOperatorType;
import com.orion.visor.module.asset.entity.request.exec.*; import com.orion.visor.module.asset.entity.request.exec.*;
@@ -39,6 +40,7 @@ public class ExecJobController {
@Resource @Resource
private ExecJobService execJobService; private ExecJobService execJobService;
@DemoDisableApi
@OperatorLog(ExecJobOperatorType.CREATE) @OperatorLog(ExecJobOperatorType.CREATE)
@PostMapping("/create") @PostMapping("/create")
@Operation(summary = "创建计划任务") @Operation(summary = "创建计划任务")
@@ -47,6 +49,7 @@ public class ExecJobController {
return execJobService.createExecJob(request); return execJobService.createExecJob(request);
} }
@DemoDisableApi
@OperatorLog(ExecJobOperatorType.UPDATE) @OperatorLog(ExecJobOperatorType.UPDATE)
@PutMapping("/update") @PutMapping("/update")
@Operation(summary = "更新计划任务") @Operation(summary = "更新计划任务")
@@ -55,6 +58,7 @@ public class ExecJobController {
return execJobService.updateExecJobById(request); return execJobService.updateExecJobById(request);
} }
@DemoDisableApi
@OperatorLog(ExecJobOperatorType.UPDATE_STATUS) @OperatorLog(ExecJobOperatorType.UPDATE_STATUS)
@PutMapping("/update-status") @PutMapping("/update-status")
@Operation(summary = "更新计划任务状态") @Operation(summary = "更新计划任务状态")
@@ -88,6 +92,7 @@ public class ExecJobController {
return execJobService.getExecJobPage(request); return execJobService.getExecJobPage(request);
} }
@DemoDisableApi
@OperatorLog(ExecJobOperatorType.DELETE) @OperatorLog(ExecJobOperatorType.DELETE)
@DeleteMapping("/delete") @DeleteMapping("/delete")
@Operation(summary = "删除计划任务") @Operation(summary = "删除计划任务")
@@ -97,6 +102,7 @@ public class ExecJobController {
return execJobService.deleteExecJobById(id); return execJobService.deleteExecJobById(id);
} }
@DemoDisableApi
@OperatorLog(ExecJobOperatorType.DELETE) @OperatorLog(ExecJobOperatorType.DELETE)
@DeleteMapping("/batch-delete") @DeleteMapping("/batch-delete")
@Operation(summary = "批量删除计划任务") @Operation(summary = "批量删除计划任务")

View File

@@ -49,5 +49,12 @@ public class HostTerminalController {
return hostTerminalService.getTerminalAccessToken(); return hostTerminalService.getTerminalAccessToken();
} }
@GetMapping("/transfer")
@Operation(summary = "获取主机终端 transferToken")
@PreAuthorize("@ss.hasPermission('asset:host-terminal:access')")
public String getTerminalTransferToken() {
return hostTerminalService.getTerminalTransferToken();
}
} }

View File

@@ -4,6 +4,7 @@ import com.orion.lang.define.cache.key.CacheKeyBuilder;
import com.orion.lang.define.cache.key.CacheKeyDefine; import com.orion.lang.define.cache.key.CacheKeyDefine;
import com.orion.lang.define.cache.key.struct.RedisCacheStruct; import com.orion.lang.define.cache.key.struct.RedisCacheStruct;
import com.orion.visor.module.asset.entity.dto.HostTerminalAccessDTO; import com.orion.visor.module.asset.entity.dto.HostTerminalAccessDTO;
import com.orion.visor.module.asset.entity.dto.HostTerminalTransferDTO;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -24,4 +25,12 @@ public interface HostTerminalCacheKeyDefine {
.timeout(3, TimeUnit.MINUTES) .timeout(3, TimeUnit.MINUTES)
.build(); .build();
CacheKeyDefine HOST_TERMINAL_TRANSFER = new CacheKeyBuilder()
.key("host:terminal:transfer:{}")
.desc("主机终端传输token ${token}")
.type(HostTerminalTransferDTO.class)
.struct(RedisCacheStruct.STRING)
.timeout(3, TimeUnit.MINUTES)
.build();
} }

View File

@@ -42,10 +42,6 @@ public class CommandSnippetDO extends BaseDO {
@TableField("name") @TableField("name")
private String name; private String name;
@Schema(description = "触发前缀")
@TableField("prefix")
private String prefix;
@Schema(description = "代码片段") @Schema(description = "代码片段")
@TableField("command") @TableField("command")
private String command; private String command;

View File

@@ -34,9 +34,6 @@ public class CommandSnippetCacheDTO implements LongCacheIdModel, Serializable {
@Schema(description = "名称") @Schema(description = "名称")
private String name; private String name;
@Schema(description = "触发前缀")
private String prefix;
@Schema(description = "代码片段") @Schema(description = "代码片段")
private String command; private String command;

View File

@@ -0,0 +1,31 @@
package com.orion.visor.module.asset.entity.dto;
import com.orion.visor.framework.desensitize.core.annotation.DesensitizeObject;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 主机终端传输参数
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/12/26 15:47
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@DesensitizeObject
@Schema(name = "HostTerminalTransferDTO", description = "主机终端传输参数")
public class HostTerminalTransferDTO {
@Schema(description = "userId")
private Long userId;
@Schema(description = "username")
private String username;
}

View File

@@ -33,9 +33,6 @@ public class CommandSnippetVO implements Serializable {
@Schema(description = "名称") @Schema(description = "名称")
private String name; private String name;
@Schema(description = "触发前缀")
private String prefix;
@Schema(description = "代码片段") @Schema(description = "代码片段")
private String command; private String command;

View File

@@ -5,7 +5,9 @@ import com.orion.lang.exception.argument.InvalidArgumentException;
import com.orion.lang.utils.Exceptions; import com.orion.lang.utils.Exceptions;
import com.orion.lang.utils.Strings; import com.orion.lang.utils.Strings;
import com.orion.net.host.SessionHolder; import com.orion.net.host.SessionHolder;
import com.orion.net.host.SessionLogger;
import com.orion.net.host.SessionStore; import com.orion.net.host.SessionStore;
import com.orion.visor.framework.common.constant.AppConst;
import com.orion.visor.framework.common.constant.Const; import com.orion.visor.framework.common.constant.Const;
import com.orion.visor.framework.common.utils.CryptoUtils; import com.orion.visor.framework.common.utils.CryptoUtils;
import com.orion.visor.module.asset.entity.dto.HostTerminalConnectDTO; import com.orion.visor.module.asset.entity.dto.HostTerminalConnectDTO;
@@ -40,7 +42,10 @@ public class SessionStores {
CURRENT_ADDRESS.set(address); CURRENT_ADDRESS.set(address);
// 创建会话 // 创建会话
SessionHolder sessionHolder = SessionHolder.create(); SessionHolder sessionHolder = SessionHolder.create();
sessionHolder.setLogger(SessionLogger.INFO);
SessionStore session = createSessionStore(conn, sessionHolder); SessionStore session = createSessionStore(conn, sessionHolder);
// 设置版本
session.getSession().setClientVersion("SSH-2.0-ORION_VISOR_V" + AppConst.VERSION);
// 连接 // 连接
session.connect(); session.connect();
log.info("SessionStores-open-success hostId: {}, address: {}, username: {}", hostId, address, username); log.info("SessionStores-open-success hostId: {}, address: {}, username: {}", hostId, address, username);

View File

@@ -131,9 +131,9 @@ public enum InputTypeEnum {
* SFTP 修改文件权限 * SFTP 修改文件权限
*/ */
SFTP_CHMOD("cm", SFTP_CHMOD("cm",
SftpChangeModHandler.class, SftpChangeModeHandler.class,
new String[]{"type", "sessionId", "path", "mod"}, new String[]{"type", "sessionId", "path", "mod"},
SftpChangeModRequest.class, SftpChangeModeRequest.class,
true), true),
/** /**

View File

@@ -5,7 +5,7 @@ import com.orion.visor.framework.biz.operator.log.core.utils.OperatorLogs;
import com.orion.visor.framework.common.enums.BooleanBit; import com.orion.visor.framework.common.enums.BooleanBit;
import com.orion.visor.module.asset.define.operator.HostTerminalOperatorType; import com.orion.visor.module.asset.define.operator.HostTerminalOperatorType;
import com.orion.visor.module.asset.handler.host.terminal.enums.OutputTypeEnum; import com.orion.visor.module.asset.handler.host.terminal.enums.OutputTypeEnum;
import com.orion.visor.module.asset.handler.host.terminal.model.request.SftpChangeModRequest; import com.orion.visor.module.asset.handler.host.terminal.model.request.SftpChangeModeRequest;
import com.orion.visor.module.asset.handler.host.terminal.model.response.SftpBaseResponse; import com.orion.visor.module.asset.handler.host.terminal.model.response.SftpBaseResponse;
import com.orion.visor.module.asset.handler.host.terminal.session.ISftpSession; import com.orion.visor.module.asset.handler.host.terminal.session.ISftpSession;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -23,24 +23,24 @@ import java.util.Map;
*/ */
@Slf4j @Slf4j
@Component @Component
public class SftpChangeModHandler extends AbstractTerminalHandler<SftpChangeModRequest> { public class SftpChangeModeHandler extends AbstractTerminalHandler<SftpChangeModeRequest> {
@Override @Override
public void handle(WebSocketSession channel, SftpChangeModRequest payload) { public void handle(WebSocketSession channel, SftpChangeModeRequest payload) {
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
// 获取会话 // 获取会话
String sessionId = payload.getSessionId(); String sessionId = payload.getSessionId();
ISftpSession session = hostTerminalManager.getSession(channel.getId(), sessionId); ISftpSession session = hostTerminalManager.getSession(channel.getId(), sessionId);
String path = payload.getPath(); String path = payload.getPath();
Integer mod = payload.getMod(); Integer mod = payload.getMod();
log.info("SftpChangeModHandler-handle start sessionId: {}, path: {}, mod: {}", sessionId, path, mod); log.info("SftpChangeModeHandler-handle start sessionId: {}, path: {}, mod: {}", sessionId, path, mod);
Exception ex = null; Exception ex = null;
// 修改权限 // 修改权限
try { try {
session.chmod(path, mod); session.chmod(path, mod);
log.info("SftpChangeModHandler-handle success sessionId: {}, path: {}, mod: {}", sessionId, path, mod); log.info("SftpChangeModeHandler-handle success sessionId: {}, path: {}, mod: {}", sessionId, path, mod);
} catch (Exception e) { } catch (Exception e) {
log.error("SftpChangeModHandler-handle error sessionId: {}", sessionId, e); log.error("SftpChangeModeHandler-handle error sessionId: {}", sessionId, e);
ex = e; ex = e;
} }
// 返回 // 返回

View File

@@ -65,13 +65,15 @@ public class TerminalConnectHandler extends AbstractTerminalHandler<TerminalConn
// 移除会话连接信息 // 移除会话连接信息
channel.getAttributes().remove(sessionId); channel.getAttributes().remove(sessionId);
Exception ex = null; Exception ex = null;
ITerminalSession session = null;
try { try {
// 连接主机 // 连接主机
ITerminalSession session = this.connect(sessionId, connect, channel, payload); session = this.connect(sessionId, connect, channel, payload);
// 添加会话到 manager // 添加会话到 manager
hostTerminalManager.addSession(session); hostTerminalManager.addSession(session);
} catch (Exception e) { } catch (Exception e) {
ex = e; ex = e;
Streams.close(session);
// 修改连接状态为失败 // 修改连接状态为失败
Map<String, Object> extra = Maps.newMap(4); Map<String, Object> extra = Maps.newMap(4);
extra.put(ExtraFieldConst.ERROR_MESSAGE, this.getConnectErrorMessage(e)); extra.put(ExtraFieldConst.ERROR_MESSAGE, this.getConnectErrorMessage(e));

View File

@@ -20,7 +20,7 @@ import lombok.experimental.SuperBuilder;
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class SftpChangeModRequest extends SftpBaseRequest { public class SftpChangeModeRequest extends SftpBaseRequest {
/** /**
* 10进制的8进制 权限 * 10进制的8进制 权限

View File

@@ -12,7 +12,6 @@ import com.orion.visor.framework.common.constant.Const;
import com.orion.visor.framework.common.utils.Valid; import com.orion.visor.framework.common.utils.Valid;
import com.orion.visor.module.asset.handler.host.terminal.model.TerminalConfig; import com.orion.visor.module.asset.handler.host.terminal.model.TerminalConfig;
import com.orion.visor.module.asset.handler.host.terminal.model.response.SftpFileVO; import com.orion.visor.module.asset.handler.host.terminal.model.response.SftpFileVO;
import com.orion.visor.module.asset.utils.SftpUtils;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.WebSocketSession;
@@ -84,9 +83,7 @@ public class SftpSession extends TerminalSession implements ISftpSession {
public void move(String source, String target) { public void move(String source, String target) {
source = Valid.checkNormalize(source); source = Valid.checkNormalize(source);
// 移动 // 移动
SftpUtils.move(executor, source, target); executor.move(source, target);
// FIXME kit
// executor.move(source, target);
} }
@Override @Override

View File

@@ -37,8 +37,11 @@ public class DownloadSession extends TransferSession implements StreamingRespons
protected InputStream inputStream; protected InputStream inputStream;
private Long fileSize;
public DownloadSession(HostTerminalConnectDTO connectInfo, SessionStore sessionStore, WebSocketSession channel) { public DownloadSession(HostTerminalConnectDTO connectInfo, SessionStore sessionStore, WebSocketSession channel) {
super(connectInfo, sessionStore, channel); super(connectInfo, sessionStore, channel);
this.fileSize = 0L;
} }
@Override @Override
@@ -53,7 +56,9 @@ public class DownloadSession extends TransferSession implements StreamingRespons
// 检查文件是否存在 // 检查文件是否存在
SftpFile file = executor.getFile(path); SftpFile file = executor.getFile(path);
Valid.notNull(file, ErrorMessage.FILE_ABSENT); Valid.notNull(file, ErrorMessage.FILE_ABSENT);
if (file.getSize() == 0L) { // 验证非文件夹
Valid.isTrue(!file.isDirectory(), ErrorMessage.UNABLE_DOWNLOAD_FOLDER);
if ((this.fileSize = file.getSize()) == 0L) {
// 文件为空 // 文件为空
log.info("DownloadSession.startDownload file empty channelId: {}, path: {}", channelId, path); log.info("DownloadSession.startDownload file empty channelId: {}, path: {}", channelId, path);
TransferUtils.sendMessage(channel, TransferReceiver.FINISH, null); TransferUtils.sendMessage(channel, TransferReceiver.FINISH, null);
@@ -101,14 +106,14 @@ public class DownloadSession extends TransferSession implements StreamingRespons
// 首次触发 // 首次触发
if (i == 0) { if (i == 0) {
outputStream.flush(); outputStream.flush();
this.sendProgress(size, null); this.sendProgress(size, fileSize);
} }
i++; i++;
} }
// 最后一次也要 flush // 最后一次也要 flush
if (i != 0) { if (i != 0) {
outputStream.flush(); outputStream.flush();
this.sendProgress(size, null); this.sendProgress(size, fileSize);
} }
log.info("DownloadSession.download finish channelId: {}, path: {}", channelId, path); log.info("DownloadSession.download finish channelId: {}, path: {}", channelId, path);
} catch (Exception e) { } catch (Exception e) {

View File

@@ -17,7 +17,7 @@ import javax.annotation.Resource;
import java.util.Map; import java.util.Map;
/** /**
* 终端拦截器 * 终端访问拦截器
* *
* @author Jiahang Li * @author Jiahang Li
* @version 1.0.0 * @version 1.0.0
@@ -34,11 +34,11 @@ public class TerminalAccessInterceptor implements HandshakeInterceptor {
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception { public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
// 获取 accessToken // 获取 accessToken
String accessToken = Urls.getUrlSource(request.getURI().getPath()); String accessToken = Urls.getUrlSource(request.getURI().getPath());
log.info("TerminalInterceptor-beforeHandshake start accessToken: {}", accessToken); log.info("TerminalAccessInterceptor-beforeHandshake start accessToken: {}", accessToken);
// 获取连接数据 // 获取连接数据
HostTerminalAccessDTO access = hostTerminalService.getAccessInfoByToken(accessToken); HostTerminalAccessDTO access = hostTerminalService.getAccessInfoByToken(accessToken);
if (access == null) { if (access == null) {
log.error("TerminalInterceptor-beforeHandshake absent accessToken: {}", accessToken); log.error("TerminalAccessInterceptor-beforeHandshake absent accessToken: {}", accessToken);
return false; return false;
} }
// 设置参数 // 设置参数

View File

@@ -0,0 +1,56 @@
package com.orion.visor.module.asset.interceptor;
import com.orion.lang.utils.Urls;
import com.orion.visor.framework.common.constant.ExtraFieldConst;
import com.orion.visor.framework.common.meta.TraceIdHolder;
import com.orion.visor.framework.common.utils.Requests;
import com.orion.visor.module.asset.entity.dto.HostTerminalTransferDTO;
import com.orion.visor.module.asset.service.HostTerminalService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
import javax.annotation.Resource;
import java.util.Map;
/**
* 终端传输拦截器
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/12/27 23:53
*/
@Slf4j
@Component
public class TerminalTransferInterceptor implements HandshakeInterceptor {
@Resource
private HostTerminalService hostTerminalService;
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
// 获取 transferToken
String transferToken = Urls.getUrlSource(request.getURI().getPath());
log.info("TerminalTransferInterceptor-beforeHandshake start transferToken: {}", transferToken);
// 获取连接数据
HostTerminalTransferDTO transfer = hostTerminalService.getTransferInfoByToken(transferToken);
if (transfer == null) {
log.error("TerminalTransferInterceptor-beforeHandshake absent transferToken: {}", transferToken);
return false;
}
// 设置参数
attributes.put(ExtraFieldConst.USER_ID, transfer.getUserId());
attributes.put(ExtraFieldConst.USERNAME, transfer.getUsername());
attributes.put(ExtraFieldConst.TRACE_ID, TraceIdHolder.get());
attributes.put(ExtraFieldConst.IDENTITY, Requests.getIdentity());
return true;
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
}
}

View File

@@ -3,6 +3,7 @@ package com.orion.visor.module.asset.service;
import com.orion.visor.module.asset.entity.domain.HostDO; import com.orion.visor.module.asset.entity.domain.HostDO;
import com.orion.visor.module.asset.entity.dto.HostTerminalAccessDTO; import com.orion.visor.module.asset.entity.dto.HostTerminalAccessDTO;
import com.orion.visor.module.asset.entity.dto.HostTerminalConnectDTO; import com.orion.visor.module.asset.entity.dto.HostTerminalConnectDTO;
import com.orion.visor.module.asset.entity.dto.HostTerminalTransferDTO;
import com.orion.visor.module.asset.entity.vo.HostTerminalThemeVO; import com.orion.visor.module.asset.entity.vo.HostTerminalThemeVO;
import java.util.List; import java.util.List;
@@ -30,6 +31,13 @@ public interface HostTerminalService {
*/ */
String getTerminalAccessToken(); String getTerminalAccessToken();
/**
* 获取主机终端传输 transferToken
*
* @return transferToken
*/
String getTerminalTransferToken();
/** /**
* 通过 accessToken 获取主机终端访问信息 * 通过 accessToken 获取主机终端访问信息
* *
@@ -38,6 +46,14 @@ public interface HostTerminalService {
*/ */
HostTerminalAccessDTO getAccessInfoByToken(String token); HostTerminalAccessDTO getAccessInfoByToken(String token);
/**
* 通过 transferToken 获取主机终端传输信息
*
* @param token token
* @return config
*/
HostTerminalTransferDTO getTransferInfoByToken(String token);
/** /**
* 获取连接信息 * 获取连接信息
* *

View File

@@ -17,6 +17,7 @@ import com.orion.visor.module.asset.entity.domain.HostIdentityDO;
import com.orion.visor.module.asset.entity.domain.HostKeyDO; import com.orion.visor.module.asset.entity.domain.HostKeyDO;
import com.orion.visor.module.asset.entity.dto.HostTerminalAccessDTO; import com.orion.visor.module.asset.entity.dto.HostTerminalAccessDTO;
import com.orion.visor.module.asset.entity.dto.HostTerminalConnectDTO; import com.orion.visor.module.asset.entity.dto.HostTerminalConnectDTO;
import com.orion.visor.module.asset.entity.dto.HostTerminalTransferDTO;
import com.orion.visor.module.asset.entity.vo.HostTerminalThemeVO; import com.orion.visor.module.asset.entity.vo.HostTerminalThemeVO;
import com.orion.visor.module.asset.enums.*; import com.orion.visor.module.asset.enums.*;
import com.orion.visor.module.asset.handler.host.config.model.HostSshConfigModel; import com.orion.visor.module.asset.handler.host.config.model.HostSshConfigModel;
@@ -74,6 +75,10 @@ public class HostTerminalServiceImpl implements HostTerminalService {
@Override @Override
public List<HostTerminalThemeVO> getTerminalThemes() { public List<HostTerminalThemeVO> getTerminalThemes() {
// if (true) {
// String arr = "";
// return JSON.parseArray(arr, HostTerminalThemeVO.class);
// }
List<JSONObject> themes = dictValueApi.getDictValue(THEME_DICT_KEY); List<JSONObject> themes = dictValueApi.getDictValue(THEME_DICT_KEY);
return themes.stream() return themes.stream()
.map(s -> HostTerminalThemeVO.builder() .map(s -> HostTerminalThemeVO.builder()
@@ -99,6 +104,21 @@ public class HostTerminalServiceImpl implements HostTerminalService {
return accessToken; return accessToken;
} }
@Override
public String getTerminalTransferToken() {
LoginUser user = Valid.notNull(SecurityUtils.getLoginUser());
log.info("HostConnectService.getTerminalTransferToken userId: {}", user.getId());
String transferToken = UUIds.random19();
HostTerminalTransferDTO transfer = HostTerminalTransferDTO.builder()
.userId(user.getId())
.username(user.getUsername())
.build();
// 设置 transfer 缓存
String key = HostTerminalCacheKeyDefine.HOST_TERMINAL_TRANSFER.format(transferToken);
RedisStrings.setJson(key, HostTerminalCacheKeyDefine.HOST_TERMINAL_TRANSFER, transfer);
return transferToken;
}
@Override @Override
public HostTerminalAccessDTO getAccessInfoByToken(String token) { public HostTerminalAccessDTO getAccessInfoByToken(String token) {
// 获取缓存 // 获取缓存
@@ -111,6 +131,18 @@ public class HostTerminalServiceImpl implements HostTerminalService {
return access; return access;
} }
@Override
public HostTerminalTransferDTO getTransferInfoByToken(String token) {
// 获取缓存
String key = HostTerminalCacheKeyDefine.HOST_TERMINAL_TRANSFER.format(token);
HostTerminalTransferDTO transfer = RedisStrings.getJson(key, HostTerminalCacheKeyDefine.HOST_TERMINAL_TRANSFER);
// 删除缓存
if (transfer != null) {
RedisStrings.delete(key);
}
return transfer;
}
@Override @Override
public HostTerminalConnectDTO getTerminalConnectInfo(Long hostId) { public HostTerminalConnectDTO getTerminalConnectInfo(Long hostId) {
log.info("HostConnectService.getTerminalConnectInfo-withHost hostId: {}", hostId); log.info("HostConnectService.getTerminalConnectInfo-withHost hostId: {}", hostId);

View File

@@ -2,9 +2,7 @@ package com.orion.visor.module.asset.utils;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.orion.lang.utils.Booleans; import com.orion.lang.utils.Booleans;
import com.orion.lang.utils.Exceptions;
import com.orion.lang.utils.Strings; import com.orion.lang.utils.Strings;
import com.orion.lang.utils.io.Files1;
import com.orion.net.host.sftp.SftpExecutor; import com.orion.net.host.sftp.SftpExecutor;
import com.orion.net.host.sftp.SftpFile; import com.orion.net.host.sftp.SftpFile;
import com.orion.visor.module.asset.define.config.AppSftpConfig; import com.orion.visor.module.asset.define.config.AppSftpConfig;
@@ -41,49 +39,8 @@ public class SftpUtils {
SftpFileBackupParams backupParams = new SftpFileBackupParams(file.getName(), System.currentTimeMillis()); SftpFileBackupParams backupParams = new SftpFileBackupParams(file.getName(), System.currentTimeMillis());
String target = Strings.format(config.getBackupFileName(), JSON.parseObject(JSON.toJSONString(backupParams))); String target = Strings.format(config.getBackupFileName(), JSON.parseObject(JSON.toJSONString(backupParams)));
// 移动 // 移动
// FIXME kit executor.move(path, target);
move(executor, path, target);
// executor.move(path, target);
} }
} }
/**
* 移动文件
* FIXME kit DELETE
*
* @param executor executor
* @param source source
* @param target target
*/
public static void move(SftpExecutor executor, String source, String target) {
try {
source = Files1.getPath(source);
target = Files1.getPath(target);
if (target.charAt(0) == '/') {
// 检查是否需要创建目标文件目录
if (!isSameParentPath(source, target)) {
executor.makeDirectories(Files1.getParentPath(target));
}
// 绝对路径
executor.getChannel().rename(source, Files1.getPath(Files1.normalize(target)));
} else {
// 相对路径
executor.getChannel().rename(source, Files1.getPath(Files1.normalize(Files1.getPath(source + "/../" + target))));
}
} catch (Exception e) {
throw Exceptions.sftp(e);
}
}
/**
* FIXME kit DELETE
*
* @param source source
* @param target target
* @return res
*/
private static boolean isSameParentPath(String source, String target) {
return Files1.getParentPath(source).equals(Files1.getParentPath(target));
}
} }

View File

@@ -8,7 +8,6 @@
<result column="user_id" property="userId"/> <result column="user_id" property="userId"/>
<result column="group_id" property="groupId"/> <result column="group_id" property="groupId"/>
<result column="name" property="name"/> <result column="name" property="name"/>
<result column="prefix" property="prefix"/>
<result column="command" property="command"/> <result column="command" property="command"/>
<result column="create_time" property="createTime"/> <result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/> <result column="update_time" property="updateTime"/>
@@ -19,7 +18,7 @@
<!-- 通用查询结果列 --> <!-- 通用查询结果列 -->
<sql id="Base_Column_List"> <sql id="Base_Column_List">
id, user_id, group_id, name, prefix, command, create_time, update_time, creator, updater, deleted id, user_id, group_id, name, command, create_time, update_time, creator, updater, deleted
</sql> </sql>
</mapper> </mapper>

View File

@@ -28,13 +28,15 @@ public class TerminalThemeGenerator {
List<File> files = Files1.listFiles("D:\\idea-project\\iTerm2-Color-Schemes\\vhs"); List<File> files = Files1.listFiles("D:\\idea-project\\iTerm2-Color-Schemes\\vhs");
// 过滤的 theme // 过滤的 theme
List<String> schemaFilter = Lists.of( List<String> schemaFilter = Lists.of(
"Dracula", "Atom", "Dracula", "Builtin Tango Light",
"catppuccin-mocha", "MaterialDesignColors", "Atom", "AtomOneLight",
"catppuccin-macchiato", "OneHalfDark", "OneHalfDark", "OneHalfLight",
"Apple System Colors", "Builtin Tango Light", "Apple System Colors", "Tomorrow",
"Duotone Dark", "BlulocoLight", "catppuccin-mocha", "catppuccin-latte",
"Chester", "CLRS", "catppuccin-macchiato", "BlulocoLight",
"Calamity", "Tomorrow" "catppuccin-frappe", "MaterialDesignColors",
"GitHub Dark", "Github",
"DimmedMonokai", "Duotone Dark"
); );
// 颜色大写 // 颜色大写
ValueFilter colorFilter = (Object object, String name, Object value) -> { ValueFilter colorFilter = (Object object, String name, Object value) -> {
@@ -60,7 +62,7 @@ public class TerminalThemeGenerator {
theme.setDark(Colors.isDarkColor(background)); theme.setDark(Colors.isDarkColor(background));
theme.setSchema(JSON.parseObject(JSON.toJSONString(schema), TerminalThemeSchema.class)); theme.setSchema(JSON.parseObject(JSON.toJSONString(schema), TerminalThemeSchema.class));
return theme; return theme;
}).collect(Collectors.toList()); }).skip(0).limit(50).collect(Collectors.toList());
// 排序 // 排序
if (!Lists.isEmpty(schemaFilter)) { if (!Lists.isEmpty(schemaFilter)) {
arr.sort(Comparator.comparing(s -> schemaFilter.indexOf(s.getName()))); arr.sort(Comparator.comparing(s -> schemaFilter.indexOf(s.getName())));
@@ -70,11 +72,12 @@ public class TerminalThemeGenerator {
for (TerminalTheme theme : arr) { for (TerminalTheme theme : arr) {
System.out.println("name: " + theme.name); System.out.println("name: " + theme.name);
System.out.println("dark: " + theme.dark); System.out.println("dark: " + theme.dark);
System.out.println("value: \n" + JSON.toJSONString(theme.schema, colorFilter)); System.out.println("value: " + JSON.toJSONString(theme.schema, colorFilter));
System.out.println("json: " + JSON.toJSONString(theme, colorFilter));
System.out.println(); System.out.println();
} }
// String json = JSON.toJSONString(arr, colorFilter); String json = JSON.toJSONString(arr, colorFilter);
// System.out.println("\n" + json); System.out.println("\n" + json);
} }
/* /*

View File

@@ -0,0 +1,24 @@
package com.orion.visor.module.infra.api;
import com.orion.visor.module.infra.entity.dto.user.SystemUserAuthDTO;
/**
* 认证服务实现
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/8/14 21:37
*/
public interface AuthenticationApi {
/**
* 通过密码认证
*
* @param username username
* @param password password
* @param addFailedCount addFailedCount
* @return result
*/
SystemUserAuthDTO authByPassword(String username, String password, boolean addFailedCount);
}

View File

@@ -0,0 +1,58 @@
package com.orion.visor.module.infra.api;
import java.util.List;
/**
* 权限 对外服务类
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/8/19 15:22
*/
public interface PermissionApi {
/**
* 用户是否为管理员用户
*
* @param id id
* @return isAdmin
*/
boolean isAdminUser(Long id);
/**
* 检查当前用户是否含有此角色
*
* @param userId userId
* @param role role
* @return 是否包含
*/
boolean hasRole(Long userId, String role);
/**
* 检查当前用户是否含有任意角色
*
* @param userId userId
* @param roles roles
* @return 是否包含
*/
boolean hasAnyRole(Long userId, List<String> roles);
/**
* 检查当前用户是否含有此权限
*
* @param userId userId
* @param permission permission
* @return 是否包含
*/
boolean hasPermission(Long userId, String permission);
/**
* 检查当前用户是否含任意权限
*
* @param userId userId
* @param permissions permissions
* @return 是否包含
*/
boolean hasAnyPermission(Long userId, List<String> permissions);
}

View File

@@ -11,6 +11,22 @@ import com.orion.visor.module.infra.entity.dto.user.SystemUserDTO;
*/ */
public interface SystemUserApi { public interface SystemUserApi {
/**
* 通过 id 查询用户名
*
* @param id id
* @return username
*/
String getUsernameById(Long id);
/**
* 通过 id 查询花名
*
* @param id id
* @return nickname
*/
String getNicknameById(Long id);
/** /**
* 通过 id 查询用户 * 通过 id 查询用户
* *
@@ -19,12 +35,4 @@ public interface SystemUserApi {
*/ */
SystemUserDTO getUserById(Long id); SystemUserDTO getUserById(Long id);
/**
* 用户是否为管理员用户
*
* @param id id
* @return isAdmin
*/
boolean isAdminUser(Long id);
} }

View File

@@ -0,0 +1,45 @@
package com.orion.visor.module.infra.entity.dto.user;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 用户状态检查 业务对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/8/14 21:52
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "SystemUserAuthDTO", description = "用户认证 业务对象")
public class SystemUserAuthDTO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "id")
private Long id;
@Schema(description = "用户名")
private String username;
@Schema(description = "花名")
private String nickname;
@Schema(description = "密码是否正确")
private Boolean passRight;
@Schema(description = "认证是否通过")
private Boolean authed;
@Schema(description = "错误信息")
private String errorMessage;
}

View File

@@ -0,0 +1,51 @@
package com.orion.visor.module.infra.api.impl;
import com.orion.visor.framework.common.constant.ErrorMessage;
import com.orion.visor.framework.common.utils.Valid;
import com.orion.visor.module.infra.api.AuthenticationApi;
import com.orion.visor.module.infra.entity.domain.SystemUserDO;
import com.orion.visor.module.infra.entity.dto.user.SystemUserAuthDTO;
import com.orion.visor.module.infra.service.AuthenticationService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* 认证服务实现
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/8/14 21:37
*/
@Slf4j
@Service
public class AuthenticationApiImpl implements AuthenticationApi {
@Resource
private AuthenticationService authenticationService;
@Override
public SystemUserAuthDTO authByPassword(String username, String password, boolean addFailedCount) {
SystemUserAuthDTO result = new SystemUserAuthDTO();
try {
// 登录预检
SystemUserDO user = authenticationService.preCheckLogin(username, password);
result.setId(user.getId());
result.setUsername(user.getUsername());
result.setNickname(user.getNickname());
// 检查用户密码
boolean passRight = authenticationService.checkUserPassword(user, password, addFailedCount);
result.setPassRight(passRight);
Valid.isTrue(passRight, ErrorMessage.USERNAME_PASSWORD_ERROR);
// 检查用户状态
authenticationService.checkUserStatus(user);
result.setAuthed(true);
} catch (Exception e) {
result.setAuthed(false);
result.setErrorMessage(e.getMessage());
}
return result;
}
}

View File

@@ -0,0 +1,48 @@
package com.orion.visor.module.infra.api.impl;
import com.orion.visor.module.infra.api.PermissionApi;
import com.orion.visor.module.infra.service.PermissionService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
/**
* 权限 对外服务类实现
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/8/19 15:25
*/
@Service
public class PermissionApiImpl implements PermissionApi {
@Resource
private PermissionService permissionService;
@Override
public boolean isAdminUser(Long id) {
return permissionService.isAdminUser(id);
}
@Override
public boolean hasRole(Long userId, String role) {
return permissionService.hasRole(userId, role);
}
@Override
public boolean hasAnyRole(Long userId, List<String> roles) {
return permissionService.hasAnyRole(userId, roles);
}
@Override
public boolean hasPermission(Long userId, String permission) {
return permissionService.hasPermission(userId, permission);
}
@Override
public boolean hasAnyPermission(Long userId, List<String> permissions) {
return permissionService.hasAnyPermission(userId, permissions);
}
}

View File

@@ -5,7 +5,6 @@ import com.orion.visor.module.infra.convert.SystemUserProviderConvert;
import com.orion.visor.module.infra.dao.SystemUserDAO; import com.orion.visor.module.infra.dao.SystemUserDAO;
import com.orion.visor.module.infra.entity.domain.SystemUserDO; import com.orion.visor.module.infra.entity.domain.SystemUserDO;
import com.orion.visor.module.infra.entity.dto.user.SystemUserDTO; import com.orion.visor.module.infra.entity.dto.user.SystemUserDTO;
import com.orion.visor.module.infra.service.SystemUserService;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.Resource; import javax.annotation.Resource;
@@ -23,8 +22,25 @@ public class SystemUserApiImpl implements SystemUserApi {
@Resource @Resource
private SystemUserDAO systemUserDAO; private SystemUserDAO systemUserDAO;
@Resource @Override
private SystemUserService systemUserService; public String getUsernameById(Long id) {
return systemUserDAO.of()
.createWrapper()
.select(SystemUserDO::getUsername)
.eq(SystemUserDO::getId, id)
.then()
.getOne(SystemUserDO::getUsername);
}
@Override
public String getNicknameById(Long id) {
return systemUserDAO.of()
.createWrapper()
.select(SystemUserDO::getNickname)
.eq(SystemUserDO::getId, id)
.then()
.getOne(SystemUserDO::getNickname);
}
@Override @Override
public SystemUserDTO getUserById(Long id) { public SystemUserDTO getUserById(Long id) {
@@ -35,9 +51,4 @@ public class SystemUserApiImpl implements SystemUserApi {
return SystemUserProviderConvert.MAPPER.to(user); return SystemUserProviderConvert.MAPPER.to(user);
} }
@Override
public boolean isAdminUser(Long id) {
return systemUserService.isAdminUser(id);
}
} }

View File

@@ -1,5 +1,6 @@
package com.orion.visor.module.infra.controller; package com.orion.visor.module.infra.controller;
import com.orion.visor.framework.common.validator.group.Page;
import com.orion.visor.framework.log.core.annotation.IgnoreLog; import com.orion.visor.framework.log.core.annotation.IgnoreLog;
import com.orion.visor.framework.log.core.enums.IgnoreLogMode; import com.orion.visor.framework.log.core.enums.IgnoreLogMode;
import com.orion.visor.framework.web.core.annotation.RestWrapper; import com.orion.visor.framework.web.core.annotation.RestWrapper;
@@ -38,7 +39,7 @@ public class SystemMessageController {
@IgnoreLog(IgnoreLogMode.ALL) @IgnoreLog(IgnoreLogMode.ALL)
@PostMapping("/list") @PostMapping("/list")
@Operation(summary = "查询系统消息列表") @Operation(summary = "查询系统消息列表")
public List<SystemMessageVO> getSystemMessageList(@RequestBody SystemMessageQueryRequest request) { public List<SystemMessageVO> getSystemMessageList(@Validated(Page.class) @RequestBody SystemMessageQueryRequest request) {
return systemMessageService.getSystemMessageList(request); return systemMessageService.getSystemMessageList(request);
} }

View File

@@ -5,7 +5,7 @@ import com.orion.visor.framework.log.core.enums.IgnoreLogMode;
import com.orion.visor.framework.web.core.annotation.RestWrapper; import com.orion.visor.framework.web.core.annotation.RestWrapper;
import com.orion.visor.module.infra.entity.vo.SystemMenuVO; import com.orion.visor.module.infra.entity.vo.SystemMenuVO;
import com.orion.visor.module.infra.entity.vo.UserPermissionVO; import com.orion.visor.module.infra.entity.vo.UserPermissionVO;
import com.orion.visor.module.infra.service.PermissionService; import com.orion.visor.module.infra.service.UserPermissionService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -26,22 +26,23 @@ import java.util.List;
* @version 1.0.0 * @version 1.0.0
* @since 2023/7/14 11:20 * @since 2023/7/14 11:20
*/ */
@Tag(name = "infra - 权限服务") @Tag(name = "infra - 用户权限服务")
@Slf4j @Slf4j
@Validated @Validated
@RestWrapper @RestWrapper
@RestController @RestController
@RequestMapping("/infra/permission") @RequestMapping("/infra/user-permission")
public class PermissionController { @SuppressWarnings({"ELValidationInJSP", "SpringElInspection"})
public class UserPermissionController {
@Resource @Resource
private PermissionService permissionService; private UserPermissionService userPermissionService;
@PutMapping("/refresh-cache") @PutMapping("/refresh-cache")
@Operation(summary = "刷新角色权限缓存") @Operation(summary = "刷新角色权限缓存")
@PreAuthorize("@ss.hasPermission('infra:system-menu:management:refresh-cache')") @PreAuthorize("@ss.hasPermission('infra:system-menu:management:refresh-cache')")
public Boolean refreshCache() { public Boolean refreshCache() {
permissionService.initPermissionCache(); userPermissionService.initPermissionCache();
return true; return true;
} }
@@ -49,14 +50,14 @@ public class PermissionController {
@GetMapping("/menu") @GetMapping("/menu")
@Operation(summary = "获取用户菜单") @Operation(summary = "获取用户菜单")
public List<SystemMenuVO> getUserMenuList() { public List<SystemMenuVO> getUserMenuList() {
return permissionService.getUserMenuList(); return userPermissionService.getUserMenuList();
} }
@IgnoreLog(IgnoreLogMode.RET) @IgnoreLog(IgnoreLogMode.RET)
@GetMapping("/user") @GetMapping("/user")
@Operation(summary = "获取用户权限聚合信息") @Operation(summary = "获取用户权限聚合信息")
public UserPermissionVO getUserPermission() { public UserPermissionVO getUserPermission() {
return permissionService.getUserPermission(); return userPermissionService.getUserPermission();
} }
} }

View File

@@ -34,11 +34,22 @@ public interface SystemRoleDAO extends IMapper<SystemRoleDO> {
/** /**
* 通过 userId 和 roleCode 查询 roleId (检查用户是否包含某个角色) * 通过 userId 和 roleCode 查询 roleId (检查用户是否包含某个角色)
* *
* @param userId userId * @param userId userId
* @param code code * @param codeList codeList
* @return roleId * @return roleId
*/ */
Long getRoleIdByUserIdAndRoleCode(@Param("userId") Long userId, @Param("code") String code); List<Long> getRoleIdByUserIdAndRoleCode(@Param("userId") Long userId,
@Param("codeList") List<String> codeList);
/**
* 通过 roleId 和 permission 查询 permission (检查角色是否包含某个权限)
*
* @param roleIdList roleIdList
* @param permissionList permissionList
* @return permission
*/
List<String> getPermissionByRoleIdAndPermission(@Param("roleIdList") List<Long> roleIdList,
@Param("permissionList") List<String> permissionList);
/** /**
* 查询用户角色 * 查询用户角色

View File

@@ -31,6 +31,11 @@ public class AppAuthenticationConfig {
*/ */
private Integer maxRefreshCount; private Integer maxRefreshCount;
/**
* 登录失败发送站内信阈值
*/
private Integer loginFailedSendThreshold;
/** /**
* 登录失败锁定次数 * 登录失败锁定次数
*/ */

View File

@@ -0,0 +1,53 @@
package com.orion.visor.module.infra.define.message;
import com.orion.visor.module.infra.define.SystemMessageDefine;
import com.orion.visor.module.infra.enums.MessageClassifyEnum;
import lombok.Getter;
/**
* 用户 系统消息定义
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/5/14 17:23
*/
@Getter
public enum SystemUserMessageDefine implements SystemMessageDefine {
/**
* 登录失败
*/
LOGIN_FAILED(MessageClassifyEnum.NOTICE,
"登录失败",
"您的账号在 <sb>${time}</sb> 登录系统时身份认证失败, 您的密码可能已经泄漏。如非本人操作请尽快修改密码。(<sb>${address} - ${location}</sb>)"),
;
SystemUserMessageDefine(MessageClassifyEnum classify, String title, String content) {
this.classify = classify;
this.type = this.name();
this.title = title;
this.content = content;
}
/**
* 消息分类
*/
private final MessageClassifyEnum classify;
/**
* 消息类型
*/
private final String type;
/**
* 标题
*/
private final String title;
/**
* 内容
*/
private final String content;
}

View File

@@ -1,10 +1,8 @@
package com.orion.visor.module.infra.entity.request.message; package com.orion.visor.module.infra.entity.request.message;
import com.orion.visor.framework.common.entity.PageRequest;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor; import lombok.*;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/** /**
* 系统消息 查询请求对象 * 系统消息 查询请求对象
@@ -17,11 +15,9 @@ import lombok.NoArgsConstructor;
@Builder @Builder
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@Schema(name = "SystemMessageQueryRequest", description = "系统消息 查询请求对象") @Schema(name = "SystemMessageQueryRequest", description = "系统消息 查询请求对象")
public class SystemMessageQueryRequest { public class SystemMessageQueryRequest extends PageRequest {
@Schema(description = "大小")
private Integer limit;
@Schema(description = "maxId") @Schema(description = "maxId")
private Long maxId; private Long maxId;

View File

@@ -8,7 +8,7 @@ import com.orion.visor.module.infra.entity.dto.LoginTokenDTO;
import com.orion.visor.module.infra.enums.LoginTokenStatusEnum; import com.orion.visor.module.infra.enums.LoginTokenStatusEnum;
import com.orion.visor.module.infra.enums.UserStatusEnum; import com.orion.visor.module.infra.enums.UserStatusEnum;
import com.orion.visor.module.infra.service.AuthenticationService; import com.orion.visor.module.infra.service.AuthenticationService;
import com.orion.visor.module.infra.service.PermissionService; import com.orion.visor.module.infra.service.UserPermissionService;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.Resource; import javax.annotation.Resource;
@@ -27,30 +27,30 @@ public class SecurityFrameworkServiceImpl implements SecurityFrameworkService {
private AuthenticationService authenticationService; private AuthenticationService authenticationService;
@Resource @Resource
private PermissionService permissionService; private UserPermissionService userPermissionService;
@Override @Override
public boolean hasPermission(String permission) { public boolean hasPermission(String permission) {
// 检查是否有权限 // 检查是否有权限
return permissionService.hasPermission(permission); return userPermissionService.hasPermission(permission);
} }
@Override @Override
public boolean hasAnyPermission(String... permissions) { public boolean hasAnyPermission(String... permissions) {
// 检查是否有权限 // 检查是否有权限
return permissionService.hasAnyPermission(permissions); return userPermissionService.hasAnyPermission(permissions);
} }
@Override @Override
public boolean hasRole(String role) { public boolean hasRole(String role) {
// 检查是否有角色 // 检查是否有角色
return permissionService.hasRole(role); return userPermissionService.hasRole(role);
} }
@Override @Override
public boolean hasAnyRole(String... roles) { public boolean hasAnyRole(String... roles) {
// 检查是否有角色 // 检查是否有角色
return permissionService.hasAnyRole(roles); return userPermissionService.hasAnyRole(roles);
} }
@Override @Override

View File

@@ -314,6 +314,11 @@ public class TerminalPreferenceModel implements GenericsDataModel {
*/ */
private Boolean openSftp; private Boolean openSftp;
/**
* 上传文件
*/
private Boolean uploadFile;
/** /**
* 清空 * 清空
*/ */

View File

@@ -91,6 +91,7 @@ public class TerminalPreferenceStrategy extends AbstractGenericsDataStrategy<Ter
new TerminalPreferenceModel.ShortcutKeysModel("toBottom", true, true, false, "ArrowDown", true), new TerminalPreferenceModel.ShortcutKeysModel("toBottom", true, true, false, "ArrowDown", true),
new TerminalPreferenceModel.ShortcutKeysModel("selectAll", true, true, false, "KeyA", true), new TerminalPreferenceModel.ShortcutKeysModel("selectAll", true, true, false, "KeyA", true),
new TerminalPreferenceModel.ShortcutKeysModel("search", true, true, false, "KeyF", true), new TerminalPreferenceModel.ShortcutKeysModel("search", true, true, false, "KeyF", true),
new TerminalPreferenceModel.ShortcutKeysModel("uploadFile", true, true, false, "KeyU", true),
new TerminalPreferenceModel.ShortcutKeysModel("commandEditor", true, false, true, "KeyE", true), new TerminalPreferenceModel.ShortcutKeysModel("commandEditor", true, false, true, "KeyE", true),
new TerminalPreferenceModel.ShortcutKeysModel("fontSizePlus", true, false, true, "Equal", true), new TerminalPreferenceModel.ShortcutKeysModel("fontSizePlus", true, false, true, "Equal", true),
new TerminalPreferenceModel.ShortcutKeysModel("fontSizeSubtract", true, false, true, "Minus", true) new TerminalPreferenceModel.ShortcutKeysModel("fontSizeSubtract", true, false, true, "Minus", true)
@@ -101,8 +102,8 @@ public class TerminalPreferenceStrategy extends AbstractGenericsDataStrategy<Ter
String actionBarSetting = TerminalPreferenceModel.ActionBarSettingModel.builder() String actionBarSetting = TerminalPreferenceModel.ActionBarSettingModel.builder()
.commandInput(false) .commandInput(false)
.connectStatus(true) .connectStatus(true)
.toTop(true) .toTop(false)
.toBottom(true) .toBottom(false)
.selectAll(false) .selectAll(false)
.search(true) .search(true)
.copy(true) .copy(true)
@@ -112,7 +113,10 @@ public class TerminalPreferenceStrategy extends AbstractGenericsDataStrategy<Ter
.fontSizePlus(false) .fontSizePlus(false)
.fontSizeSubtract(false) .fontSizeSubtract(false)
.commandEditor(true) .commandEditor(true)
.fontSizePlus(false)
.fontSizeSubtract(false)
.openSftp(true) .openSftp(true)
.uploadFile(true)
.clear(true) .clear(true)
.disconnect(false) .disconnect(false)
.build() .build()
@@ -123,7 +127,7 @@ public class TerminalPreferenceStrategy extends AbstractGenericsDataStrategy<Ter
.theme(new JSONObject()) .theme(new JSONObject())
.displaySetting(JSONObject.parseObject(defaultDisplaySetting)) .displaySetting(JSONObject.parseObject(defaultDisplaySetting))
.actionBarSetting(JSONObject.parseObject(actionBarSetting)) .actionBarSetting(JSONObject.parseObject(actionBarSetting))
.rightMenuSetting(Lists.of("selectAll", "copy", "paste", "fontSizePlus", "fontSizeSubtract", "search", "clear")) .rightMenuSetting(Lists.of("selectAll", "copy", "paste", "search", "clear"))
.interactSetting(JSONObject.parseObject(defaultInteractSetting)) .interactSetting(JSONObject.parseObject(defaultInteractSetting))
.pluginsSetting(JSONObject.parseObject(defaultPluginsSetting)) .pluginsSetting(JSONObject.parseObject(defaultPluginsSetting))
.sessionSetting(JSONObject.parseObject(defaultSessionSetting)) .sessionSetting(JSONObject.parseObject(defaultSessionSetting))

View File

@@ -1,6 +1,7 @@
package com.orion.visor.module.infra.service; package com.orion.visor.module.infra.service;
import com.orion.visor.framework.common.security.LoginUser; import com.orion.visor.framework.common.security.LoginUser;
import com.orion.visor.module.infra.entity.domain.SystemUserDO;
import com.orion.visor.module.infra.entity.dto.LoginTokenDTO; import com.orion.visor.module.infra.entity.dto.LoginTokenDTO;
import com.orion.visor.module.infra.entity.request.user.UserLoginRequest; import com.orion.visor.module.infra.entity.request.user.UserLoginRequest;
import com.orion.visor.module.infra.entity.vo.UserLoginVO; import com.orion.visor.module.infra.entity.vo.UserLoginVO;
@@ -48,4 +49,30 @@ public interface AuthenticationService {
*/ */
LoginTokenDTO getLoginTokenInfo(String loginToken); LoginTokenDTO getLoginTokenInfo(String loginToken);
/**
* 登录预检查
*
* @param username username
* @param password password
* @return user
*/
SystemUserDO preCheckLogin(String username, String password);
/**
* 检查用户密码
*
* @param user user
* @param password password
* @param addFailedCount addFailedCount
* @return passRight
*/
boolean checkUserPassword(SystemUserDO user, String password, boolean addFailedCount);
/**
* 检查用户状态
*
* @param user user
*/
void checkUserStatus(SystemUserDO user);
} }

View File

@@ -1,92 +1,58 @@
package com.orion.visor.module.infra.service; package com.orion.visor.module.infra.service;
import com.orion.visor.module.infra.entity.domain.SystemRoleDO;
import com.orion.visor.module.infra.entity.dto.SystemMenuCacheDTO;
import com.orion.visor.module.infra.entity.vo.SystemMenuVO;
import com.orion.visor.module.infra.entity.vo.UserPermissionVO;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* 权限服务 * 权限服务
* *
* @author Jiahang Li * @author Jiahang Li
* @version 1.0.0 * @version 1.0.0
* @since 2023/7/16 1:03 * @since 2024/8/19 15:29
*/ */
public interface PermissionService { public interface PermissionService {
/** /**
* 获取 角色缓存 * 检测用户是否是为管理员
* *
* @return cache * @param userId userId
* @return 是否为管理员
*/ */
Map<Long, SystemRoleDO> getRoleCache(); boolean isAdminUser(Long userId);
/** /**
* 获取 菜单缓存 以作角色权限直接引用 * 检查当前用户是否含有此角色
* *
* @return cache * @param userId userId
*/ * @param role role
List<SystemMenuCacheDTO> getMenuCache();
/**
* 获取 角色菜单关联
*
* @return cache
*/
Map<Long, List<SystemMenuCacheDTO>> getRoleMenuCache();
/**
* 初始化权限缓存
*/
void initPermissionCache();
/**
* 检查当前用户是否含有此角色 (有效性判断)
*
* @param role role
* @return 是否包含 * @return 是否包含
*/ */
boolean hasRole(String role); boolean hasRole(Long userId, String role);
/** /**
* 检查当前用户是否含有任意角色 (有效性判断) * 检查当前用户是否含有任意角色
* *
* @param roles roles * @param userId userId
* @param roles roles
* @return 是否包含 * @return 是否包含
*/ */
boolean hasAnyRole(String... roles); boolean hasAnyRole(Long userId, List<String> roles);
/** /**
* 检查当前用户是否含有此权限 (有效性判断) * 检查当前用户是否含有此权限
* *
* @param userId userId
* @param permission permission * @param permission permission
* @return 是否包含 * @return 是否包含
*/ */
boolean hasPermission(String permission); boolean hasPermission(Long userId, String permission);
/** /**
* 检查当前用户是否含任意权限 (有效性判断) * 检查当前用户是否含任意权限
* *
* @param userId userId
* @param permissions permissions * @param permissions permissions
* @return 是否包含 * @return 是否包含
*/ */
boolean hasAnyPermission(String... permissions); boolean hasAnyPermission(Long userId, List<String> permissions);
/**
* 获取用户菜单
*
* @return 菜单
*/
List<SystemMenuVO> getUserMenuList();
/**
* 获取用户权限
*
* @return 权限信息
*/
UserPermissionVO getUserPermission();
} }

View File

@@ -92,12 +92,4 @@ public interface SystemUserService {
*/ */
void resetPassword(UserResetPasswordRequest request); void resetPassword(UserResetPasswordRequest request);
/**
* 检测用户是否是为管理员
*
* @param userId userId
* @return 是否为管理员
*/
boolean isAdminUser(Long userId);
} }

View File

@@ -0,0 +1,92 @@
package com.orion.visor.module.infra.service;
import com.orion.visor.module.infra.entity.domain.SystemRoleDO;
import com.orion.visor.module.infra.entity.dto.SystemMenuCacheDTO;
import com.orion.visor.module.infra.entity.vo.SystemMenuVO;
import com.orion.visor.module.infra.entity.vo.UserPermissionVO;
import java.util.List;
import java.util.Map;
/**
* 用户权限服务
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/7/16 1:03
*/
public interface UserPermissionService {
/**
* 获取 角色缓存
*
* @return cache
*/
Map<Long, SystemRoleDO> getRoleCache();
/**
* 获取 菜单缓存 以作角色权限直接引用
*
* @return cache
*/
List<SystemMenuCacheDTO> getMenuCache();
/**
* 获取 角色菜单关联
*
* @return cache
*/
Map<Long, List<SystemMenuCacheDTO>> getRoleMenuCache();
/**
* 初始化权限缓存
*/
void initPermissionCache();
/**
* 检查当前用户是否含有此角色 (有效性判断)
*
* @param role role
* @return 是否包含
*/
boolean hasRole(String role);
/**
* 检查当前用户是否含有任意角色 (有效性判断)
*
* @param roles roles
* @return 是否包含
*/
boolean hasAnyRole(String... roles);
/**
* 检查当前用户是否含有此权限 (有效性判断)
*
* @param permission permission
* @return 是否包含
*/
boolean hasPermission(String permission);
/**
* 检查当前用户是否含任意权限 (有效性判断)
*
* @param permissions permissions
* @return 是否包含
*/
boolean hasAnyPermission(String... permissions);
/**
* 获取用户菜单
*
* @return 菜单
*/
List<SystemMenuVO> getUserMenuList();
/**
* 获取用户权限
*
* @return 权限信息
*/
UserPermissionVO getUserPermission();
}

View File

@@ -1,14 +1,17 @@
package com.orion.visor.module.infra.service.impl; package com.orion.visor.module.infra.service.impl;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.orion.lang.annotation.Keep;
import com.orion.lang.define.wrapper.Pair; import com.orion.lang.define.wrapper.Pair;
import com.orion.lang.utils.Exceptions; import com.orion.lang.utils.Exceptions;
import com.orion.lang.utils.Strings;
import com.orion.lang.utils.collect.Lists; import com.orion.lang.utils.collect.Lists;
import com.orion.lang.utils.crypto.Signatures; import com.orion.lang.utils.crypto.Signatures;
import com.orion.lang.utils.time.Dates;
import com.orion.visor.framework.biz.operator.log.core.utils.OperatorLogs; import com.orion.visor.framework.biz.operator.log.core.utils.OperatorLogs;
import com.orion.visor.framework.common.annotation.Keep;
import com.orion.visor.framework.common.constant.Const; import com.orion.visor.framework.common.constant.Const;
import com.orion.visor.framework.common.constant.ErrorMessage; import com.orion.visor.framework.common.constant.ErrorMessage;
import com.orion.visor.framework.common.constant.ExtraFieldConst;
import com.orion.visor.framework.common.security.LoginUser; import com.orion.visor.framework.common.security.LoginUser;
import com.orion.visor.framework.common.security.UserRole; import com.orion.visor.framework.common.security.UserRole;
import com.orion.visor.framework.common.utils.CryptoUtils; import com.orion.visor.framework.common.utils.CryptoUtils;
@@ -17,30 +20,30 @@ import com.orion.visor.framework.common.utils.Valid;
import com.orion.visor.framework.redis.core.utils.RedisStrings; import com.orion.visor.framework.redis.core.utils.RedisStrings;
import com.orion.visor.framework.redis.core.utils.RedisUtils; import com.orion.visor.framework.redis.core.utils.RedisUtils;
import com.orion.visor.framework.security.core.utils.SecurityUtils; import com.orion.visor.framework.security.core.utils.SecurityUtils;
import com.orion.visor.module.infra.api.SystemMessageApi;
import com.orion.visor.module.infra.convert.SystemUserConvert; import com.orion.visor.module.infra.convert.SystemUserConvert;
import com.orion.visor.module.infra.dao.SystemUserDAO; import com.orion.visor.module.infra.dao.SystemUserDAO;
import com.orion.visor.module.infra.dao.SystemUserRoleDAO; import com.orion.visor.module.infra.dao.SystemUserRoleDAO;
import com.orion.visor.module.infra.define.cache.UserCacheKeyDefine; import com.orion.visor.module.infra.define.cache.UserCacheKeyDefine;
import com.orion.visor.module.infra.define.config.AppAuthenticationConfig; import com.orion.visor.module.infra.define.config.AppAuthenticationConfig;
import com.orion.visor.module.infra.define.message.SystemUserMessageDefine;
import com.orion.visor.module.infra.entity.domain.SystemUserDO; import com.orion.visor.module.infra.entity.domain.SystemUserDO;
import com.orion.visor.module.infra.entity.dto.LoginTokenDTO; import com.orion.visor.module.infra.entity.dto.LoginTokenDTO;
import com.orion.visor.module.infra.entity.dto.LoginTokenIdentityDTO; import com.orion.visor.module.infra.entity.dto.LoginTokenIdentityDTO;
import com.orion.visor.module.infra.entity.dto.message.SystemMessageDTO;
import com.orion.visor.module.infra.entity.request.user.UserLoginRequest; import com.orion.visor.module.infra.entity.request.user.UserLoginRequest;
import com.orion.visor.module.infra.entity.vo.UserLoginVO; import com.orion.visor.module.infra.entity.vo.UserLoginVO;
import com.orion.visor.module.infra.enums.LoginTokenStatusEnum; import com.orion.visor.module.infra.enums.LoginTokenStatusEnum;
import com.orion.visor.module.infra.enums.UserStatusEnum; import com.orion.visor.module.infra.enums.UserStatusEnum;
import com.orion.visor.module.infra.service.AuthenticationService; import com.orion.visor.module.infra.service.AuthenticationService;
import com.orion.visor.module.infra.service.PermissionService; import com.orion.visor.module.infra.service.UserPermissionService;
import com.orion.web.servlet.web.Servlets; import com.orion.web.servlet.web.Servlets;
import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.util.Date; import java.util.*;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -64,7 +67,10 @@ public class AuthenticationServiceImpl implements AuthenticationService {
private SystemUserRoleDAO systemUserRoleDAO; private SystemUserRoleDAO systemUserRoleDAO;
@Resource @Resource
private PermissionService permissionService; private UserPermissionService userPermissionService;
@Resource
private SystemMessageApi systemMessageApi;
@Keep @Keep
@Resource @Resource
@@ -72,39 +78,35 @@ public class AuthenticationServiceImpl implements AuthenticationService {
@Override @Override
public UserLoginVO login(UserLoginRequest request, HttpServletRequest servletRequest) { public UserLoginVO login(UserLoginRequest request, HttpServletRequest servletRequest) {
// 设置日志上下文的用户 否则登录失败不会记录日志
OperatorLogs.setUser(SystemUserConvert.MAPPER.toLoginUser(request));
// 登录前检查
this.preCheckLogin(request);
// 获取登录用户
SystemUserDO user = systemUserDAO.of()
.createWrapper()
.eq(SystemUserDO::getUsername, request.getUsername())
.then()
.getOne();
Valid.notNull(user, ErrorMessage.USERNAME_PASSWORD_ERROR);
// 重新设置日志上下文
OperatorLogs.setUser(SystemUserConvert.MAPPER.toLoginUser(user));
// 检查密码
boolean passwordCorrect = this.checkPassword(request, user);
Valid.isTrue(passwordCorrect, ErrorMessage.USERNAME_PASSWORD_ERROR);
// 检查用户状态
UserStatusEnum.checkUserStatus(user.getStatus());
// 设置上次登录时间
this.setLastLoginTime(user.getId());
// 删除用户缓存
this.deleteUserCache(user);
// 重设用户缓存
this.setUserCache(user);
// 获取登录信息 // 获取登录信息
String remoteAddr = IpUtils.getRemoteAddr(servletRequest); String remoteAddr = IpUtils.getRemoteAddr(servletRequest);
String location = IpUtils.getLocation(remoteAddr); String location = IpUtils.getLocation(remoteAddr);
String userAgent = Servlets.getUserAgent(servletRequest); String userAgent = Servlets.getUserAgent(servletRequest);
// 设置日志上下文的用户 否则登录失败不会记录日志
OperatorLogs.setUser(SystemUserConvert.MAPPER.toLoginUser(request));
// 登录前检查
SystemUserDO user = this.preCheckLogin(request.getUsername(), request.getPassword());
// 重新设置日志上下文
OperatorLogs.setUser(SystemUserConvert.MAPPER.toLoginUser(user));
// 用户密码校验
boolean passRight = this.checkUserPassword(user, request.getPassword(), true);
// 发送站内信
this.sendLoginFailedErrorMessage(passRight, user, remoteAddr, location);
Valid.isTrue(passRight, ErrorMessage.USERNAME_PASSWORD_ERROR);
// 用户状态校验
this.checkUserStatus(user);
Long id = user.getId();
// 设置上次登录时间
this.setLastLoginTime(id);
// 删除用户缓存
this.deleteUserCache(user);
// 重设用户缓存
this.setUserCache(user);
long current = System.currentTimeMillis(); long current = System.currentTimeMillis();
// 不允许多端登录 // 不允许多端登录
if (!appAuthenticationConfig.getAllowMultiDevice()) { if (!appAuthenticationConfig.getAllowMultiDevice()) {
// 无效化其他缓存 // 无效化其他缓存
this.invalidOtherDeviceToken(user.getId(), current, remoteAddr, location, userAgent); this.invalidOtherDeviceToken(id, current, remoteAddr, location, userAgent);
} }
// 生成 loginToken // 生成 loginToken
String token = this.generatorLoginToken(user, current, remoteAddr, location, userAgent); String token = this.generatorLoginToken(user, current, remoteAddr, location, userAgent);
@@ -189,62 +191,83 @@ public class AuthenticationServiceImpl implements AuthenticationService {
return refresh; return refresh;
} }
/** @Override
* 获取 token pair public SystemUserDO preCheckLogin(String username, String password) {
*
* @param loginToken loginToken
* @return pair
*/
private Pair<Long, Long> getLoginTokenPair(String loginToken) {
if (loginToken == null) {
return null;
}
try {
String value = CryptoUtils.decryptBase62(loginToken);
String[] pair = value.split(":");
return Pair.of(Long.valueOf(pair[0]), Long.valueOf(pair[1]));
} catch (Exception e) {
return null;
}
}
/**
* 登录预检查
*
* @param request request
*/
private void preCheckLogin(UserLoginRequest request) {
// 检查密码长度是否正确 MD5 长度为 32 // 检查密码长度是否正确 MD5 长度为 32
if (request.getPassword().length() != Const.MD5_LEN) { if (password.length() != Const.MD5_LEN) {
throw Exceptions.argument(ErrorMessage.USERNAME_PASSWORD_ERROR); throw Exceptions.argument(ErrorMessage.USERNAME_PASSWORD_ERROR);
} }
// 检查登录失败次数 // 检查登录失败次数
String failedCountKey = UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(request.getUsername()); String failedCountKey = UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(username);
String failedCount = redisTemplate.opsForValue().get(failedCountKey); String failedCount = redisTemplate.opsForValue().get(failedCountKey);
if (failedCount != null if (failedCount != null
&& Integer.parseInt(failedCount) >= appAuthenticationConfig.getLoginFailedLockCount()) { && Integer.parseInt(failedCount) >= appAuthenticationConfig.getLoginFailedLockCount()) {
throw Exceptions.argument(ErrorMessage.MAX_LOGIN_FAILED); throw Exceptions.argument(ErrorMessage.MAX_LOGIN_FAILED);
} }
// 获取登录用户
SystemUserDO user = systemUserDAO.of()
.createWrapper()
.eq(SystemUserDO::getUsername, username)
.then()
.getOne();
Valid.notNull(user, ErrorMessage.USERNAME_PASSWORD_ERROR);
return user;
}
@Override
public boolean checkUserPassword(SystemUserDO user, String password, boolean addFailedCount) {
// 检查密码
boolean passRight = user.getPassword().equals(Signatures.md5(password));
if (!passRight && addFailedCount) {
// 刷新登录失败缓存
String failedCountKey = UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(user.getUsername());
redisTemplate.opsForValue().increment(failedCountKey);
RedisUtils.setExpire(failedCountKey, appAuthenticationConfig.getLoginFailedLockTime(), TimeUnit.MINUTES);
}
return passRight;
}
@Override
public void checkUserStatus(SystemUserDO user) {
// 检查用户状态
UserStatusEnum.checkUserStatus(user.getStatus());
} }
/** /**
* 检查密码 * 发送登录失败错误消息
* *
* @param request request * @param passRight passRight
* @param user user * @param user user
* @return 是否正确 * @param remoteAddr remoteAddr
* @param location location
*/ */
@SuppressWarnings("ALL") private void sendLoginFailedErrorMessage(boolean passRight, SystemUserDO user,
private boolean checkPassword(UserLoginRequest request, SystemUserDO user) { String remoteAddr, String location) {
// 密码正确 if (passRight) {
if (user.getPassword().equals(Signatures.md5(request.getPassword()))) { return;
return true;
} }
// 刷新登录失败缓存 String failedCountKey = UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(user.getUsername());
String failedCountKey = UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(request.getUsername()); String failedCountStr = redisTemplate.opsForValue().get(failedCountKey);
redisTemplate.opsForValue().increment(failedCountKey); if (failedCountStr == null || !Strings.isInteger(failedCountStr)) {
RedisUtils.setExpire(failedCountKey, appAuthenticationConfig.getLoginFailedLockTime(), TimeUnit.MINUTES); return;
return false; }
// 直接用相等 因为只触发一次
if (!appAuthenticationConfig.getLoginFailedSendThreshold().equals(Integer.valueOf(failedCountStr))) {
return;
}
// 发送站内信
Map<String, Object> params = new HashMap<>();
params.put(ExtraFieldConst.ADDRESS, remoteAddr);
params.put(ExtraFieldConst.LOCATION, location);
params.put(ExtraFieldConst.TIME, Dates.current());
SystemMessageDTO message = SystemMessageDTO.builder()
.receiverId(user.getId())
.receiverUsername(user.getUsername())
.relKey(user.getUsername())
.params(params)
.build();
// 发送
systemMessageApi.create(SystemUserMessageDefine.LOGIN_FAILED, message);
} }
/** /**
@@ -273,6 +296,25 @@ public class AuthenticationServiceImpl implements AuthenticationService {
redisTemplate.delete(Lists.of(userInfoKey, loginFailedCountKey)); redisTemplate.delete(Lists.of(userInfoKey, loginFailedCountKey));
} }
/**
* 获取 token pair
*
* @param loginToken loginToken
* @return pair
*/
private Pair<Long, Long> getLoginTokenPair(String loginToken) {
if (loginToken == null) {
return null;
}
try {
String value = CryptoUtils.decryptBase62(loginToken);
String[] pair = value.split(":");
return Pair.of(Long.valueOf(pair[0]), Long.valueOf(pair[1]));
} catch (Exception e) {
return null;
}
}
/** /**
* 设置用户缓存 * 设置用户缓存
* *
@@ -283,7 +325,7 @@ public class AuthenticationServiceImpl implements AuthenticationService {
Long id = user.getId(); Long id = user.getId();
// 查询用户角色 // 查询用户角色
List<Long> roleIds = systemUserRoleDAO.selectRoleIdByUserId(id); List<Long> roleIds = systemUserRoleDAO.selectRoleIdByUserId(id);
List<UserRole> roleList = permissionService.getRoleCache() List<UserRole> roleList = userPermissionService.getRoleCache()
.values() .values()
.stream() .stream()
.filter(s -> roleIds.contains(s.getId())) .filter(s -> roleIds.contains(s.getId()))

View File

@@ -85,6 +85,8 @@ public class DataPermissionServiceImpl implements DataPermissionService {
.eq(DataPermissionDO::getRoleId, roleId) .eq(DataPermissionDO::getRoleId, roleId)
.eq(DataPermissionDO::getType, type); .eq(DataPermissionDO::getType, type);
dataPermissionDAO.delete(wrapper); dataPermissionDAO.delete(wrapper);
// 删除缓存
this.deleteCache(type, userId, roleId);
if (Lists.isEmpty(request.getRelIdList())) { if (Lists.isEmpty(request.getRelIdList())) {
return; return;
} }
@@ -100,8 +102,6 @@ public class DataPermissionServiceImpl implements DataPermissionService {
.build()) .build())
.collect(Collectors.toList()); .collect(Collectors.toList());
dataPermissionDAO.insertBatch(records); dataPermissionDAO.insertBatch(records);
// 删除缓存
this.deleteCache(type, userId, roleId);
} }
@Override @Override

View File

@@ -1,305 +1,67 @@
package com.orion.visor.module.infra.service.impl; package com.orion.visor.module.infra.service.impl;
import com.orion.lang.utils.Arrays1;
import com.orion.lang.utils.collect.Lists; import com.orion.lang.utils.collect.Lists;
import com.orion.lang.utils.collect.Maps;
import com.orion.visor.framework.common.constant.Const;
import com.orion.visor.framework.common.security.LoginUser;
import com.orion.visor.framework.common.security.UserRole;
import com.orion.visor.framework.security.core.utils.SecurityUtils;
import com.orion.visor.module.infra.convert.SystemMenuConvert;
import com.orion.visor.module.infra.convert.SystemUserConvert;
import com.orion.visor.module.infra.dao.SystemMenuDAO;
import com.orion.visor.module.infra.dao.SystemRoleDAO; import com.orion.visor.module.infra.dao.SystemRoleDAO;
import com.orion.visor.module.infra.dao.SystemRoleMenuDAO;
import com.orion.visor.module.infra.define.RoleDefine; import com.orion.visor.module.infra.define.RoleDefine;
import com.orion.visor.module.infra.entity.domain.SystemMenuDO;
import com.orion.visor.module.infra.entity.domain.SystemRoleDO; import com.orion.visor.module.infra.entity.domain.SystemRoleDO;
import com.orion.visor.module.infra.entity.domain.SystemRoleMenuDO;
import com.orion.visor.module.infra.entity.dto.SystemMenuCacheDTO;
import com.orion.visor.module.infra.entity.vo.SystemMenuVO;
import com.orion.visor.module.infra.entity.vo.UserCollectInfoVO;
import com.orion.visor.module.infra.entity.vo.UserPermissionVO;
import com.orion.visor.module.infra.enums.MenuStatusEnum;
import com.orion.visor.module.infra.enums.MenuTypeEnum;
import com.orion.visor.module.infra.enums.PreferenceTypeEnum;
import com.orion.visor.module.infra.enums.RoleStatusEnum; import com.orion.visor.module.infra.enums.RoleStatusEnum;
import com.orion.visor.module.infra.service.PermissionService; import com.orion.visor.module.infra.service.PermissionService;
import com.orion.visor.module.infra.service.PreferenceService;
import com.orion.visor.module.infra.service.SystemMenuService;
import com.orion.visor.module.infra.service.TipsService;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.*; import java.util.List;
import java.util.concurrent.Future;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
/** /**
* 权限服务 * 权限 服务实现类
* *
* @author Jiahang Li * @author Jiahang Li
* @version 1.0.0 * @version 1.0.0
* @since 2023/7/16 1:05 * @since 2024/8/19 15:29
*/ */
@Slf4j
@Service @Service
public class PermissionServiceImpl implements PermissionService { public class PermissionServiceImpl implements PermissionService {
@Getter
private final Map<Long, SystemRoleDO> roleCache = new HashMap<>();
@Getter
private final List<SystemMenuCacheDTO> menuCache = new ArrayList<>();
@Getter
private final Map<Long, List<SystemMenuCacheDTO>> roleMenuCache = new HashMap<>();
@Resource @Resource
private SystemRoleDAO systemRoleDAO; private SystemRoleDAO systemRoleDAO;
@Resource
private SystemMenuDAO systemMenuDAO;
@Resource
private SystemRoleMenuDAO systemRoleMenuDAO;
@Resource
private SystemMenuService systemMenuService;
@Resource
private PreferenceService preferenceService;
@Resource
private TipsService tipsService;
@PostConstruct
@Override @Override
public void initPermissionCache() { public boolean isAdminUser(Long userId) {
long start = System.currentTimeMillis(); return this.hasAnyRole(userId, Lists.of(RoleDefine.ADMIN_CODE));
log.info("initPermissionCache-start");
roleCache.clear();
menuCache.clear();
roleMenuCache.clear();
// 加载所有角色
List<SystemRoleDO> roles = systemRoleDAO.selectList(null);
for (SystemRoleDO role : roles) {
roleCache.put(role.getId(), role);
}
// 加载所有菜单信息
List<SystemMenuDO> menuList = systemMenuDAO.selectList(null);
List<SystemMenuCacheDTO> menus = SystemMenuConvert.MAPPER.toCache(menuList);
Map<Long, SystemMenuCacheDTO> menuMapping = menus.stream()
.collect(Collectors.toMap(SystemMenuCacheDTO::getId, Function.identity()));
menuCache.addAll(menus);
// 查询所有角色菜单
systemRoleMenuDAO.selectList(null)
.stream()
.collect(Collectors.groupingBy(SystemRoleMenuDO::getRoleId,
Collectors.mapping(SystemRoleMenuDO::getMenuId, Collectors.toList())))
.forEach((roleId, menuIdList) -> {
// 获取菜单引用
List<SystemMenuCacheDTO> roleMenus = menuIdList.stream()
.map(menuMapping::get)
.filter(Objects::nonNull)
.collect(Collectors.toList());
// 获取角色引用
roleMenuCache.put(roleId, roleMenus);
});
log.info("initPermissionCache-end used: {}ms", System.currentTimeMillis() - start);
} }
@Override @Override
public boolean hasRole(String role) { public boolean hasRole(Long userId, String role) {
// 获取用户角色 return this.hasAnyRole(userId, Lists.of(role));
Map<Long, String> roles = this.getUserEnabledRoles(); }
@Override
public boolean hasAnyRole(Long userId, List<String> roles) {
return !systemRoleDAO.getRoleIdByUserIdAndRoleCode(userId, roles).isEmpty();
}
@Override
public boolean hasPermission(Long userId, String permission) {
return this.hasAnyPermission(userId, Lists.singleton(permission));
}
@Override
public boolean hasAnyPermission(Long userId, List<String> permissions) {
// 查询用户角色
List<SystemRoleDO> roles = systemRoleDAO.selectRoleByUserId(userId);
roles.removeIf(s -> !RoleStatusEnum.ENABLED.getStatus().equals(s.getStatus()));
if (roles.isEmpty()) { if (roles.isEmpty()) {
return false; return false;
} }
// 检查是否为超级管理员或包含此角色 // 判断是否为 admin
return RoleDefine.containsAdmin(roles.values()) || roles.containsValue(role); boolean isAdmin = roles.stream().anyMatch(s -> s.getCode().equals(RoleDefine.ADMIN_CODE));
} if (isAdmin) {
@Override
public boolean hasAnyRole(String... roles) {
if (Arrays1.isEmpty(roles)) {
return true; return true;
} }
// 获取用户角色 List<Long> roleIdList = roles.stream()
Map<Long, String> enableRoles = this.getUserEnabledRoles(); .map(SystemRoleDO::getId)
if (enableRoles.isEmpty()) {
return false;
}
// 检查是否为超级管理员 || 有此角色
return RoleDefine.containsAdmin(enableRoles.values())
|| Arrays.stream(roles).anyMatch(enableRoles::containsValue);
}
@Override
public boolean hasPermission(String permission) {
// 获取用户角色
Map<Long, String> roles = this.getUserEnabledRoles();
if (roles.isEmpty()) {
return false;
}
// 检查是否为超级管理员
if (RoleDefine.containsAdmin(roles.values())) {
return true;
}
// 检查普通角色是否有此权限
return roles.keySet()
.stream()
.anyMatch(s -> this.checkRoleHasPermission(s, permission));
}
@Override
public boolean hasAnyPermission(String... permissions) {
if (Arrays1.isEmpty(permissions)) {
return true;
}
// 获取用户角色
Map<Long, String> roles = this.getUserEnabledRoles();
if (roles.isEmpty()) {
return false;
}
// 检查是否为超级管理员
if (RoleDefine.containsAdmin(roles.values())) {
return true;
}
// 检查用户角色是否包含权限
return Arrays.stream(permissions)
.anyMatch(perm -> roles.keySet()
.stream()
.anyMatch(s -> this.checkRoleHasPermission(s, perm)));
}
@Override
public List<SystemMenuVO> getUserMenuList() {
// 获取用户角色
Map<Long, String> roles = this.getUserEnabledRoles();
if (roles.isEmpty()) {
return Lists.empty();
}
// 查询角色菜单
Stream<SystemMenuCacheDTO> mergeStream;
if (RoleDefine.containsAdmin(roles.values())) {
// 管理员拥有全部菜单
mergeStream = menuCache.stream();
} else {
// 当前用户所适配的角色菜单
mergeStream = roles.keySet()
.stream()
.map(roleMenuCache::get)
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.distinct();
}
// 状态过滤
List<SystemMenuVO> menus = mergeStream
.filter(s -> MenuStatusEnum.ENABLED.getStatus().equals(s.getStatus()))
.filter(s -> !MenuTypeEnum.FUNCTION.getType().equals(s.getType()))
.map(SystemMenuConvert.MAPPER::to)
.collect(Collectors.toList()); .collect(Collectors.toList());
// 构建菜单树 return !systemRoleDAO.getPermissionByRoleIdAndPermission(roleIdList, permissions).isEmpty();
return systemMenuService.buildSystemMenuTree(menus);
}
@SneakyThrows
@Override
public UserPermissionVO getUserPermission() {
// 获取用户信息
UserCollectInfoVO user = SystemUserConvert.MAPPER.toCollectInfo(SecurityUtils.getLoginUser());
Long id = user.getId();
// 获取用户系统偏好
Future<Map<String, Object>> systemPreference = preferenceService.getPreferenceAsync(id, PreferenceTypeEnum.SYSTEM);
// 获取用户角色
Map<Long, String> roles = this.getUserEnabledRoles();
// 获取用户权限
List<String> permissions;
if (roles.isEmpty()) {
permissions = Lists.empty();
} else {
if (RoleDefine.containsAdmin(roles.values())) {
// 管理员拥有全部权限
permissions = Lists.of(Const.ASTERISK);
} else {
// 当前用户所适配的角色的权限
permissions = roles.keySet()
.stream()
.map(roleMenuCache::get)
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.filter(s -> MenuStatusEnum.ENABLED.getStatus().equals(s.getStatus()))
.map(SystemMenuCacheDTO::getPermission)
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
}
}
// 设置已提示的 key
user.setTippedKeys(tipsService.getTippedKeys());
// 获取异步结果
user.setSystemPreference(systemPreference.get());
// 组装数据
return UserPermissionVO.builder()
.user(user)
.roles(roles.values())
.permissions(permissions)
.build();
}
/**
* 检查角色是否有权限
*
* @param roleId roleId
* @param permission permission
* @return 是否有权限
*/
private boolean checkRoleHasPermission(Long roleId, String permission) {
// 获取角色权限列表
List<SystemMenuCacheDTO> menus = roleMenuCache.get(roleId);
if (Lists.isEmpty(menus)) {
return false;
}
// 检查是否有此权限
return menus.stream()
.filter(s -> MenuStatusEnum.ENABLED.getStatus().equals(s.getStatus()))
.map(SystemMenuCacheDTO::getPermission)
.filter(Objects::nonNull)
.anyMatch(permission::equals);
}
/**
* 获取用户启用的角色
*
* @return roles
*/
private Map<Long, String> getUserEnabledRoles() {
// 获取当前用户角色
List<UserRole> userRoles = Optional.ofNullable(SecurityUtils.getLoginUser())
.map(LoginUser::getRoles)
.orElse(Lists.empty());
if (Lists.isEmpty(userRoles)) {
return Maps.empty();
}
// 获取角色编码
Map<Long, String> roles = userRoles.stream()
.map(UserRole::getId)
.map(roleCache::get)
.filter(Objects::nonNull)
// 过滤未启用的角色
.filter(r -> RoleStatusEnum.ENABLED.getStatus().equals(r.getStatus()))
.collect(Collectors.toMap(SystemRoleDO::getId, SystemRoleDO::getCode));
if (Maps.isEmpty(roles)) {
return Maps.empty();
}
return roles;
} }
} }

View File

@@ -21,7 +21,7 @@ import com.orion.visor.module.infra.entity.vo.SystemMenuVO;
import com.orion.visor.module.infra.enums.MenuStatusEnum; import com.orion.visor.module.infra.enums.MenuStatusEnum;
import com.orion.visor.module.infra.enums.MenuTypeEnum; import com.orion.visor.module.infra.enums.MenuTypeEnum;
import com.orion.visor.module.infra.enums.MenuVisibleEnum; import com.orion.visor.module.infra.enums.MenuVisibleEnum;
import com.orion.visor.module.infra.service.PermissionService; import com.orion.visor.module.infra.service.UserPermissionService;
import com.orion.visor.module.infra.service.SystemMenuService; import com.orion.visor.module.infra.service.SystemMenuService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
@@ -51,7 +51,7 @@ public class SystemMenuServiceImpl implements SystemMenuService {
private SystemRoleMenuDAO systemRoleMenuDAO; private SystemRoleMenuDAO systemRoleMenuDAO;
@Resource @Resource
private PermissionService permissionService; private UserPermissionService userPermissionService;
@Override @Override
public Long createSystemMenu(SystemMenuCreateRequest request) { public Long createSystemMenu(SystemMenuCreateRequest request) {
@@ -68,7 +68,7 @@ public class SystemMenuServiceImpl implements SystemMenuService {
int effect = systemMenuDAO.insert(record); int effect = systemMenuDAO.insert(record);
log.info("SystemMenuService-createSystemMenu effect: {}, record: {}", effect, JSON.toJSONString(record)); log.info("SystemMenuService-createSystemMenu effect: {}, record: {}", effect, JSON.toJSONString(record));
// 保存至缓存 // 保存至缓存
List<SystemMenuCacheDTO> menuCache = permissionService.getMenuCache(); List<SystemMenuCacheDTO> menuCache = userPermissionService.getMenuCache();
menuCache.add(SystemMenuConvert.MAPPER.toCache(record)); menuCache.add(SystemMenuConvert.MAPPER.toCache(record));
return record.getId(); return record.getId();
} }
@@ -89,7 +89,7 @@ public class SystemMenuServiceImpl implements SystemMenuService {
// 重新查询转换为缓存 // 重新查询转换为缓存
SystemMenuCacheDTO cache = SystemMenuConvert.MAPPER.toCache(systemMenuDAO.selectById(id)); SystemMenuCacheDTO cache = SystemMenuConvert.MAPPER.toCache(systemMenuDAO.selectById(id));
// 获取原始缓存 // 获取原始缓存
permissionService.getMenuCache() userPermissionService.getMenuCache()
.stream() .stream()
.filter(s -> s.getId().equals(id)) .filter(s -> s.getId().equals(id))
.findFirst() .findFirst()
@@ -115,7 +115,7 @@ public class SystemMenuServiceImpl implements SystemMenuService {
Integer type = request.getType(); Integer type = request.getType();
Integer status = request.getStatus(); Integer status = request.getStatus();
// 从缓存中查询 // 从缓存中查询
List<SystemMenuVO> menus = permissionService.getMenuCache() List<SystemMenuVO> menus = userPermissionService.getMenuCache()
.stream() .stream()
.filter(s -> Strings.isBlank(name) || s.getName().contains(name)) .filter(s -> Strings.isBlank(name) || s.getName().contains(name))
.filter(s -> type == null || s.getType().equals(type)) .filter(s -> type == null || s.getType().equals(type))
@@ -197,7 +197,7 @@ public class SystemMenuServiceImpl implements SystemMenuService {
// 添加日志参数 // 添加日志参数
OperatorLogs.add(OperatorLogs.NAME, record.getName()); OperatorLogs.add(OperatorLogs.NAME, record.getName());
// 从缓存中查询 // 从缓存中查询
List<SystemMenuCacheDTO> cache = permissionService.getMenuCache(); List<SystemMenuCacheDTO> cache = userPermissionService.getMenuCache();
// 获取要更新的id // 获取要更新的id
List<Long> updateIdList = this.getChildrenIdList(id, cache, record.getType()); List<Long> updateIdList = this.getChildrenIdList(id, cache, record.getType());
// 修改状态 // 修改状态
@@ -229,7 +229,7 @@ public class SystemMenuServiceImpl implements SystemMenuService {
// 添加日志参数 // 添加日志参数
OperatorLogs.add(OperatorLogs.NAME, record.getName()); OperatorLogs.add(OperatorLogs.NAME, record.getName());
// 从缓存中查询 // 从缓存中查询
List<SystemMenuCacheDTO> cache = permissionService.getMenuCache(); List<SystemMenuCacheDTO> cache = userPermissionService.getMenuCache();
// 获取要删除的id // 获取要删除的id
List<Long> deletedIdList = this.getChildrenIdList(id, cache, record.getType()); List<Long> deletedIdList = this.getChildrenIdList(id, cache, record.getType());
// 删除菜单 // 删除菜单
@@ -239,7 +239,7 @@ public class SystemMenuServiceImpl implements SystemMenuService {
// 删除菜单缓存 // 删除菜单缓存
cache.removeIf(s -> deletedIdList.contains(s.getId())); cache.removeIf(s -> deletedIdList.contains(s.getId()));
// 删除引用缓存 // 删除引用缓存
permissionService.getRoleMenuCache() userPermissionService.getRoleMenuCache()
.values() .values()
.forEach(roleMenus -> roleMenus.removeIf(s -> deletedIdList.contains(s.getId()))); .forEach(roleMenus -> roleMenus.removeIf(s -> deletedIdList.contains(s.getId())));
log.info("SystemMenuService-deleteSystemMenu deletedIdList: {}, effect: {}", deletedIdList, effect); log.info("SystemMenuService-deleteSystemMenu deletedIdList: {}, effect: {}", deletedIdList, effect);

View File

@@ -74,9 +74,9 @@ public class SystemMessageServiceImpl implements SystemMessageService {
.eq(SystemMessageDO::getClassify, request.getClassify()) .eq(SystemMessageDO::getClassify, request.getClassify())
.lt(SystemMessageDO::getId, request.getMaxId()) .lt(SystemMessageDO::getId, request.getMaxId())
.eq(SystemMessageDO::getStatus, status) .eq(SystemMessageDO::getStatus, status)
.last(Const.LIMIT + Const.SPACE + request.getLimit())
.orderByDesc(SystemMessageDO::getId) .orderByDesc(SystemMessageDO::getId)
.then() .then()
.limit(request.getLimit())
.list(SystemMessageConvert.MAPPER::to); .list(SystemMessageConvert.MAPPER::to);
} }

View File

@@ -16,7 +16,7 @@ import com.orion.visor.module.infra.entity.domain.SystemRoleDO;
import com.orion.visor.module.infra.entity.domain.SystemRoleMenuDO; import com.orion.visor.module.infra.entity.domain.SystemRoleMenuDO;
import com.orion.visor.module.infra.entity.dto.SystemMenuCacheDTO; import com.orion.visor.module.infra.entity.dto.SystemMenuCacheDTO;
import com.orion.visor.module.infra.entity.request.menu.SystemRoleGrantMenuRequest; import com.orion.visor.module.infra.entity.request.menu.SystemRoleGrantMenuRequest;
import com.orion.visor.module.infra.service.PermissionService; import com.orion.visor.module.infra.service.UserPermissionService;
import com.orion.visor.module.infra.service.SystemRoleMenuService; import com.orion.visor.module.infra.service.SystemRoleMenuService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -49,7 +49,7 @@ public class SystemRoleMenuServiceImpl implements SystemRoleMenuService {
private SystemRoleMenuDAO systemRoleMenuDAO; private SystemRoleMenuDAO systemRoleMenuDAO;
@Resource @Resource
private PermissionService permissionService; private UserPermissionService userPermissionService;
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@@ -104,7 +104,7 @@ public class SystemRoleMenuServiceImpl implements SystemRoleMenuService {
effect += insertMenuIdList.size(); effect += insertMenuIdList.size();
} }
// 更新缓存 // 更新缓存
Map<Long, List<SystemMenuCacheDTO>> cache = permissionService.getRoleMenuCache(); Map<Long, List<SystemMenuCacheDTO>> cache = userPermissionService.getRoleMenuCache();
List<SystemMenuCacheDTO> roleCache = cache.computeIfAbsent(roleId, s -> new ArrayList<>()); List<SystemMenuCacheDTO> roleCache = cache.computeIfAbsent(roleId, s -> new ArrayList<>());
roleCache.clear(); roleCache.clear();
roleCache.addAll(SystemMenuConvert.MAPPER.toCache(menuList)); roleCache.addAll(SystemMenuConvert.MAPPER.toCache(menuList));

View File

@@ -20,7 +20,7 @@ import com.orion.visor.module.infra.entity.request.role.SystemRoleUpdateRequest;
import com.orion.visor.module.infra.entity.vo.SystemRoleVO; import com.orion.visor.module.infra.entity.vo.SystemRoleVO;
import com.orion.visor.module.infra.enums.RoleStatusEnum; import com.orion.visor.module.infra.enums.RoleStatusEnum;
import com.orion.visor.module.infra.service.DataPermissionService; import com.orion.visor.module.infra.service.DataPermissionService;
import com.orion.visor.module.infra.service.PermissionService; import com.orion.visor.module.infra.service.UserPermissionService;
import com.orion.visor.module.infra.service.SystemRoleService; import com.orion.visor.module.infra.service.SystemRoleService;
import com.orion.visor.module.infra.service.SystemUserRoleService; import com.orion.visor.module.infra.service.SystemUserRoleService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -51,7 +51,7 @@ public class SystemRoleServiceImpl implements SystemRoleService {
private SystemRoleMenuDAO systemRoleMenuDAO; private SystemRoleMenuDAO systemRoleMenuDAO;
@Resource @Resource
private PermissionService permissionService; private UserPermissionService userPermissionService;
@Resource @Resource
private SystemUserRoleService systemUserRoleService; private SystemUserRoleService systemUserRoleService;
@@ -72,7 +72,7 @@ public class SystemRoleServiceImpl implements SystemRoleService {
int effect = systemRoleDAO.insert(record); int effect = systemRoleDAO.insert(record);
log.info("SystemRoleService-createSystemRole effect: {}, domain: {}", effect, JSON.toJSONString(record)); log.info("SystemRoleService-createSystemRole effect: {}, domain: {}", effect, JSON.toJSONString(record));
// 设置到缓存 // 设置到缓存
permissionService.getRoleCache().put(record.getId(), record); userPermissionService.getRoleCache().put(record.getId(), record);
return record.getId(); return record.getId();
} }
@@ -92,7 +92,7 @@ public class SystemRoleServiceImpl implements SystemRoleService {
int effect = systemRoleDAO.updateById(updateRecord); int effect = systemRoleDAO.updateById(updateRecord);
log.info("SystemRoleService-updateSystemRoleById effect: {}, updateRecord: {}", effect, JSON.toJSONString(updateRecord)); log.info("SystemRoleService-updateSystemRoleById effect: {}, updateRecord: {}", effect, JSON.toJSONString(updateRecord));
// 设置到缓存 // 设置到缓存
SystemRoleDO roleCache = permissionService.getRoleCache().get(id); SystemRoleDO roleCache = userPermissionService.getRoleCache().get(id);
roleCache.setName(updateRecord.getName()); roleCache.setName(updateRecord.getName());
return effect; return effect;
} }
@@ -117,7 +117,7 @@ public class SystemRoleServiceImpl implements SystemRoleService {
int effect = systemRoleDAO.updateById(updateRecord); int effect = systemRoleDAO.updateById(updateRecord);
log.info("SystemRoleService-updateRoleStatus effect: {}, updateRecord: {}", effect, JSON.toJSONString(updateRecord)); log.info("SystemRoleService-updateRoleStatus effect: {}, updateRecord: {}", effect, JSON.toJSONString(updateRecord));
// 修改本地缓存状态 // 修改本地缓存状态
SystemRoleDO roleCache = permissionService.getRoleCache().get(id); SystemRoleDO roleCache = userPermissionService.getRoleCache().get(id);
roleCache.setStatus(status); roleCache.setStatus(status);
// 删除数据权限缓存 // 删除数据权限缓存
dataPermissionService.clearRoleCache(id); dataPermissionService.clearRoleCache(id);
@@ -180,9 +180,9 @@ public class SystemRoleServiceImpl implements SystemRoleService {
// 删除角色菜单关联 // 删除角色菜单关联
effect += systemRoleMenuDAO.deleteByRoleId(id); effect += systemRoleMenuDAO.deleteByRoleId(id);
// 删除角色缓存 // 删除角色缓存
permissionService.getRoleCache().remove(id); userPermissionService.getRoleCache().remove(id);
// 删除菜单缓存 // 删除菜单缓存
permissionService.getRoleMenuCache().remove(id); userPermissionService.getRoleMenuCache().remove(id);
// 删除用户缓存中的角色 // 删除用户缓存中的角色
systemUserRoleService.deleteUserCacheRoleAsync(id, userIdList); systemUserRoleService.deleteUserCacheRoleAsync(id, userIdList);
// 删除数据权限缓存 // 删除数据权限缓存

View File

@@ -25,7 +25,6 @@ import com.orion.visor.module.infra.dao.OperatorLogDAO;
import com.orion.visor.module.infra.dao.SystemRoleDAO; import com.orion.visor.module.infra.dao.SystemRoleDAO;
import com.orion.visor.module.infra.dao.SystemUserDAO; import com.orion.visor.module.infra.dao.SystemUserDAO;
import com.orion.visor.module.infra.dao.SystemUserRoleDAO; import com.orion.visor.module.infra.dao.SystemUserRoleDAO;
import com.orion.visor.module.infra.define.RoleDefine;
import com.orion.visor.module.infra.define.cache.TipsCacheKeyDefine; import com.orion.visor.module.infra.define.cache.TipsCacheKeyDefine;
import com.orion.visor.module.infra.define.cache.UserCacheKeyDefine; import com.orion.visor.module.infra.define.cache.UserCacheKeyDefine;
import com.orion.visor.module.infra.define.config.AppAuthenticationConfig; import com.orion.visor.module.infra.define.config.AppAuthenticationConfig;
@@ -302,11 +301,6 @@ public class SystemUserServiceImpl implements SystemUserService {
} }
} }
@Override
public boolean isAdminUser(Long userId) {
return systemRoleDAO.getRoleIdByUserIdAndRoleCode(userId, RoleDefine.ADMIN_CODE) != null;
}
/** /**
* 检查用户名否存在 * 检查用户名否存在
* *

View File

@@ -0,0 +1,305 @@
package com.orion.visor.module.infra.service.impl;
import com.orion.lang.utils.Arrays1;
import com.orion.lang.utils.collect.Lists;
import com.orion.lang.utils.collect.Maps;
import com.orion.visor.framework.common.constant.Const;
import com.orion.visor.framework.common.security.LoginUser;
import com.orion.visor.framework.common.security.UserRole;
import com.orion.visor.framework.security.core.utils.SecurityUtils;
import com.orion.visor.module.infra.convert.SystemMenuConvert;
import com.orion.visor.module.infra.convert.SystemUserConvert;
import com.orion.visor.module.infra.dao.SystemMenuDAO;
import com.orion.visor.module.infra.dao.SystemRoleDAO;
import com.orion.visor.module.infra.dao.SystemRoleMenuDAO;
import com.orion.visor.module.infra.define.RoleDefine;
import com.orion.visor.module.infra.entity.domain.SystemMenuDO;
import com.orion.visor.module.infra.entity.domain.SystemRoleDO;
import com.orion.visor.module.infra.entity.domain.SystemRoleMenuDO;
import com.orion.visor.module.infra.entity.dto.SystemMenuCacheDTO;
import com.orion.visor.module.infra.entity.vo.SystemMenuVO;
import com.orion.visor.module.infra.entity.vo.UserCollectInfoVO;
import com.orion.visor.module.infra.entity.vo.UserPermissionVO;
import com.orion.visor.module.infra.enums.MenuStatusEnum;
import com.orion.visor.module.infra.enums.MenuTypeEnum;
import com.orion.visor.module.infra.enums.PreferenceTypeEnum;
import com.orion.visor.module.infra.enums.RoleStatusEnum;
import com.orion.visor.module.infra.service.PreferenceService;
import com.orion.visor.module.infra.service.SystemMenuService;
import com.orion.visor.module.infra.service.TipsService;
import com.orion.visor.module.infra.service.UserPermissionService;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.Future;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 用户权限服务
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/7/16 1:05
*/
@Slf4j
@Service
public class UserPermissionServiceImpl implements UserPermissionService {
@Getter
private final Map<Long, SystemRoleDO> roleCache = new HashMap<>();
@Getter
private final List<SystemMenuCacheDTO> menuCache = new ArrayList<>();
@Getter
private final Map<Long, List<SystemMenuCacheDTO>> roleMenuCache = new HashMap<>();
@Resource
private SystemRoleDAO systemRoleDAO;
@Resource
private SystemMenuDAO systemMenuDAO;
@Resource
private SystemRoleMenuDAO systemRoleMenuDAO;
@Resource
private SystemMenuService systemMenuService;
@Resource
private PreferenceService preferenceService;
@Resource
private TipsService tipsService;
@PostConstruct
@Override
public void initPermissionCache() {
long start = System.currentTimeMillis();
log.info("initPermissionCache-start");
roleCache.clear();
menuCache.clear();
roleMenuCache.clear();
// 加载所有角色
List<SystemRoleDO> roles = systemRoleDAO.selectList(null);
for (SystemRoleDO role : roles) {
roleCache.put(role.getId(), role);
}
// 加载所有菜单信息
List<SystemMenuDO> menuList = systemMenuDAO.selectList(null);
List<SystemMenuCacheDTO> menus = SystemMenuConvert.MAPPER.toCache(menuList);
Map<Long, SystemMenuCacheDTO> menuMapping = menus.stream()
.collect(Collectors.toMap(SystemMenuCacheDTO::getId, Function.identity()));
menuCache.addAll(menus);
// 查询所有角色菜单
systemRoleMenuDAO.selectList(null)
.stream()
.collect(Collectors.groupingBy(SystemRoleMenuDO::getRoleId,
Collectors.mapping(SystemRoleMenuDO::getMenuId, Collectors.toList())))
.forEach((roleId, menuIdList) -> {
// 获取菜单引用
List<SystemMenuCacheDTO> roleMenus = menuIdList.stream()
.map(menuMapping::get)
.filter(Objects::nonNull)
.collect(Collectors.toList());
// 获取角色引用
roleMenuCache.put(roleId, roleMenus);
});
log.info("initPermissionCache-end used: {}ms", System.currentTimeMillis() - start);
}
@Override
public boolean hasRole(String role) {
// 获取用户角色
Map<Long, String> roles = this.getUserEnabledRoles();
if (roles.isEmpty()) {
return false;
}
// 检查是否为超级管理员或包含此角色
return RoleDefine.containsAdmin(roles.values()) || roles.containsValue(role);
}
@Override
public boolean hasAnyRole(String... roles) {
if (Arrays1.isEmpty(roles)) {
return true;
}
// 获取用户角色
Map<Long, String> enableRoles = this.getUserEnabledRoles();
if (enableRoles.isEmpty()) {
return false;
}
// 检查是否为超级管理员 || 有此角色
return RoleDefine.containsAdmin(enableRoles.values())
|| Arrays.stream(roles).anyMatch(enableRoles::containsValue);
}
@Override
public boolean hasPermission(String permission) {
// 获取用户角色
Map<Long, String> roles = this.getUserEnabledRoles();
if (roles.isEmpty()) {
return false;
}
// 检查是否为超级管理员
if (RoleDefine.containsAdmin(roles.values())) {
return true;
}
// 检查普通角色是否有此权限
return roles.keySet()
.stream()
.anyMatch(s -> this.checkRoleHasPermission(s, permission));
}
@Override
public boolean hasAnyPermission(String... permissions) {
if (Arrays1.isEmpty(permissions)) {
return true;
}
// 获取用户角色
Map<Long, String> roles = this.getUserEnabledRoles();
if (roles.isEmpty()) {
return false;
}
// 检查是否为超级管理员
if (RoleDefine.containsAdmin(roles.values())) {
return true;
}
// 检查用户角色是否包含权限
return Arrays.stream(permissions)
.anyMatch(perm -> roles.keySet()
.stream()
.anyMatch(s -> this.checkRoleHasPermission(s, perm)));
}
@Override
public List<SystemMenuVO> getUserMenuList() {
// 获取用户角色
Map<Long, String> roles = this.getUserEnabledRoles();
if (roles.isEmpty()) {
return Lists.empty();
}
// 查询角色菜单
Stream<SystemMenuCacheDTO> mergeStream;
if (RoleDefine.containsAdmin(roles.values())) {
// 管理员拥有全部菜单
mergeStream = menuCache.stream();
} else {
// 当前用户所适配的角色菜单
mergeStream = roles.keySet()
.stream()
.map(roleMenuCache::get)
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.distinct();
}
// 状态过滤
List<SystemMenuVO> menus = mergeStream
.filter(s -> MenuStatusEnum.ENABLED.getStatus().equals(s.getStatus()))
.filter(s -> !MenuTypeEnum.FUNCTION.getType().equals(s.getType()))
.map(SystemMenuConvert.MAPPER::to)
.collect(Collectors.toList());
// 构建菜单树
return systemMenuService.buildSystemMenuTree(menus);
}
@SneakyThrows
@Override
public UserPermissionVO getUserPermission() {
// 获取用户信息
UserCollectInfoVO user = SystemUserConvert.MAPPER.toCollectInfo(SecurityUtils.getLoginUser());
Long id = user.getId();
// 获取用户系统偏好
Future<Map<String, Object>> systemPreference = preferenceService.getPreferenceAsync(id, PreferenceTypeEnum.SYSTEM);
// 获取用户角色
Map<Long, String> roles = this.getUserEnabledRoles();
// 获取用户权限
List<String> permissions;
if (roles.isEmpty()) {
permissions = Lists.empty();
} else {
if (RoleDefine.containsAdmin(roles.values())) {
// 管理员拥有全部权限
permissions = Lists.of(Const.ASTERISK);
} else {
// 当前用户所适配的角色的权限
permissions = roles.keySet()
.stream()
.map(roleMenuCache::get)
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.filter(s -> MenuStatusEnum.ENABLED.getStatus().equals(s.getStatus()))
.map(SystemMenuCacheDTO::getPermission)
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
}
}
// 设置已提示的 key
user.setTippedKeys(tipsService.getTippedKeys());
// 获取异步结果
user.setSystemPreference(systemPreference.get());
// 组装数据
return UserPermissionVO.builder()
.user(user)
.roles(roles.values())
.permissions(permissions)
.build();
}
/**
* 检查角色是否有权限
*
* @param roleId roleId
* @param permission permission
* @return 是否有权限
*/
private boolean checkRoleHasPermission(Long roleId, String permission) {
// 获取角色权限列表
List<SystemMenuCacheDTO> menus = roleMenuCache.get(roleId);
if (Lists.isEmpty(menus)) {
return false;
}
// 检查是否有此权限
return menus.stream()
.filter(s -> MenuStatusEnum.ENABLED.getStatus().equals(s.getStatus()))
.map(SystemMenuCacheDTO::getPermission)
.filter(Objects::nonNull)
.anyMatch(permission::equals);
}
/**
* 获取用户启用的角色
*
* @return roles
*/
private Map<Long, String> getUserEnabledRoles() {
// 获取当前用户角色
List<UserRole> userRoles = Optional.ofNullable(SecurityUtils.getLoginUser())
.map(LoginUser::getRoles)
.orElse(Lists.empty());
if (Lists.isEmpty(userRoles)) {
return Maps.empty();
}
// 获取角色编码
Map<Long, String> roles = userRoles.stream()
.map(UserRole::getId)
.map(roleCache::get)
.filter(Objects::nonNull)
// 过滤未启用的角色
.filter(r -> RoleStatusEnum.ENABLED.getStatus().equals(r.getStatus()))
.collect(Collectors.toMap(SystemRoleDO::getId, SystemRoleDO::getCode));
if (Maps.isEmpty(roles)) {
return Maps.empty();
}
return roles;
}
}

View File

@@ -22,6 +22,11 @@
"type": "java.lang.Integer", "type": "java.lang.Integer",
"description": "凭证续签最大次数." "description": "凭证续签最大次数."
}, },
{
"name": "app.authentication.loginFailedSendThreshold",
"type": "java.lang.Integer",
"description": "登录失败发送站内信阈值."
},
{ {
"name": "app.authentication.loginFailedLockCount", "name": "app.authentication.loginFailedLockCount",
"type": "java.lang.Integer", "type": "java.lang.Integer",

View File

@@ -24,12 +24,42 @@
SELECT role_id SELECT role_id
FROM system_user_role FROM system_user_role
WHERE user_id = #{userId} WHERE user_id = #{userId}
AND deleted = 0 AND deleted = 0
AND role_id IN (SELECT id FROM system_role WHERE CODE = #{code} AND deleted = 0) LIMIT 1 AND role_id IN
(SELECT id
FROM system_role
WHERE deleted = 0
AND status = 1
AND code IN
<foreach collection="codeList" item="item" open="(" close=")" separator=",">
#{item}
</foreach>
)
</select>
<select id="getPermissionByRoleIdAndPermission" resultType="java.lang.String">
SELECT m.permission
FROM system_menu m
LEFT JOIN system_role_menu rm ON rm.menu_id = m.id
WHERE rm.deleted = 0
AND m.deleted = 0
AND m.type = 3
AND m.status = 1
<if test="permissionList != null and permissionList.size() > 0">
AND m.permission IN
<foreach collection="permissionList" item="item" open="(" close=")" separator=",">
#{item}
</foreach>
</if>
AND rm.role_id IN
<foreach collection="roleIdList" item="item" open="(" close=")" separator=",">
#{item}
</foreach>
</select> </select>
<select id="selectRoleByUserId" resultMap="BaseResultMap"> <select id="selectRoleByUserId" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/> SELECT
<include refid="Base_Column_List"/>
FROM system_role FROM system_role
WHERE deleted = 0 WHERE deleted = 0
AND id IN (SELECT role_id FROM system_user_role WHERE user_id = #{userId} AND deleted = 0) AND id IN (SELECT role_id FROM system_user_role WHERE user_id = #{userId} AND deleted = 0)

View File

@@ -1,6 +1,6 @@
VITE_API_BASE_URL= 'http://127.0.0.1:9200/orion-visor/api' VITE_API_BASE_URL= 'http://127.0.0.1:9200/orion-visor/api'
VITE_WS_BASE_URL= 'ws://127.0.0.1:9200/orion-visor/keep-alive' VITE_WS_BASE_URL= 'ws://127.0.0.1:9200/orion-visor/keep-alive'
VITE_APP_VERSION= '2.1.1' VITE_APP_VERSION= '2.1.4'
VITE_APP_RELEASE= 'community' VITE_APP_RELEASE= 'community'
VITE_SFTP_PREVIEW_MB= 2 VITE_SFTP_PREVIEW_MB= 2
VITE_DEMO_MODE= false VITE_DEMO_MODE= false

View File

@@ -1,6 +1,6 @@
VITE_API_BASE_URL= '/orion-visor/api' VITE_API_BASE_URL= '/orion-visor/api'
VITE_WS_BASE_URL= '/orion-visor/keep-alive' VITE_WS_BASE_URL= '/orion-visor/keep-alive'
VITE_APP_VERSION= '2.1.1' VITE_APP_VERSION= '2.1.4'
VITE_APP_RELEASE= 'community' VITE_APP_RELEASE= 'community'
VITE_SFTP_PREVIEW_MB= 2 VITE_SFTP_PREVIEW_MB= 2
VITE_DEMO_MODE= false VITE_DEMO_MODE= false

View File

@@ -1,7 +1,7 @@
{ {
"name": "orion-visor-ui", "name": "orion-visor-ui",
"description": "Orion Visor UI", "description": "Orion Visor UI",
"version": "2.1.1", "version": "2.1.4",
"private": true, "private": true,
"author": "Jiahang Li", "author": "Jiahang Li",
"license": "Apache 2.0", "license": "Apache 2.0",

View File

@@ -29,7 +29,6 @@ export interface CommandSnippetQueryResponse extends CommandSnippetQueryResponse
export interface CommandSnippetQueryResponseExtra { export interface CommandSnippetQueryResponseExtra {
visible: boolean; visible: boolean;
expand?: boolean;
} }
/** /**

View File

@@ -51,6 +51,13 @@ export function getTerminalAccessToken() {
return axios.get<string>('/asset/host-terminal/access'); return axios.get<string>('/asset/host-terminal/access');
} }
/**
* 获取主机终端 transferToken
*/
export function getTerminalTransferToken() {
return axios.get<string>('/asset/host-terminal/transfer');
}
/** /**
* 打开主机终端 websocket * 打开主机终端 websocket
*/ */

View File

@@ -31,7 +31,6 @@ export interface PathBookmarkQueryResponse extends PathBookmarkQueryResponseExtr
export interface PathBookmarkQueryResponseExtra { export interface PathBookmarkQueryResponseExtra {
visible: boolean; visible: boolean;
expand?: boolean;
} }
/** /**

View File

@@ -93,5 +93,5 @@ export function deleteMenu(id: number) {
* 刷新缓存 * 刷新缓存
*/ */
export function refreshCache() { export function refreshCache() {
return axios.put('/infra/permission/refresh-cache'); return axios.put('/infra/user-permission/refresh-cache');
} }

View File

@@ -1,10 +1,10 @@
import type { Pagination } from '@/types/global';
import axios from 'axios'; import axios from 'axios';
/** /**
* 系统消息查询请求 * 系统消息查询请求
*/ */
export interface MessageQueryRequest { export interface MessageQueryRequest extends Pagination {
limit?: number;
maxId?: number; maxId?: number;
classify?: string; classify?: string;
queryUnread?: boolean; queryUnread?: boolean;

View File

@@ -50,12 +50,12 @@ export function logout() {
* 获取用户信息 * 获取用户信息
*/ */
export function getUserPermission() { export function getUserPermission() {
return axios.get<UserPermissionResponse>('/infra/permission/user'); return axios.get<UserPermissionResponse>('/infra/user-permission/user');
} }
/** /**
* 获取菜单列表 * 获取菜单列表
*/ */
export function getMenuList() { export function getMenuList() {
return axios.get<Array<MenuQueryResponse>>('/infra/permission/menu'); return axios.get<Array<MenuQueryResponse>>('/infra/user-permission/menu');
} }

View File

@@ -82,7 +82,7 @@ body {
border-radius: 2px; border-radius: 2px;
cursor: pointer; cursor: pointer;
border: 1px solid transparent; border: 1px solid transparent;
transition: background-color 0.1s cubic-bezier(0, 0, 1, 1); transition: background-color 0.15s cubic-bezier(0, 0, 1, 1);
&:hover { &:hover {
background: var(--color-fill-3); background: var(--color-fill-3);
@@ -230,6 +230,26 @@ body {
margin-bottom: 16px; margin-bottom: 16px;
} }
.fs12 {
font-size: 12px;
}
.fs13 {
font-size: 13px;
}
.fs14 {
font-size: 14px;
}
.fs15 {
font-size: 15px;
}
.fs16 {
font-size: 16px;
}
.text-ellipsis { .text-ellipsis {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
@@ -301,13 +321,13 @@ body {
::-webkit-scrollbar-track { ::-webkit-scrollbar-track {
background-color: var(--color-fill-1); background-color: var(--color-fill-1);
border-radius: 8px; border-radius: 4px;
} }
::-webkit-scrollbar-thumb { ::-webkit-scrollbar-thumb {
border: 1px solid transparent; border: 1px solid transparent;
background-clip: padding-box; background-clip: padding-box;
border-radius: 8px; border-radius: 4px;
background-color: var(--color-fill-4); background-color: var(--color-fill-4);
//&:hover { //&:hover {

View File

@@ -15,11 +15,13 @@ body {
--color-bg-panel-tabs: var(--color-bg-panel); --color-bg-panel-tabs: var(--color-bg-panel);
--color-bg-panel-tabs-active: #F9F9F9; --color-bg-panel-tabs-active: #F9F9F9;
--color-bg-panel-icon-1: #F5F5F5; --color-bg-panel-icon-1: #F5F5F5;
--color-bg-panel-bar: #F3F4F5; --color-bg-panel-bar: #F0F0F0;
--color-panel-text-1: var(--color-content-text-1); --color-panel-text-1: var(--color-content-text-1);
--color-panel-text-2: var(--color-content-text-3); --color-panel-text-2: var(--color-content-text-3);
--color-panel-gradient-start: rgba(218, 218, 218, 1); --color-panel-gradient-start: rgba(218, 218, 218, 1);
--color-panel-gradient-end: rgba(218, 218, 218, 0); --color-panel-gradient-end: rgba(218, 218, 218, 0);
--color-button-bg: #E3E3E3;
--color-button-bg-active: var(--color-sidebar-icon-checked);
--search-bg-focus: rgba(234, 234, 234, .75); --search-bg-focus: rgba(234, 234, 234, .75);
--search-bg: rgba(234, 234, 234, .95); --search-bg: rgba(234, 234, 234, .95);
--search-color-text: #0E0E0E; --search-color-text: #0E0E0E;
@@ -52,6 +54,8 @@ body[terminal-theme='dark'] {
--color-panel-text-2: var(--color-content-text-3); --color-panel-text-2: var(--color-content-text-3);
--color-panel-gradient-start: rgba(38, 38, 38, 1); --color-panel-gradient-start: rgba(38, 38, 38, 1);
--color-panel-gradient-end: rgba(38, 38, 38, 0); --color-panel-gradient-end: rgba(38, 38, 38, 0);
--color-button-bg: var(--color-sidebar-icon-bg);
--color-button-bg-active: #484848;
--search-bg: rgba(12, 12, 12, .75); --search-bg: rgba(12, 12, 12, .75);
--search-bg-focus: rgba(12, 12, 12, .95); --search-bg-focus: rgba(12, 12, 12, .95);
--search-color-text: #E0E0E0; --search-color-text: #E0E0E0;
@@ -508,7 +512,7 @@ body[terminal-theme='dark'] .arco-modal-container {
color: var(--color-sidebar-icon); color: var(--color-sidebar-icon);
border-radius: 4px; border-radius: 4px;
border: 1px solid transparent; border: 1px solid transparent;
transition: 0.1s cubic-bezier(0, 0, 1, 1); transition: 0.15s cubic-bezier(0, 0, 1, 1);
cursor: pointer; cursor: pointer;
&:hover { &:hover {

View File

@@ -54,7 +54,7 @@
import type { HostQueryResponse } from '@/api/asset/host'; import type { HostQueryResponse } from '@/api/asset/host';
import { dataColor } from '@/utils'; import { dataColor } from '@/utils';
import { tagColor } from '@/views/asset/host-list/types/const'; import { tagColor } from '@/views/asset/host-list/types/const';
import { useRowSelection } from '@/types/table'; import { useRowSelection } from '@/hooks/table';
import columns from '../types/table.columns'; import columns from '../types/table.columns';
import { computed } from 'vue'; import { computed } from 'vue';

View File

@@ -227,7 +227,7 @@ export default class LogAppender implements ILogAppender {
// 复制 // 复制
copy(): void { copy(): void {
copyText(this.current.terminal.getSelection(), '已复制'); copyText(this.current.terminal.getSelection(), true);
this.focus(); this.focus();
} }

View File

@@ -175,7 +175,7 @@
import { downloadExecCommandLogFile } from '@/api/exec/exec-command-log'; import { downloadExecCommandLogFile } from '@/api/exec/exec-command-log';
import { downloadExecJobLogFile } from '@/api/job/exec-job-log'; import { downloadExecJobLogFile } from '@/api/job/exec-job-log';
import { downloadFile } from '@/utils/file'; import { downloadFile } from '@/utils/file';
import XtermSearchModal from '@/components/xtrem/search-modal/index.vue'; import XtermSearchModal from '@/components/xterm/search-modal/index.vue';
import '@xterm/xterm/css/xterm.css'; import '@xterm/xterm/css/xterm.css';
const props = defineProps<{ const props = defineProps<{

View File

@@ -59,7 +59,7 @@
</template> </template>
<!-- 模板命令 --> <!-- 模板命令 -->
<template #command="{ record }"> <template #command="{ record }">
<span class="copy-left" @click="copy(record.command, '已复制')"> <span class="copy-left" @click="copy(record.command, true)">
<icon-copy /> <icon-copy />
</span> </span>
<span :title="record.command">{{ record.command }}</span> <span :title="record.command">{{ record.command }}</span>
@@ -89,7 +89,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { ExecTemplateQueryRequest, ExecTemplateQueryResponse } from '@/api/exec/exec-template'; import type { ExecTemplateQueryRequest, ExecTemplateQueryResponse } from '@/api/exec/exec-template';
import { reactive, ref } from 'vue'; import { reactive, ref } from 'vue';
import { usePagination } from '@/types/table'; import { useTablePagination } from '@/hooks/table';
import useVisible from '@/hooks/visible'; import useVisible from '@/hooks/visible';
import useLoading from '@/hooks/loading'; import useLoading from '@/hooks/loading';
import { copy } from '@/hooks/copy'; import { copy } from '@/hooks/copy';
@@ -100,7 +100,7 @@
const { visible, setVisible } = useVisible(); const { visible, setVisible } = useVisible();
const { loading, setLoading } = useLoading(); const { loading, setLoading } = useLoading();
const pagination = usePagination(); const pagination = useTablePagination();
const tableRenderData = ref<ExecTemplateQueryResponse[]>([]); const tableRenderData = ref<ExecTemplateQueryResponse[]>([]);
const formModel = reactive<ExecTemplateQueryRequest>({ const formModel = reactive<ExecTemplateQueryRequest>({

View File

@@ -9,7 +9,7 @@
<script lang="ts"> <script lang="ts">
export default { export default {
name: 'pathBookmarkGroupSelect' name: 'pathBookmarkGroupSelector'
}; };
</script> </script>

View File

@@ -9,7 +9,7 @@
<script lang="ts"> <script lang="ts">
export default { export default {
name: 'commandSnippetGroupSelect' name: 'commandSnippetGroupSelector'
}; };
</script> </script>

View File

@@ -104,7 +104,7 @@
<template #append> <template #append>
<span class="allow-click span-blue" <span class="allow-click span-blue"
title="点击复制" title="点击复制"
@click="copy(inputValues.cron,'已复制')"> @click="copy(inputValues.cron, true)">
<icon-copy /> <icon-copy />
</span> </span>
</template> </template>

View File

@@ -40,7 +40,7 @@
<template #beforeValue="{ record }"> <template #beforeValue="{ record }">
<span class="copy-left" <span class="copy-left"
title="复制" title="复制"
@click="copy(record.beforeValue, '已复制')"> @click="copy(record.beforeValue, true)">
<icon-copy /> <icon-copy />
</span> </span>
<span>{{ record.beforeValue }}</span> <span>{{ record.beforeValue }}</span>
@@ -49,7 +49,7 @@
<template #afterValue="{ record }"> <template #afterValue="{ record }">
<span class="copy-left" <span class="copy-left"
title="复制" title="复制"
@click="copy(record.afterValue, '已复制')"> @click="copy(record.afterValue, true)">
<icon-copy /> <icon-copy />
</span> </span>
<span>{{ record.afterValue }}</span> <span>{{ record.afterValue }}</span>
@@ -85,7 +85,7 @@
import useLoading from '@/hooks/loading'; import useLoading from '@/hooks/loading';
import useVisible from '@/hooks/visible'; import useVisible from '@/hooks/visible';
import { getHistoryValuePage } from '@/api/meta/history-value'; import { getHistoryValuePage } from '@/api/meta/history-value';
import { usePagination } from '@/types/table'; import { useTablePagination } from '@/hooks/table';
import { copy } from '@/hooks/copy'; import { copy } from '@/hooks/copy';
import columns from './table.columns'; import columns from './table.columns';
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
@@ -96,7 +96,7 @@
}>(); }>();
const emits = defineEmits(['updated']); const emits = defineEmits(['updated']);
const pagination = usePagination(); const pagination = useTablePagination();
const { visible, setVisible } = useVisible(); const { visible, setVisible } = useVisible();
const { loading, setLoading } = useLoading(); const { loading, setLoading } = useLoading();

View File

@@ -129,6 +129,7 @@
: undefined; : undefined;
// 查询数据 // 查询数据
const { data } = await getSystemMessageList({ const { data } = await getSystemMessageList({
page: 1,
limit: messageLimit, limit: messageLimit,
classify: currentClassify.value, classify: currentClassify.value,
queryUnread: queryUnread.value, queryUnread: queryUnread.value,

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