fix: improve audit log (#176)

* fix: group owner and member can't view project log

* feat: improve audit log aspect

* fix: miss group id when request project log
This commit is contained in:
vran 2022-05-14 18:11:55 +08:00 committed by GitHub
parent bd07d8a018
commit 59570a9ee6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 100 additions and 35 deletions

View File

@ -25,7 +25,9 @@ public class AuditLogController {
private final OperationLogService operationLogService; private final OperationLogService operationLogService;
@GetMapping(Routes.OperationLog.LIST) @GetMapping(Routes.OperationLog.LIST)
@PreAuthorize("hasAnyAuthority('SYS_OWNER')") @PreAuthorize("hasAnyAuthority('SYS_OWNER',"
+ " 'GROUP_OWNER?groupId='+#condition.involveGroupId,"
+ " 'GROUP_MEMBER?groupId='+#condition.involveGroupId)")
@Operation(summary = "查询操作日志") @Operation(summary = "查询操作日志")
public JsonData<Page<OperationLogPageResponse>> list(@PageableDefault(sort = "id", direction = Sort.Direction.DESC) public JsonData<Page<OperationLogPageResponse>> list(@PageableDefault(sort = "id", direction = Sort.Direction.DESC)
Pageable page, Pageable page,

View File

@ -39,9 +39,10 @@ public class DocumentController {
private final ProjectService projectService; private final ProjectService projectService;
@PostMapping(Routes.Document.SYNC_ONE) @PostMapping(Routes.Document.SYNC_ONE)
@AuditLog(module = AuditLog.Modules.PROJECT, name = "文档同步", involvedProjectId = "#projectId") @AuditLog(module = AuditLog.Modules.PROJECT, name = "创建同步任务", involvedProjectId = "#projectId",
@Operation(summary = "同步文档") retrieveInvolvedGroupId = true)
public JsonData<Integer> sync(@PathVariable Integer projectId) { @Operation(summary = "创建同步任务")
public JsonData<Integer> createSyncTask(@PathVariable Integer projectId) {
Integer userId = LoginUserContext.getLoginUserId(); Integer userId = LoginUserContext.getLoginUserId();
Optional<Integer> taskIdOpt = projectService.createSyncTask(projectId, userId, false); Optional<Integer> taskIdOpt = projectService.createSyncTask(projectId, userId, false);
return JsonData.ok(taskIdOpt); return JsonData.ok(taskIdOpt);
@ -67,6 +68,8 @@ public class DocumentController {
@GetMapping(Routes.Document.EXPORT) @GetMapping(Routes.Document.EXPORT)
@Operation(summary = "导出文档") @Operation(summary = "导出文档")
@AuditLog(module = AuditLog.Modules.PROJECT, name = "导出文档", involvedProjectId = "#projectId",
retrieveInvolvedGroupId = true)
public ResponseEntity<StreamingResponseBody> getDocumentFiles(@PathVariable Integer projectId, public ResponseEntity<StreamingResponseBody> getDocumentFiles(@PathVariable Integer projectId,
@RequestParam(required = false) @RequestParam(required = false)
Long version, Long version,

View File

@ -30,7 +30,8 @@ public class DocumentDescriptionController {
@PreAuthorize("hasAnyAuthority('SYS_OWNER', 'GROUP_OWNER?groupId='+#groupId, 'GROUP_MEMBER?groupId='+#groupId)") @PreAuthorize("hasAnyAuthority('SYS_OWNER', 'GROUP_OWNER?groupId='+#groupId, 'GROUP_MEMBER?groupId='+#groupId)")
@AuditLog(module = AuditLog.Modules.PROJECT, @AuditLog(module = AuditLog.Modules.PROJECT,
name = "更新描述", name = "更新描述",
involvedProjectId = "#projectId") involvedProjectId = "#projectId",
retrieveInvolvedGroupId = true)
@Operation(summary = "更新描述") @Operation(summary = "更新描述")
public JsonData<Void> save(@PathVariable Integer groupId, public JsonData<Void> save(@PathVariable Integer groupId,
@PathVariable Integer projectId, @PathVariable Integer projectId,

View File

@ -35,7 +35,7 @@ public class DocumentDiscussionController {
@PathVariable Integer projectId, @PathVariable Integer projectId,
@PageableDefault(sort = "id", @PageableDefault(sort = "id",
direction = Sort.Direction.DESC) direction = Sort.Direction.DESC)
Pageable request, Pageable request,
DiscussionListCondition condition) { DiscussionListCondition condition) {
var data = documentDiscussionService.list(groupId, projectId, request, condition); var data = documentDiscussionService.list(groupId, projectId, request, condition);
return JsonData.ok(data); return JsonData.ok(data);
@ -45,7 +45,8 @@ public class DocumentDiscussionController {
@PreAuthorize("hasAnyAuthority('SYS_OWNER', 'GROUP_OWNER?groupId='+#groupId)") @PreAuthorize("hasAnyAuthority('SYS_OWNER', 'GROUP_OWNER?groupId='+#groupId)")
@AuditLog(module = AuditLog.Modules.PROJECT, @AuditLog(module = AuditLog.Modules.PROJECT,
name = "删除评论", name = "删除评论",
involvedProjectId = "#projectId") involvedProjectId = "#projectId",
retrieveInvolvedGroupId = true)
@Operation(summary = "删除评论") @Operation(summary = "删除评论")
public JsonData<Void> delete(@PathVariable Integer groupId, public JsonData<Void> delete(@PathVariable Integer groupId,
@PathVariable Integer projectId, @PathVariable Integer projectId,
@ -58,7 +59,8 @@ public class DocumentDiscussionController {
@PreAuthorize("hasAnyAuthority('SYS_OWNER', 'GROUP_OWNER?groupId='+#groupId, 'GROUP_MEMBER?groupId='+#groupId)") @PreAuthorize("hasAnyAuthority('SYS_OWNER', 'GROUP_OWNER?groupId='+#groupId, 'GROUP_MEMBER?groupId='+#groupId)")
@AuditLog(module = AuditLog.Modules.PROJECT, @AuditLog(module = AuditLog.Modules.PROJECT,
name = "新增评论", name = "新增评论",
involvedProjectId = "#projectId") involvedProjectId = "#projectId",
retrieveInvolvedGroupId = true)
@Operation(summary = "新增评论") @Operation(summary = "新增评论")
public JsonData<Void> create(@PathVariable Integer groupId, public JsonData<Void> create(@PathVariable Integer groupId,
@PathVariable Integer projectId, @PathVariable Integer projectId,

View File

@ -1,6 +1,7 @@
package com.databasir.api; package com.databasir.api;
import com.databasir.common.JsonData; import com.databasir.common.JsonData;
import com.databasir.core.domain.log.annotation.AuditLog;
import com.databasir.core.domain.mock.MockDataService; import com.databasir.core.domain.mock.MockDataService;
import com.databasir.core.domain.mock.data.ColumnMockRuleSaveRequest; import com.databasir.core.domain.mock.data.ColumnMockRuleSaveRequest;
import com.databasir.core.domain.mock.data.MockDataGenerateCondition; import com.databasir.core.domain.mock.data.MockDataGenerateCondition;
@ -45,6 +46,9 @@ public class MockDataController {
@PostMapping(Routes.MockData.SAVE_MOCK_RULE) @PostMapping(Routes.MockData.SAVE_MOCK_RULE)
@PreAuthorize("hasAnyAuthority('SYS_OWNER', 'GROUP_OWNER?groupId='+#groupId, 'GROUP_MEMBER?groupId='+#groupId)") @PreAuthorize("hasAnyAuthority('SYS_OWNER', 'GROUP_OWNER?groupId='+#groupId, 'GROUP_MEMBER?groupId='+#groupId)")
@Operation(summary = "保存 Mock Rule") @Operation(summary = "保存 Mock Rule")
@AuditLog(module = AuditLog.Modules.PROJECT, name = "保存 Mock Rule",
involvedProjectId = "#projectId",
involvedGroupId = "#groupId")
public JsonData<Void> saveMockRules(@PathVariable Integer groupId, public JsonData<Void> saveMockRules(@PathVariable Integer groupId,
@PathVariable Integer projectId, @PathVariable Integer projectId,
@PathVariable Integer tableId, @PathVariable Integer tableId,

View File

@ -107,6 +107,8 @@ public class ProjectController {
@PatchMapping(Routes.GroupProject.CANCEL_MANUAL_TASK) @PatchMapping(Routes.GroupProject.CANCEL_MANUAL_TASK)
@Operation(summary = "取消同步任务") @Operation(summary = "取消同步任务")
@AuditLog(module = AuditLog.Modules.PROJECT, name = "取消同步任务", involvedProjectId = "#projectId",
retrieveInvolvedGroupId = true)
public JsonData<Void> cancelTask(@PathVariable Integer projectId, public JsonData<Void> cancelTask(@PathVariable Integer projectId,
@PathVariable Integer taskId) { @PathVariable Integer taskId) {
projectService.cancelTask(projectId, taskId); projectService.cancelTask(projectId, taskId);

View File

@ -2,6 +2,7 @@ package com.databasir.api;
import com.databasir.api.config.security.DatabasirUserDetails; import com.databasir.api.config.security.DatabasirUserDetails;
import com.databasir.common.JsonData; import com.databasir.common.JsonData;
import com.databasir.core.domain.log.annotation.AuditLog;
import com.databasir.core.domain.user.data.FavoriteProjectPageCondition; import com.databasir.core.domain.user.data.FavoriteProjectPageCondition;
import com.databasir.core.domain.user.data.FavoriteProjectPageResponse; import com.databasir.core.domain.user.data.FavoriteProjectPageResponse;
import com.databasir.core.domain.user.service.UserProjectService; import com.databasir.core.domain.user.service.UserProjectService;
@ -39,6 +40,9 @@ public class UserProjectController {
@PostMapping(Routes.UserProject.ADD) @PostMapping(Routes.UserProject.ADD)
@Operation(summary = "添加用户关注项目") @Operation(summary = "添加用户关注项目")
@AuditLog(module = AuditLog.Modules.PROJECT, name = "关注项目",
involvedProjectId = "#projectId",
retrieveInvolvedGroupId = true)
public JsonData<Void> addFavorite(@PathVariable Integer projectId) { public JsonData<Void> addFavorite(@PathVariable Integer projectId) {
DatabasirUserDetails user = (DatabasirUserDetails) SecurityContextHolder.getContext() DatabasirUserDetails user = (DatabasirUserDetails) SecurityContextHolder.getContext()
.getAuthentication() .getAuthentication()
@ -50,6 +54,9 @@ public class UserProjectController {
@DeleteMapping(Routes.UserProject.REMOVE) @DeleteMapping(Routes.UserProject.REMOVE)
@Operation(summary = "删除用户关注项目") @Operation(summary = "删除用户关注项目")
@AuditLog(module = AuditLog.Modules.PROJECT, name = "取消关注",
involvedProjectId = "#projectId",
retrieveInvolvedGroupId = true)
public JsonData<Void> removeFavorite(@PathVariable Integer projectId) { public JsonData<Void> removeFavorite(@PathVariable Integer projectId) {
DatabasirUserDetails user = (DatabasirUserDetails) SecurityContextHolder.getContext() DatabasirUserDetails user = (DatabasirUserDetails) SecurityContextHolder.getContext()
.getAuthentication() .getAuthentication()

View File

@ -5,6 +5,8 @@ import com.databasir.common.JsonData;
import com.databasir.core.domain.log.annotation.AuditLog; import com.databasir.core.domain.log.annotation.AuditLog;
import com.databasir.core.domain.log.data.OperationLogRequest; import com.databasir.core.domain.log.data.OperationLogRequest;
import com.databasir.core.domain.log.service.OperationLogService; import com.databasir.core.domain.log.service.OperationLogService;
import com.databasir.dao.impl.ProjectDao;
import com.databasir.dao.tables.pojos.ProjectPojo;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.JoinPoint;
@ -33,13 +35,19 @@ public class OperationLogAspect {
private final OperationLogService operationLogService; private final OperationLogService operationLogService;
private final ProjectDao projectDao;
private SpelExpressionParser spelExpressionParser = new SpelExpressionParser(); private SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer(); private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
@AfterReturning(value = "@annotation(operation)", returning = "returnValue") @AfterReturning(value = "@annotation(operation)", returning = "returnValue")
public void log(JoinPoint joinPoint, Object returnValue, AuditLog operation) { public void log(JoinPoint joinPoint, Object returnValue, AuditLog operation) {
saveLog(operation, joinPoint, (JsonData<Object>) returnValue); if (returnValue instanceof JsonData) {
saveLog(operation, joinPoint, (JsonData<Object>) returnValue);
} else {
saveLog(operation, joinPoint, JsonData.ok());
}
} }
@AfterThrowing(value = "@annotation(operation)", throwing = "ex") @AfterThrowing(value = "@annotation(operation)", throwing = "ex")
@ -62,6 +70,14 @@ public class OperationLogAspect {
.orElse(null); .orElse(null);
Integer involvedUserId = getValueBySPEL(method, arguments, operation.involvedUserId(), Integer.class) Integer involvedUserId = getValueBySPEL(method, arguments, operation.involvedUserId(), Integer.class)
.orElse(null); .orElse(null);
// auto fill involvedProjectId
if (involvedGroupId == null
&& operation.retrieveInvolvedGroupId()
&& involvedProjectId != null) {
involvedGroupId = projectDao.selectOptionalById(involvedProjectId)
.map(ProjectPojo::getGroupId)
.orElse(null);
}
int userId = userId(); int userId = userId();
String username = principal.getUserPojo().getUsername(); String username = principal.getUserPojo().getUsername();
String nickname = principal.getUserPojo().getNickname(); String nickname = principal.getUserPojo().getNickname();

View File

@ -5,8 +5,10 @@ import com.databasir.core.domain.document.service.DocumentService;
import com.databasir.core.domain.log.data.OperationLogRequest; import com.databasir.core.domain.log.data.OperationLogRequest;
import com.databasir.core.domain.log.service.OperationLogService; import com.databasir.core.domain.log.service.OperationLogService;
import com.databasir.dao.enums.ProjectSyncTaskStatus; import com.databasir.dao.enums.ProjectSyncTaskStatus;
import com.databasir.dao.impl.ProjectDao;
import com.databasir.dao.impl.ProjectSyncTaskDao; import com.databasir.dao.impl.ProjectSyncTaskDao;
import com.databasir.dao.impl.UserDao; import com.databasir.dao.impl.UserDao;
import com.databasir.dao.tables.pojos.ProjectSyncTaskPojo;
import com.databasir.dao.tables.pojos.UserPojo; import com.databasir.dao.tables.pojos.UserPojo;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -14,7 +16,10 @@ import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Collectors;
@Component @Component
@RequiredArgsConstructor @RequiredArgsConstructor
@ -29,6 +34,8 @@ public class ProjectSyncTaskScheduler {
private final ProjectSyncTaskDao projectSyncTaskDao; private final ProjectSyncTaskDao projectSyncTaskDao;
private final ProjectDao projectDao;
private final ThreadPoolTaskExecutor projectSyncTaskThreadPoolTaskExecutor; private final ThreadPoolTaskExecutor projectSyncTaskThreadPoolTaskExecutor;
/** /**
@ -37,26 +44,33 @@ public class ProjectSyncTaskScheduler {
@Scheduled(fixedRate = 5000L) @Scheduled(fixedRate = 5000L)
public void startSyncTask() { public void startSyncTask() {
final int size = 10; final int size = 10;
projectSyncTaskDao.listNewTasks(size).forEach(task -> { List<ProjectSyncTaskPojo> tasks = projectSyncTaskDao.listNewTasks(size);
List<Integer> projectIds = tasks.stream()
.map(ProjectSyncTaskPojo::getProjectId)
.distinct()
.collect(Collectors.toList());
Map<Integer, Integer> groupIdAndProjectIdMap = projectDao.selectGroupIdsByProjectIdIn(projectIds);
tasks.forEach(task -> {
projectSyncTaskThreadPoolTaskExecutor.execute(() -> { projectSyncTaskThreadPoolTaskExecutor.execute(() -> {
Integer taskId = task.getId(); Integer taskId = task.getId();
Integer projectId = task.getProjectId(); Integer projectId = task.getProjectId();
Integer groupId = groupIdAndProjectIdMap.get(projectId);
Integer userId = task.getUserId(); Integer userId = task.getUserId();
sync(taskId, projectId, userId); sync(taskId, groupId, projectId, userId);
}); });
}); });
} }
private void sync(Integer taskId, Integer projectId, Integer userId) { private void sync(Integer taskId, Integer groupId, Integer projectId, Integer userId) {
try { try {
updateSyncTaskStatus(taskId, ProjectSyncTaskStatus.RUNNING, "running"); updateSyncTaskStatus(taskId, ProjectSyncTaskStatus.RUNNING, "running");
documentService.syncByProjectId(projectId); documentService.syncByProjectId(projectId);
updateSyncTaskStatus(taskId, ProjectSyncTaskStatus.FINISHED, "ok"); updateSyncTaskStatus(taskId, ProjectSyncTaskStatus.FINISHED, "ok");
saveOperationLog(projectId, userId, null); saveOperationLog(groupId, projectId, userId, null);
} catch (Exception e) { } catch (Exception e) {
String result = Objects.requireNonNullElse(e.getMessage(), "unknown"); String result = Objects.requireNonNullElse(e.getMessage(), "unknown");
updateSyncTaskStatus(taskId, ProjectSyncTaskStatus.FAILED, result); updateSyncTaskStatus(taskId, ProjectSyncTaskStatus.FAILED, result);
saveOperationLog(projectId, userId, e); saveOperationLog(groupId, projectId, userId, e);
throw e; throw e;
} }
} }
@ -65,19 +79,19 @@ public class ProjectSyncTaskScheduler {
projectSyncTaskDao.updateStatusAndResultById(taskId, status, result); projectSyncTaskDao.updateStatusAndResultById(taskId, status, result);
} }
private void saveOperationLog(Integer projectId, Integer userId, Exception ex) { private void saveOperationLog(Integer groupId, Integer projectId, Integer userId, Exception ex) {
String operatorNickName; String operatorNickName;
String operatorUsername; String operatorUsername;
String operationName; String operationName;
if (Objects.equals(-1, userId)) { if (Objects.equals(-1, userId)) {
operatorNickName = "system"; operatorNickName = "system";
operatorUsername = "system"; operatorUsername = "system";
operationName = "文档定时同步"; operationName = "定时同步";
} else { } else {
UserPojo user = userDao.selectById(userId); UserPojo user = userDao.selectById(userId);
operatorNickName = user.getNickname(); operatorNickName = user.getNickname();
operatorUsername = user.getUsername(); operatorUsername = user.getUsername();
operationName = "文档手动同步"; operationName = "手动同步";
} }
JsonData response; JsonData response;
if (ex == null) { if (ex == null) {
@ -95,6 +109,7 @@ public class ProjectSyncTaskScheduler {
.operationResponse(response) .operationResponse(response)
.isSuccess(ex == null) .isSuccess(ex == null)
.involvedProjectId(projectId) .involvedProjectId(projectId)
.involvedGroupId(groupId)
.build(); .build();
operationLogService.save(operationLog); operationLogService.save(operationLog);
} }

View File

@ -1 +1 @@
<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/favicon.ico"><title>databasir</title><script defer="defer" type="module" src="/js/chunk-vendors.9effab81.js"></script><script defer="defer" type="module" src="/js/app.06544fba.js"></script><link href="/css/chunk-vendors.81898547.css" rel="stylesheet"><link href="/css/app.15b40a89.css" rel="stylesheet"><script defer="defer" src="/js/chunk-vendors-legacy.fb0c8458.js" nomodule></script><script defer="defer" src="/js/app-legacy.b786ff99.js" nomodule></script></head><body><noscript><strong>We're sorry but databasir doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html> <!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/favicon.ico"><title>databasir</title><script defer="defer" type="module" src="/js/chunk-vendors.9effab81.js"></script><script defer="defer" type="module" src="/js/app.1aa391ea.js"></script><link href="/css/chunk-vendors.81898547.css" rel="stylesheet"><link href="/css/app.15b40a89.css" rel="stylesheet"><script defer="defer" src="/js/chunk-vendors-legacy.fb0c8458.js" nomodule></script><script defer="defer" src="/js/app-legacy.18135b1d.js" nomodule></script></head><body><noscript><strong>We're sorry but databasir doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -28,6 +28,8 @@ public @interface AuditLog {
*/ */
String involvedUserId() default "N/A"; String involvedUserId() default "N/A";
boolean retrieveInvolvedGroupId() default false;
interface Modules { interface Modules {
String UNKNOWN = "UNKNOWN"; String UNKNOWN = "UNKNOWN";
String PROJECT = "project"; String PROJECT = "project";

View File

@ -115,4 +115,15 @@ public class ProjectDao extends BaseDao<ProjectPojo> {
.groupBy(DATA_SOURCE.DATABASE_TYPE) .groupBy(DATA_SOURCE.DATABASE_TYPE)
.fetchMap(DATA_SOURCE.DATABASE_TYPE, DSL.count(DATA_SOURCE)); .fetchMap(DATA_SOURCE.DATABASE_TYPE, DSL.count(DATA_SOURCE));
} }
public Map<Integer, Integer> selectGroupIdsByProjectIdIn(List<Integer> projectIds) {
if (projectIds == null || projectIds.isEmpty()) {
return Collections.emptyMap();
}
return getDslContext()
.select(PROJECT.ID, PROJECT.GROUP_ID)
.from(PROJECT)
.where(PROJECT.ID.in(projectIds))
.fetchMap(PROJECT.ID, PROJECT.GROUP_ID);
}
} }

@ -1 +1 @@
Subproject commit 8a953f7a8a2dec3d838c2faf34c847e9e77a074c Subproject commit b027e133261b2df4e56bc6cab26fcb99113e0f0e