diff --git a/backend/src/main/java/io/dataease/auth/annotation/DeLog.java b/backend/src/main/java/io/dataease/auth/annotation/DeLog.java new file mode 100644 index 0000000000..428b32a1c7 --- /dev/null +++ b/backend/src/main/java/io/dataease/auth/annotation/DeLog.java @@ -0,0 +1,34 @@ +package io.dataease.auth.annotation; + +import io.dataease.commons.constants.SysLogConstants.OPERATE_TYPE; +import io.dataease.commons.constants.SysLogConstants.SOURCE_TYPE; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface DeLog { + + OPERATE_TYPE operatetype(); + + + SOURCE_TYPE sourcetype(); + + + int positionIndex() default -1; + String positionKey() default ""; + + + int sourceIndex() default 0; + String sourceKey() default ""; + + int targetIndex() default -1; + String targetKey() default ""; + SOURCE_TYPE targetType() default SOURCE_TYPE.USER; + + String value() default ""; + +} diff --git a/backend/src/main/java/io/dataease/auth/aop/DeCleanerAnnotationHandler.java b/backend/src/main/java/io/dataease/auth/aop/DeCleanerAnnotationHandler.java index ea13f93158..3584de725a 100644 --- a/backend/src/main/java/io/dataease/auth/aop/DeCleanerAnnotationHandler.java +++ b/backend/src/main/java/io/dataease/auth/aop/DeCleanerAnnotationHandler.java @@ -2,10 +2,10 @@ package io.dataease.auth.aop; import io.dataease.auth.annotation.DeCleaner; import io.dataease.auth.api.dto.CurrentUserDto; -import io.dataease.auth.util.ReflectUtil; import io.dataease.commons.constants.AuthConstants; import io.dataease.commons.constants.DePermissionType; import io.dataease.commons.model.AuthURD; +import io.dataease.commons.utils.AopUtils; import io.dataease.commons.utils.AuthUtils; import io.dataease.commons.utils.LogUtil; import io.dataease.listener.util.CacheUtils; @@ -19,11 +19,8 @@ import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; -import java.lang.reflect.Array; import java.lang.reflect.Method; -import java.util.Collection; import java.util.List; -import java.util.Map; import java.util.Optional; @Aspect @@ -43,7 +40,7 @@ public class DeCleanerAnnotationHandler { if (ObjectUtils.isNotEmpty(key) && ArrayUtils.isNotEmpty(args)) { int pi = deCleaner.paramIndex(); Object arg = point.getArgs()[pi]; - paramValue = getParamValue(arg, key, 0); + paramValue = AopUtils.getParamValue(arg, key, 0); } switch (type.name()) { @@ -136,44 +133,5 @@ public class DeCleanerAnnotationHandler { }); } - private Object getParamValue(Object arg, String key, int layer) throws Exception{ - if (ObjectUtils.isNotEmpty(arg)) return null; - Class parameterType = arg.getClass(); - if (parameterType.isPrimitive() || ReflectUtil.isWrapClass(parameterType) || ReflectUtil.isString(parameterType)) { - return arg; - } else if (ReflectUtil.isArray(parameterType)) { - Object result; - for (int i = 0; i < Array.getLength(arg); i++) { - Object o = Array.get(arg, i); - if (ObjectUtils.isNotEmpty((result = getParamValue(o, key, layer)))) { - return result; - } - } - return null; - } else if (ReflectUtil.isCollection(parameterType)) { - Object[] array = ((Collection) arg).toArray(); - Object result; - for (int i = 0; i < array.length; i++) { - Object o = array[i]; - if (ObjectUtils.isNotEmpty((result = getParamValue(o, key, layer)))) { - return result; - } - } - return null; - } else if (ReflectUtil.isMap(parameterType)) { - Map argMap = (Map) arg; - String[] values = key.split("\\."); - Object o = argMap.get(values[layer]); - return getParamValue(o, key, ++layer); - } else { - // 当作自定义类处理 - String[] values = key.split("\\."); - String fieldName = values[layer]; - - Object fieldValue = ReflectUtil.getFieldValue(arg, values[layer]); - return getParamValue(fieldValue, key, ++layer); - - } - } } diff --git a/backend/src/main/java/io/dataease/auth/aop/DeLogAnnotationHandler.java b/backend/src/main/java/io/dataease/auth/aop/DeLogAnnotationHandler.java new file mode 100644 index 0000000000..7b3f41e148 --- /dev/null +++ b/backend/src/main/java/io/dataease/auth/aop/DeLogAnnotationHandler.java @@ -0,0 +1,143 @@ +package io.dataease.auth.aop; + + +import io.dataease.auth.annotation.DeLog; +import io.dataease.commons.constants.SysLogConstants; +import io.dataease.commons.utils.AopUtils; +import io.dataease.controller.ResultHolder; +import io.dataease.dto.SysLogDTO; +import io.dataease.dto.log.FolderItem; +import io.dataease.service.sys.log.LogManager; +import io.dataease.service.sys.log.LogService; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +@Aspect +@Component +public class DeLogAnnotationHandler { + + @Resource + private LogManager logManager; + + @Resource + private LogService logService; + + private static List befores = new ArrayList<>(); + + @PostConstruct + public void init() { + befores.add(SysLogConstants.OPERATE_TYPE.DELETE.getValue()); + befores.add(SysLogConstants.OPERATE_TYPE.UNSHARE.getValue()); + befores.add(SysLogConstants.OPERATE_TYPE.UNAUTHORIZE.getValue()); + } + + private SysLogDTO exec(JoinPoint point, DeLog deLog) throws Exception{ + + Object[] args = point.getArgs(); + if (ArrayUtils.isEmpty(args)) return null; + + SysLogConstants.OPERATE_TYPE operatetype = deLog.operatetype(); + SysLogConstants.SOURCE_TYPE sourcetype = deLog.sourcetype(); + + String sourceKey = StringUtils.isNotBlank(deLog.sourceKey()) ? deLog.sourceKey() : deLog.value(); + int sourceIndex = deLog.sourceIndex(); + if (args.length <= sourceIndex) return null; + Object arg = args[sourceIndex]; + Object sourceIdValue = AopUtils.getParamValue(arg, sourceKey, 0); + if (ObjectUtils.isEmpty(sourceIdValue)) return null; + + SysLogDTO sysLogDTO = new SysLogDTO(); + sysLogDTO.setOperateType(operatetype.getValue()); + sysLogDTO.setSourceType(sourcetype.getValue()); + sysLogDTO.setSourceId(sourceIdValue.toString()); + FolderItem sourceInfo = logManager.nameWithId(sourceIdValue.toString(), sourcetype.getValue()); + if (ObjectUtils.isEmpty(sourceInfo)) { + return null; + } + sysLogDTO.setSourceName(sourceInfo.getName()); + + // 填充资源位置信息 + int positionIndex = deLog.positionIndex(); + if (positionIndex > -1 && args.length > positionIndex){ + String positionKey = deLog.positionKey(); + Object positionArg = args[positionIndex]; + Object bottomPositionValue = AopUtils.getParamValue(positionArg, positionKey, 0); + if (ObjectUtils.isNotEmpty(bottomPositionValue)) { + if (sourcetype == SysLogConstants.SOURCE_TYPE.DATASOURCE) { + FolderItem folderItem = logManager.dsTypeInfo(bottomPositionValue.toString()); + List items = new ArrayList<>(); + items.add(folderItem); + sysLogDTO.setPositions(items); + }else { + List parentsAndSelf = parents(bottomPositionValue.toString(), sourcetype); + sysLogDTO.setPositions(parentsAndSelf); + } + + } + } + // 填充资源目标位置信息 + int targetIndex = deLog.targetIndex(); + if (targetIndex > -1 && args.length > targetIndex){ + String targetKey = deLog.targetKey(); + Object targetArg = args[targetIndex]; + SysLogConstants.SOURCE_TYPE targetType = deLog.targetType(); + Object bottomTargetValue = AopUtils.getParamValue(targetArg, targetKey, 0); + if (ObjectUtils.isNotEmpty(bottomTargetValue)) { + List parentsAndSelf = parents(bottomTargetValue.toString(), targetType); + sysLogDTO.setRemarks(parentsAndSelf); + } + } + return sysLogDTO; + } + + @Around(value = "@annotation(io.dataease.auth.annotation.DeLog)") + public Object logAround(ProceedingJoinPoint point) throws Throwable { + SysLogDTO logDTO = null; + Object result = null; + DeLog log = getLog(point); + if(befores.contains(log.operatetype().getValue())) { + // 前置处理 比如删除操作 需要在数据删除之前查询 + logDTO = exec(point, log); + result = point.proceed(point.getArgs()); + }else { + // 后置处理 比如保存操作 需要在保存之后才有主键 + result = point.proceed(point.getArgs()); + logDTO = exec(point, log); + } + + if (ObjectUtils.isNotEmpty(result) && result instanceof ResultHolder && !((ResultHolder) result).isSuccess()) { + return result; + } + Optional.ofNullable(logDTO).ifPresent(logService::saveLog); + return result; + } + + + + private DeLog getLog(JoinPoint point) { + MethodSignature ms = (MethodSignature) point.getSignature(); + Method method = ms.getMethod(); + DeLog deLog = method.getAnnotation(DeLog.class); + return deLog; + } + + public List parents(String value, SysLogConstants.SOURCE_TYPE type) { + return logManager.parentsAndSelf(value, type); + } + + +} diff --git a/backend/src/main/java/io/dataease/commons/constants/SysLogConstants.java b/backend/src/main/java/io/dataease/commons/constants/SysLogConstants.java new file mode 100644 index 0000000000..01e68c4647 --- /dev/null +++ b/backend/src/main/java/io/dataease/commons/constants/SysLogConstants.java @@ -0,0 +1,72 @@ +package io.dataease.commons.constants; + +import java.util.Arrays; +import java.util.Optional; + +public class SysLogConstants { + + public static String operateTypeName(Integer value) { + Optional any = Arrays.stream(OPERATE_TYPE.class.getEnumConstants()).filter(e -> e.value == value).findAny(); + if (any.isPresent()) return any.get().name; + return null; + } + + public enum OPERATE_TYPE { + CREATE(1, "OPERATE_TYPE_CREATE"), + MODIFY(2, "OPERATE_TYPE_MODIFY"), + DELETE(3, "OPERATE_TYPE_DELETE"), + SHARE(4, "OPERATE_TYPE_SHARE"), + UNSHARE(5, "OPERATE_TYPE_UNSHARE"), + AUTHORIZE(6, "OPERATE_TYPE_AUTHORIZE"), + UNAUTHORIZE(7, "OPERATE_TYPE_UNAUTHORIZE"), + CREATELINK(8, "OPERATE_TYPE_CREATELINK"), + DELETELINK(9, "OPERATE_TYPE_DELETELINK"), + MODIFYLINK(10, "OPERATE_TYPE_MODIFYLINK"); + private Integer value; + private String name; + OPERATE_TYPE(Integer value, String name) { + this.value = value; + this.name = name; + } + + public Integer getValue() { + return value; + } + + public String getName() { + return name; + } + } + + public static String sourceTypeName(Integer value) { + Optional any = Arrays.stream(SOURCE_TYPE.class.getEnumConstants()).filter(e -> e.value == value).findAny(); + if (any.isPresent()) return any.get().name; + return null; + } + + public enum SOURCE_TYPE { + DATASOURCE(1, "SOURCE_TYPE_DATASOURCE"), + DATASET(2, "SOURCE_TYPE_DATASET"), + PANEL(3, "SOURCE_TYPE_PANEL"), + VIEW(4, "SOURCE_TYPE_VIEW"), + /*LINK(5, "SOURCE_TYPE_LINK"),*/ + USER(6, "SOURCE_TYPE_USER"), + DEPT(7, "SOURCE_TYPE_DEPT"), + ROLE(8, "SOURCE_TYPE_ROLE"); + private Integer value; + private String name; + + SOURCE_TYPE(Integer value, String name) { + this.value = value; + this.name = name; + } + + public Integer getValue() { + return value; + } + + public String getName() { + return name; + } + } +} diff --git a/backend/src/main/java/io/dataease/commons/utils/AopUtils.java b/backend/src/main/java/io/dataease/commons/utils/AopUtils.java new file mode 100644 index 0000000000..4168027cf4 --- /dev/null +++ b/backend/src/main/java/io/dataease/commons/utils/AopUtils.java @@ -0,0 +1,52 @@ +package io.dataease.commons.utils; + +import io.dataease.auth.util.ReflectUtil; +import org.apache.commons.lang3.ObjectUtils; + +import java.lang.reflect.Array; +import java.util.Collection; +import java.util.Map; + +public class AopUtils { + + public static Object getParamValue(Object arg, String key, int layer) throws Exception{ + if (ObjectUtils.isEmpty(arg)) return null; + Class parameterType = arg.getClass(); + if (parameterType.isPrimitive() || ReflectUtil.isWrapClass(parameterType) || ReflectUtil.isString(parameterType)) { + return arg; + } else if (ReflectUtil.isArray(parameterType)) { + Object result; + for (int i = 0; i < Array.getLength(arg); i++) { + Object o = Array.get(arg, i); + + if (ObjectUtils.isNotEmpty((result = getParamValue(o, key, layer)))) { + return result; + } + } + return null; + } else if (ReflectUtil.isCollection(parameterType)) { + Object[] array = ((Collection) arg).toArray(); + Object result; + for (int i = 0; i < array.length; i++) { + Object o = array[i]; + if (ObjectUtils.isNotEmpty((result = getParamValue(o, key, layer)))) { + return result; + } + } + return null; + } else if (ReflectUtil.isMap(parameterType)) { + Map argMap = (Map) arg; + String[] values = key.split("\\."); + Object o = argMap.get(values[layer]); + return getParamValue(o, key, ++layer); + } else { + // 当作自定义类处理 + String[] values = key.split("\\."); + String fieldName = values[layer]; + + Object fieldValue = ReflectUtil.getFieldValue(arg, values[layer]); + return getParamValue(fieldValue, key, ++layer); + + } + } +} diff --git a/backend/src/main/java/io/dataease/controller/datasource/DatasourceController.java b/backend/src/main/java/io/dataease/controller/datasource/DatasourceController.java index 23ffd28b71..0614342afc 100644 --- a/backend/src/main/java/io/dataease/controller/datasource/DatasourceController.java +++ b/backend/src/main/java/io/dataease/controller/datasource/DatasourceController.java @@ -1,7 +1,10 @@ package io.dataease.controller.datasource; import com.github.xiaoymin.knife4j.annotations.ApiSupport; +import io.dataease.auth.annotation.DeLog; import io.dataease.auth.annotation.DePermission; +import io.dataease.commons.constants.SysLogConstants; +import io.dataease.controller.datasource.request.DeleteDsRequest; import io.dataease.plugins.common.base.domain.Datasource; import io.dataease.commons.constants.DePermissionType; import io.dataease.commons.constants.ResourceAuthLevel; @@ -39,6 +42,12 @@ public class DatasourceController { @DePermission(type = DePermissionType.DATASOURCE, value = "id") @ApiOperation("新增数据源") @PostMapping("/add") + @DeLog( + operatetype = SysLogConstants.OPERATE_TYPE.CREATE, + sourcetype = SysLogConstants.SOURCE_TYPE.DATASOURCE, + positionIndex = 0,positionKey = "type", + value = "id" + ) public Datasource addDatasource(@RequestBody Datasource datasource) throws Exception{ return datasourceService.addDatasource(datasource); } @@ -81,15 +90,27 @@ public class DatasourceController { @RequiresPermissions("datasource:read") @DePermission(type = DePermissionType.DATASOURCE, level = ResourceAuthLevel.DATASOURCE_LEVEL_MANAGE) @ApiOperation("删除数据源") - @PostMapping("/delete/{datasourceID}") - public ResultHolder deleteDatasource(@PathVariable(value = "datasourceID") String datasourceID) throws Exception { - return datasourceService.deleteDatasource(datasourceID); + @PostMapping("/delete") + @DeLog( + operatetype = SysLogConstants.OPERATE_TYPE.DELETE, + sourcetype = SysLogConstants.SOURCE_TYPE.DATASOURCE, + positionIndex = 0,positionKey = "type", + value = "id" + ) + public ResultHolder deleteDatasource(@RequestBody DeleteDsRequest request) throws Exception { + return datasourceService.deleteDatasource(request.getId()); } @RequiresPermissions("datasource:read") @DePermission(type = DePermissionType.DATASOURCE, value = "id", level = ResourceAuthLevel.DATASOURCE_LEVEL_MANAGE) @ApiOperation("更新数据源") @PostMapping("/update") + @DeLog( + operatetype = SysLogConstants.OPERATE_TYPE.MODIFY, + sourcetype = SysLogConstants.SOURCE_TYPE.DATASOURCE, + positionIndex = 0,positionKey = "type", + value = "id" + ) public void updateDatasource(@RequestBody UpdataDsRequest dsRequest) throws Exception{ datasourceService.updateDatasource(dsRequest); } diff --git a/backend/src/main/java/io/dataease/controller/datasource/request/DeleteDsRequest.java b/backend/src/main/java/io/dataease/controller/datasource/request/DeleteDsRequest.java new file mode 100644 index 0000000000..c31818bbf8 --- /dev/null +++ b/backend/src/main/java/io/dataease/controller/datasource/request/DeleteDsRequest.java @@ -0,0 +1,16 @@ +package io.dataease.controller.datasource.request; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +@Data +public class DeleteDsRequest implements Serializable { + + @ApiModelProperty(value = "ID",required = true) + private String id; + + @ApiModelProperty(value = "类型", required = true) + private String type; +} diff --git a/backend/src/main/java/io/dataease/controller/sys/SysLogController.java b/backend/src/main/java/io/dataease/controller/sys/SysLogController.java new file mode 100644 index 0000000000..1b85e090db --- /dev/null +++ b/backend/src/main/java/io/dataease/controller/sys/SysLogController.java @@ -0,0 +1,57 @@ +package io.dataease.controller.sys; + +import com.github.pagehelper.Page; +import com.github.pagehelper.PageHelper; +import com.github.xiaoymin.knife4j.annotations.ApiSupport; +import io.dataease.commons.utils.PageUtils; +import io.dataease.commons.utils.Pager; +import io.dataease.controller.handler.annotation.I18n; +import io.dataease.controller.sys.base.BaseGridRequest; +import io.dataease.dto.SysLogGridDTO; +import io.dataease.dto.log.FolderItem; +import io.dataease.service.sys.log.LogService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiImplicitParams; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +@RestController +@Api(tags = "系统:日志管理") +@ApiSupport(order = 220) +@RequestMapping("/api/log") +public class SysLogController { + + @Resource + private LogService logService; + + @I18n + @ApiOperation("查询日志") + @PostMapping("/logGrid/{goPage}/{pageSize}") + @ApiImplicitParams({ + @ApiImplicitParam(paramType = "path", name = "goPage", value = "页码", required = true, dataType = "Integer"), + @ApiImplicitParam(paramType = "path", name = "pageSize", value = "页容量", required = true, dataType = "Integer"), + @ApiImplicitParam(name = "request", value = "查询条件", required = true) + }) + public Pager> logGrid(@PathVariable int goPage, @PathVariable int pageSize, + @RequestBody BaseGridRequest request) { + Page page = PageHelper.startPage(goPage, pageSize, true); + return PageUtils.setPageInfo(page, logService.query(request)); + } + + @ApiOperation("操作类型") + @PostMapping("/opTypes") + public List types() { + return logService.types(); + } + + @ApiOperation("导出操作日志") + @PostMapping("/export") + public void export(HttpServletResponse response) throws Exception{ + logService.exportExcel(response); + } +} diff --git a/backend/src/main/java/io/dataease/controller/sys/SysUserController.java b/backend/src/main/java/io/dataease/controller/sys/SysUserController.java index 711a574880..53d488862b 100644 --- a/backend/src/main/java/io/dataease/controller/sys/SysUserController.java +++ b/backend/src/main/java/io/dataease/controller/sys/SysUserController.java @@ -3,7 +3,9 @@ package io.dataease.controller.sys; import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; import com.github.xiaoymin.knife4j.annotations.ApiSupport; +import io.dataease.auth.annotation.DeLog; import io.dataease.auth.api.dto.CurrentUserDto; +import io.dataease.commons.constants.SysLogConstants; import io.dataease.exception.DataEaseException; import io.dataease.i18n.Translator; import io.dataease.plugins.common.base.domain.SysRole; @@ -69,6 +71,11 @@ public class SysUserController { @ApiOperation("创建用户") @RequiresPermissions("user:add") @PostMapping("/create") + @DeLog( + operatetype = SysLogConstants.OPERATE_TYPE.CREATE, + sourcetype = SysLogConstants.SOURCE_TYPE.USER, + value = "userId" + ) public void create(@RequestBody SysUserCreateRequest request) { sysUserService.save(request); } @@ -76,6 +83,11 @@ public class SysUserController { @ApiOperation("更新用户") @RequiresPermissions("user:edit") @PostMapping("/update") + @DeLog( + operatetype = SysLogConstants.OPERATE_TYPE.MODIFY, + sourcetype = SysLogConstants.SOURCE_TYPE.USER, + value = "userId" + ) public void update(@RequestBody SysUserCreateRequest request) { sysUserService.update(request); } @@ -84,6 +96,10 @@ public class SysUserController { @RequiresPermissions("user:del") @PostMapping("/delete/{userId}") @ApiImplicitParam(paramType = "path", value = "用户ID", name = "userId", required = true, dataType = "Integer") + @DeLog( + operatetype = SysLogConstants.OPERATE_TYPE.DELETE, + sourcetype = SysLogConstants.SOURCE_TYPE.USER + ) public void delete(@PathVariable("userId") Long userId) { sysUserService.delete(userId); } @@ -92,6 +108,11 @@ public class SysUserController { @RequiresPermissions("user:edit") @RequiresRoles("1") @PostMapping("/updateStatus") + @DeLog( + operatetype = SysLogConstants.OPERATE_TYPE.MODIFY, + sourcetype = SysLogConstants.SOURCE_TYPE.USER, + value = "userId" + ) public void updateStatus(@RequestBody SysUserStateRequest request) { sysUserService.updateStatus(request); } diff --git a/backend/src/main/java/io/dataease/dto/SysLogDTO.java b/backend/src/main/java/io/dataease/dto/SysLogDTO.java new file mode 100644 index 0000000000..262b1e4113 --- /dev/null +++ b/backend/src/main/java/io/dataease/dto/SysLogDTO.java @@ -0,0 +1,24 @@ +package io.dataease.dto; + +import io.dataease.dto.log.FolderItem; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +@Data +public class SysLogDTO implements Serializable { + + private Integer sourceType; + + private String sourceId; + + private String sourceName; + + private Integer operateType; + + private List positions; + + private List remarks; + +} diff --git a/backend/src/main/java/io/dataease/dto/SysLogGridDTO.java b/backend/src/main/java/io/dataease/dto/SysLogGridDTO.java new file mode 100644 index 0000000000..706d4a2762 --- /dev/null +++ b/backend/src/main/java/io/dataease/dto/SysLogGridDTO.java @@ -0,0 +1,25 @@ +package io.dataease.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +@Data +public class SysLogGridDTO implements Serializable { + + @ApiModelProperty("操作类型") + private String opType; + + @ApiModelProperty("资源类型") + private String sourceType; + + @ApiModelProperty("详细信息") + private String detail; + + @ApiModelProperty("操作人") + private String user; + + @ApiModelProperty("操作时间") + private Long time; +} diff --git a/backend/src/main/java/io/dataease/dto/log/FolderItem.java b/backend/src/main/java/io/dataease/dto/log/FolderItem.java new file mode 100644 index 0000000000..ad69b5c73d --- /dev/null +++ b/backend/src/main/java/io/dataease/dto/log/FolderItem.java @@ -0,0 +1,16 @@ +package io.dataease.dto.log; + +import lombok.Data; + +import java.io.Serializable; + +@Data +public class FolderItem implements Serializable { + + private String id; + + private String name; + + private Integer type; + +} diff --git a/backend/src/main/java/io/dataease/ext/ExtSysLogMapper.java b/backend/src/main/java/io/dataease/ext/ExtSysLogMapper.java new file mode 100644 index 0000000000..ae7875c5f0 --- /dev/null +++ b/backend/src/main/java/io/dataease/ext/ExtSysLogMapper.java @@ -0,0 +1,15 @@ +package io.dataease.ext; + +import io.dataease.dto.log.FolderItem; +import io.dataease.ext.query.GridExample; +import io.dataease.plugins.common.base.domain.SysLogWithBLOBs; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +public interface ExtSysLogMapper { + + List query(GridExample example); + + List idAndName(@Param("ids") List ids, @Param("type") Integer type); +} diff --git a/backend/src/main/java/io/dataease/ext/ExtSysLogMapper.xml b/backend/src/main/java/io/dataease/ext/ExtSysLogMapper.xml new file mode 100644 index 0000000000..ca90d1006e --- /dev/null +++ b/backend/src/main/java/io/dataease/ext/ExtSysLogMapper.xml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + diff --git a/backend/src/main/java/io/dataease/plugins/server/XDeptServer.java b/backend/src/main/java/io/dataease/plugins/server/XDeptServer.java index 0d4f46036a..1f74bfa203 100644 --- a/backend/src/main/java/io/dataease/plugins/server/XDeptServer.java +++ b/backend/src/main/java/io/dataease/plugins/server/XDeptServer.java @@ -1,7 +1,9 @@ package io.dataease.plugins.server; +import io.dataease.auth.annotation.DeLog; import io.dataease.auth.service.ExtAuthService; +import io.dataease.commons.constants.SysLogConstants; import io.dataease.commons.utils.BeanUtils; import io.dataease.controller.sys.response.DeptNodeResponse; import io.dataease.plugins.common.entity.XpackGridRequest; @@ -70,6 +72,12 @@ public class XDeptServer { @RequiresPermissions("dept:add") @ApiOperation("创建") @PostMapping("/create") + @DeLog( + operatetype = SysLogConstants.OPERATE_TYPE.CREATE, + sourcetype = SysLogConstants.SOURCE_TYPE.DEPT, + positionIndex = 0,positionKey = "pid", + value = "deptId" + ) public int create(@RequestBody XpackCreateDept dept){ DeptXpackService deptService = SpringContextUtil.getBean(DeptXpackService.class); return deptService.add(dept); @@ -78,6 +86,12 @@ public class XDeptServer { @RequiresPermissions("dept:del") @ApiOperation("删除") @PostMapping("/delete") + @DeLog( + operatetype = SysLogConstants.OPERATE_TYPE.DELETE, + sourcetype = SysLogConstants.SOURCE_TYPE.DEPT, + positionIndex = 0,positionKey = "pid", + value = "deptId" + ) public void delete(@RequestBody List requests){ DeptXpackService deptService = SpringContextUtil.getBean(DeptXpackService.class); requests.forEach(request -> { @@ -89,6 +103,12 @@ public class XDeptServer { @RequiresPermissions("dept:edit") @ApiOperation("更新") @PostMapping("/update") + @DeLog( + operatetype = SysLogConstants.OPERATE_TYPE.MODIFY, + sourcetype = SysLogConstants.SOURCE_TYPE.DEPT, + positionIndex = 0,positionKey = "pid", + value = "deptId" + ) public int update(@RequestBody XpackCreateDept dept){ DeptXpackService deptService = SpringContextUtil.getBean(DeptXpackService.class); return deptService.update(dept); diff --git a/backend/src/main/java/io/dataease/plugins/server/XRoleServer.java b/backend/src/main/java/io/dataease/plugins/server/XRoleServer.java index 200a00155f..ff9db5f9ff 100644 --- a/backend/src/main/java/io/dataease/plugins/server/XRoleServer.java +++ b/backend/src/main/java/io/dataease/plugins/server/XRoleServer.java @@ -3,7 +3,9 @@ package io.dataease.plugins.server; import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; +import io.dataease.auth.annotation.DeLog; import io.dataease.auth.service.ExtAuthService; +import io.dataease.commons.constants.SysLogConstants; import io.dataease.commons.utils.PageUtils; import io.dataease.commons.utils.Pager; import io.dataease.plugins.common.entity.XpackGridRequest; @@ -30,6 +32,11 @@ public class XRoleServer { @RequiresPermissions("role:add") @ApiOperation("新增角色") @PostMapping("/create") + @DeLog( + operatetype = SysLogConstants.OPERATE_TYPE.CREATE, + sourcetype = SysLogConstants.SOURCE_TYPE.ROLE, + value = "roleId" + ) public void create(@RequestBody XpackRoleDto role){ RoleXpackService roleXpackService = SpringContextUtil.getBean(RoleXpackService.class); roleXpackService.save(role); @@ -39,6 +46,10 @@ public class XRoleServer { @RequiresPermissions("role:del") @ApiOperation("删除角色") @PostMapping("/delete/{roleId}") + @DeLog( + operatetype = SysLogConstants.OPERATE_TYPE.DELETE, + sourcetype = SysLogConstants.SOURCE_TYPE.ROLE + ) public void delete(@PathVariable("roleId") Long roleId){ RoleXpackService roleXpackService = SpringContextUtil.getBean(RoleXpackService.class); extAuthService.clearDeptResource(roleId); @@ -49,6 +60,11 @@ public class XRoleServer { @RequiresPermissions("role:edit") @ApiOperation("更新角色") @PostMapping("/update") + @DeLog( + operatetype = SysLogConstants.OPERATE_TYPE.MODIFY, + sourcetype = SysLogConstants.SOURCE_TYPE.ROLE, + value = "roleId" + ) public void update(@RequestBody XpackRoleDto role){ RoleXpackService roleXpackService = SpringContextUtil.getBean(RoleXpackService.class); roleXpackService.update(role); diff --git a/backend/src/main/java/io/dataease/service/sys/SysUserService.java b/backend/src/main/java/io/dataease/service/sys/SysUserService.java index b9b2ed382a..80025e9252 100644 --- a/backend/src/main/java/io/dataease/service/sys/SysUserService.java +++ b/backend/src/main/java/io/dataease/service/sys/SysUserService.java @@ -94,6 +94,7 @@ public class SysUserService { } int insert = sysUserMapper.insert(user); SysUser dbUser = findOne(user); + request.setUserId(dbUser.getUserId()); saveUserRoles(dbUser.getUserId(), request.getRoleIds());//插入用户角色关联 return insert; } diff --git a/backend/src/main/java/io/dataease/service/sys/log/LogManager.java b/backend/src/main/java/io/dataease/service/sys/log/LogManager.java new file mode 100644 index 0000000000..36f9ce3d3e --- /dev/null +++ b/backend/src/main/java/io/dataease/service/sys/log/LogManager.java @@ -0,0 +1,133 @@ +package io.dataease.service.sys.log; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import io.dataease.commons.constants.SysLogConstants; +import io.dataease.commons.utils.AuthUtils; +import io.dataease.dto.log.FolderItem; +import io.dataease.ext.ExtSysLogMapper; +import io.dataease.i18n.Translator; +import io.dataease.plugins.common.base.domain.SysLogWithBLOBs; +import io.dataease.plugins.common.dto.datasource.DataSourceType; +import io.dataease.service.datasource.DatasourceService; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@Component +public class LogManager { + + protected static final String contentFormat = "【%s】"; + + protected static final String positionFormat = "在【%s】"; + + protected static final String format = "给%s【%s】"; + + protected Gson gson = new Gson(); + + + protected Type type = new TypeToken>() {}.getType(); + + + + @Resource + private ExtSysLogMapper extSysLogMapper; + + @Resource + private DatasourceService datasourceService; + + + public String detailInfo(SysLogWithBLOBs vo) { + String sourceName = vo.getSourceName(); + String postion = null; + String operateTypeName = SysLogConstants.operateTypeName(vo.getOperateType()); + operateTypeName = Translator.get(operateTypeName); + String sourceTypeName = SysLogConstants.sourceTypeName(vo.getSourceType()); + sourceTypeName = Translator.get(sourceTypeName); + String result = operateTypeName + sourceTypeName + String.format(contentFormat, sourceName) + remarkInfo(vo); + + if ((postion = vo.getPosition()) != null) { + List folderItems = gson.fromJson(postion, type); + String template = folderItems.stream().map(folderItem -> folderItem.getName()).collect(Collectors.joining("/")); + String postionResult = String.format(positionFormat, template); + return postionResult + result; + } + return result; + } + + + + public String remarkInfo(SysLogWithBLOBs vo) { + String remakrk = null; + if ((remakrk = vo.getRemark()) != null) { + List targetInfos = gson.fromJson(remakrk, type); + String target = targetInfos.stream().map(item -> { + Integer targetType = item.getType(); + String targetTypeName = SysLogConstants.sourceTypeName(targetType); + return String.format(format, targetTypeName, item.getName()); + }).collect(Collectors.joining("/")); + return target; + } + return ""; + } + + public List parentsAndSelf(String id, SysLogConstants.SOURCE_TYPE type) { + Integer value = type.getValue(); + String typeValue = ""; + switch (value) { + case 2: + typeValue = "dataset"; + break; + case 3: + typeValue = "panel"; + break; + case 7: + typeValue = "dept"; + break; + default: + break; + } + List ids = new ArrayList<>(); + if (StringUtils.isNotBlank(typeValue)) { + ids.addAll(AuthUtils.parentResources(id, typeValue)); + }else { + ids.add(id); + } + List folderItems = extSysLogMapper.idAndName(ids, value); + folderItems.forEach(item -> item.setType(value)); + return folderItems; + } + + public FolderItem nameWithId(String id, Integer type) { + List ids = new ArrayList<>(); + ids.add(id); + List folderItems = extSysLogMapper.idAndName(ids, type); + if (CollectionUtils.isNotEmpty(folderItems)) { + return folderItems.get(0); + } + return null; + } + + public FolderItem dsTypeInfo(String typeId) { + ArrayList dataSourceTypes = new ArrayList<>(datasourceService.types()); + String name = null; + for (int i = 0; i < dataSourceTypes.size(); i++) { + if (dataSourceTypes.get(i).getType().equals(typeId)){ + name = dataSourceTypes.get(i).getName(); + break; + } + } + FolderItem folderItem = new FolderItem(); + folderItem.setId(typeId); + folderItem.setName(StringUtils.isNotBlank(name) ? name : typeId); + return folderItem; + } + + +} diff --git a/backend/src/main/java/io/dataease/service/sys/log/LogService.java b/backend/src/main/java/io/dataease/service/sys/log/LogService.java new file mode 100644 index 0000000000..688b2df2b9 --- /dev/null +++ b/backend/src/main/java/io/dataease/service/sys/log/LogService.java @@ -0,0 +1,113 @@ +package io.dataease.service.sys.log; + + +import com.google.gson.Gson; +import io.dataease.auth.api.dto.CurrentUserDto; +import io.dataease.commons.constants.SysLogConstants; +import io.dataease.commons.utils.AuthUtils; +import io.dataease.commons.utils.BeanUtils; +import io.dataease.controller.sys.base.BaseGridRequest; +import io.dataease.dto.SysLogDTO; +import io.dataease.dto.SysLogGridDTO; +import io.dataease.dto.log.FolderItem; +import io.dataease.ext.ExtSysLogMapper; +import io.dataease.ext.query.GridExample; +import io.dataease.i18n.Translator; +import io.dataease.plugins.common.base.domain.SysLogWithBLOBs; +import io.dataease.plugins.common.base.mapper.SysLogMapper; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +@Service +public class LogService { + + private Gson gson = new Gson(); + + private static Integer[] unPanelOperates = {4, 5, 8, 9, 10}; + + @Resource + private SysLogMapper sysLogMapper; + + @Resource + private ExtSysLogMapper extSysLogMapper; + + @Resource + private LogManager logManager; + + + + public List query(BaseGridRequest request) { + GridExample gridExample = request.convertExample(); + List voLogs = extSysLogMapper.query(gridExample); + List dtos = voLogs.stream().map(this::convertDTO).collect(Collectors.toList()); + return dtos; + } + + + public List types() { + List integers = Arrays.stream(unPanelOperates).collect(Collectors.toList()); + List results = new ArrayList<>(); + SysLogConstants.SOURCE_TYPE[] sourceTypes = SysLogConstants.SOURCE_TYPE.values(); + SysLogConstants.OPERATE_TYPE[] operateTypes = SysLogConstants.OPERATE_TYPE.values(); + for (int i = 0; i < sourceTypes.length; i++) { + SysLogConstants.SOURCE_TYPE sourceType = sourceTypes[i]; + for (int j = 0; j < operateTypes.length; j++) { + SysLogConstants.OPERATE_TYPE operateType = operateTypes[j]; + if (sourceType.getValue() > 3 && operateType.getValue() > 3){ + continue; + } + if (sourceType.getValue() != 3 && integers.contains(operateType.getValue())) { + continue; + } + FolderItem folderItem = new FolderItem(); + folderItem.setId(operateType.getValue() + "-" + sourceType.getValue()); + String operateTypeName = operateType.getName(); + String sourceTypeName = sourceType.getName(); + folderItem.setName( Translator.get(operateTypeName) + Translator.get(sourceTypeName)); + results.add(folderItem); + } + } + + return results; + } + + public SysLogGridDTO convertDTO(SysLogWithBLOBs vo) { + SysLogGridDTO sysLogGridDTO = new SysLogGridDTO(); + sysLogGridDTO.setOpType(SysLogConstants.operateTypeName(vo.getOperateType())); + sysLogGridDTO.setSourceType(SysLogConstants.sourceTypeName(vo.getSourceType())); + sysLogGridDTO.setTime(vo.getTime()); + sysLogGridDTO.setUser(vo.getNickName()); + sysLogGridDTO.setDetail(logManager.detailInfo(vo)); + return sysLogGridDTO; + } + + public void saveLog(SysLogDTO sysLogDTO) { + // String ip = ""; + CurrentUserDto user = AuthUtils.getUser(); + SysLogWithBLOBs sysLogWithBLOBs = BeanUtils.copyBean(new SysLogWithBLOBs(), sysLogDTO); + if (CollectionUtils.isNotEmpty(sysLogDTO.getPositions())) { + sysLogWithBLOBs.setPosition(gson.toJson(sysLogDTO.getPositions())); + } + if (CollectionUtils.isNotEmpty(sysLogDTO.getRemarks())) { + sysLogWithBLOBs.setRemark(gson.toJson(sysLogDTO.getRemarks())); + } + sysLogWithBLOBs.setTime(System.currentTimeMillis()); + sysLogWithBLOBs.setUserId(user.getUserId()); + sysLogWithBLOBs.setLoginName(user.getUsername()); + sysLogWithBLOBs.setNickName(user.getNickName()); + // sysLogWithBLOBs.setIp(ip); + sysLogMapper.insert(sysLogWithBLOBs); + } + + public void exportExcel(HttpServletResponse response) throws Exception{ + + } + +} diff --git a/backend/src/main/resources/i18n/messages_en_US.properties b/backend/src/main/resources/i18n/messages_en_US.properties index 89119243dc..a548ee5173 100644 --- a/backend/src/main/resources/i18n/messages_en_US.properties +++ b/backend/src/main/resources/i18n/messages_en_US.properties @@ -128,3 +128,23 @@ i18n_wrong_content=Wrong content i18n_wrong_tel=Wrong tel format i18n_wrong_email=Wrong email format i18n_wrong_name_format=Wrong name format + + +OPERATE_TYPE_CREATE=Create +OPERATE_TYPE_MODIFY=Modify +OPERATE_TYPE_DELETE=Delete +OPERATE_TYPE_SHARE=Share +OPERATE_TYPE_UNSHARE=Unshare +OPERATE_TYPE_AUTHORIZE=Authorize +OPERATE_TYPE_UNAUTHORIZE=Unauthorize +OPERATE_TYPE_CREATELINK=Create Link +OPERATE_TYPE_DELETELINK=Delete Link +OPERATE_TYPE_MODIFYLINK=Modify Link + +SOURCE_TYPE_DATASOURCE=数据源 +SOURCE_TYPE_DATASET=数据集 +SOURCE_TYPE_PANEL=仪表板 +SOURCE_TYPE_VIEW=视图 +SOURCE_TYPE_USER=用户 +SOURCE_TYPE_DEPT=组织 +SOURCE_TYPE_ROLE=角色 diff --git a/backend/src/main/resources/i18n/messages_zh_CN.properties b/backend/src/main/resources/i18n/messages_zh_CN.properties index 86b0db5e04..5cfced746a 100644 --- a/backend/src/main/resources/i18n/messages_zh_CN.properties +++ b/backend/src/main/resources/i18n/messages_zh_CN.properties @@ -128,3 +128,26 @@ i18n_wrong_tel=电话格式错误 i18n_wrong_email=邮箱格式错误 i18n_wrong_name_format=姓名格式错误 +OPERATE_TYPE_CREATE=创建 +OPERATE_TYPE_MODIFY=修改 +OPERATE_TYPE_DELETE=删除 +OPERATE_TYPE_SHARE=分享 +OPERATE_TYPE_UNSHARE=取消分享 +OPERATE_TYPE_AUTHORIZE=授权 +OPERATE_TYPE_UNAUTHORIZE=取消授权 +OPERATE_TYPE_CREATELINK=创建公共链接 +OPERATE_TYPE_DELETELINK=删除公共链接 +OPERATE_TYPE_MODIFYLINK=修改公共链接 + +SOURCE_TYPE_DATASOURCE=数据源 +SOURCE_TYPE_DATASET=数据集 +SOURCE_TYPE_PANEL=仪表板 +SOURCE_TYPE_VIEW=视图 +SOURCE_TYPE_USER=用户 +SOURCE_TYPE_DEPT=组织 +SOURCE_TYPE_ROLE=角色 + +I18N_OPERATE_TYPE=操作类型 +I18N_DETAIL=操作详情 +I18N_USER=操作人 +I18N_TIME=操作时间 diff --git a/backend/src/main/resources/i18n/messages_zh_TW.properties b/backend/src/main/resources/i18n/messages_zh_TW.properties index 47bcb1d54b..09448cd130 100644 --- a/backend/src/main/resources/i18n/messages_zh_TW.properties +++ b/backend/src/main/resources/i18n/messages_zh_TW.properties @@ -128,3 +128,22 @@ i18n_wrong_content=內容不合法 i18n_wrong_tel=電話格式錯誤 i18n_wrong_email=郵箱格式錯誤 i18n_wrong_name_format=姓名格式錯誤 + +OPERATE_TYPE_CREATE=創建 +OPERATE_TYPE_MODIFY=修改 +OPERATE_TYPE_DELETE=刪除 +OPERATE_TYPE_SHARE=分享 +OPERATE_TYPE_UNSHARE=取消分享 +OPERATE_TYPE_AUTHORIZE=授權 +OPERATE_TYPE_UNAUTHORIZE=取消授權 +OPERATE_TYPE_CREATELINK=創建公共鏈接 +OPERATE_TYPE_DELETELINK=刪除公共鏈接 +OPERATE_TYPE_MODIFYLINK=修改公共鏈接 + +SOURCE_TYPE_DATASOURCE=數據源 +SOURCE_TYPE_DATASET=數據集 +SOURCE_TYPE_PANEL=儀表板 +SOURCE_TYPE_VIEW=視圖 +SOURCE_TYPE_USER=用戶 +SOURCE_TYPE_DEPT=組織 +SOURCE_TYPE_ROLE=角色 diff --git a/frontend/package.json b/frontend/package.json index 5cdccb511b..bcd7c8910e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -27,7 +27,7 @@ "element-ui": "2.15.7", "file-save": "^0.2.0", "file-saver": "^2.0.5", - "fit2cloud-ui": "1.5.4", + "fit2cloud-ui": "^1.8.0", "flv.js": "^1.6.2", "html2canvasde": "^v1.1.4-de", "jquery": "^3.1.1", diff --git a/frontend/src/api/system/datasource.js b/frontend/src/api/system/datasource.js index ada40a1f3b..f85c70d160 100644 --- a/frontend/src/api/system/datasource.js +++ b/frontend/src/api/system/datasource.js @@ -47,11 +47,12 @@ export function editDs(data) { }) } -export function delDs(id) { +export function delDs(data) { return request({ - url: 'datasource/delete/' + id, + url: 'datasource/delete', loading: true, - method: 'post' + method: 'post', + data }) } @@ -81,7 +82,7 @@ export function getSchema(data) { }) } -export function checkApiDatasource(data){ +export function checkApiDatasource(data) { return request({ url: 'datasource/checkApiDatasource', method: 'post', diff --git a/frontend/src/api/system/log.js b/frontend/src/api/system/log.js new file mode 100644 index 0000000000..f91e5547d3 --- /dev/null +++ b/frontend/src/api/system/log.js @@ -0,0 +1,26 @@ +import request from '@/utils/request' + +export function logGrid(page, size, data) { + return request({ + url: '/api/log/logGrid/' + page + '/' + size, + method: 'post', + data, + loading: true + }) +} + +export function opTypes() { + return request({ + url: '/api/log/opTypes', + method: 'post', + loading: true + }) +} + +export function exportExcel() { + return request({ + url: '/api/log/export', + method: 'post', + loading: true + }) +} diff --git a/frontend/src/lang/zh.js b/frontend/src/lang/zh.js index 0ba1fa5899..efb5fabf93 100644 --- a/frontend/src/lang/zh.js +++ b/frontend/src/lang/zh.js @@ -2041,5 +2041,14 @@ export default { port: '端口', user: '用户名', passwd: '密码' + }, + log: { + title: '操作日志', + optype: '操作类型', + detail: '操作详情', + user: '操作用户', + time: '操作时间', + export: '导出', + search_by_key: '搜索详情' } } diff --git a/frontend/src/styles/index.scss b/frontend/src/styles/index.scss index c1a934eefd..b71acf0da5 100644 --- a/frontend/src/styles/index.scss +++ b/frontend/src/styles/index.scss @@ -827,3 +827,6 @@ div:focus { // position: relative !important; right: 0px; } +.fu-operator-component__operator { + display: none !important; +} \ No newline at end of file diff --git a/frontend/src/views/system/datasource/DsTree.vue b/frontend/src/views/system/datasource/DsTree.vue index 837883936c..a9fb49237c 100644 --- a/frontend/src/views/system/datasource/DsTree.vue +++ b/frontend/src/views/system/datasource/DsTree.vue @@ -5,11 +5,16 @@ {{ $t('commons.datasource') }} - + - + @@ -39,35 +44,41 @@ - + - + - + - + - + {{ data.name }} - + {{ data.name }} - + {{ data.name }} @@ -103,7 +114,7 @@ + +