Compare commits

..

11 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
64 changed files with 1289 additions and 577 deletions

View File

@@ -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,7 +1,7 @@
version: '3.3'
services:
service:
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-service:2.1.3
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-service:2.1.4
privileged: true
ports:
- 1081:80
@@ -32,7 +32,7 @@ services:
- mysql
- redis
mysql:
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-mysql:2.1.3
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-mysql:2.1.4
privileged: true
ports:
- 3307:3306
@@ -52,7 +52,7 @@ services:
retries: 10
start_period: 3s
redis:
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-redis:2.1.3
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-redis:2.1.4
privileged: true
ports:
- 6380:6379

View File

@@ -1,7 +1,7 @@
version: '3.3'
services:
service:
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-service:2.1.3
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-service:2.1.4
privileged: true
ports:
- ${SERVICE_PORT:-1081}:80
@@ -19,9 +19,9 @@ services:
- ${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:
mysql:
@@ -32,7 +32,7 @@ services:
- mysql
- redis
mysql:
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-mysql:2.1.3
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-mysql:2.1.4
privileged: true
ports:
- 3307:3306
@@ -47,12 +47,12 @@ services:
- ${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
redis:
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-redis:2.1.3
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-redis:2.1.4
privileged: true
ports:
- 6380:6379
@@ -63,12 +63,12 @@ services:
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
adminer:
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-adminer:2.1.3
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-adminer:2.1.4
ports:
- 8081:8080
depends_on:

View File

@@ -1,4 +1,4 @@
#/bin/bash
version=2.1.3
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,5 +1,5 @@
#/bin/bash
version=2.1.3
version=2.1.4
cp -r ../../sql ./sql
docker build -t orion-visor-mysql:${version} .
rm -rf ./sql

View File

@@ -1,5 +1,5 @@
#/bin/bash
version=2.1.3
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}

View File

@@ -1,4 +1,4 @@
#/bin/bash
version=2.1.3
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}

View File

@@ -1,5 +1,5 @@
#/bin/bash
version=2.1.3
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} .

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

@@ -14,7 +14,7 @@
<url>https://github.com/dromara/orion-visor</url>
<properties>
<revision>2.1.3</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>

View File

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

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

@@ -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.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

