sftp 操作日志.

This commit is contained in:
lijiahangmax
2024-03-05 00:02:06 +08:00
parent f1ade4e182
commit a75ead9a58
22 changed files with 628 additions and 39 deletions

View File

@@ -5,7 +5,8 @@
> sql 脚本
```sql
ALTER TABLE `operator_log`
ADD INDEX `idx_type`(`type`);
```
## v1.0.0

View File

@@ -14,6 +14,7 @@ import org.hibernate.validator.constraints.Range;
* @since 2023/7/12 23:14
*/
@Data
@Schema(description = "公共页码请求")
public class PageRequest implements IPageRequest {
@Range(min = 1, max = 10000, groups = Page.class)

View File

@@ -0,0 +1,12 @@
### 分页查询 SFTP 操作日志
POST {{baseUrl}}/asset/sftp-log/query
Content-Type: application/json
Authorization: {{token}}
{
"page": 1,
"limit": 10
}
###

View File

@@ -0,0 +1,51 @@
package com.orion.ops.module.asset.controller;
import com.orion.lang.define.wrapper.DataGrid;
import com.orion.ops.framework.common.validator.group.Page;
import com.orion.ops.framework.log.core.annotation.IgnoreLog;
import com.orion.ops.framework.log.core.enums.IgnoreLogMode;
import com.orion.ops.framework.web.core.annotation.RestWrapper;
import com.orion.ops.module.asset.entity.request.host.HostSftpLogQueryRequest;
import com.orion.ops.module.asset.entity.vo.HostSftpLogVO;
import com.orion.ops.module.asset.service.HostSftpLogService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* SFTP 操作日志服务 api
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023-12-26 22:09
*/
@Tag(name = "asset - SFTP 操作日志服务")
@Slf4j
@Validated
@RestWrapper
@RestController
@RequestMapping("/asset/sftp-log")
@SuppressWarnings({"ELValidationInJSP", "SpringElInspection"})
public class HostSftpLogController {
@Resource
private HostSftpLogService hostSftpLogService;
@IgnoreLog(IgnoreLogMode.RET)
@PostMapping("/query")
@Operation(summary = "分页查询 SFTP 操作日志")
@PreAuthorize("@ss.hasPermission('infra:operator-log:query')")
public DataGrid<HostSftpLogVO> querySftpLogPage(@Validated(Page.class) @RequestBody HostSftpLogQueryRequest request) {
return hostSftpLogService.querySftpLogPage(request);
}
}

View File

@@ -0,0 +1,24 @@
package com.orion.ops.module.asset.convert;
import com.orion.ops.module.asset.entity.vo.HostSftpLogVO;
import com.orion.ops.module.infra.entity.dto.operator.OperatorLogDTO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
/**
* SFTP 操作日志 内部对象转换器
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023-12-26 22:09
*/
@Mapper
public interface HostSftpLogConvert {
HostSftpLogConvert MAPPER = Mappers.getMapper(HostSftpLogConvert.class);
@Mapping(target = "extra", ignore = true)
HostSftpLogVO to(OperatorLogDTO request);
}

View File

@@ -1,9 +1,12 @@
package com.orion.ops.module.asset.define.operator;
import com.orion.lang.utils.collect.Lists;
import com.orion.ops.framework.biz.operator.log.core.annotation.Module;
import com.orion.ops.framework.biz.operator.log.core.factory.InitializingOperatorTypes;
import com.orion.ops.framework.biz.operator.log.core.model.OperatorType;
import java.util.List;
import static com.orion.ops.framework.biz.operator.log.core.enums.OperatorRiskLevel.*;
/**
@@ -36,6 +39,18 @@ public class HostTerminalOperatorType extends InitializingOperatorTypes {
public static final String SFTP_DOWNLOAD = "host-terminal:sftp-download";
public static final List<String> SFTP_TYPES = Lists.of(
SFTP_MKDIR,
SFTP_TOUCH,
SFTP_MOVE,
SFTP_REMOVE,
SFTP_TRUNCATE,
SFTP_CHMOD,
SFTP_SET_CONTENT,
SFTP_UPLOAD,
SFTP_DOWNLOAD
);
@Override
public OperatorType[] types() {
return new OperatorType[]{

View File

@@ -0,0 +1,43 @@
package com.orion.ops.module.asset.entity.request.host;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.orion.ops.framework.common.entity.PageRequest;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import javax.validation.constraints.Size;
import java.util.Date;
/**
* SFTP 操作日志 查询请求对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024-3-4 22:59
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@Schema(name = "HostSftpLogQueryRequest", description = "SFTP 操作日志 查询请求对象")
public class HostSftpLogQueryRequest extends PageRequest {
@Schema(description = "用户id")
private Long userId;
@Schema(description = "hostId")
private Long hostId;
@Size(max = 64)
@Schema(description = "操作类型")
private String type;
@Schema(description = "操作结果 0失败 1成功")
private Integer result;
@Schema(description = "开始时间-区间")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date[] startTimeRange;
}

View File

@@ -0,0 +1,89 @@
package com.orion.ops.module.asset.entity.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
import java.util.Map;
/**
* SFTP 操作日志 实体对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023-10-10 17:08
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "HostSftpLogVO", description = "SFTP 操作日志 实体对象")
public class HostSftpLogVO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "id")
private Long id;
@Schema(description = "用户id")
private Long userId;
@Schema(description = "主机id")
private Long hostId;
@Schema(description = "用户名")
private String username;
@Schema(description = "traceId")
private String traceId;
@Schema(description = "请求ip")
private String address;
@Schema(description = "请求地址")
private String location;
@Schema(description = "userAgent")
private String userAgent;
@Schema(description = "风险等级")
private String riskLevel;
@Schema(description = "模块")
private String module;
@Schema(description = "操作类型")
private String type;
@Schema(description = "日志")
private String logInfo;
@Schema(description = "参数")
private Map<String, Object> extra;
@Schema(description = "操作结果 0失败 1成功")
private Integer result;
@Schema(description = "错误信息")
private String errorMessage;
@Schema(description = "返回值")
private String returnValue;
@Schema(description = "操作时间")
private Integer duration;
@Schema(description = "开始时间")
private Date startTime;
@Schema(description = "结束时间")
private Date endTime;
@Schema(description = "创建时间")
private Date createTime;
}

View File

@@ -0,0 +1,24 @@
package com.orion.ops.module.asset.service;
import com.orion.lang.define.wrapper.DataGrid;
import com.orion.ops.module.asset.entity.request.host.HostSftpLogQueryRequest;
import com.orion.ops.module.asset.entity.vo.HostSftpLogVO;
/**
* SFTP 操作日志 服务类
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023-12-26 22:09
*/
public interface HostSftpLogService {
/**
* 分页查询 SFTP 操作日志
*
* @param request request
* @return rows
*/
DataGrid<HostSftpLogVO> querySftpLogPage(HostSftpLogQueryRequest request);
}

View File

@@ -0,0 +1,73 @@
package com.orion.ops.module.asset.service.impl;
import com.orion.lang.define.wrapper.DataGrid;
import com.orion.lang.utils.Arrays1;
import com.orion.lang.utils.Strings;
import com.orion.ops.module.asset.convert.HostSftpLogConvert;
import com.orion.ops.module.asset.define.operator.HostTerminalOperatorType;
import com.orion.ops.module.asset.entity.request.host.HostSftpLogQueryRequest;
import com.orion.ops.module.asset.entity.vo.HostSftpLogVO;
import com.orion.ops.module.asset.service.HostSftpLogService;
import com.orion.ops.module.infra.api.OperatorLogApi;
import com.orion.ops.module.infra.entity.dto.operator.OperatorLogDTO;
import com.orion.ops.module.infra.entity.dto.operator.OperatorLogQueryDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
import java.util.stream.Collectors;
/**
* SFTP 操作日志 服务实现类
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/3/4 23:35
*/
@Slf4j
@Service
public class HostSftpLogServiceImpl implements HostSftpLogService {
@Resource
private OperatorLogApi operatorLogApi;
@Override
public DataGrid<HostSftpLogVO> querySftpLogPage(HostSftpLogQueryRequest request) {
Long hostId = request.getHostId();
String type = request.getType();
// 构建参数
OperatorLogQueryDTO query = OperatorLogQueryDTO.builder()
.userId(request.getUserId())
.result(request.getResult())
.startTimeStart(Arrays1.getIfPresent(request.getStartTimeRange(), 0))
.startTimeEnd(Arrays1.getIfPresent(request.getStartTimeRange(), 1))
.build();
query.setPage(request.getPage());
query.setLimit(request.getLimit());
if (Strings.isBlank(type)) {
// 查询全部 SFTP 类型
query.setTypeList(HostTerminalOperatorType.SFTP_TYPES);
} else {
query.setType(type);
}
// 模糊查询
if (hostId != null) {
query.setExtra("\"hostId\": " + hostId + ",");
}
// 查询
DataGrid<OperatorLogDTO> dataGrid = operatorLogApi.getOperatorLogList(query);
// 返回
DataGrid<HostSftpLogVO> result = new DataGrid<>();
List<HostSftpLogVO> rows = dataGrid.stream()
.map(HostSftpLogConvert.MAPPER::to)
.collect(Collectors.toList());
result.setRows(rows);
result.setPage(dataGrid.getPage());
result.setLimit(dataGrid.getLimit());
result.setSize(dataGrid.getSize());
result.setTotal(dataGrid.getTotal());
return result;
}
}

View File

@@ -0,0 +1,24 @@
package com.orion.ops.module.infra.api;
import com.orion.lang.define.wrapper.DataGrid;
import com.orion.ops.module.infra.entity.dto.operator.OperatorLogDTO;
import com.orion.ops.module.infra.entity.dto.operator.OperatorLogQueryDTO;
/**
* 操作日志服务
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/3/4 23:11
*/
public interface OperatorLogApi {
/**
* 操作日志服务
*
* @param request request
* @return rows
*/
DataGrid<OperatorLogDTO> getOperatorLogList(OperatorLogQueryDTO request);
}

View File

@@ -0,0 +1,85 @@
package com.orion.ops.module.infra.entity.dto.operator;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
/**
* 操作日志 业务对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023-10-10 17:08
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "OperatorLogDTO", description = "操作日志 业务对象")
public class OperatorLogDTO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "id")
private Long id;
@Schema(description = "用户id")
private Long userId;
@Schema(description = "用户名")
private String username;
@Schema(description = "traceId")
private String traceId;
@Schema(description = "请求ip")
private String address;
@Schema(description = "请求地址")
private String location;
@Schema(description = "userAgent")
private String userAgent;
@Schema(description = "风险等级")
private String riskLevel;
@Schema(description = "模块")
private String module;
@Schema(description = "操作类型")
private String type;
@Schema(description = "日志")
private String logInfo;
@Schema(description = "参数")
private String extra;
@Schema(description = "操作结果 0失败 1成功")
private Integer result;
@Schema(description = "错误信息")
private String errorMessage;
@Schema(description = "返回值")
private String returnValue;
@Schema(description = "操作时间")
private Integer duration;
@Schema(description = "开始时间")
private Date startTime;
@Schema(description = "结束时间")
private Date endTime;
@Schema(description = "创建时间")
private Date createTime;
}

View File

@@ -0,0 +1,61 @@
package com.orion.ops.module.infra.entity.dto.operator;
import com.orion.ops.framework.common.entity.PageRequest;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import javax.validation.constraints.Size;
import java.util.Date;
import java.util.List;
/**
* 操作日志 查询对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023-10-10 17:08
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@Schema(name = "OperatorLogQueryDTO", description = "操作日志 查询对象")
public class OperatorLogQueryDTO extends PageRequest {
private static final long serialVersionUID = 1L;
@Schema(description = "id")
private Long id;
@Schema(description = "用户id")
private Long userId;
@Size(max = 32)
@Schema(description = "模块")
private String module;
@Size(max = 1)
@Schema(description = "风险等级")
private String riskLevel;
@Size(max = 64)
@Schema(description = "操作类型")
private String type;
@Schema(description = "操作类型")
private List<String> typeList;
@Schema(description = "参数")
private String extra;
@Schema(description = "操作结果 0失败 1成功")
private Integer result;
@Schema(description = "开始时间区间 - 开始")
private Date startTimeStart;
@Schema(description = "开始时间区间 - 结束")
private Date startTimeEnd;
}

View File

@@ -0,0 +1,61 @@
package com.orion.ops.module.infra.api.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.orion.lang.define.wrapper.DataGrid;
import com.orion.ops.framework.common.utils.Valid;
import com.orion.ops.module.infra.api.OperatorLogApi;
import com.orion.ops.module.infra.convert.OperatorLogProviderConvert;
import com.orion.ops.module.infra.dao.OperatorLogDAO;
import com.orion.ops.module.infra.entity.domain.OperatorLogDO;
import com.orion.ops.module.infra.entity.dto.operator.OperatorLogDTO;
import com.orion.ops.module.infra.entity.dto.operator.OperatorLogQueryDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* 操作日志服务实现
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/3/4 23:18
*/
@Slf4j
@Service
public class OperatorLogApiImpl implements OperatorLogApi {
@Resource
private OperatorLogDAO operatorLogDAO;
@Override
public DataGrid<OperatorLogDTO> getOperatorLogList(OperatorLogQueryDTO request) {
Valid.valid(request);
return operatorLogDAO.of()
.page(request)
.wrapper(this.buildQueryWrapper(request))
.dataGrid(OperatorLogProviderConvert.MAPPER::to);
}
/**
* 构建查询 wrapper
*
* @param request request
* @return wrapper
*/
private LambdaQueryWrapper<OperatorLogDO> buildQueryWrapper(OperatorLogQueryDTO request) {
return operatorLogDAO.wrapper()
.eq(OperatorLogDO::getId, request.getId())
.eq(OperatorLogDO::getUserId, request.getUserId())
.eq(OperatorLogDO::getRiskLevel, request.getRiskLevel())
.eq(OperatorLogDO::getModule, request.getModule())
.eq(OperatorLogDO::getType, request.getType())
.in(OperatorLogDO::getType, request.getTypeList())
.eq(OperatorLogDO::getResult, request.getResult())
.like(OperatorLogDO::getExtra, request.getExtra())
.ge(OperatorLogDO::getStartTime, request.getStartTimeStart())
.le(OperatorLogDO::getStartTime, request.getStartTimeEnd())
.orderByDesc(OperatorLogDO::getId);
}
}

View File

@@ -14,9 +14,4 @@ Authorization: {{token}}
"endTime": ""
}
### 查询登录日志
GET {{baseUrl}}/infra/operator-log/login-history?username=admin
Content-Type: application/json
Authorization: {{token}}
###

