操作记录和审计相关功能

This commit is contained in:
chenxudong 2025-02-25 12:07:33 +08:00
parent 384c54a842
commit 0e4e320b94
16 changed files with 217 additions and 46 deletions

View File

@ -115,6 +115,7 @@ public class LoginInterceptor implements HandlerInterceptor {
.accessDuration(accessEndTime - accessStartTime)
.action(userOperation.value())
.requestUrl(request.getRequestURL().toString())
.requestIp(parseIpFromUrl(request.getRequestURL().toString()))
.reqArgs(reqArgs)
.remoteAddr(getRealIp(request))
.accessSuccess(true)
@ -159,4 +160,12 @@ public class LoginInterceptor implements HandlerInterceptor {
return ipAddress;
}
private static String parseIpFromUrl(String url) {
int start = url.indexOf("//");
url = url.substring(start + 2);
int end = url.indexOf("/");
return url.substring(0, end);
}
}

View File

@ -0,0 +1,11 @@
package com.electromagnetic.industry.software.manage.controller;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AccessLogController {
}

View File

@ -160,4 +160,9 @@ public class EdFileInfoController {
public ResponseEntity<InputStreamResource> preview(@RequestParam String id, HttpServletResponse response) {
return edFileInfoService.preview(id, response, DataOwnEnum.COMMON.code);
}
}

View File

