feat: auto record operation log

This commit is contained in:
vran
2022-02-17 22:38:08 +08:00
parent 133b9476e5
commit 0d53f398c7
40 changed files with 1553 additions and 75 deletions

View File

@@ -5,6 +5,7 @@ import com.databasir.common.SystemException;
import com.databasir.core.domain.document.data.DatabaseDocumentResponse;
import com.databasir.core.domain.document.data.DatabaseDocumentVersionResponse;
import com.databasir.core.domain.document.service.DocumentService;
import com.databasir.core.domain.log.annotation.Operation;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
@@ -33,6 +34,7 @@ public class DocumentController {
private final DocumentService documentService;
@PostMapping(Routes.Document.SYNC_ONE)
@Operation(module = Operation.Modules.PROJECT, name = "文档同步", involvedProjectId = "#projectId")
public JsonData<Void> sync(@PathVariable Integer projectId) {
documentService.syncByProjectId(projectId);
return JsonData.ok();

View File

@@ -2,6 +2,7 @@ package com.databasir.api;
import com.databasir.api.config.security.DatabasirUserDetails;
import com.databasir.common.JsonData;
import com.databasir.core.domain.log.annotation.Operation;
import com.databasir.core.domain.remark.data.RemarkCreateRequest;
import com.databasir.core.domain.remark.data.RemarkListCondition;
import com.databasir.core.domain.remark.data.RemarkResponse;
@@ -38,6 +39,9 @@ public class DocumentRemarkController {
@DeleteMapping(Routes.DocumentRemark.DELETE)
@PreAuthorize("hasAnyAuthority('SYS_OWNER', 'GROUP_OWNER?groupId='+#groupId)")
@Operation(module = Operation.Modules.PROJECT,
name = "删除批注",
involvedProjectId = "#projectId")
public JsonData<Void> delete(@PathVariable Integer groupId,
@PathVariable Integer projectId,
@PathVariable Integer remarkId) {
@@ -47,6 +51,9 @@ public class DocumentRemarkController {
@PostMapping(Routes.DocumentRemark.CREATE)
@PreAuthorize("hasAnyAuthority('SYS_OWNER', 'GROUP_OWNER?groupId='+#groupId, 'GROUP_MEMBER?groupId='+#groupId)")
@Operation(module = Operation.Modules.PROJECT,
name = "新增批注",
involvedProjectId = "#projectId")
public JsonData<Void> create(@PathVariable Integer groupId,
@PathVariable Integer projectId,
@RequestBody @Valid RemarkCreateRequest request) {

View File

@@ -4,6 +4,7 @@ import com.databasir.api.validator.UserOperationValidator;
import com.databasir.common.JsonData;
import com.databasir.core.domain.group.data.*;
import com.databasir.core.domain.group.service.GroupService;
import com.databasir.core.domain.log.annotation.Operation;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
@@ -28,6 +29,7 @@ public class GroupController {
@PostMapping(Routes.Group.CREATE)
@PreAuthorize("hasAnyAuthority('SYS_OWNER')")
@Operation(module = Operation.Modules.GROUP, name = "创建分组")
public JsonData<Void> create(@RequestBody @Valid GroupCreateRequest request) {
groupService.create(request);
return JsonData.ok();
@@ -35,6 +37,9 @@ public class GroupController {
@PatchMapping(Routes.Group.UPDATE)
@PreAuthorize("hasAnyAuthority('SYS_OWNER', 'GROUP_OWNER'.concat('?groupId='.concat(#request.id)))")
@Operation(module = Operation.Modules.GROUP,
name = "更新分组",
involvedGroupId = "#request.id")
public JsonData<Void> update(@RequestBody @Valid GroupUpdateRequest request) {
groupService.update(request);
return JsonData.ok();
@@ -49,6 +54,9 @@ public class GroupController {
@DeleteMapping(Routes.Group.DELETE)
@PreAuthorize("hasAnyAuthority('SYS_OWNER', 'GROUP_OWNER'.concat('?groupId='.concat(#groupId)))")
@Operation(module = Operation.Modules.GROUP,
name = "删除分组",
involvedGroupId = "#groupId")
public JsonData<Void> deleteById(@PathVariable Integer groupId) {
groupService.delete(groupId);
return JsonData.ok();
@@ -69,6 +77,10 @@ public class GroupController {
@PostMapping(Routes.Group.ADD_MEMBER)
@PreAuthorize("hasAnyAuthority('SYS_OWNER', 'GROUP_OWNER'.concat('?groupId='.concat(#groupId)))")
@Operation(module = Operation.Modules.GROUP,
name = "添加组员",
involvedGroupId = "#groupId",
involvedUserId = "#request.userId")
public JsonData<Void> addGroupMember(@PathVariable Integer groupId,
@RequestBody @Valid GroupMemberCreateRequest request) {
userOperationValidator.forbiddenIfUpdateSelfRole(request.getUserId());
@@ -82,6 +94,10 @@ public class GroupController {
@DeleteMapping(Routes.Group.DELETE_MEMBER)
@PreAuthorize("hasAnyAuthority('SYS_OWNER', 'GROUP_OWNER'.concat('?groupId='.concat(#groupId)))")
@Operation(module = Operation.Modules.GROUP,
name = "移除组员",
involvedGroupId = "#groupId",
involvedUserId = "#userId")
public JsonData<Void> removeGroupMember(@PathVariable Integer groupId,
@PathVariable Integer userId) {
userOperationValidator.forbiddenIfUpdateSelfRole(userId);
@@ -91,6 +107,10 @@ public class GroupController {
@PatchMapping(Routes.Group.UPDATE_MEMBER)
@PreAuthorize("hasAnyAuthority('SYS_OWNER', 'GROUP_OWNER'.concat('?groupId='.concat(#groupId)))")
@Operation(module = Operation.Modules.GROUP,
name = "更新组员角色",
involvedGroupId = "#groupId",
involvedUserId = "#userId")
public JsonData<Void> updateGroupMemberRole(@PathVariable Integer groupId,
@PathVariable Integer userId,
@RequestBody GroupMemberRoleUpdateRequest request) {

View File

@@ -4,6 +4,7 @@ import com.databasir.common.DatabasirException;
import com.databasir.common.JsonData;
import com.databasir.common.exception.InvalidTokenException;
import com.databasir.core.domain.DomainErrors;
import com.databasir.core.domain.log.annotation.Operation;
import com.databasir.core.domain.login.data.AccessTokenRefreshRequest;
import com.databasir.core.domain.login.data.AccessTokenRefreshResponse;
import com.databasir.core.domain.login.service.LoginService;
@@ -32,6 +33,7 @@ public class LoginController {
private final LoginService loginService;
@GetMapping(Routes.Login.LOGOUT)
@Operation(module = Operation.Modules.USER, name = "注销登录")
public JsonData<Void> logout() {
SecurityContextHolder.clearContext();
return JsonData.ok();

View File

@@ -2,6 +2,7 @@ package com.databasir.api;
import com.databasir.api.validator.CronExpressionValidator;
import com.databasir.common.JsonData;
import com.databasir.core.domain.log.annotation.Operation;
import com.databasir.core.domain.project.data.*;
import com.databasir.core.domain.project.service.ProjectService;
import lombok.RequiredArgsConstructor;
@@ -26,6 +27,9 @@ public class ProjectController {
@PostMapping(Routes.GroupProject.CREATE)
@PreAuthorize("hasAnyAuthority('SYS_OWNER', 'GROUP_OWNER?groupId='+#request.groupId, 'GROUP_MEMBER?groupId='+#request.groupId)")
@Operation(module = Operation.Modules.PROJECT,
name = "创建项目",
involvedGroupId = "#request.groupId")
public JsonData<Void> create(@RequestBody @Valid ProjectCreateRequest request) {
cronExpressionValidator.isValidCron(request);
projectService.create(request);
@@ -34,6 +38,10 @@ public class ProjectController {
@PatchMapping(Routes.GroupProject.UPDATE)
@PreAuthorize("hasAnyAuthority('SYS_OWNER', 'GROUP_OWNER?groupId='+#groupId, 'GROUP_MEMBER?groupId='+#groupId)")
@Operation(module = Operation.Modules.PROJECT,
name = "更新项目",
involvedGroupId = "#groupId",
involvedProjectId = "#request.id")
public JsonData<Void> update(@RequestBody @Valid ProjectUpdateRequest request,
@PathVariable Integer groupId) {
cronExpressionValidator.isValidCron(request);
@@ -43,6 +51,10 @@ public class ProjectController {
@DeleteMapping(Routes.GroupProject.DELETE)
@PreAuthorize("hasAnyAuthority('SYS_OWNER', 'GROUP_OWNER?groupId='+#groupId, 'GROUP_MEMBER?groupId='+#groupId)")
@Operation(module = Operation.Modules.PROJECT,
name = "删除项目",
involvedGroupId = "#groupId",
involvedProjectId = "#projectId")
public JsonData<Void> delete(@PathVariable Integer groupId,
@PathVariable Integer projectId) {
projectService.delete(projectId);

View File

@@ -2,6 +2,7 @@ package com.databasir.api;
import com.databasir.common.JsonData;
import com.databasir.core.domain.log.annotation.Operation;
import com.databasir.core.domain.system.data.SystemEmailResponse;
import com.databasir.core.domain.system.data.SystemEmailUpdateRequest;
import com.databasir.core.domain.system.service.SystemService;
@@ -28,6 +29,7 @@ public class SettingController {
}
@PostMapping(Routes.Setting.UPDATE_SYS_EMAIL)
@Operation(module = Operation.Modules.PROJECT, name = "更新邮件配置")
public JsonData<Void> updateSystemEmailSetting(@RequestBody @Valid SystemEmailUpdateRequest request) {
systemService.updateEmailSetting(request);
return JsonData.ok();

View File

@@ -3,6 +3,7 @@ package com.databasir.api;
import com.databasir.api.validator.UserOperationValidator;
import com.databasir.common.JsonData;
import com.databasir.common.exception.Forbidden;
import com.databasir.core.domain.log.annotation.Operation;
import com.databasir.core.domain.user.data.*;
import com.databasir.core.domain.user.service.UserService;
import lombok.RequiredArgsConstructor;
@@ -34,6 +35,7 @@ public class UserController {
@PostMapping(Routes.User.DISABLE)
@PreAuthorize("hasAnyAuthority('SYS_OWNER')")
@Operation(module = Operation.Modules.USER, name = "禁用用户", involvedUserId = "#userId")
public JsonData<Void> disableUser(@PathVariable Integer userId) {
userService.switchEnableStatus(userId, false);
return JsonData.ok();
@@ -41,6 +43,7 @@ public class UserController {
@PostMapping(Routes.User.ENABLE)
@PreAuthorize("hasAnyAuthority('SYS_OWNER')")
@Operation(module = Operation.Modules.USER, name = "启用用户", involvedUserId = "#userId")
public JsonData<Void> enableUser(@PathVariable Integer userId) {
userService.switchEnableStatus(userId, true);
return JsonData.ok();
@@ -48,6 +51,7 @@ public class UserController {
@PostMapping(Routes.User.CREATE)
@PreAuthorize("hasAnyAuthority('SYS_OWNER')")
@Operation(module = Operation.Modules.USER, name = "创建用户")
public JsonData<Void> create(@RequestBody @Valid UserCreateRequest request) {
userService.create(request);
return JsonData.ok();
@@ -60,6 +64,7 @@ public class UserController {
@PostMapping(Routes.User.RENEW_PASSWORD)
@PreAuthorize("hasAnyAuthority('SYS_OWNER')")
@Operation(module = Operation.Modules.USER, name = "重置用户密码", involvedUserId = "#userId")
public JsonData<Void> renewPassword(@PathVariable Integer userId) {
userService.renewPassword(userId);
return JsonData.ok();
@@ -67,6 +72,7 @@ public class UserController {
@PostMapping(Routes.User.ADD_OR_REMOVE_SYS_OWNER)
@PreAuthorize("hasAnyAuthority('SYS_OWNER')")
@Operation(module = Operation.Modules.USER, name = "添加系统管理员", involvedUserId = "#userId")
public JsonData<Void> addSysOwner(@PathVariable Integer userId) {
userOperationValidator.forbiddenIfUpdateSelfRole(userId);
userService.addSysOwnerTo(userId);
@@ -75,6 +81,7 @@ public class UserController {
@DeleteMapping(Routes.User.ADD_OR_REMOVE_SYS_OWNER)
@PreAuthorize("hasAnyAuthority('SYS_OWNER')")
@Operation(module = Operation.Modules.USER, name = "移除系统管理员", involvedUserId = "#userId")
public JsonData<Void> removeSysOwner(@PathVariable Integer userId) {
userOperationValidator.forbiddenIfUpdateSelfRole(userId);
userService.removeSysOwnerFrom(userId);
@@ -82,6 +89,7 @@ public class UserController {
}
@PostMapping(Routes.User.UPDATE_PASSWORD)
@Operation(module = Operation.Modules.USER, name = "更新密码", involvedUserId = "#userId")
public JsonData<Void> updatePassword(@PathVariable Integer userId,
@RequestBody @Valid UserPasswordUpdateRequest request) {
if (userOperationValidator.isMyself(userId)) {
@@ -93,6 +101,7 @@ public class UserController {
}
@PostMapping(Routes.User.UPDATE_NICKNAME)
@Operation(module = Operation.Modules.USER, name = "更新昵称", involvedUserId = "#userId")
public JsonData<Void> updateNickname(@PathVariable Integer userId,
@RequestBody @Valid UserNicknameUpdateRequest request) {
if (userOperationValidator.isMyself(userId)) {

View File

@@ -0,0 +1,117 @@
package com.databasir.api.advice;
import com.databasir.api.config.security.DatabasirUserDetails;
import com.databasir.common.JsonData;
import com.databasir.core.domain.log.annotation.Operation;
import com.databasir.core.domain.log.data.OperationLogRequest;
import com.databasir.core.domain.log.service.OperationLogService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.Optional;
@Component
@RequiredArgsConstructor
@Aspect
@Slf4j
public class OperationLogAspect {
private final OperationLogService operationLogService;
private SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
@AfterReturning(value = "@annotation(operation)", returning = "returnValue")
public void log(JoinPoint joinPoint, Object returnValue, Operation operation) {
saveLog(operation, joinPoint, (JsonData<Object>) returnValue);
}
@AfterThrowing(value = "@annotation(operation)", throwing = "ex")
public void log(JoinPoint joinPoint, RuntimeException ex, Operation operation) {
saveLog(operation, joinPoint, JsonData.error("-1", ex.getMessage()));
throw ex;
}
private void saveLog(Operation operation, JoinPoint joinPoint, JsonData<Object> result) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
Object[] arguments = joinPoint.getArgs();
DatabasirUserDetails principal = (DatabasirUserDetails) SecurityContextHolder.getContext()
.getAuthentication()
.getPrincipal();
Integer involvedProjectId = getValueBySPEL(method, arguments, operation.involvedProjectId(), Integer.class)
.orElse(null);
Integer involvedGroupId = getValueBySPEL(method, arguments, operation.involvedGroupId(), Integer.class)
.orElse(null);
Integer involvedUserId = getValueBySPEL(method, arguments, operation.involvedUserId(), Integer.class)
.orElse(null);
int userId = userId();
String username = principal.getUserPojo().getUsername();
String nickname = principal.getUserPojo().getNickname();
if (userId == Operation.Types.SYSTEM_USER_ID) {
username = "system";
nickname = "system";
}
OperationLogRequest request = OperationLogRequest.builder()
.operatorUserId(userId)
.operatorUsername(username)
.operatorNickname(nickname)
.operationModule(operation.module())
.operationCode(method.getName())
.operationName(operation.name())
.operationResponse(result)
.isSuccess(result.getErrCode() == null)
.involvedProjectId(involvedProjectId)
.involvedGroupId(involvedGroupId)
.involvedUserId(involvedUserId)
.build();
operationLogService.save(request);
}
private int userId() {
DatabasirUserDetails principal = (DatabasirUserDetails) SecurityContextHolder.getContext()
.getAuthentication()
.getPrincipal();
return principal.getUserPojo().getId();
}
private <T> Optional<T> getValueBySPEL(Method method,
Object[] arguments,
String expression,
Class<T> valueType) {
if (expression == null || "N/A".equals(expression)) {
return Optional.empty();
}
String[] parameterNames =
Objects.requireNonNullElse(parameterNameDiscoverer.getParameterNames(method), new String[0]);
EvaluationContext context = new StandardEvaluationContext();
for (int len = 0; len < parameterNames.length; len++) {
context.setVariable(parameterNames[len], arguments[len]);
}
try {
Expression expr = spelExpressionParser.parseExpression(expression);
return Optional.ofNullable(expr.getValue(context, valueType));
} catch (Exception e) {
log.warn("parse expression error: " + expression, e);
return Optional.empty();
}
}
}

View File

@@ -1,6 +1,9 @@
package com.databasir.job;
import com.databasir.common.JsonData;
import com.databasir.core.domain.document.service.DocumentService;
import com.databasir.core.domain.log.data.OperationLogRequest;
import com.databasir.core.domain.log.service.OperationLogService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
@@ -12,14 +15,47 @@ import org.quartz.JobExecutionException;
@Slf4j
public class ProjectDocumentAutoSyncJob implements Job {
private final OperationLogService operationLogService;
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
JobDataMap dataMap = context.getMergedJobDataMap();
log.info("start sync project document: " + dataMap.toString());
DocumentService documentService = (DocumentService) dataMap.get("documentService");
Integer projectId = dataMap.getInt("projectId");
documentService.syncByProjectId(projectId);
log.info("sync project document {} over....", projectId);
try {
documentService.syncByProjectId(projectId);
OperationLogRequest request = OperationLogRequest.builder()
.isSuccess(true)
.operatorNickname("system")
.operatorUsername("system")
.operatorUserId(-1)
.operationName("文档自动同步")
.operationCode("autoSyncDocumentation")
.operationModule("project")
.operationResponse(JsonData.ok())
.isSuccess(true)
.involvedProjectId(projectId)
.build();
operationLogService.save(request);
log.info("sync project document {} over....", projectId);
} catch (Exception e) {
OperationLogRequest request = OperationLogRequest.builder()
.isSuccess(true)
.operatorNickname("system")
.operatorUsername("system")
.operatorUserId(-1)
.operationName("文档自动同步")
.operationCode("autoSyncDocumentation")
.operationModule("project")
.operationResponse(JsonData.error("-1", e.getMessage()))
.isSuccess(false)
.involvedProjectId(projectId)
.build();
operationLogService.save(request);
throw e;
}
}
}