View File

@@ -8,7 +8,6 @@ import com.orion.ops.framework.log.core.enums.IgnoreLogMode;
import com.orion.ops.framework.web.core.annotation.RestWrapper;
import com.orion.ops.module.infra.define.operator.OperatorLogOperatorType;
import com.orion.ops.module.infra.entity.request.operator.OperatorLogQueryRequest;
import com.orion.ops.module.infra.entity.vo.LoginHistoryVO;
import com.orion.ops.module.infra.entity.vo.OperatorLogVO;
import com.orion.ops.module.infra.service.OperatorLogService;
import io.swagger.v3.oas.annotations.Operation;
@@ -72,13 +71,5 @@ public class OperatorLogController {
return operatorLogService.clearOperatorLog(request);
}
@IgnoreLog(IgnoreLogMode.RET)
@GetMapping("/login-history")
@Operation(summary = "查询用户登录日志")
@PreAuthorize("@ss.hasPermission('infra:system-user:login-history')")
public List<LoginHistoryVO> getLoginHistory(@RequestParam("username") String username) {
return operatorLogService.getLoginHistory(username);
}
}

View File

@@ -67,4 +67,10 @@ DELETE {{baseUrl}}/infra/system-user/delete?id=1
Authorization: {{token}}
### 查询登录日志
GET {{baseUrl}}/infra/system-user/login-history?username=admin
Content-Type: application/json
Authorization: {{token}}
###

