Compare commits

..

34 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
李佳航
1b2753a2b7 Merge pull request #44 from dromara/dev
Dev
2024-07-26 11:11:47 +08:00
lijiahang
29b44b8b77 🚨 修复 ts 构建报错. 2024-07-26 10:56:36 +08:00
lijiahang
7290b1364c 🔖 升级版本. 2024-07-26 10:25:38 +08:00
lijiahang
3851ead8bb 💄 修改滚动条样式. 2024-07-26 10:18:39 +08:00
lijiahang
305312cc26 🐛 修复文件上传列表显示错误. 2024-07-25 13:46:48 +08:00
188 changed files with 2506 additions and 1569 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 ###
.vscode/
.env

View File

@@ -19,12 +19,12 @@
<a target="_blank"
style="text-decoration: none !important;"
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 target="_blank"
style="text-decoration: none !important;"
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 target="_blank"
style="text-decoration: none !important;"
@@ -53,7 +53,7 @@
* 🔗 演示地址: http://101.43.254.243:1081/
* 🔏 演示账号: admin/admin
* ⭐ 体验后可以点一下 `star` 这对我很重要! [github](https://github.com/dromara/orion-visor) [gitee](https://gitee.com/dromara/orion-visor) [gitcode](https://gitcode.com/qq_41011894/orion-visor/overview)
* ⭐ 体验后可以点一下 `star` 这对我很重要! [github](https://github.com/dromara/orion-visor) [gitee](https://gitee.com/dromara/orion-visor) [gitcode](https://gitcode.com/dromara/orion-visor/overview)
* 🌈 如果本项目对你有帮助请帮忙推广一下 让更多的人知道此项目!
* 🎭 演示环境部分功能不可用, 完整功能请本地部署!
* 📛 演示环境请不要随便删除数据!
@@ -63,7 +63,7 @@
```bash
# clone
git clone https://github.com/dromara/orion-visor
git clone --depth=1 https://github.com/dromara/orion-visor
cd orion-visor
# 启动
docker compose up -d

View File

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

View File

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

View File

@@ -2,6 +2,6 @@
docker compose down
# demo 启动
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
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
version=2.1.0
version=2.1.4
cp -r ../../sql ./sql
docker build -t orion-visor-mysql:${version} .
rm -rf ./sql
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
version=2.1.0
version=2.1.4
docker build -t 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
version=2.1.0
version=2.1.4
mv ../../orion-visor-launch/target/orion-visor-launch.jar ./orion-visor-launch.jar
mv ../../orion-visor-ui/dist ./dist
docker build -t orion-visor-service:${version} .
rm -rf ./orion-visor-launch.jar
rm -rf ./dist
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 {
proxy_pass http://localhost:9200/orion-visor/api;
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-Proto $scheme;
}

View File

@@ -19,12 +19,12 @@
<a target="_blank"
style="text-decoration: none !important;"
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 target="_blank"
style="text-decoration: none !important;"
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 target="_blank"
style="text-decoration: none !important;"

View File

@@ -21,22 +21,35 @@ Dashboard 修改)
```shell
# github
git clone https://github.com/lijiahangmax/orion-visor
git clone --depth=1 https://github.com/lijiahangmax/orion-visor
# gitee
git clone https://gitee.com/lijiahangmax/orion-visor
git clone --depth=1 https://gitee.com/lijiahangmax/orion-visor
```
### 构建镜像
### 拉取镜像
```
# 进入仓库目录
cd orion-visor
# 修改 docker-compose.yml (建议修改)
# MYSQL_USER mysql 用户名
# MYSQL_PASSWORD mysql 用户密码
# MYSQL_ROOT_PASSWORD mysql root 密码
# REDIS_PASSWORD redis 密码
# SECRET_KEY 加密密钥
# 创建名为 .env 的 .env.example 副本
cp .env.example .env
# 将其中的值删除以保持默认或将其修改为你喜欢的值
# SERVICE_PORT 你希望服务监听的端口
# VOLUME_BASE 你希望数据持久化保存的目录, 如果不提前创建将以 docker 进程宿主身份创建(通常是 root)
# 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
```
### 修改加密方式
### 修改 MySQL 账户的加密方式
```
访问 adminer: http://localhost:8081

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,8 @@
package com.orion.visor.framework.common.meta;
import com.alibaba.ttl.TransmittableThreadLocal;
import com.orion.lang.id.UUIds;
import org.slf4j.MDC;
/**
* traceId 持有者
@@ -23,16 +25,74 @@ public class TraceIdHolder {
*/
private static final ThreadLocal<String> HOLDER = new TransmittableThreadLocal<>();
/**
* 获取 traceId
*
* @return traceId
*/
public static String 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() {
// 移除应用上下文
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.baomidou.mybatisplus.generator.config.po.TableField;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.orion.lang.utils.Exceptions;
import com.orion.lang.utils.Strings;
import com.orion.visor.framework.common.constant.Const;
import com.orion.visor.framework.common.constant.FieldConst;
@@ -47,7 +48,7 @@ public class DictParser {
.stream()
.filter(s -> variable.equals(s.getName()) || variable.equals(s.getPropertyName()))
.findFirst()
.orElseThrow(() -> new RuntimeException("未查询到字典映射字段 " + variable));
.orElseThrow(() -> Exceptions.runtime("未查询到字典映射字段 " + variable));
// 设置字段名称
if (meta.getField() == null) {
meta.setField(Strings.firstUpper(tableField.getPropertyName()));

View File

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

View File

@@ -94,8 +94,8 @@
:data="tableRenderData"
:pagination="pagination"
:bordered="false"
@page-change="(page) => fetchTableData(page, pagination.pageSize)"
@page-size-change="(size) => fetchTableData(1, size)">
@page-change="(page: number) => fetchTableData(page, pagination.pageSize)"
@page-size-change="(size: number) => fetchTableData(1, size)">
#foreach($field in ${table.fields})
#if(${dictMap.containsKey(${field.propertyName})})
<!-- $field.comment -->
@@ -151,9 +151,9 @@
import {} from '../types/const';
#end
#if($vue.enableRowSelection)
import { usePagination, useRowSelection } from '@/types/table';
import { useTablePagination, useRowSelection } from '@/hooks/table';
#else
import { usePagination } from '@/types/table';
import { useTablePagination } from '@/hooks/table';
#end
#if($dictMap.entrySet().size() > 0)
import { useDictStore } from '@/store';
@@ -161,7 +161,7 @@
const emits = defineEmits(['openAdd', 'openUpdate']);
const pagination = usePagination();
const pagination = useTablePagination();
#if($vue.enableRowSelection)
const rowSelection = useRowSelection();
#end

View File

@@ -98,6 +98,13 @@ public class SecurityUtils {
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) {
// 创建 authentication
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);
}

View File

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

View File

@@ -167,6 +167,8 @@ app:
allow-refresh: true
# 凭证续签最大次数
max-refresh-count: 3
# 登录失败发送站内信阈值
login-failed-send-threshold: 3
# 登录失败锁定次数
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.interceptor.ExecLogTailInterceptor;
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.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
@@ -28,6 +29,9 @@ public class AssetWebSocketConfiguration implements WebSocketConfigurer {
@Resource
private TerminalAccessInterceptor terminalAccessInterceptor;
@Resource
private TerminalTransferInterceptor terminalTransferInterceptor;
@Resource
private ExecLogTailInterceptor execLogTailInterceptor;
@@ -42,13 +46,13 @@ public class AssetWebSocketConfiguration implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// 终端
// 终端会话
registry.addHandler(terminalMessageDispatcher, prefix + "/host/terminal/{accessToken}")
.addInterceptors(terminalAccessInterceptor)
.setAllowedOrigins("*");
// 文件传输
registry.addHandler(transferMessageDispatcher, prefix + "/host/transfer/{accessToken}")
.addInterceptors(terminalAccessInterceptor)
registry.addHandler(transferMessageDispatcher, prefix + "/host/transfer/{transferToken}")
.addInterceptors(terminalTransferInterceptor)
.setAllowedOrigins("*");
// 执行日志
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.log.core.annotation.IgnoreLog;
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.module.asset.define.operator.ExecJobOperatorType;
import com.orion.visor.module.asset.entity.request.exec.*;
@@ -39,6 +40,7 @@ public class ExecJobController {
@Resource
private ExecJobService execJobService;
@DemoDisableApi
@OperatorLog(ExecJobOperatorType.CREATE)
@PostMapping("/create")
@Operation(summary = "创建计划任务")
@@ -47,6 +49,7 @@ public class ExecJobController {
return execJobService.createExecJob(request);
}
@DemoDisableApi
@OperatorLog(ExecJobOperatorType.UPDATE)
@PutMapping("/update")
@Operation(summary = "更新计划任务")
@@ -55,6 +58,7 @@ public class ExecJobController {
return execJobService.updateExecJobById(request);
}
@DemoDisableApi
@OperatorLog(ExecJobOperatorType.UPDATE_STATUS)
@PutMapping("/update-status")
@Operation(summary = "更新计划任务状态")
@@ -88,6 +92,7 @@ public class ExecJobController {
return execJobService.getExecJobPage(request);
}
@DemoDisableApi
@OperatorLog(ExecJobOperatorType.DELETE)
@DeleteMapping("/delete")
@Operation(summary = "删除计划任务")
@@ -97,6 +102,7 @@ public class ExecJobController {
return execJobService.deleteExecJobById(id);
}
@DemoDisableApi
@OperatorLog(ExecJobOperatorType.DELETE)
@DeleteMapping("/batch-delete")
@Operation(summary = "批量删除计划任务")

View File

@@ -49,5 +49,12 @@ public class HostTerminalController {
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.struct.RedisCacheStruct;
import com.orion.visor.module.asset.entity.dto.HostTerminalAccessDTO;
import com.orion.visor.module.asset.entity.dto.HostTerminalTransferDTO;
import java.util.concurrent.TimeUnit;
@@ -24,4 +25,12 @@ public interface HostTerminalCacheKeyDefine {
.timeout(3, TimeUnit.MINUTES)
.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")
private String name;
@Schema(description = "触发前缀")
@TableField("prefix")
private String prefix;
@Schema(description = "代码片段")
@TableField("command")
private String command;

View File

@@ -34,9 +34,6 @@ public class CommandSnippetCacheDTO implements LongCacheIdModel, Serializable {
@Schema(description = "名称")
private String name;
@Schema(description = "触发前缀")
private String prefix;
@Schema(description = "代码片段")
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 = "名称")
private String name;
@Schema(description = "触发前缀")
private String prefix;
@Schema(description = "代码片段")
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.Strings;
import com.orion.net.host.SessionHolder;
import com.orion.net.host.SessionLogger;
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.utils.CryptoUtils;
import com.orion.visor.module.asset.entity.dto.HostTerminalConnectDTO;
@@ -40,7 +42,10 @@ public class SessionStores {
CURRENT_ADDRESS.set(address);
// 创建会话
SessionHolder sessionHolder = SessionHolder.create();
sessionHolder.setLogger(SessionLogger.INFO);
SessionStore session = createSessionStore(conn, sessionHolder);
// 设置版本
session.getSession().setClientVersion("SSH-2.0-ORION_VISOR_V" + AppConst.VERSION);
// 连接
session.connect();
log.info("SessionStores-open-success hostId: {}, address: {}, username: {}", hostId, address, username);

View File

@@ -131,9 +131,9 @@ public enum InputTypeEnum {
* SFTP 修改文件权限
*/
SFTP_CHMOD("cm",
SftpChangeModHandler.class,
SftpChangeModeHandler.class,
new String[]{"type", "sessionId", "path", "mod"},
SftpChangeModRequest.class,
SftpChangeModeRequest.class,
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.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.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.session.ISftpSession;
import lombok.extern.slf4j.Slf4j;
@@ -23,24 +23,24 @@ import java.util.Map;
*/
@Slf4j
@Component
public class SftpChangeModHandler extends AbstractTerminalHandler<SftpChangeModRequest> {
public class SftpChangeModeHandler extends AbstractTerminalHandler<SftpChangeModeRequest> {
@Override
public void handle(WebSocketSession channel, SftpChangeModRequest payload) {
public void handle(WebSocketSession channel, SftpChangeModeRequest payload) {
long startTime = System.currentTimeMillis();
// 获取会话
String sessionId = payload.getSessionId();
ISftpSession session = hostTerminalManager.getSession(channel.getId(), sessionId);
String path = payload.getPath();
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;
// 修改权限
try {
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) {
log.error("SftpChangeModHandler-handle error sessionId: {}", sessionId, e);
log.error("SftpChangeModeHandler-handle error sessionId: {}", sessionId, e);
ex = e;
}
// 返回

View File

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

View File

@@ -20,7 +20,7 @@ import lombok.experimental.SuperBuilder;
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class SftpChangeModRequest extends SftpBaseRequest {
public class SftpChangeModeRequest extends SftpBaseRequest {
/**
* 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.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.utils.SftpUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.socket.WebSocketSession;
@@ -84,9 +83,7 @@ public class SftpSession extends TerminalSession implements ISftpSession {
public void move(String source, String target) {
source = Valid.checkNormalize(source);
// 移动
SftpUtils.move(executor, source, target);
// FIXME kit
// executor.move(source, target);
executor.move(source, target);
}
@Override

View File

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

View File

@@ -17,7 +17,7 @@ import javax.annotation.Resource;
import java.util.Map;
/**
* 终端拦截器
* 终端访问拦截器
*
* @author Jiahang Li
* @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 {
// 获取 accessToken
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);
if (access == null) {
log.error("TerminalInterceptor-beforeHandshake absent accessToken: {}", accessToken);
log.error("TerminalAccessInterceptor-beforeHandshake absent accessToken: {}", accessToken);
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.dto.HostTerminalAccessDTO;
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 java.util.List;
@@ -30,6 +31,13 @@ public interface HostTerminalService {
*/
String getTerminalAccessToken();
/**
* 获取主机终端传输 transferToken
*
* @return transferToken
*/
String getTerminalTransferToken();
/**
* 通过 accessToken 获取主机终端访问信息
*
@@ -38,6 +46,14 @@ public interface HostTerminalService {
*/
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.dto.HostTerminalAccessDTO;
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.enums.*;
import com.orion.visor.module.asset.handler.host.config.model.HostSshConfigModel;
@@ -74,6 +75,10 @@ public class HostTerminalServiceImpl implements HostTerminalService {
@Override
public List<HostTerminalThemeVO> getTerminalThemes() {
// if (true) {
// String arr = "";
// return JSON.parseArray(arr, HostTerminalThemeVO.class);
// }
List<JSONObject> themes = dictValueApi.getDictValue(THEME_DICT_KEY);
return themes.stream()
.map(s -> HostTerminalThemeVO.builder()
@@ -99,6 +104,21 @@ public class HostTerminalServiceImpl implements HostTerminalService {
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
public HostTerminalAccessDTO getAccessInfoByToken(String token) {
// 获取缓存
@@ -111,6 +131,18 @@ public class HostTerminalServiceImpl implements HostTerminalService {
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
public HostTerminalConnectDTO getTerminalConnectInfo(Long 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.orion.lang.utils.Booleans;
import com.orion.lang.utils.Exceptions;
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.SftpFile;
import com.orion.visor.module.asset.define.config.AppSftpConfig;
@@ -41,49 +39,8 @@ public class SftpUtils {
SftpFileBackupParams backupParams = new SftpFileBackupParams(file.getName(), System.currentTimeMillis());
String target = Strings.format(config.getBackupFileName(), JSON.parseObject(JSON.toJSONString(backupParams)));
// 移动
// FIXME kit
move(executor, path, target);
// executor.move(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="group_id" property="groupId"/>
<result column="name" property="name"/>
<result column="prefix" property="prefix"/>
<result column="command" property="command"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
@@ -19,7 +18,7 @@
<!-- 通用查询结果列 -->
<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>
</mapper>

View File

@@ -28,13 +28,15 @@ public class TerminalThemeGenerator {
List<File> files = Files1.listFiles("D:\\idea-project\\iTerm2-Color-Schemes\\vhs");
// 过滤的 theme
List<String> schemaFilter = Lists.of(
"Dracula", "Atom",
"catppuccin-mocha", "MaterialDesignColors",
"catppuccin-macchiato", "OneHalfDark",
"Apple System Colors", "Builtin Tango Light",
"Duotone Dark", "BlulocoLight",
"Chester", "CLRS",
"Calamity", "Tomorrow"
"Dracula", "Builtin Tango Light",
"Atom", "AtomOneLight",
"OneHalfDark", "OneHalfLight",
"Apple System Colors", "Tomorrow",
"catppuccin-mocha", "catppuccin-latte",
"catppuccin-macchiato", "BlulocoLight",
"catppuccin-frappe", "MaterialDesignColors",
"GitHub Dark", "Github",
"DimmedMonokai", "Duotone Dark"
);
// 颜色大写
ValueFilter colorFilter = (Object object, String name, Object value) -> {
@@ -60,7 +62,7 @@ public class TerminalThemeGenerator {
theme.setDark(Colors.isDarkColor(background));
theme.setSchema(JSON.parseObject(JSON.toJSONString(schema), TerminalThemeSchema.class));
return theme;
}).collect(Collectors.toList());
}).skip(0).limit(50).collect(Collectors.toList());
// 排序
if (!Lists.isEmpty(schemaFilter)) {
arr.sort(Comparator.comparing(s -> schemaFilter.indexOf(s.getName())));
@@ -70,11 +72,12 @@ public class TerminalThemeGenerator {
for (TerminalTheme theme : arr) {
System.out.println("name: " + theme.name);
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();
}
// String json = JSON.toJSONString(arr, colorFilter);
// System.out.println("\n" + json);
String json = JSON.toJSONString(arr, colorFilter);
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 {
/**
* 通过 id 查询用户名
*
* @param id id
* @return username
*/
String getUsernameById(Long id);
/**
* 通过 id 查询花名
*
* @param id id
* @return nickname
*/
String getNicknameById(Long id);
/**
* 通过 id 查询用户
*
@@ -19,12 +35,4 @@ public interface SystemUserApi {
*/
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.entity.domain.SystemUserDO;
import com.orion.visor.module.infra.entity.dto.user.SystemUserDTO;
import com.orion.visor.module.infra.service.SystemUserService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@@ -23,8 +22,25 @@ public class SystemUserApiImpl implements SystemUserApi {
@Resource
private SystemUserDAO systemUserDAO;
@Resource
private SystemUserService systemUserService;
@Override
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
public SystemUserDTO getUserById(Long id) {
@@ -35,9 +51,4 @@ public class SystemUserApiImpl implements SystemUserApi {
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;
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.enums.IgnoreLogMode;
import com.orion.visor.framework.web.core.annotation.RestWrapper;
@@ -38,7 +39,7 @@ public class SystemMessageController {
@IgnoreLog(IgnoreLogMode.ALL)
@PostMapping("/list")
@Operation(summary = "查询系统消息列表")
public List<SystemMessageVO> getSystemMessageList(@RequestBody SystemMessageQueryRequest request) {
public List<SystemMessageVO> getSystemMessageList(@Validated(Page.class) @RequestBody SystemMessageQueryRequest 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.module.infra.entity.vo.SystemMenuVO;
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.tags.Tag;
import lombok.extern.slf4j.Slf4j;
@@ -26,22 +26,23 @@ import java.util.List;
* @version 1.0.0
* @since 2023/7/14 11:20
*/
@Tag(name = "infra - 权限服务")
@Tag(name = "infra - 用户权限服务")
@Slf4j
@Validated
@RestWrapper
@RestController
@RequestMapping("/infra/permission")
public class PermissionController {
@RequestMapping("/infra/user-permission")
@SuppressWarnings({"ELValidationInJSP", "SpringElInspection"})
public class UserPermissionController {
@Resource
private PermissionService permissionService;
private UserPermissionService userPermissionService;
@PutMapping("/refresh-cache")
@Operation(summary = "刷新角色权限缓存")
@PreAuthorize("@ss.hasPermission('infra:system-menu:management:refresh-cache')")
public Boolean refreshCache() {
permissionService.initPermissionCache();
userPermissionService.initPermissionCache();
return true;
}
@@ -49,14 +50,14 @@ public class PermissionController {
@GetMapping("/menu")
@Operation(summary = "获取用户菜单")
public List<SystemMenuVO> getUserMenuList() {
return permissionService.getUserMenuList();
return userPermissionService.getUserMenuList();
}
@IgnoreLog(IgnoreLogMode.RET)
@GetMapping("/user")
@Operation(summary = "获取用户权限聚合信息")
public UserPermissionVO getUserPermission() {
return permissionService.getUserPermission();
return userPermissionService.getUserPermission();
}
}

View File

@@ -34,11 +34,22 @@ public interface SystemRoleDAO extends IMapper<SystemRoleDO> {
/**
* 通过 userId 和 roleCode 查询 roleId (检查用户是否包含某个角色)
*
* @param userId userId
* @param code code
* @param userId userId
* @param codeList codeList
* @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 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;
import com.orion.visor.framework.common.entity.PageRequest;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.*;
/**
* 系统消息 查询请求对象
@@ -17,11 +15,9 @@ import lombok.NoArgsConstructor;
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@Schema(name = "SystemMessageQueryRequest", description = "系统消息 查询请求对象")
public class SystemMessageQueryRequest {
@Schema(description = "大小")
private Integer limit;
public class SystemMessageQueryRequest extends PageRequest {
@Schema(description = "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.UserStatusEnum;
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 javax.annotation.Resource;
@@ -27,30 +27,30 @@ public class SecurityFrameworkServiceImpl implements SecurityFrameworkService {
private AuthenticationService authenticationService;
@Resource
private PermissionService permissionService;
private UserPermissionService userPermissionService;
@Override
public boolean hasPermission(String permission) {
// 检查是否有权限
return permissionService.hasPermission(permission);
return userPermissionService.hasPermission(permission);
}
@Override
public boolean hasAnyPermission(String... permissions) {
// 检查是否有权限
return permissionService.hasAnyPermission(permissions);
return userPermissionService.hasAnyPermission(permissions);
}
@Override
public boolean hasRole(String role) {
// 检查是否有角色
return permissionService.hasRole(role);
return userPermissionService.hasRole(role);
}
@Override
public boolean hasAnyRole(String... roles) {
// 检查是否有角色
return permissionService.hasAnyRole(roles);
return userPermissionService.hasAnyRole(roles);
}
@Override

View File

@@ -314,6 +314,11 @@ public class TerminalPreferenceModel implements GenericsDataModel {
*/
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("selectAll", true, true, false, "KeyA", 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("fontSizePlus", true, false, true, "Equal", 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()
.commandInput(false)
.connectStatus(true)
.toTop(true)
.toBottom(true)
.toTop(false)
.toBottom(false)
.selectAll(false)
.search(true)
.copy(true)
@@ -112,7 +113,10 @@ public class TerminalPreferenceStrategy extends AbstractGenericsDataStrategy<Ter
.fontSizePlus(false)
.fontSizeSubtract(false)
.commandEditor(true)
.fontSizePlus(false)
.fontSizeSubtract(false)
.openSftp(true)
.uploadFile(true)
.clear(true)
.disconnect(false)
.build()
@@ -123,7 +127,7 @@ public class TerminalPreferenceStrategy extends AbstractGenericsDataStrategy<Ter
.theme(new JSONObject())
.displaySetting(JSONObject.parseObject(defaultDisplaySetting))
.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))
.pluginsSetting(JSONObject.parseObject(defaultPluginsSetting))
.sessionSetting(JSONObject.parseObject(defaultSessionSetting))

View File

@@ -1,6 +1,7 @@
package com.orion.visor.module.infra.service;
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.request.user.UserLoginRequest;
import com.orion.visor.module.infra.entity.vo.UserLoginVO;
@@ -48,4 +49,30 @@ public interface AuthenticationService {
*/
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;
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
* @since 2024/8/19 15:29
*/
public interface PermissionService {
/**
* 获取 角色缓存
* 检测用户是否是为管理员
*
* @return cache
* @param userId userId
* @return 是否为管理员
*/
Map<Long, SystemRoleDO> getRoleCache();
boolean isAdminUser(Long userId);
/**
* 获取 菜单缓存 以作角色权限直接引用
* 检查当前用户是否含有此角色
*
* @return cache
*/
List<SystemMenuCacheDTO> getMenuCache();
/**
* 获取 角色菜单关联
*
* @return cache
*/
Map<Long, List<SystemMenuCacheDTO>> getRoleMenuCache();
/**
* 初始化权限缓存
*/
void initPermissionCache();
/**
* 检查当前用户是否含有此角色 (有效性判断)
*
* @param role role
* @param userId userId
* @param role role
* @return 是否包含
*/
boolean hasRole(String role);
boolean hasRole(Long userId, String role);
/**
* 检查当前用户是否含有任意角色 (有效性判断)
* 检查当前用户是否含有任意角色
*
* @param roles roles
* @param userId userId
* @param roles roles
* @return 是否包含
*/
boolean hasAnyRole(String... roles);
boolean hasAnyRole(Long userId, List<String> roles);
/**
* 检查当前用户是否含有此权限 (有效性判断)
* 检查当前用户是否含有此权限
*
* @param userId userId
* @param permission permission
* @return 是否包含
*/
boolean hasPermission(String permission);
boolean hasPermission(Long userId, String permission);
/**
* 检查当前用户是否含任意权限 (有效性判断)
* 检查当前用户是否含任意权限
*
* @param userId userId
* @param permissions permissions
* @return 是否包含
*/
boolean hasAnyPermission(String... permissions);
/**
* 获取用户菜单
*
* @return 菜单
*/
List<SystemMenuVO> getUserMenuList();
/**
* 获取用户权限
*
* @return 权限信息
*/
UserPermissionVO getUserPermission();
boolean hasAnyPermission(Long userId, List<String> permissions);
}

View File

@@ -92,12 +92,4 @@ public interface SystemUserService {
*/
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;
import com.alibaba.fastjson.JSON;
import com.orion.lang.annotation.Keep;
import com.orion.lang.define.wrapper.Pair;
import com.orion.lang.utils.Exceptions;
import com.orion.lang.utils.Strings;
import com.orion.lang.utils.collect.Lists;
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.common.annotation.Keep;
import com.orion.visor.framework.common.constant.Const;
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.UserRole;
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.RedisUtils;
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.dao.SystemUserDAO;
import com.orion.visor.module.infra.dao.SystemUserRoleDAO;
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.message.SystemUserMessageDefine;
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.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.vo.UserLoginVO;
import com.orion.visor.module.infra.enums.LoginTokenStatusEnum;
import com.orion.visor.module.infra.enums.UserStatusEnum;
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 org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@@ -64,7 +67,10 @@ public class AuthenticationServiceImpl implements AuthenticationService {
private SystemUserRoleDAO systemUserRoleDAO;
@Resource
private PermissionService permissionService;
private UserPermissionService userPermissionService;
@Resource
private SystemMessageApi systemMessageApi;
@Keep
@Resource
@@ -72,39 +78,35 @@ public class AuthenticationServiceImpl implements AuthenticationService {
@Override
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 location = IpUtils.getLocation(remoteAddr);
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();
// 不允许多端登录
if (!appAuthenticationConfig.getAllowMultiDevice()) {
// 无效化其他缓存
this.invalidOtherDeviceToken(user.getId(), current, remoteAddr, location, userAgent);
this.invalidOtherDeviceToken(id, current, remoteAddr, location, userAgent);
}
// 生成 loginToken
String token = this.generatorLoginToken(user, current, remoteAddr, location, userAgent);
@@ -189,62 +191,83 @@ public class AuthenticationServiceImpl implements AuthenticationService {
return refresh;
}
/**
* 获取 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;
}
}
/**
* 登录预检查
*
* @param request request
*/
private void preCheckLogin(UserLoginRequest request) {
@Override
public SystemUserDO preCheckLogin(String username, String password) {
// 检查密码长度是否正确 MD5 长度为 32
if (request.getPassword().length() != Const.MD5_LEN) {
if (password.length() != Const.MD5_LEN) {
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);
if (failedCount != null
&& Integer.parseInt(failedCount) >= appAuthenticationConfig.getLoginFailedLockCount()) {
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 user user
* @return 是否正确
* @param passRight passRight
* @param user user
* @param remoteAddr remoteAddr
* @param location location
*/
@SuppressWarnings("ALL")
private boolean checkPassword(UserLoginRequest request, SystemUserDO user) {
// 密码正确
if (user.getPassword().equals(Signatures.md5(request.getPassword()))) {
return true;
private void sendLoginFailedErrorMessage(boolean passRight, SystemUserDO user,
String remoteAddr, String location) {
if (passRight) {
return;
}
// 刷新登录失败缓存
String failedCountKey = UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(request.getUsername());
redisTemplate.opsForValue().increment(failedCountKey);
RedisUtils.setExpire(failedCountKey, appAuthenticationConfig.getLoginFailedLockTime(), TimeUnit.MINUTES);
return false;
String failedCountKey = UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(user.getUsername());
String failedCountStr = redisTemplate.opsForValue().get(failedCountKey);
if (failedCountStr == null || !Strings.isInteger(failedCountStr)) {
return;
}
// 直接用相等 因为只触发一次
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));
}
/**
* 获取 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();
// 查询用户角色
List<Long> roleIds = systemUserRoleDAO.selectRoleIdByUserId(id);
List<UserRole> roleList = permissionService.getRoleCache()
List<UserRole> roleList = userPermissionService.getRoleCache()
.values()
.stream()
.filter(s -> roleIds.contains(s.getId()))

View File

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

View File

@@ -1,305 +1,67 @@
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.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 javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.Future;
import java.util.function.Function;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 权限服务
* 权限 服务实现类
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/7/16 1:05
* @since 2024/8/19 15:29
*/
@Slf4j
@Service
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
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);
public boolean isAdminUser(Long userId) {
return this.hasAnyRole(userId, Lists.of(RoleDefine.ADMIN_CODE));
}
@Override
public boolean hasRole(String role) {
// 获取用户角色
Map<Long, String> roles = this.getUserEnabledRoles();
public boolean hasRole(Long userId, String role) {
return this.hasAnyRole(userId, Lists.of(role));
}
@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()) {
return false;
}
// 检查是否为超级管理员或包含此角色
return RoleDefine.containsAdmin(roles.values()) || roles.containsValue(role);
}
@Override
public boolean hasAnyRole(String... roles) {
if (Arrays1.isEmpty(roles)) {
// 判断是否为 admin
boolean isAdmin = roles.stream().anyMatch(s -> s.getCode().equals(RoleDefine.ADMIN_CODE));
if (isAdmin) {
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)
List<Long> roleIdList = roles.stream()
.map(SystemRoleDO::getId)
.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;
return !systemRoleDAO.getPermissionByRoleIdAndPermission(roleIdList, permissions).isEmpty();
}
}

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

View File

@@ -74,9 +74,9 @@ public class SystemMessageServiceImpl implements SystemMessageService {
.eq(SystemMessageDO::getClassify, request.getClassify())
.lt(SystemMessageDO::getId, request.getMaxId())
.eq(SystemMessageDO::getStatus, status)
.last(Const.LIMIT + Const.SPACE + request.getLimit())
.orderByDesc(SystemMessageDO::getId)
.then()
.limit(request.getLimit())
.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.dto.SystemMenuCacheDTO;
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 lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -49,7 +49,7 @@ public class SystemRoleMenuServiceImpl implements SystemRoleMenuService {
private SystemRoleMenuDAO systemRoleMenuDAO;
@Resource
private PermissionService permissionService;
private UserPermissionService userPermissionService;
@Override
@Transactional(rollbackFor = Exception.class)
@@ -104,7 +104,7 @@ public class SystemRoleMenuServiceImpl implements SystemRoleMenuService {
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<>());
roleCache.clear();
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.enums.RoleStatusEnum;
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.SystemUserRoleService;
import lombok.extern.slf4j.Slf4j;
@@ -51,7 +51,7 @@ public class SystemRoleServiceImpl implements SystemRoleService {
private SystemRoleMenuDAO systemRoleMenuDAO;
@Resource
private PermissionService permissionService;
private UserPermissionService userPermissionService;
@Resource
private SystemUserRoleService systemUserRoleService;
@@ -72,7 +72,7 @@ public class SystemRoleServiceImpl implements SystemRoleService {
int effect = systemRoleDAO.insert(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();
}
@@ -92,7 +92,7 @@ public class SystemRoleServiceImpl implements SystemRoleService {
int effect = systemRoleDAO.updateById(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());
return effect;
}
@@ -117,7 +117,7 @@ public class SystemRoleServiceImpl implements SystemRoleService {
int effect = systemRoleDAO.updateById(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);
// 删除数据权限缓存
dataPermissionService.clearRoleCache(id);
@@ -180,9 +180,9 @@ public class SystemRoleServiceImpl implements SystemRoleService {
// 删除角色菜单关联
effect += systemRoleMenuDAO.deleteByRoleId(id);
// 删除角色缓存
permissionService.getRoleCache().remove(id);
userPermissionService.getRoleCache().remove(id);
// 删除菜单缓存
permissionService.getRoleMenuCache().remove(id);
userPermissionService.getRoleMenuCache().remove(id);
// 删除用户缓存中的角色
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.SystemUserDAO;
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.UserCacheKeyDefine;
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",
"description": "凭证续签最大次数."
},
{
"name": "app.authentication.loginFailedSendThreshold",
"type": "java.lang.Integer",
"description": "登录失败发送站内信阈值."
},
{
"name": "app.authentication.loginFailedLockCount",
"type": "java.lang.Integer",

View File

@@ -24,12 +24,42 @@
SELECT role_id
FROM system_user_role
WHERE user_id = #{userId}
AND deleted = 0
AND role_id IN (SELECT id FROM system_role WHERE CODE = #{code} AND deleted = 0) LIMIT 1
AND deleted = 0
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 id="selectRoleByUserId" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
SELECT
<include refid="Base_Column_List"/>
FROM system_role
WHERE 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_WS_BASE_URL= 'ws://127.0.0.1:9200/orion-visor/keep-alive'
VITE_APP_VERSION= '2.1.0'
VITE_APP_VERSION= '2.1.4'
VITE_APP_RELEASE= 'community'
VITE_SFTP_PREVIEW_MB= 2
VITE_DEMO_MODE= false

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -93,5 +93,5 @@ export function deleteMenu(id: number) {
* 刷新缓存
*/
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';
/**
* 系统消息查询请求
*/
export interface MessageQueryRequest {
limit?: number;
export interface MessageQueryRequest extends Pagination {
maxId?: number;
classify?: string;
queryUnread?: boolean;

View File

@@ -50,12 +50,12 @@ export function logout() {
* 获取用户信息
*/
export function getUserPermission() {
return axios.get<UserPermissionResponse>('/infra/permission/user');
return axios.get<UserPermissionResponse>('/infra/user-permission/user');
}
/**
* 获取菜单列表
*/
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;
cursor: pointer;
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 {
background: var(--color-fill-3);
@@ -230,6 +230,26 @@ body {
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 {
overflow: hidden;
text-overflow: ellipsis;
@@ -301,13 +321,13 @@ body {
::-webkit-scrollbar-track {
background-color: var(--color-fill-1);
border-radius: 8px;
border-radius: 4px;
}
::-webkit-scrollbar-thumb {
border: 1px solid transparent;
background-clip: padding-box;
border-radius: 8px;
border-radius: 4px;
background-color: var(--color-fill-4);
//&:hover {

View File

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

View File

@@ -50,7 +50,7 @@
</template>
</a-button>
</a-tooltip>
<a-dropdown trigger="click" @select="s => changeLocale(s as string)">
<a-dropdown trigger="click" @select="(s: string) => changeLocale(s)">
<div ref="localeRef" class="trigger-btn" />
<template #content>
<a-doption v-for="item in locales"

View File

@@ -12,7 +12,7 @@
:checkable="checkable"
:check-strictly="true"
@drop="moveGroup"
@select="(s) => emits('onSelected', s)">
@select="(s: any) => emits('onSelected', s)">
<!-- 标题 -->
<template #title="node">
<!-- 修改名称输入框 -->

View File

@@ -54,7 +54,7 @@
import type { HostQueryResponse } from '@/api/asset/host';
import { dataColor } from '@/utils';
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 { computed } from 'vue';

View File

@@ -119,11 +119,6 @@
align-items: center;
}
}
.exec-host-items {
width: 100%;
height: calc(100% - 38px);
}
}
:deep(.log-header) {

View File

@@ -13,23 +13,26 @@
</div>
<!-- 主机列表 -->
<div class="exec-host-items">
<div v-for="item in hosts"
:key="item.id"
class="exec-host-item"
:class="[ current === item.id ? 'exec-host-item-selected' : '' ]"
@click="emits('selected', item.id)">
<!-- 主机名称 -->
<div class="exec-host-item-name">
<span class="host-name">{{ item.hostName }}</span>
<span class="host-address">{{ item.hostAddress }}</span>
<a-scrollbar>
<div v-for="(item, index) in hosts"
:key="item.id"
class="exec-host-item"
:class="[ current === item.id ? 'exec-host-item-selected' : '' ]"
:style="{ marginBottom: index === hosts.length -1 ? 0 : '8px' }"
@click="emits('selected', item.id)">
<!-- 主机名称 -->
<div class="exec-host-item-name">
<span class="host-name">{{ item.hostName }}</span>
<span class="host-address">{{ item.hostAddress }}</span>
</div>
<!-- 状态 -->
<div class="exec-host-item-status">
<a-tag :color="getDictValue(execHostStatusKey, item.status, 'execColor')">
{{ getDictValue(execHostStatusKey, item.status) }}
</a-tag>
</div>
</div>
<!-- 状态 -->
<div class="exec-host-item-status">
<a-tag :color="getDictValue(execHostStatusKey, item.status, 'execColor')">
{{ getDictValue(execHostStatusKey, item.status) }}
</a-tag>
</div>
</div>
</a-scrollbar>
</div>
</div>
</template>
@@ -78,13 +81,19 @@
}
.exec-host-items {
position: absolute;
width: calc(100% - 32px);
height: calc(100% - 68px);
overflow: auto;
position: relative;
width: 100%;
height: calc(100% - 38px);
&::-webkit-scrollbar-track {
display: none;
:deep(.arco-scrollbar) {
position: absolute;
width: 100%;
height: 100%;
&-container {
height: 100%;
overflow-y: auto;
}
}
}
@@ -99,7 +108,6 @@
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
background: var(--color-fill-2);
transition: all .2s;
user-select: none;

View File

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

View File

@@ -175,7 +175,7 @@
import { downloadExecCommandLogFile } from '@/api/exec/exec-command-log';
import { downloadExecJobLogFile } from '@/api/job/exec-job-log';
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';
const props = defineProps<{

View File

@@ -51,15 +51,15 @@
:pagination="pagination"
:bordered="false"
:scroll="{ x: '100%', y: '60vh' }"
@page-change="(page) => fetchTableData(page, pagination.pageSize)"
@page-size-change="(size) => fetchTableData(1, size)">
@page-change="(page: number) => fetchTableData(page, pagination.pageSize)"
@page-size-change="(size: number) => fetchTableData(1, size)">
<!-- 模板名称 -->
<template #name="{ record }">
<span class="span-blue">{{ record.name }}</span>
</template>
<!-- 模板命令 -->
<template #command="{ record }">
<span class="copy-left" @click="copy(record.command, '已复制')">
<span class="copy-left" @click="copy(record.command, true)">
<icon-copy />
</span>
<span :title="record.command">{{ record.command }}</span>
@@ -89,7 +89,7 @@
<script lang="ts" setup>
import type { ExecTemplateQueryRequest, ExecTemplateQueryResponse } from '@/api/exec/exec-template';
import { reactive, ref } from 'vue';
import { usePagination } from '@/types/table';
import { useTablePagination } from '@/hooks/table';
import useVisible from '@/hooks/visible';
import useLoading from '@/hooks/loading';
import { copy } from '@/hooks/copy';
@@ -100,7 +100,7 @@
const { visible, setVisible } = useVisible();
const { loading, setLoading } = useLoading();
const pagination = usePagination();
const pagination = useTablePagination();
const tableRenderData = ref<ExecTemplateQueryResponse[]>([]);
const formModel = reactive<ExecTemplateQueryRequest>({

View File

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

View File

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

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