@ -1,5 +1,6 @@
package com.electromagnetic.industry.software.manage.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.electromagnetic.industry.software.manage.pojo.models.User;
import com.electromagnetic.industry.software.manage.pojo.other.PublishParam;
import com.electromagnetic.industry.software.manage.pojo.other.SearchKeyWords;
@ -9,7 +10,7 @@ import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface UserMapper {
public interface UserMapper extends BaseMapper<User> {
/**
* 插入

View File

@ -31,6 +31,9 @@ public class UserAccessLog {
// 请求的url
private String requestUrl;
// 请求的ip
private String requestIp;
// 请求的参数
private String reqArgs;
@ -55,4 +58,7 @@ public class UserAccessLog {
// 数据id
private String dataId;
// 父id最权限需要
private String parentId;
}

View File

@ -0,0 +1,14 @@
package com.electromagnetic.industry.software.manage.pojo.req;
import lombok.Data;
@Data
public class AccessLogQueryDTO {
private String dataId;
private int pageNum;
private int pageSize;
}

View File

@ -0,0 +1,40 @@
package com.electromagnetic.industry.software.manage.pojo.resp;
import lombok.Data;
import java.util.Date;
@Data
public class AccessLogQueryVO {
private String id;
private String userId;
private String username;
// 进行的操作
private String action;
// 操作对象
private String operationModule;
// 操作详情
private String operationMsg;
// 操作时间
private Date createTime;
// 请求远端地址
private String remoteAddr;
// 请求是否成功
private Boolean accessSuccess;
// 请求耗时
private Long accessDuration;
// 请求的ip
private String requestIp;
}

View File

@ -1,19 +0,0 @@
package com.electromagnetic.industry.software.manage.pojo.resp;
import com.electromagnetic.industry.software.manage.pojo.other.FileInfoVO;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class FileInfoQueryPageVO {
private long total;
private List<FileInfoVO> records = new ArrayList<>();
}

View File

@ -0,0 +1,23 @@
package com.electromagnetic.industry.software.manage.pojo.resp;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.List;
@NoArgsConstructor
@Data
public class RespPageVO<T> implements Serializable {
private static final long serialVersionUID = 1L;
private List<T> list;
private long total;
public RespPageVO(long total, List<T> list) {
this.list = list;
this.total = total;
}
}

View File

@ -0,0 +1,15 @@
package com.electromagnetic.industry.software.manage.service;
import com.electromagnetic.industry.software.common.resp.ElectromagneticResult;
import com.electromagnetic.industry.software.manage.pojo.req.AccessLogQueryDTO;
public interface UserAccessLogService {
/**
* 分页查询操作记录审计
* @param accessLogQueryDTO
* @return
*/
ElectromagneticResult<?> info(AccessLogQueryDTO accessLogQueryDTO, boolean adminQuery);
}

View File

@ -11,7 +11,6 @@ import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.electromagnetic.industry.software.common.cons.ElectromagneticConstants;
import com.electromagnetic.industry.software.common.enums.*;
import com.electromagnetic.industry.software.common.exception.BizException;
import com.electromagnetic.industry.software.common.resp.ElectromagneticResult;
@ -24,7 +23,6 @@ import com.electromagnetic.industry.software.manage.pojo.models.EdFileInfo;
import com.electromagnetic.industry.software.manage.pojo.resp.FileProjectVO;
import com.electromagnetic.industry.software.manage.pojo.resp.ProjectVO;
import com.electromagnetic.industry.software.manage.service.FileSystemService;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

View File

@ -145,8 +145,8 @@ public class EdFileInfoServiceImpl extends ServiceImpl<EdFileInfoMapper, EdFileI
long total = edFileInfoPage.getTotal();
List<FileInfoVO> records = BeanUtil.copyToList(edFileInfoPage.getRecords(), FileInfoVO.class);
resetFileSize(records);
UserThreadLocal.setSuccessInfo("", "查询搜索文件成功");
return ElectromagneticResultUtil.success(new FileInfoQueryPageVO(total, records));
UserThreadLocal.setSuccessInfo("", "", "查询搜索文件成功");
return ElectromagneticResultUtil.success(new RespPageVO<>(total, records));
}
@Override
@ -174,7 +174,7 @@ public class EdFileInfoServiceImpl extends ServiceImpl<EdFileInfoMapper, EdFileI
Assert.isTrue(EleCommonUtil.isFileNameValid(createFolderDTO.getNewFolderName()), NAME_VALID_MSG);
String folderId = IdWorker.getSnowFlakeIdString();
ElectromagneticResult<?> res = commonService.addFolder(createFolderDTO.getParentId(), createFolderDTO.getNewFolderName(), false, folderId, createFolderDTO.getFileNote(), dataOwnCode);
UserThreadLocal.setSuccessInfo(res.getData() + "", "创建文件夹成功");
UserThreadLocal.setSuccessInfo(createFolderDTO.getParentId(), res.getData() + "", "创建文件夹成功");
return res;
}
@ -196,7 +196,7 @@ public class EdFileInfoServiceImpl extends ServiceImpl<EdFileInfoMapper, EdFileI
res.add(fileProjectVO);
});
res.sort(Comparator.comparing(FileProjectVO::getSort));
UserThreadLocal.setSuccessInfo("", "查询项目层级结构成功");
UserThreadLocal.setSuccessInfo("", "", "查询项目层级结构成功");
return ElectromagneticResultUtil.success(res);
}
@ -247,7 +247,7 @@ public class EdFileInfoServiceImpl extends ServiceImpl<EdFileInfoMapper, EdFileI
headers.add("Expires", "0");
String newFileName = Base64.encode(fileName.substring(0, fileName.lastIndexOf(".")));
response.setHeader("content-disposition", "attachment;filename=" + newFileName);
UserThreadLocal.setSuccessInfo(id, "下载文件 {} 成功", fileName);
UserThreadLocal.setSuccessInfo(fileInfo.getFileId(), "下载文件 {} 成功", fileName);
// 构建响应实体(可以返回<byte[]或Resource返回类型取决body入参类型)
return ResponseEntity
.ok()
@ -335,7 +335,7 @@ public class EdFileInfoServiceImpl extends ServiceImpl<EdFileInfoMapper, EdFileI
@Override
@Transactional(rollbackFor = Exception.class)
public ElectromagneticResult<?> versionBack(String fileId, int targetVersion) {
EdFileInfo fileInfo = this.baseMapper.selectList(Wrappers.<EdFileInfo>lambdaQuery().eq(EdFileInfo::getFileId, fileId).last("limit 1")).get(0);
try {
this.baseMapper.update(null, Wrappers.lambdaUpdate(EdFileInfo.class)
.eq(EdFileInfo::getFileId, fileId)
@ -349,7 +349,7 @@ public class EdFileInfoServiceImpl extends ServiceImpl<EdFileInfoMapper, EdFileI
log.error(info, e);
throw new BizException(info);
}
UserThreadLocal.setSuccessInfo(fileId, "回退版本成功,新版本为 {}", targetVersion);
UserThreadLocal.setSuccessInfo(fileInfo.getParentId(), fileId, "回退版本成功,新版本为 {}", targetVersion);
return ElectromagneticResultUtil.success(true);
}
@ -442,7 +442,7 @@ public class EdFileInfoServiceImpl extends ServiceImpl<EdFileInfoMapper, EdFileI
ZipUtil.unzip(zipDirPath, tmpDir);
update2Database(tmpDir, dataOwnCode);
fileSystemService.deleteFile(zipDirPath, destColibPath);
UserThreadLocal.setSuccessInfo("", "导入数据库成功");
UserThreadLocal.setSuccessInfo("", "", "导入数据库成功");
return ElectromagneticResultUtil.success(true);
}
@ -712,7 +712,7 @@ public class EdFileInfoServiceImpl extends ServiceImpl<EdFileInfoMapper, EdFileI
headers.add("Pragma", "no-cache");
headers.add("Expires", "0");
response.setHeader("content-disposition", "attachment;filename=" + fileName);
UserThreadLocal.setSuccessInfo("", "导出数据库成功");
UserThreadLocal.setSuccessInfo("", "", "导出数据库成功");
// 构建响应实体(可以返回<byte[]或Resource返回类型取决body入参类型)
return ResponseEntity
.ok()
@ -805,7 +805,7 @@ public class EdFileInfoServiceImpl extends ServiceImpl<EdFileInfoMapper, EdFileI
log.error(info, e);
throw new BizException(info);
}
UserThreadLocal.setSuccessInfo(finalEdFileInfo.getId(), "文件 {} 为上传到 {} 成功,同名同后缀的处理方式为 {},存入的文件名为 {}", fileName, destPath, strategyStr, finalEdFileInfo.getFileName()+ "." + finalEdFileInfo.getFileType());
UserThreadLocal.setSuccessInfo(finalEdFileInfo.getFileId(), "文件 {} 为上传到 {} 成功,同名同后缀的处理方式为 {},存入的文件名为 {}", fileName, destPath, strategyStr, finalEdFileInfo.getFileName()+ "." + finalEdFileInfo.getFileType());
return ElectromagneticResultUtil.success(true);
}
@ -821,7 +821,7 @@ public class EdFileInfoServiceImpl extends ServiceImpl<EdFileInfoMapper, EdFileI
.select(EdFileInfo::getParentId, EdFileInfo::getId, EdFileInfo::getFileId, EdFileInfo::getFileVersion, EdFileInfo::getPreVersion, EdFileInfo::getFileCode, EdFileInfo::getEffectFlag, EdFileInfo::getFileName)
.eq(EdFileInfo::getFileId, fileId));
List<FileVersionViewVO> fileVersionViewVOS = BeanUtil.copyToList(edFileInfos, FileVersionViewVO.class);
UserThreadLocal.setSuccessInfo(fileId, "查询版本信息成功");
UserThreadLocal.setSuccessInfo(edFileInfos.get(0).getParentId(), fileId, "查询版本信息成功");
return ElectromagneticResultUtil.success(fileVersionViewVOS);
}
@ -866,7 +866,7 @@ public class EdFileInfoServiceImpl extends ServiceImpl<EdFileInfoMapper, EdFileI
} else {
srcFileInfo = handMoveConflict(targetFolderId, strategy, srcFileInfo, destFolderInfo, dataOwnCode);
}
UserThreadLocal.setSuccessInfo(id, "文件 {} 移动到 {},成功,处理文件同名同后缀的方式为 {},最终文件名为 {}", srcFileInfo.getFileName() + "." + srcFileInfo.getFileName(),
UserThreadLocal.setSuccessInfo(srcFileInfo.getFileId(), "文件 {} 移动到 {},成功,处理文件同名同后缀的方式为 {},最终文件名为 {}", srcFileInfo.getFileName() + "." + srcFileInfo.getFileName(),
commonService.getDbPath(destFolderInfo.getFilePath()), FileRepeatEnum.getDesc(strategy), srcFileInfo.getFileName());
return ElectromagneticResultUtil.success(true);
}
@ -975,7 +975,7 @@ public class EdFileInfoServiceImpl extends ServiceImpl<EdFileInfoMapper, EdFileI
} else {
destFileInfo = handCopyConflict(targetFolderId, strategy, srcFileInfo, destFolderInfo, dataOwnCode);
}
UserThreadLocal.setSuccessInfo(id, "文件 {} 复制到 {},成功,处理文件同名同后缀的方式为 {},最终文件名为 {}", destFileInfo.getFileName() + "." + destFileInfo.getFileName(),
UserThreadLocal.setSuccessInfo(destFileInfo.getFileId(), "文件 {} 复制到 {},成功,处理文件同名同后缀的方式为 {},最终文件名为 {}", destFileInfo.getFileName() + "." + destFileInfo.getFileName(),
commonService.getDbPath(destFolderInfo.getFilePath()), FileRepeatEnum.getDesc(strategy), srcFileInfo.getFileName());
return ElectromagneticResultUtil.success(true);
}
@ -1012,7 +1012,7 @@ public class EdFileInfoServiceImpl extends ServiceImpl<EdFileInfoMapper, EdFileI
Page<EdFileInfo> edFileInfoPage = this.baseMapper.selectPage(new Page<>(pageNum, pageSize), lambdaQuery);
long total = edFileInfoPage.getTotal();
List<UploadRecordDTO> uploadRecordDTOS = BeanUtil.copyToList(edFileInfoPage.getRecords(), UploadRecordDTO.class);
UserThreadLocal.setSuccessInfo("", "查看了发布管理");
UserThreadLocal.setSuccessInfo("", "", "查看了发布管理");
return ElectromagneticResultUtil.success(new UploadRecordVO(total, uploadRecordDTOS));
}
@ -1028,7 +1028,7 @@ public class EdFileInfoServiceImpl extends ServiceImpl<EdFileInfoMapper, EdFileI
User singleUser = userMapper.getSingleUser(fileInfo.getCreatedBy());
fileInfo.setCreatedBy(singleUser.getUserName());
FileInfoVO fileInfoVO = BeanUtil.copyProperties(fileInfo, FileInfoVO.class);
UserThreadLocal.setSuccessInfo(id, "查询了文件的详细信息");
UserThreadLocal.setSuccessInfo(fileInfo.getParentId(), id, "查询了文件的详细信息");
return ElectromagneticResultUtil.success(fileInfoVO);
}
@ -1235,7 +1235,7 @@ public class EdFileInfoServiceImpl extends ServiceImpl<EdFileInfoMapper, EdFileI
.eq(EdFileInfo::getParentId, parentId)
.eq(EdFileInfo::getEffectFlag, EffectFlagEnum.EFFECT.code));
List<ChildFolderVO> res = BeanUtil.copyToList(edFileInfos, ChildFolderVO.class);
UserThreadLocal.setSuccessInfo(parentId, "查询了子文件集");
UserThreadLocal.setSuccessInfo(parentId, parentId, "查询了子文件集");
return ElectromagneticResultUtil.success(res);
}
@ -1266,7 +1266,7 @@ public class EdFileInfoServiceImpl extends ServiceImpl<EdFileInfoMapper, EdFileI
fileName = Base64.encode(fileName.substring(0, fileName.lastIndexOf(".")));
response.setHeader("content-disposition", "attachment;filename=" + fileName);
// 构建响应实体(可以返回<byte[]或Resource返回类型取决body入参类型)
UserThreadLocal.setSuccessInfo(id, "文件预览成功,文件名为 {}", fileInfo.getFileName() + "." + fileInfo.getFileType());
UserThreadLocal.setSuccessInfo(fileInfo.getFileId(), "文件预览成功,文件名为 {}", fileInfo.getFileName() + "." + fileInfo.getFileType());
return ResponseEntity
.ok()
.headers(headers)