View File

@@ -10,8 +10,10 @@ import com.orion.ops.framework.log.core.enums.IgnoreLogMode;
import com.orion.ops.framework.web.core.annotation.RestWrapper;
import com.orion.ops.module.infra.define.operator.SystemUserOperatorType;
import com.orion.ops.module.infra.entity.request.user.*;
import com.orion.ops.module.infra.entity.vo.LoginHistoryVO;
import com.orion.ops.module.infra.entity.vo.SystemUserVO;
import com.orion.ops.module.infra.entity.vo.UserSessionVO;
import com.orion.ops.module.infra.service.OperatorLogService;
import com.orion.ops.module.infra.service.SystemUserManagementService;
import com.orion.ops.module.infra.service.SystemUserRoleService;
import com.orion.ops.module.infra.service.SystemUserService;
@@ -51,6 +53,9 @@ public class SystemUserController {
@Resource
private SystemUserManagementService systemUserManagementService;
@Resource
private OperatorLogService operatorLogService;
@OperatorLog(SystemUserOperatorType.CREATE)
@PostMapping("/create")
@Operation(summary = "创建用户")
@@ -159,5 +164,12 @@ public class SystemUserController {
return HttpWrapper.ok();
}
}
@IgnoreLog(IgnoreLogMode.RET)
@GetMapping("/login-history")
@Operation(summary = "查询用户登录日志")
@PreAuthorize("@ss.hasPermission('infra:system-user:login-history')")
public List<LoginHistoryVO> getLoginHistory(@RequestParam("username") String username) {
return operatorLogService.getLoginHistory(username);
}
}