@@ -7,6 +7,7 @@ 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;
@@ -43,6 +44,8 @@ public class SessionStores {
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

@@ -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

@@ -75,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()

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

@@ -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

@@ -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

@@ -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

@@ -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

@@ -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.3'
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.3'
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.3",
"version": "2.1.4",
"private": true,
"author": "Jiahang Li",
"license": "Apache 2.0",

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

@@ -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

@@ -5,7 +5,7 @@
<!-- 子标题 -->
<div v-if="!isDemoMode" class="login-form-sub-title">{{ $t('login.form.sub.title') }}</div>
<!-- 演示模式 -->
<div v-else class="login-form-sub-title ">演示模式账号: admin/admin</div>
<div v-else class="login-form-sub-title">{{ $t('login.form.demo.title') }}</div>
<!-- 错误信息 -->
<div class="login-form-error-msg">{{ errorMessage }}</div>
<!-- 登录表单 -->

View File

@@ -1,6 +1,7 @@
export default {
'login.form.title': '登录 Orion Visor',
'login.form.sub.title': '一站式服务器运维解决方案',
'login.form.demo.title': '演示模式账号: admin/admin',
'login.form.userName.errMsg': '用户名不能为空',
'login.form.password.errMsg': '密码不能为空',
'login.form.login.errMsg': '登录出错, 轻刷新重试',

View File

@@ -49,22 +49,21 @@
<script lang="ts" setup>
import type { SidebarAction } from '../../types/define';
import { useFullscreen } from '@vueuse/core';
import { computed } from 'vue';
import { useTerminalStore } from '@/store';
import IconActions from '../layout/icon-actions.vue';
const { isFullscreen, toggle: toggleFullScreen } = useFullscreen();
const emits = defineEmits(['fullscreen']);
const { tabManager } = useTerminalStore();
// 顶部操作
const actions = computed<Array<SidebarAction>>(() => [
const actions: Array<SidebarAction> = [
{
icon: isFullscreen.value ? 'icon-fullscreen-exit' : 'icon-fullscreen',
content: isFullscreen.value ? '点击退出全屏模式' : '点击切换全屏模式',
click: toggleFullScreen
icon: 'icon-fullscreen',
content: '全屏模式',
click: () => emits('fullscreen')
},
]);
];
</script>
@@ -101,17 +100,17 @@
}
&-tabs {
width: calc(100% - @logo-width - var(--sidebar-icon-wrapper-size));
width: calc(100% - @logo-width - 100px);
display: flex;
}
&-right {
width: var(--sidebar-icon-wrapper-size);
width: 100px;
display: flex;
justify-content: flex-end;
&-actions {
width: var(--sidebar-icon-wrapper-size);
width: 100px;
display: flex;
justify-content: flex-end;
}

View File

@@ -249,7 +249,7 @@
width: 100%;
height: calc(100% - @ssh-header-height);
position: relative;
padding: 8px 2px 2px 8px;
padding: 8px 4px 4px 8px;
.ssh-inst {
width: 100%;

View File

@@ -1,8 +1,10 @@
<template>
<div class="host-terminal-layout" v-if="render">
<div v-if="render"
class="host-terminal-layout"
:class="{ 'terminal-full-layout': fullscreen }">
<!-- 头部区域 -->
<header class="host-terminal-layout-header">
<layout-header />
<layout-header @fullscreen="enterFullscreen" />
</header>
<!-- 主体区域 -->
<main class="host-terminal-layout-main">
@@ -29,6 +31,14 @@
@screenshot="screenshot" />
</div>
</main>
<!-- 退出全屏 -->
<a-button v-if="fullscreen"
class="exit-fullscreen"
shape="circle"
title="退出全屏"
@click="exitFullscreen">
<icon-fullscreen-exit />
</a-button>
<!-- 命令片段列表抽屉 -->
<command-snippet-drawer ref="snippetRef" @closed="autoFocus" />
<!-- 路径书签列表抽屉 -->
@@ -50,6 +60,7 @@
import { dictKeys, PanelSessionType, TerminalTabs } from './types/const';
import { useCacheStore, useDictStore, useTerminalStore } from '@/store';
import { useRoute } from 'vue-router';
import { useFullscreen } from '@vueuse/core';
import useLoading from '@/hooks/loading';
import debug from '@/utils/env';
import { Message } from '@arco-design/web-vue';
@@ -66,6 +77,7 @@
const { fetchPreference, getCurrentSession, openSession, preference, loadHosts, hosts, tabManager } = useTerminalStore();
const { loading, setLoading } = useLoading(true);
const { enter: enterFull, exit: exitFull } = useFullscreen();
const route = useRoute();
const originTitle = document.title;
@@ -73,6 +85,7 @@
const snippetRef = ref();
const pathRef = ref();
const transferRef = ref();
const fullscreen = ref();
// 终端截屏
const screenshot = () => {
@@ -82,10 +95,18 @@
}
};
// 关闭视口处理
const handleBeforeUnload = (event: any) => {
event.preventDefault();
event.returnValue = confirm('系统可能不会保存您所做的更改');
// 进入全屏
const enterFullscreen = () => {
fullscreen.value = true;
// 进入全屏
enterFull();
};
// 退出全屏
const exitFullscreen = () => {
fullscreen.value = false;
// 退出全屏
exitFull();
};
// 自动聚焦
@@ -93,6 +114,12 @@
getCurrentSession<ISshSession>(PanelSessionType.SSH.type)?.focus();
};
// 关闭视口处理
const handleBeforeUnload = (event: any) => {
event.preventDefault();
event.returnValue = confirm('系统可能不会保存您所做的更改');
};
// 打开默认打开页面
onBeforeMount(() => {
// 打开默认 tab
@@ -105,18 +132,19 @@
});
// 加载用户终端偏好
onBeforeMount(async () => {
onBeforeMount(() => {
// 加载偏好
await fetchPreference();
// 设置系统主题配色
const dark = preference.theme.dark;
document.body.setAttribute('terminal-theme', dark ? 'dark' : 'light');
render.value = true;
fetchPreference().then(() => {
// 设置系统主题配色
const dark = preference.theme.dark;
document.body.setAttribute('terminal-theme', dark ? 'dark' : 'light');
render.value = true;
});
});
// 加载字典值
onBeforeMount(async () => {
await useDictStore().loadKeys(dictKeys);
onBeforeMount(() => {
useDictStore().loadKeys(dictKeys);
});
// 加载主机信息
@@ -176,6 +204,24 @@
position: relative;
color: var(--color-content-text-2);
&.terminal-full-layout {
.host-terminal-layout-header, .host-terminal-layout-left, .host-terminal-layout-right {
display: none;
}
.host-terminal-layout-main {
height: 100%;
:deep(.host-terminal-layout-content) {
width: 100%;
}
}
:deep(.terminal-panels-container) {
height: 100vh !important;
}
}
&-header {
width: 100%;
height: var(--header-height);
@@ -212,6 +258,13 @@
background: var(--color-bg-content);
overflow: auto;
}
.exit-fullscreen {
position: absolute;
right: 24px;
bottom: 24px;
z-index: 9999;
}
}
</style>

View File

@@ -83,15 +83,21 @@
<template #keyName="{record}">
{{ record.keyName }}<span style="margin: 0 4px;">-</span>{{ record.keyDescription }}
</template>
<!-- 值 -->
<!-- 配置值 -->
<template #value="{ record }">
<span class="copy-left" title="复制" @click="copy(record.value)">
<icon-copy />
</span>
<a-tooltip position="tl" :content="record.value">
<span>{{ record.value }}</span>
<a-tooltip position="tl"
:content="record.value"
@click="copy(record.value, true)">
<span class="text-copy">{{ record.value }}</span>
</a-tooltip>
</template>
<!-- 额外参数 -->
<template #extra="{ record }">
<span class="text-copy"
@click="copy(record.extra, true)">
{{ record.extra }}
</span>
</template>
<!-- 操作 -->
<template #handle="{ record }">
<div class="table-handle-wrapper">

View File

@@ -272,11 +272,13 @@
// 添加后回调
const addedCallback = () => {
formRef.value.resetFields();
loadMenuData(true);
};
// 更新后回调
const updatedCallback = () => {
formRef.value.resetFields();
loadMenuData(true);
};

View File

@@ -17,7 +17,7 @@ const columns = [
title: '类型',
dataIndex: 'type',
slotName: 'type',
width: 80,
width: 68,
}, {
title: '排序',
dataIndex: 'sort',
@@ -32,20 +32,21 @@ const columns = [
title: '权限标识',
dataIndex: 'permission',
slotName: 'permission',
minWidth: 138,
minWidth: 168,
ellipsis: true,
tooltip: true
}, {
title: '组件名称',
dataIndex: 'component',
slotName: 'component',
minWidth: 138,
minWidth: 218,
ellipsis: true,
tooltip: true,
}, {
title: '链接路径',
dataIndex: 'path',
slotName: 'path',
width: 168,
ellipsis: true,
tooltip: true,
}, {

View File

@@ -29,8 +29,20 @@
</template>
<!-- 操作日志 -->
<template #originLogInfo="{ record }">
<icon-copy class="copy-left" @click="copy(record.originLogInfo, true)" />
<span v-html="replaceHtmlTag(record.logInfo)" />
<!-- 操作日志 -->
<a-tooltip position="tl"
:content="record.originLogInfo">
<span v-html="replaceHtmlTag(record.logInfo)"
class="text-copy"
@click="copy(record.originLogInfo, true)" />
</a-tooltip>
<!-- 错误消息 -->
<br v-if="record.errorMessage">
<span v-if="record.errorMessage"
class="table-cell-sub-value text-copy error-message"
@click="copy(record.errorMessage, true)">
{{ record.errorMessage }}
</span>
</template>
<!-- 留痕地址 -->
<template #address="{ record }">
@@ -170,4 +182,8 @@
</script>
<style lang="less" scoped>
.error-message {
color: rgb(var(--red-6));
font-size: 12px;
}
</style>

View File

@@ -77,8 +77,20 @@
</template>
<!-- 操作日志 -->
<template #originLogInfo="{ record }">
<icon-copy class="copy-left" @click="copy(record.originLogInfo, true)" />
<span v-html="replaceHtmlTag(record.logInfo)" />
<!-- 操作日志 -->
<a-tooltip position="tl"
:content="record.originLogInfo">
<span v-html="replaceHtmlTag(record.logInfo)"
class="text-copy"
@click="copy(record.originLogInfo, true)" />
</a-tooltip>
<!-- 错误消息 -->
<br v-if="record.errorMessage">
<span v-if="record.errorMessage"
class="table-cell-sub-value text-copy error-message"
@click="copy(record.errorMessage, true)">
{{ record.errorMessage }}
</span>
</template>
<!-- 留痕地址 -->
<template #address="{ record }">
@@ -226,4 +238,8 @@
</script>
<style lang="less" scoped>
.error-message {
color: rgb(var(--red-6));
font-size: 12px;
}
</style>

View File

@@ -33,7 +33,6 @@ const columns = [
minWidth: 238,
align: 'left',
ellipsis: true,
tooltip: true,
}, {
title: '留痕地址',
dataIndex: 'address',

View File

@@ -22,7 +22,7 @@
</modules>
<properties>
<revision>2.1.3</revision>
<revision>2.1.4</revision>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<maven.surefire.plugin.version>3.0.0-M5</maven.surefire.plugin.version>

View File

@@ -43,7 +43,7 @@ INSERT INTO `dict_key` VALUES (39, 'pathBookmarkType', 'STRING', '[]', '路径
INSERT INTO `dict_key` VALUES (40, 'sftpTransferStatus', 'STRING', '[{\"name\": \"status\", \"type\": \"STRING\"}, {\"name\": \"color\", \"type\": \"COLOR\"}, {\"name\": \"icon\", \"type\": \"STRING\"}]', 'SFTP 传输状态', '2024-05-06 11:54:49', '2024-05-06 11:54:49', '1', '1', 0);
INSERT INTO `dict_key` VALUES (41, 'uploadTaskStatus', 'STRING', '[{\"name\": \"color\", \"type\": \"COLOR\"}]', '上传任务状态', '2024-05-07 22:18:48', '2024-05-08 22:06:23', '1', '1', 0);
INSERT INTO `dict_key` VALUES (42, 'uploadTaskFileStatus', 'STRING', '[{\"name\": \"status\", \"type\": \"STRING\"}]', '上传任务文件状态', '2024-05-08 10:30:29', '2024-05-10 17:34:13', '1', '1', 0);
INSERT INTO `dict_key` VALUES (43, 'messageType', 'STRING', '[{\"name\": \"tagLabel\", \"type\": \"STRING\"}, {\"name\": \"tagVisible\", \"type\": \"STRING\"}, {\"name\": \"tagColor\", \"type\": \"STRING\"}, {\"name\": \"redirectComponent\", \"type\": \"STRING\"}]', '消息类型', '2024-05-13 12:07:56', '2024-05-31 17:31:37', '1', '1', 0);
INSERT INTO `dict_key` VALUES (43, 'messageType', 'STRING', '[{\"name\": \"tagLabel\", \"type\": \"STRING\"}, {\"name\": \"tagVisible\", \"type\": \"BOOLEAN\"}, {\"name\": \"tagColor\", \"type\": \"COLOR\"}, {\"name\": \"redirectComponent\", \"type\": \"STRING\"}]', '消息类型', '2024-05-13 12:07:56', '2024-08-19 12:28:30', '1', '1', 0);
INSERT INTO `dict_key` VALUES (44, 'messageClassify', 'STRING', '[]', '消息分类', '2024-05-13 15:06:27', '2024-05-31 17:31:37', '1', '1', 0);
INSERT INTO `dict_key` VALUES (53, 'terminalTheme', 'STRING', '[{\"name\": \"dark\", \"type\": \"BOOLEAN\"}]', '终端主题', '2024-07-04 19:14:34', '2024-07-04 19:14:34', '1', '1', 0);
INSERT INTO `dict_key` VALUES (57, 'hostStatus', 'STRING', '[{\"name\": \"color\", \"type\": \"COLOR\"}, {\"name\": \"status\", \"type\": \"STRING\"}]', '主机状态', '2024-07-17 12:51:10', '2024-07-22 16:53:23', '1', '1', 0);
@@ -288,27 +288,32 @@ INSERT INTO `dict_value` VALUES (291, 2, 'operatorLogType', 'upload-task:cancel'
INSERT INTO `dict_value` VALUES (292, 2, 'operatorLogType', 'upload-task:delete', '删除上传记录', '{}', 30, '2024-05-08 22:23:44', '2024-05-08 22:23:44', '1', '1', 0);
INSERT INTO `dict_value` VALUES (293, 2, 'operatorLogType', 'upload-task:clear', '清理上传记录', '{}', 40, '2024-05-08 22:23:59', '2024-05-08 22:23:59', '1', '1', 0);
INSERT INTO `dict_value` VALUES (294, 41, 'uploadTaskStatus', 'FAILED', '已失败', '{\"color\": \"red\"}', 40, '2024-05-10 11:29:17', '2024-05-10 11:29:17', '1', '1', 0);
INSERT INTO `dict_value` VALUES (295, 43, 'messageType', 'EXEC_FAILED', '执行失败', '{\"tagColor\": \"red\", \"tagLabel\": \"部分失败\", \"tagVisible\": \"true\", \"redirectComponent\": \"execCommand\"}', 10, '2024-05-13 12:07:56', '2024-05-31 17:31:18', '1', '1', 0);
INSERT INTO `dict_value` VALUES (296, 43, 'messageType', 'UPLOAD_FAILED', '上传失败', '{\"tagColor\": \"red\", \"tagLabel\": \"部分失败\", \"tagVisible\": \"true\", \"redirectComponent\": \"batchUpload\"}', 20, '2024-05-13 12:07:56', '2024-05-31 17:31:18', '1', '1', 0);
INSERT INTO `dict_value` VALUES (295, 43, 'messageType', 'EXEC_FAILED', '执行失败', '{\"tagColor\": \"red\", \"tagLabel\": \"部分失败\", \"tagVisible\": true, \"redirectComponent\": \"execCommand\"}', 10, '2024-05-13 12:07:56', '2024-08-19 12:28:48', '1', '1', 0);
INSERT INTO `dict_value` VALUES (296, 43, 'messageType', 'UPLOAD_FAILED', '上传失败', '{\"tagColor\": \"red\", \"tagLabel\": \"部分失败\", \"tagVisible\": true, \"redirectComponent\": \"batchUpload\"}', 20, '2024-05-13 12:07:56', '2024-08-19 12:28:51', '1', '1', 0);
INSERT INTO `dict_value` VALUES (297, 44, 'messageClassify', 'NOTICE', '通知', '{}', 10, '2024-05-13 15:06:27', '2024-05-31 17:31:18', '1', '1', 0);
INSERT INTO `dict_value` VALUES (298, 44, 'messageClassify', 'TODO', '待办', '{}', 20, '2024-05-13 15:06:27', '2024-05-31 17:31:18', '1', '1', 0);
INSERT INTO `dict_value` VALUES (353, 53, 'terminalTheme', '{\"background\":\"#1E1F29\",\"foreground\":\"#F8F8F2\",\"cursor\":\"#BBBBBB\",\"selectionBackground\":\"#44475A\",\"black\":\"#000000\",\"red\":\"#FF5555\",\"green\":\"#50FA7B\",\"yellow\":\"#F1FA8C\",\"blue\":\"#BD93F9\",\"cyan\":\"#8BE9FD\",\"white\":\"#BBBBBB\",\"brightBlack\":\"#555555\",\"brightRed\":\"#FF5555\",\"brightGreen\":\"#50FA7B\",\"brightYellow\":\"#F1FA8C\",\"brightBlue\":\"#BD93F9\",\"brightCyan\":\"#8BE9FD\",\"brightWhite\":\"#FFFFFF\"}', 'Dracula', '{\"dark\": true}', 10, '2024-07-04 19:15:42', '2024-07-04 19:15:42', '1', '1', 0);
INSERT INTO `dict_value` VALUES (354, 53, 'terminalTheme', '{\"background\":\"#161719\",\"foreground\":\"#C5C8C6\",\"cursor\":\"#D0D0D0\",\"selectionBackground\":\"#444444\",\"black\":\"#000000\",\"red\":\"#FD5FF1\",\"green\":\"#87C38A\",\"yellow\":\"#FFD7B1\",\"blue\":\"#85BEFD\",\"cyan\":\"#85BEFD\",\"white\":\"#E0E0E0\",\"brightBlack\":\"#000000\",\"brightRed\":\"#FD5FF1\",\"brightGreen\":\"#94FA36\",\"brightYellow\":\"#F5FFA8\",\"brightBlue\":\"#96CBFE\",\"brightCyan\":\"#85BEFD\",\"brightWhite\":\"#E0E0E0\"}', 'Atom', '{\"dark\": true}', 20, '2024-07-04 19:21:38', '2024-07-04 19:21:38', '1', '1', 0);
INSERT INTO `dict_value` VALUES (355, 53, 'terminalTheme', '{\"background\":\"#1E1E2E\",\"foreground\":\"#CDD6F4\",\"cursor\":\"#F5E0DC\",\"selectionBackground\":\"#585B70\",\"black\":\"#45475A\",\"red\":\"#F38BA8\",\"green\":\"#A6E3A1\",\"yellow\":\"#F9E2AF\",\"blue\":\"#89B4FA\",\"cyan\":\"#94E2D5\",\"white\":\"#BAC2DE\",\"brightBlack\":\"#585B70\",\"brightRed\":\"#F38BA8\",\"brightGreen\":\"#A6E3A1\",\"brightYellow\":\"#F9E2AF\",\"brightBlue\":\"#89B4FA\",\"brightCyan\":\"#94E2D5\",\"brightWhite\":\"#A6ADC8\"}', 'catppuccin-mocha', '{\"dark\": true}', 30, '2024-07-04 19:21:49', '2024-07-04 19:21:49', '1', '1', 0);
INSERT INTO `dict_value` VALUES (356, 53, 'terminalTheme', '{\"background\":\"#1D262A\",\"foreground\":\"#E7EBED\",\"cursor\":\"#EAEAEA\",\"selectionBackground\":\"#4E6A78\",\"black\":\"#435B67\",\"red\":\"#FC3841\",\"green\":\"#5CF19E\",\"yellow\":\"#FED032\",\"blue\":\"#37B6FF\",\"cyan\":\"#59FFD1\",\"white\":\"#FFFFFF\",\"brightBlack\":\"#A1B0B8\",\"brightRed\":\"#FC746D\",\"brightGreen\":\"#ADF7BE\",\"brightYellow\":\"#FEE16C\",\"brightBlue\":\"#70CFFF\",\"brightCyan\":\"#9AFFE6\",\"brightWhite\":\"#FFFFFF\"}', 'MaterialDesignColors', '{\"dark\": true}', 40, '2024-07-04 19:22:02', '2024-07-04 19:22:02', '1', '1', 0);
INSERT INTO `dict_value` VALUES (357, 53, 'terminalTheme', '{\"background\":\"#24273A\",\"foreground\":\"#CAD3F5\",\"cursor\":\"#F4DBD6\",\"selectionBackground\":\"#5B6078\",\"black\":\"#494D64\",\"red\":\"#ED8796\",\"green\":\"#A6DA95\",\"yellow\":\"#EED49F\",\"blue\":\"#8AADF4\",\"cyan\":\"#8BD5CA\",\"white\":\"#B8C0E0\",\"brightBlack\":\"#5B6078\",\"brightRed\":\"#ED8796\",\"brightGreen\":\"#A6DA95\",\"brightYellow\":\"#EED49F\",\"brightBlue\":\"#8AADF4\",\"brightCyan\":\"#8BD5CA\",\"brightWhite\":\"#A5ADCB\"}', 'catppuccin-macchiato', '{\"dark\": true}', 50, '2024-07-04 19:22:16', '2024-07-04 19:22:16', '1', '1', 0);
INSERT INTO `dict_value` VALUES (358, 53, 'terminalTheme', '{\"background\":\"#282C34\",\"foreground\":\"#DCDFE4\",\"cursor\":\"#A3B3CC\",\"selectionBackground\":\"#474E5D\",\"black\":\"#282C34\",\"red\":\"#E06C75\",\"green\":\"#98C379\",\"yellow\":\"#E5C07B\",\"blue\":\"#61AFEF\",\"cyan\":\"#56B6C2\",\"white\":\"#DCDFE4\",\"brightBlack\":\"#282C34\",\"brightRed\":\"#E06C75\",\"brightGreen\":\"#98C379\",\"brightYellow\":\"#E5C07B\",\"brightBlue\":\"#61AFEF\",\"brightCyan\":\"#56B6C2\",\"brightWhite\":\"#DCDFE4\"}', 'OneHalfDark', '{\"dark\": true}', 60, '2024-07-04 19:22:26', '2024-07-04 19:22:26', '1', '1', 0);
INSERT INTO `dict_value` VALUES (359, 53, 'terminalTheme', '{\"background\":\"#1E1E1E\",\"foreground\":\"#FFFFFF\",\"cursor\":\"#98989D\",\"selectionBackground\":\"#3F638B\",\"black\":\"#1A1A1A\",\"red\":\"#CC372E\",\"green\":\"#26A439\",\"yellow\":\"#CDAC08\",\"blue\":\"#0869CB\",\"cyan\":\"#479EC2\",\"white\":\"#98989D\",\"brightBlack\":\"#464646\",\"brightRed\":\"#FF453A\",\"brightGreen\":\"#32D74B\",\"brightYellow\":\"#FFD60A\",\"brightBlue\":\"#0A84FF\",\"brightCyan\":\"#76D6FF\",\"brightWhite\":\"#FFFFFF\"}', 'Apple System Colors', '{\"dark\": true}', 70, '2024-07-04 19:22:45', '2024-07-04 19:22:45', '1', '1', 0);
INSERT INTO `dict_value` VALUES (360, 53, 'terminalTheme', '{\"background\":\"#FFFFFF\",\"foreground\":\"#000000\",\"cursor\":\"#000000\",\"selectionBackground\":\"#B5D5FF\",\"black\":\"#000000\",\"red\":\"#CC0000\",\"green\":\"#4E9A06\",\"yellow\":\"#C4A000\",\"blue\":\"#3465A4\",\"cyan\":\"#06989A\",\"white\":\"#D3D7CF\",\"brightBlack\":\"#555753\",\"brightRed\":\"#EF2929\",\"brightGreen\":\"#8AE234\",\"brightYellow\":\"#FCE94F\",\"brightBlue\":\"#729FCF\",\"brightCyan\":\"#34E2E2\",\"brightWhite\":\"#EEEEEC\"}', 'Builtin Tango Light', '{\"dark\": false}', 80, '2024-07-04 19:22:57', '2024-07-04 19:22:57', '1', '1', 0);
INSERT INTO `dict_value` VALUES (361, 53, 'terminalTheme', '{\"background\":\"#1F1D27\",\"foreground\":\"#B7A1FF\",\"cursor\":\"#FF9839\",\"selectionBackground\":\"#353147\",\"black\":\"#1F1D27\",\"red\":\"#D9393E\",\"green\":\"#2DCD73\",\"yellow\":\"#D9B76E\",\"blue\":\"#FFC284\",\"cyan\":\"#2488FF\",\"white\":\"#B7A1FF\",\"brightBlack\":\"#353147\",\"brightRed\":\"#D9393E\",\"brightGreen\":\"#2DCD73\",\"brightYellow\":\"#D9B76E\",\"brightBlue\":\"#FFC284\",\"brightCyan\":\"#2488FF\",\"brightWhite\":\"#EAE5FF\"}', 'Duotone Dark', '{\"dark\": true}', 90, '2024-07-04 19:23:13', '2024-07-04 19:23:13', '1', '1', 0);
INSERT INTO `dict_value` VALUES (362, 53, 'terminalTheme', '{\"background\":\"#F9F9F9\",\"foreground\":\"#373A41\",\"cursor\":\"#F32759\",\"selectionBackground\":\"#DAF0FF\",\"black\":\"#373A41\",\"red\":\"#D52753\",\"green\":\"#23974A\",\"yellow\":\"#DF631C\",\"blue\":\"#275FE4\",\"cyan\":\"#27618D\",\"white\":\"#BABBC2\",\"brightBlack\":\"#676A77\",\"brightRed\":\"#FF6480\",\"brightGreen\":\"#3CBC66\",\"brightYellow\":\"#C5A332\",\"brightBlue\":\"#0099E1\",\"brightCyan\":\"#6D93BB\",\"brightWhite\":\"#D3D3D3\"}', 'BlulocoLight', '{\"dark\": false}', 100, '2024-07-04 19:23:32', '2024-07-04 19:23:32', '1', '1', 0);
INSERT INTO `dict_value` VALUES (363, 53, 'terminalTheme', '{\"background\":\"#2C3643\",\"foreground\":\"#FFFFFF\",\"cursor\":\"#B4B1B1\",\"selectionBackground\":\"#67747C\",\"black\":\"#080200\",\"red\":\"#FA5E5B\",\"green\":\"#16C98D\",\"yellow\":\"#FFC83F\",\"blue\":\"#288AD6\",\"cyan\":\"#28DDDE\",\"white\":\"#E7E7E7\",\"brightBlack\":\"#6F6B68\",\"brightRed\":\"#FA5E5B\",\"brightGreen\":\"#16C98D\",\"brightYellow\":\"#FEEF6D\",\"brightBlue\":\"#278AD6\",\"brightCyan\":\"#27DEDE\",\"brightWhite\":\"#FFFFFF\"}', 'Chester', '{\"dark\": true}', 110, '2024-07-04 19:23:41', '2024-07-04 19:23:41', '1', '1', 0);
INSERT INTO `dict_value` VALUES (364, 53, 'terminalTheme', '{\"background\":\"#FFFFFF\",\"foreground\":\"#262626\",\"cursor\":\"#6FD3FC\",\"selectionBackground\":\"#6FD3FC\",\"black\":\"#000000\",\"red\":\"#F8282A\",\"green\":\"#328A5D\",\"yellow\":\"#FA701D\",\"blue\":\"#135CD0\",\"cyan\":\"#33C3C1\",\"white\":\"#B3B3B3\",\"brightBlack\":\"#555753\",\"brightRed\":\"#FB0416\",\"brightGreen\":\"#2CC631\",\"brightYellow\":\"#FDD727\",\"brightBlue\":\"#1670FF\",\"brightCyan\":\"#3AD5CE\",\"brightWhite\":\"#EEEEEC\"}', 'CLRS', '{\"dark\": false}', 120, '2024-07-04 19:24:06', '2024-07-04 19:24:06', '1', '1', 0);
INSERT INTO `dict_value` VALUES (365, 53, 'terminalTheme', '{\"background\":\"#2F2833\",\"foreground\":\"#D5CED9\",\"cursor\":\"#D5CED9\",\"selectionBackground\":\"#7E6C88\",\"black\":\"#2F2833\",\"red\":\"#FC644D\",\"green\":\"#A5F69C\",\"yellow\":\"#E9D7A5\",\"blue\":\"#3B79C7\",\"cyan\":\"#74D3DE\",\"white\":\"#D5CED9\",\"brightBlack\":\"#7E6C88\",\"brightRed\":\"#FC644D\",\"brightGreen\":\"#A5F69C\",\"brightYellow\":\"#E9D7A5\",\"brightBlue\":\"#3B79C7\",\"brightCyan\":\"#74D3DE\",\"brightWhite\":\"#FFFFFF\"}', 'Calamity', '{\"dark\": true}', 130, '2024-07-04 19:24:17', '2024-07-04 19:24:17', '1', '1', 0);
INSERT INTO `dict_value` VALUES (366, 53, 'terminalTheme', '{\"background\":\"#FFFFFF\",\"foreground\":\"#4D4D4C\",\"cursor\":\"#4D4D4C\",\"selectionBackground\":\"#D6D6D6\",\"black\":\"#000000\",\"red\":\"#C82829\",\"green\":\"#718C00\",\"yellow\":\"#EAB700\",\"blue\":\"#4271AE\",\"cyan\":\"#3E999F\",\"white\":\"#FFFFFF\",\"brightBlack\":\"#000000\",\"brightRed\":\"#C82829\",\"brightGreen\":\"#718C00\",\"brightYellow\":\"#EAB700\",\"brightBlue\":\"#4271AE\",\"brightCyan\":\"#3E999F\",\"brightWhite\":\"#FFFFFF\"}', 'Tomorrow', '{\"dark\": false}', 140, '2024-07-04 19:24:27', '2024-07-04 21:07:57', '1', '1', 0);
INSERT INTO `dict_value` VALUES (353, 53, 'terminalTheme', '{\"background\":\"#1E1F29\",\"foreground\":\"#F8F8F2\",\"cursor\":\"#BBBBBB\",\"selectionBackground\":\"#44475A\",\"black\":\"#000000\",\"red\":\"#FF5555\",\"green\":\"#50FA7B\",\"yellow\":\"#F1FA8C\",\"blue\":\"#BD93F9\",\"cyan\":\"#8BE9FD\",\"white\":\"#BBBBBB\",\"brightBlack\":\"#555555\",\"brightRed\":\"#FF5555\",\"brightGreen\":\"#50FA7B\",\"brightYellow\":\"#F1FA8C\",\"brightBlue\":\"#BD93F9\",\"brightCyan\":\"#8BE9FD\",\"brightWhite\":\"#FFFFFF\"}', 'Dracula', '{\"dark\": true}', 10, '2024-07-04 19:15:42', '2024-08-19 18:49:12', '1', '1', 0);
INSERT INTO `dict_value` VALUES (354, 53, 'terminalTheme', '{\"background\":\"#FFFFFF\",\"foreground\":\"#000000\",\"cursor\":\"#000000\",\"selectionBackground\":\"#B5D5FF\",\"black\":\"#000000\",\"red\":\"#CC0000\",\"green\":\"#4E9A06\",\"yellow\":\"#C4A000\",\"blue\":\"#3465A4\",\"cyan\":\"#06989A\",\"white\":\"#D3D7CF\",\"brightBlack\":\"#555753\",\"brightRed\":\"#EF2929\",\"brightGreen\":\"#8AE234\",\"brightYellow\":\"#FCE94F\",\"brightBlue\":\"#729FCF\",\"brightCyan\":\"#34E2E2\",\"brightWhite\":\"#EEEEEC\"}', 'Builtin Tango Light', '{\"dark\": false}', 20, '2024-07-04 19:21:38', '2024-08-19 18:49:31', '1', '1', 0);
INSERT INTO `dict_value` VALUES (355, 53, 'terminalTheme', '{\"background\":\"#161719\",\"foreground\":\"#C5C8C6\",\"cursor\":\"#D0D0D0\",\"selectionBackground\":\"#444444\",\"black\":\"#000000\",\"red\":\"#FD5FF1\",\"green\":\"#87C38A\",\"yellow\":\"#FFD7B1\",\"blue\":\"#85BEFD\",\"cyan\":\"#85BEFD\",\"white\":\"#E0E0E0\",\"brightBlack\":\"#000000\",\"brightRed\":\"#FD5FF1\",\"brightGreen\":\"#94FA36\",\"brightYellow\":\"#F5FFA8\",\"brightBlue\":\"#96CBFE\",\"brightCyan\":\"#85BEFD\",\"brightWhite\":\"#E0E0E0\"}', 'Atom', '{\"dark\": true}', 30, '2024-07-04 19:21:49', '2024-08-19 18:49:49', '1', '1', 0);
INSERT INTO `dict_value` VALUES (356, 53, 'terminalTheme', '{\"background\":\"#F9F9F9\",\"foreground\":\"#2A2C33\",\"cursor\":\"#BBBBBB\",\"selectionBackground\":\"#EDEDED\",\"black\":\"#000000\",\"red\":\"#DE3E35\",\"green\":\"#3F953A\",\"yellow\":\"#D2B67C\",\"blue\":\"#2F5AF3\",\"cyan\":\"#3F953A\",\"white\":\"#BBBBBB\",\"brightBlack\":\"#000000\",\"brightRed\":\"#DE3E35\",\"brightGreen\":\"#3F953A\",\"brightYellow\":\"#D2B67C\",\"brightBlue\":\"#2F5AF3\",\"brightCyan\":\"#3F953A\",\"brightWhite\":\"#FFFFFF\"}', 'AtomOneLight', '{\"dark\": false}', 40, '2024-07-04 19:22:02', '2024-08-19 18:50:00', '1', '1', 0);
INSERT INTO `dict_value` VALUES (357, 53, 'terminalTheme', '{\"background\":\"#282C34\",\"foreground\":\"#DCDFE4\",\"cursor\":\"#A3B3CC\",\"selectionBackground\":\"#474E5D\",\"black\":\"#282C34\",\"red\":\"#E06C75\",\"green\":\"#98C379\",\"yellow\":\"#E5C07B\",\"blue\":\"#61AFEF\",\"cyan\":\"#56B6C2\",\"white\":\"#DCDFE4\",\"brightBlack\":\"#282C34\",\"brightRed\":\"#E06C75\",\"brightGreen\":\"#98C379\",\"brightYellow\":\"#E5C07B\",\"brightBlue\":\"#61AFEF\",\"brightCyan\":\"#56B6C2\",\"brightWhite\":\"#DCDFE4\"}', 'OneHalfDark', '{\"dark\": true}', 50, '2024-07-04 19:22:16', '2024-08-19 18:50:15', '1', '1', 0);
INSERT INTO `dict_value` VALUES (358, 53, 'terminalTheme', '{\"background\":\"#FAFAFA\",\"foreground\":\"#383A42\",\"cursor\":\"#BFCEFF\",\"selectionBackground\":\"#BFCEFF\",\"black\":\"#383A42\",\"red\":\"#E45649\",\"green\":\"#50A14F\",\"yellow\":\"#C18401\",\"blue\":\"#0184BC\",\"cyan\":\"#0997B3\",\"white\":\"#FAFAFA\",\"brightBlack\":\"#4F525E\",\"brightRed\":\"#E06C75\",\"brightGreen\":\"#98C379\",\"brightYellow\":\"#E5C07B\",\"brightBlue\":\"#61AFEF\",\"brightCyan\":\"#56B6C2\",\"brightWhite\":\"#FFFFFF\"}', 'OneHalfLight', '{\"dark\": false}', 60, '2024-07-04 19:22:26', '2024-08-19 18:50:27', '1', '1', 0);
INSERT INTO `dict_value` VALUES (359, 53, 'terminalTheme', '{\"background\":\"#1E1E1E\",\"foreground\":\"#FFFFFF\",\"cursor\":\"#98989D\",\"selectionBackground\":\"#3F638B\",\"black\":\"#1A1A1A\",\"red\":\"#CC372E\",\"green\":\"#26A439\",\"yellow\":\"#CDAC08\",\"blue\":\"#0869CB\",\"cyan\":\"#479EC2\",\"white\":\"#98989D\",\"brightBlack\":\"#464646\",\"brightRed\":\"#FF453A\",\"brightGreen\":\"#32D74B\",\"brightYellow\":\"#FFD60A\",\"brightBlue\":\"#0A84FF\",\"brightCyan\":\"#76D6FF\",\"brightWhite\":\"#FFFFFF\"}', 'Apple System Colors', '{\"dark\": true}', 70, '2024-07-04 19:22:45', '2024-08-19 18:50:46', '1', '1', 0);
INSERT INTO `dict_value` VALUES (360, 53, 'terminalTheme', '{\"background\":\"#FFFFFF\",\"foreground\":\"#4D4D4C\",\"cursor\":\"#4D4D4C\",\"selectionBackground\":\"#D6D6D6\",\"black\":\"#000000\",\"red\":\"#C82829\",\"green\":\"#718C00\",\"yellow\":\"#EAB700\",\"blue\":\"#4271AE\",\"cyan\":\"#3E999F\",\"white\":\"#FFFFFF\",\"brightBlack\":\"#000000\",\"brightRed\":\"#C82829\",\"brightGreen\":\"#718C00\",\"brightYellow\":\"#EAB700\",\"brightBlue\":\"#4271AE\",\"brightCyan\":\"#3E999F\",\"brightWhite\":\"#FFFFFF\"}', 'Tomorrow', '{\"dark\": false}', 80, '2024-07-04 19:22:57', '2024-08-19 18:51:03', '1', '1', 0);
INSERT INTO `dict_value` VALUES (361, 53, 'terminalTheme', '{\"background\":\"#1E1E2E\",\"foreground\":\"#CDD6F4\",\"cursor\":\"#F5E0DC\",\"selectionBackground\":\"#585B70\",\"black\":\"#45475A\",\"red\":\"#F38BA8\",\"green\":\"#A6E3A1\",\"yellow\":\"#F9E2AF\",\"blue\":\"#89B4FA\",\"cyan\":\"#94E2D5\",\"white\":\"#BAC2DE\",\"brightBlack\":\"#585B70\",\"brightRed\":\"#F38BA8\",\"brightGreen\":\"#A6E3A1\",\"brightYellow\":\"#F9E2AF\",\"brightBlue\":\"#89B4FA\",\"brightCyan\":\"#94E2D5\",\"brightWhite\":\"#A6ADC8\"}', 'catppuccin-mocha', '{\"dark\": true}', 90, '2024-07-04 19:23:13', '2024-08-19 18:51:23', '1', '1', 0);
INSERT INTO `dict_value` VALUES (362, 53, 'terminalTheme', '{\"background\":\"#EFF1F5\",\"foreground\":\"#4C4F69\",\"cursor\":\"#DC8A78\",\"selectionBackground\":\"#ACB0BE\",\"black\":\"#5C5F77\",\"red\":\"#D20F39\",\"green\":\"#40A02B\",\"yellow\":\"#DF8E1D\",\"blue\":\"#1E66F5\",\"cyan\":\"#179299\",\"white\":\"#ACB0BE\",\"brightBlack\":\"#6C6F85\",\"brightRed\":\"#D20F39\",\"brightGreen\":\"#40A02B\",\"brightYellow\":\"#DF8E1D\",\"brightBlue\":\"#1E66F5\",\"brightCyan\":\"#179299\",\"brightWhite\":\"#BCC0CC\"}', 'catppuccin-latte', '{\"dark\": false}', 100, '2024-07-04 19:23:32', '2024-08-19 18:51:43', '1', '1', 0);
INSERT INTO `dict_value` VALUES (363, 53, 'terminalTheme', '{\"background\":\"#24273A\",\"foreground\":\"#CAD3F5\",\"cursor\":\"#F4DBD6\",\"selectionBackground\":\"#5B6078\",\"black\":\"#494D64\",\"red\":\"#ED8796\",\"green\":\"#A6DA95\",\"yellow\":\"#EED49F\",\"blue\":\"#8AADF4\",\"cyan\":\"#8BD5CA\",\"white\":\"#B8C0E0\",\"brightBlack\":\"#5B6078\",\"brightRed\":\"#ED8796\",\"brightGreen\":\"#A6DA95\",\"brightYellow\":\"#EED49F\",\"brightBlue\":\"#8AADF4\",\"brightCyan\":\"#8BD5CA\",\"brightWhite\":\"#A5ADCB\"}', 'catppuccin-macchiato', '{\"dark\": true}', 110, '2024-07-04 19:23:41', '2024-08-19 18:51:55', '1', '1', 0);
INSERT INTO `dict_value` VALUES (364, 53, 'terminalTheme', '{\"background\":\"#F9F9F9\",\"foreground\":\"#373A41\",\"cursor\":\"#F32759\",\"selectionBackground\":\"#DAF0FF\",\"black\":\"#373A41\",\"red\":\"#D52753\",\"green\":\"#23974A\",\"yellow\":\"#DF631C\",\"blue\":\"#275FE4\",\"cyan\":\"#27618D\",\"white\":\"#BABBC2\",\"brightBlack\":\"#676A77\",\"brightRed\":\"#FF6480\",\"brightGreen\":\"#3CBC66\",\"brightYellow\":\"#C5A332\",\"brightBlue\":\"#0099E1\",\"brightCyan\":\"#6D93BB\",\"brightWhite\":\"#D3D3D3\"}', 'BlulocoLight', '{\"dark\": false}', 120, '2024-07-04 19:24:06', '2024-08-19 18:52:12', '1', '1', 0);
INSERT INTO `dict_value` VALUES (365, 53, 'terminalTheme', '{\"background\":\"#303446\",\"foreground\":\"#C6D0F5\",\"cursor\":\"#F2D5CF\",\"selectionBackground\":\"#626880\",\"black\":\"#51576D\",\"red\":\"#E78284\",\"green\":\"#A6D189\",\"yellow\":\"#E5C890\",\"blue\":\"#8CAAEE\",\"cyan\":\"#81C8BE\",\"white\":\"#B5BFE2\",\"brightBlack\":\"#626880\",\"brightRed\":\"#E78284\",\"brightGreen\":\"#A6D189\",\"brightYellow\":\"#E5C890\",\"brightBlue\":\"#8CAAEE\",\"brightCyan\":\"#81C8BE\",\"brightWhite\":\"#A5ADCE\"}', 'catppuccin-frappe', '{\"dark\": true}', 130, '2024-07-04 19:24:17', '2024-08-19 18:52:24', '1', '1', 0);
INSERT INTO `dict_value` VALUES (366, 53, 'terminalTheme', '{\"background\":\"#1D262A\",\"foreground\":\"#E7EBED\",\"cursor\":\"#EAEAEA\",\"selectionBackground\":\"#4E6A78\",\"black\":\"#435B67\",\"red\":\"#FC3841\",\"green\":\"#5CF19E\",\"yellow\":\"#FED032\",\"blue\":\"#37B6FF\",\"cyan\":\"#59FFD1\",\"white\":\"#FFFFFF\",\"brightBlack\":\"#A1B0B8\",\"brightRed\":\"#FC746D\",\"brightGreen\":\"#ADF7BE\",\"brightYellow\":\"#FEE16C\",\"brightBlue\":\"#70CFFF\",\"brightCyan\":\"#9AFFE6\",\"brightWhite\":\"#FFFFFF\"}', 'MaterialDesignColors', '{\"dark\": true}', 140, '2024-07-04 19:24:27', '2024-08-19 18:52:56', '1', '1', 0);
INSERT INTO `dict_value` VALUES (385, 57, 'hostStatus', 'ENABLED', '启用', '{\"color\": \"arcoblue\", \"status\": \"normal\", \"buttonStatus\": \"normal\"}', 10, '2024-07-17 12:51:10', '2024-07-22 16:53:53', '1', '1', 0);
INSERT INTO `dict_value` VALUES (386, 57, 'hostStatus', 'DISABLED', '停用', '{\"color\": \"orangered\", \"status\": \"error\", \"buttonStatus\": \"danger\"}', 20, '2024-07-17 12:51:10', '2024-07-22 16:53:46', '1', '1', 0);
INSERT INTO `dict_value` VALUES (387, 58, 'hostType', 'SSH', 'SSH', '{\"color\": \"arcoblue\"}', 10, '2024-07-17 12:51:10', '2024-07-17 15:57:24', '1', '1', 0);
INSERT INTO `dict_value` VALUES (401, 43, 'messageType', 'LOGIN_FAILED', '登录失败', '{\"tagColor\": \"red\", \"tagLabel\": \"登录失败\", \"tagVisible\": true, \"redirectComponent\": \"0\"}', 50, '2024-08-19 18:34:27', '2024-08-19 18:34:27', '1', '1', 0);
INSERT INTO `dict_value` VALUES (402, 53, 'terminalTheme', '{\"background\":\"#101216\",\"foreground\":\"#8B949E\",\"cursor\":\"#C9D1D9\",\"selectionBackground\":\"#3B5070\",\"black\":\"#000000\",\"red\":\"#F78166\",\"green\":\"#56D364\",\"yellow\":\"#E3B341\",\"blue\":\"#6CA4F8\",\"cyan\":\"#2B7489\",\"white\":\"#FFFFFF\",\"brightBlack\":\"#4D4D4D\",\"brightRed\":\"#F78166\",\"brightGreen\":\"#56D364\",\"brightYellow\":\"#E3B341\",\"brightBlue\":\"#6CA4F8\",\"brightCyan\":\"#2B7489\",\"brightWhite\":\"#FFFFFF\"}', 'GitHub Dark', '{\"dark\": true}', 150, '2024-08-19 18:53:14', '2024-08-19 18:53:20', '1', '1', 0);
INSERT INTO `dict_value` VALUES (403, 53, 'terminalTheme', '{\"background\":\"#F4F4F4\",\"foreground\":\"#3E3E3E\",\"cursor\":\"#3F3F3F\",\"selectionBackground\":\"#A9C1E2\",\"black\":\"#3E3E3E\",\"red\":\"#970B16\",\"green\":\"#07962A\",\"yellow\":\"#F8EEC7\",\"blue\":\"#003E8A\",\"cyan\":\"#89D1EC\",\"white\":\"#FFFFFF\",\"brightBlack\":\"#666666\",\"brightRed\":\"#DE0000\",\"brightGreen\":\"#87D5A2\",\"brightYellow\":\"#F1D007\",\"brightBlue\":\"#2E6CBA\",\"brightCyan\":\"#1CFAFE\",\"brightWhite\":\"#FFFFFF\"}', 'Github', '{\"dark\": false}', 160, '2024-08-19 18:53:39', '2024-08-19 18:53:39', '1', '1', 0);
INSERT INTO `dict_value` VALUES (404, 53, 'terminalTheme', '{\"background\":\"#1F1F1F\",\"foreground\":\"#B9BCBA\",\"cursor\":\"#F83E19\",\"selectionBackground\":\"#2A2D32\",\"black\":\"#3A3D43\",\"red\":\"#BE3F48\",\"green\":\"#879A3B\",\"yellow\":\"#C5A635\",\"blue\":\"#4F76A1\",\"cyan\":\"#578FA4\",\"white\":\"#B9BCBA\",\"brightBlack\":\"#888987\",\"brightRed\":\"#FB001F\",\"brightGreen\":\"#0F722F\",\"brightYellow\":\"#C47033\",\"brightBlue\":\"#186DE3\",\"brightCyan\":\"#2E706D\",\"brightWhite\":\"#FDFFB9\"}', 'DimmedMonokai', '{\"dark\": true}', 170, '2024-08-19 18:53:52', '2024-08-19 18:53:52', '1', '1', 0);
INSERT INTO `dict_value` VALUES (405, 53, 'terminalTheme', '{\"background\":\"#1F1D27\",\"foreground\":\"#B7A1FF\",\"cursor\":\"#FF9839\",\"selectionBackground\":\"#353147\",\"black\":\"#1F1D27\",\"red\":\"#D9393E\",\"green\":\"#2DCD73\",\"yellow\":\"#D9B76E\",\"blue\":\"#FFC284\",\"cyan\":\"#2488FF\",\"white\":\"#B7A1FF\",\"brightBlack\":\"#353147\",\"brightRed\":\"#D9393E\",\"brightGreen\":\"#2DCD73\",\"brightYellow\":\"#D9B76E\",\"brightBlue\":\"#FFC284\",\"brightCyan\":\"#2488FF\",\"brightWhite\":\"#EAE5FF\"}', 'Duotone Dark', '{\"dark\": true}', 180, '2024-08-19 18:54:09', '2024-08-19 18:54:09', '1', '1', 0);
-- 菜单配置
INSERT INTO `system_menu` VALUES (1, 0, '工作台', NULL, 1, 10, 1, 1, 1, 0, 'IconComputer', NULL, 'workplace', '2023-07-28 10:51:50', '2023-09-11 15:27:52', '1', '1', 0);
@@ -389,7 +394,7 @@ INSERT INTO `system_menu` VALUES (154, 148, '清空连接日志', 'asset:host-co
INSERT INTO `system_menu` VALUES (155, 148, '强制断开连接', 'asset:host-connect-log:management:force-offline', 3, 40, 1, 1, 1, 0, NULL, NULL, NULL, '2024-03-04 13:41:02', '2024-03-05 23:32:01', '1', '1', 0);
INSERT INTO `system_menu` VALUES (156, 122, '删除操作日志', 'infra:operator-log:delete', 3, 20, 1, 1, 1, 0, NULL, NULL, NULL, '2024-03-04 17:06:55', '2024-03-04 17:08:22', '1', '1', 0);
INSERT INTO `system_menu` VALUES (157, 122, '清空操作日志', 'infra:operator-log:management:clear', 3, 30, 1, 1, 1, 0, NULL, NULL, NULL, '2024-03-04 17:07:25', '2024-04-11 11:16:17', '1', '2', 0);
INSERT INTO `system_menu` VALUES (158, 152, '文件操作日志', NULL, 2, 30, 1, 1, 1, 0, 'IconFile', NULL, 'sftpLog', '2024-03-05 15:30:13', '2024-05-07 11:11:24', '1', '1', 0);
INSERT INTO `system_menu` VALUES (158, 152, '文件操作日志', NULL, 2, 40, 1, 1, 1, 0, 'IconFile', NULL, 'sftpLog', '2024-03-05 15:30:13', '2024-08-04 20:23:19', '1', '1', 0);
INSERT INTO `system_menu` VALUES (159, 158, '查询文件操作日志', 'asset:host-sftp-log:management:query', 3, 10, 1, 1, 1, 0, NULL, NULL, NULL, '2024-03-05 15:31:02', '2024-04-12 14:49:18', '1', '1', 0);
INSERT INTO `system_menu` VALUES (160, 158, '删除文件操作日志', 'asset:host-sftp-log:management:delete', 3, 20, 1, 1, 1, 0, NULL, NULL, NULL, '2024-03-05 15:31:17', '2024-04-12 14:49:21', '1', '1', 0);
INSERT INTO `system_menu` VALUES (161, 176, '执行模板', NULL, 2, 50, 1, 1, 1, 0, 'IconBookmark', NULL, 'execTemplate', '2024-03-07 18:32:41', '2024-05-14 15:58:51', '1', '1', 0);