View File

@ -246,7 +246,7 @@ public class EdPrjServiceImpl extends ServiceImpl<EdFileInfoMapper, EdFileInfo>
projectVOS.add(projectVO);
});
projectVOS.sort(Comparator.comparing(ProjectVO::getSort));
UserThreadLocal.setSuccessInfo("", "查询了所有工程");
UserThreadLocal.setSuccessInfo("", "", "查询了所有工程");
return ElectromagneticResultUtil.success(projectVOS);
}
@ -270,7 +270,7 @@ public class EdPrjServiceImpl extends ServiceImpl<EdFileInfoMapper, EdFileInfo>
.eq(EdFileInfo::getId, folderResortDTO.getId());
this.update(updateWrapper);
}
UserThreadLocal.setSuccessInfo("", "子集重排序成功");
UserThreadLocal.setSuccessInfo("", "", "子集重排序成功");
return ElectromagneticResultUtil.success(true);
} catch (Exception e) {
String info = "子集重排序异常";
@ -427,7 +427,7 @@ public class EdPrjServiceImpl extends ServiceImpl<EdFileInfoMapper, EdFileInfo>
for (String path : needSavePaths) {
fileSystemService.createDirectory(path);
}
UserThreadLocal.setSuccessInfo(targetId, "层级沿用成功");
UserThreadLocal.setSuccessInfo("", targetId, "层级沿用成功");
return ElectromagneticResultUtil.success(true);
} catch (Exception e) {
String info = StrFormatter.format("层级沿用失败,源工程 {},目标工程 {},原因 {}", sourceId, targetId, e.getMessage());

View File

@ -0,0 +1,64 @@
package com.electromagnetic.industry.software.manage.service.serviceimpl;
import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.electromagnetic.industry.software.common.resp.ElectromagneticResult;
import com.electromagnetic.industry.software.common.util.ElectromagneticResultUtil;
import com.electromagnetic.industry.software.manage.mapper.UserAccessLogMapper;
import com.electromagnetic.industry.software.manage.mapper.UserMapper;
import com.electromagnetic.industry.software.manage.pojo.models.User;
import com.electromagnetic.industry.software.manage.pojo.models.UserAccessLog;
import com.electromagnetic.industry.software.manage.pojo.req.AccessLogQueryDTO;
import com.electromagnetic.industry.software.manage.pojo.resp.AccessLogQueryVO;
import com.electromagnetic.industry.software.manage.pojo.resp.RespPageVO;
import com.electromagnetic.industry.software.manage.service.PermissionService;
import com.electromagnetic.industry.software.manage.service.UserAccessLogService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
public class UserAccessLogServiceImpl extends ServiceImpl<UserAccessLogMapper, UserAccessLog> implements UserAccessLogService {
@Resource
private UserMapper userMapper;
@Resource
private PermissionService permissionService;
/**
* 分页查询操作记录审计
*
* @param pars
* @return
*/
@Override
public ElectromagneticResult<?> info(AccessLogQueryDTO pars, boolean adminQuery) {
LambdaQueryWrapper<UserAccessLog> queryWrapper = Wrappers.lambdaQuery(UserAccessLog.class);
if (!adminQuery) {
queryWrapper.eq(UserAccessLog::getDataId, pars.getDataId());
}
Page<UserAccessLog> logs = this.baseMapper.selectPage(new Page<>(pars.getPageNum(), pars.getPageSize()), queryWrapper);
List<UserAccessLog> records = logs.getRecords();
List<AccessLogQueryVO> res = BeanUtil.copyToList(records, AccessLogQueryVO.class);
setUserName(res);
return ElectromagneticResultUtil.success(new RespPageVO<>(logs.getTotal(), res));
}
private void setUserName(List<AccessLogQueryVO> res) {
List<String> userIds = res.stream().map(AccessLogQueryVO::getUserId).collect(Collectors.toList());
LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery(User.class).select(User::getUserId, User::getUserName).in(User::getUserId, userIds);
Map<String, String> idNameMap = userMapper.selectList(wrapper).stream().collect(Collectors.toMap(User::getUserId, User::getUserName));
res.forEach(e -> e.setUsername(idNameMap.get(e.getUserId())));
}
}

View File

@ -41,6 +41,8 @@ public class UserLoginInfo {
*/
private ElectromagneticResult result;
private String parentId;
/**
* 访问请求的参数
*/
@ -52,9 +54,10 @@ public class UserLoginInfo {
private String dataId;
public void setSuccessInfo(String successMsg, String dataId) {
public void setSuccessInfo(String parentId, String successMsg, String dataId) {
this.successMsg = successMsg;
this.dataId = dataId;
this.parentId = parentId;
}
}

View File

@ -1,6 +1,7 @@
package com.electromagnetic.industry.software.common.util;
import cn.hutool.core.text.StrFormatter;
import com.electromagnetic.industry.software.common.pojo.UserLoginInfo;
import com.electromagnetic.industry.software.common.resp.ElectromagneticResult;
@ -50,9 +51,9 @@ public class UserThreadLocal {
return userThread.get().getReqArgs();
}
public static void setSuccessInfo(String dataId, String strPattern, Object... argArray) {
String successMsg = String.format(strPattern, argArray);
userThread.get().setSuccessInfo(successMsg, dataId);
public static void setSuccessInfo(String parentId, String dataId, String strPattern, Object... argArray) {
String successMsg = StrFormatter.format(strPattern, argArray);
userThread.get().setSuccessInfo(parentId, successMsg, dataId);
}
public static void setExceptionDetail(String exceptionDetail) {