View File

@@ -0,0 +1,22 @@
package com.orion.ops.module.infra.convert;
import com.orion.ops.module.infra.entity.domain.OperatorLogDO;
import com.orion.ops.module.infra.entity.dto.operator.OperatorLogDTO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* 操作日志 对外对象转换器
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023-10-10 17:08
*/
@Mapper
public interface OperatorLogProviderConvert {
OperatorLogProviderConvert MAPPER = Mappers.getMapper(OperatorLogProviderConvert.class);
OperatorLogDTO to(OperatorLogDO domain);
}

View File

@@ -41,19 +41,6 @@ export interface OperatorLogQueryResponse {
createTime: number;
}
/**
* 登录日志查询响应
*/
export interface LoginHistoryQueryResponse {
id: number;
address: string;
location: string;
userAgent: string;
result: number;
errorMessage: string;
createTime: number;
}
/**
* 分页操作日志
*/
@@ -86,10 +73,3 @@ export function getOperatorLogCount(request: OperatorLogQueryRequest) {
export function clearOperatorLog(request: OperatorLogQueryRequest) {
return axios.post<number>('/infra/operator-log/clear', request);
}
/**
* 查询登录日志
*/
export function getLoginHistory(username: string) {
return axios.get<LoginHistoryQueryResponse[]>('/infra/operator-log/login-history', { params: { username } });
}

View File

@@ -77,6 +77,19 @@ export interface UserSessionOfflineRequest {
timestamp: number;
}
/**
* 登录日志查询响应
*/
export interface LoginHistoryQueryResponse {
id: number;
address: string;
location: string;
userAgent: string;
result: number;
errorMessage: string;
createTime: number;
}
/**
* 创建用户
*/
@@ -160,3 +173,10 @@ export function getUserSessionList(id: number) {
export function offlineUserSession(request: UserSessionOfflineRequest) {
return axios.put('/infra/system-user/session/offline', request);
}
/**
* 查询登录日志
*/
export function getLoginHistory(username: string) {
return axios.get<LoginHistoryQueryResponse[]>('/infra/system-user/login-history', { params: { username } });
}

View File

@@ -59,14 +59,13 @@
</script>
<script lang="ts" setup>
import type { UserQueryResponse } from '@/api/user/user';
import type { LoginHistoryQueryResponse } from '@/api/user/operator-log';
import type { UserQueryResponse, LoginHistoryQueryResponse } from '@/api/user/user';
import type { PropType } from 'vue';
import useLoading from '@/hooks/loading';
import { ref, onBeforeMount } from 'vue';
import { ResultStatus } from '../types/const';
import { getCurrentLoginHistory } from '@/api/user/mine';
import { getLoginHistory } from '@/api/user/operator-log';
import { getLoginHistory } from '@/api/user/user';
import { dateFormat } from '@/utils';
import { isMobile } from '@/utils/is';