diff --git a/api-admin/src/main/java/com/glxp/api/admin/annotation/Log.java b/api-admin/src/main/java/com/glxp/api/admin/annotation/Log.java new file mode 100644 index 00000000..f7dfafdb --- /dev/null +++ b/api-admin/src/main/java/com/glxp/api/admin/annotation/Log.java @@ -0,0 +1,40 @@ +package com.glxp.api.admin.annotation; + + +import com.glxp.api.admin.constant.BusinessType; +import com.glxp.api.admin.constant.OperatorType; + +import java.lang.annotation.*; + +/** + * 自定义操作日志记录注解 + */ +@Target({ElementType.PARAMETER, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Log { + /** + * 模块 + */ + String title() default ""; + + /** + * 功能 + */ + BusinessType businessType() default BusinessType.OTHER; + + /** + * 操作人类别 + */ + OperatorType operatorType() default OperatorType.MANAGE; + + /** + * 是否保存请求的参数 + */ + boolean isSaveRequestData() default true; + + /** + * 是否保存响应的参数 + */ + boolean isSaveResponseData() default true; +} diff --git a/api-admin/src/main/java/com/glxp/api/admin/aspect/LogAspect.java b/api-admin/src/main/java/com/glxp/api/admin/aspect/LogAspect.java new file mode 100644 index 00000000..15ae4099 --- /dev/null +++ b/api-admin/src/main/java/com/glxp/api/admin/aspect/LogAspect.java @@ -0,0 +1,202 @@ +package com.glxp.api.admin.aspect; + +import cn.hutool.core.lang.Dict; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ObjectUtil; +import com.glxp.api.admin.annotation.Log; +import com.glxp.api.admin.constant.BusinessStatus; +import com.glxp.api.admin.entity.monitor.OperLogDTO; +import com.glxp.api.admin.exception.JsonException; +import com.glxp.api.admin.service.monitor.OperLogService; +import com.glxp.api.admin.util.JsonUtils; +import com.glxp.api.admin.util.ServletUtils; +import com.glxp.api.admin.util.SpringUtils; +import com.glxp.api.common.enums.ResultEnum; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +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.springframework.http.HttpMethod; +import org.springframework.stereotype.Component; +import org.springframework.validation.BindingResult; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.servlet.HandlerMapping; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Collection; +import java.util.Map; + +/** + * 操作日志记录处理 + */ +@Slf4j +@Aspect +@Component +public class LogAspect { + + /** + * 排除敏感属性字段 + */ + public static final String[] EXCLUDE_PROPERTIES = {"password", "oldPassword", "newPassword", "confirmPassword"}; + + /** + * 处理完请求后执行 + * + * @param joinPoint 切点 + */ + @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult") + public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult) { + handleLog(joinPoint, controllerLog, null, jsonResult); + } + + /** + * 拦截异常操作 + * + * @param joinPoint 切点 + * @param e 异常 + */ + @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e") + public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e) { + handleLog(joinPoint, controllerLog, e, null); + } + + protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult) { + try { + + // *========数据库日志=========*// + OperLogDTO operLog = new OperLogDTO(); + operLog.setStatus(BusinessStatus.SUCCESS.ordinal()); + // 请求的地址 + String ip = ServletUtils.getClientIP(); + operLog.setOperIp(ip); + operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255)); + + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (attributes == null) { + throw new JsonException(ResultEnum.NOT_NETWORK); + } + HttpServletRequest request = attributes.getRequest(); + String id = request.getHeader("ADMIN_ID"); + operLog.setOperName(id); + + if (e != null) { + operLog.setStatus(BusinessStatus.FAIL.ordinal()); + operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000)); + } + // 设置方法名称 + String className = joinPoint.getTarget().getClass().getName(); + String methodName = joinPoint.getSignature().getName(); + operLog.setMethod(className + "." + methodName + "()"); + // 设置请求方式 + operLog.setRequestMethod(ServletUtils.getRequest().getMethod()); + // 处理设置注解上的参数 + getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult); + // 保存数据库 + SpringUtils.getBean(OperLogService.class).recordOper(operLog); + } catch (Exception exp) { + // 记录本地异常日志 + log.error("==前置通知异常=="); + log.error("异常信息:{}", exp.getMessage()); + exp.printStackTrace(); + } + } + + /** + * 获取注解中对方法的描述信息 用于Controller层注解 + * + * @param log 日志 + * @param operLog 操作日志 + * @throws Exception + */ + public void getControllerMethodDescription(JoinPoint joinPoint, Log log, OperLogDTO operLog, Object jsonResult) throws Exception { + // 设置action动作 + operLog.setBusinessType(log.businessType().ordinal()); + // 设置标题 + operLog.setTitle(log.title()); + // 设置操作人类别 + operLog.setOperatorType(log.operatorType().ordinal()); + // 是否需要保存request,参数和值 + if (log.isSaveRequestData()) { + // 获取参数的信息,传入到数据库中。 + setRequestValue(joinPoint, operLog); + } + // 是否需要保存response,参数和值 + if (log.isSaveResponseData() && ObjectUtil.isNotNull(jsonResult)) { + operLog.setJsonResult(StringUtils.substring(JsonUtils.toJsonString(jsonResult), 0, 2000)); + } + } + + /** + * 获取请求的参数,放到log中 + * + * @param operLog 操作日志 + * @throws Exception 异常 + */ + private void setRequestValue(JoinPoint joinPoint, OperLogDTO operLog) throws Exception { + String requestMethod = operLog.getRequestMethod(); + if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) { + String params = argsArrayToString(joinPoint.getArgs()); + operLog.setOperParam(StringUtils.substring(params, 0, 2000)); + } else { + Map paramsMap = (Map) ServletUtils.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); + operLog.setOperParam(StringUtils.substring(paramsMap.toString(), 0, 2000)); + } + } + + /** + * 参数拼装 + */ + private String argsArrayToString(Object[] paramsArray) { + StringBuilder params = new StringBuilder(); + if (paramsArray != null && paramsArray.length > 0) { + for (Object o : paramsArray) { + if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) { + try { + String str = JsonUtils.toJsonString(o); + Dict dict = JsonUtils.parseMap(str); + if (MapUtil.isNotEmpty(dict)) { + MapUtil.removeAny(dict, EXCLUDE_PROPERTIES); + str = JsonUtils.toJsonString(dict); + } + params.append(str).append(" "); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } + return params.toString().trim(); + } + + /** + * 判断是否需要过滤的对象。 + * + * @param o 对象信息。 + * @return 如果是需要过滤的对象,则返回true;否则返回false。 + */ + @SuppressWarnings("rawtypes") + public boolean isFilterObject(final Object o) { + Class clazz = o.getClass(); + if (clazz.isArray()) { + return clazz.getComponentType().isAssignableFrom(MultipartFile.class); + } else if (Collection.class.isAssignableFrom(clazz)) { + Collection collection = (Collection) o; + for (Object value : collection) { + return value instanceof MultipartFile; + } + } else if (Map.class.isAssignableFrom(clazz)) { + Map map = (Map) o; + for (Object value : map.entrySet()) { + Map.Entry entry = (Map.Entry) value; + return entry.getValue() instanceof MultipartFile; + } + } + return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse + || o instanceof BindingResult; + } +} diff --git a/api-admin/src/main/java/com/glxp/api/admin/constant/BusinessStatus.java b/api-admin/src/main/java/com/glxp/api/admin/constant/BusinessStatus.java new file mode 100644 index 00000000..32ff41c9 --- /dev/null +++ b/api-admin/src/main/java/com/glxp/api/admin/constant/BusinessStatus.java @@ -0,0 +1,18 @@ +package com.glxp.api.admin.constant; + +/** + * 操作状态 + * + * @author ruoyi + */ +public enum BusinessStatus { + /** + * 成功 + */ + SUCCESS, + + /** + * 失败 + */ + FAIL, +} diff --git a/api-admin/src/main/java/com/glxp/api/admin/constant/BusinessType.java b/api-admin/src/main/java/com/glxp/api/admin/constant/BusinessType.java new file mode 100644 index 00000000..4448f18b --- /dev/null +++ b/api-admin/src/main/java/com/glxp/api/admin/constant/BusinessType.java @@ -0,0 +1,56 @@ +package com.glxp.api.admin.constant; + +/** + * 业务操作类型 + */ +public enum BusinessType { + /** + * 其它 + */ + OTHER, + + /** + * 新增 + */ + INSERT, + + /** + * 修改 + */ + UPDATE, + + /** + * 删除 + */ + DELETE, + + /** + * 授权 + */ + GRANT, + + /** + * 导出 + */ + EXPORT, + + /** + * 导入 + */ + IMPORT, + + /** + * 强退 + */ + FORCE, + + /** + * 生成代码 + */ + GENCODE, + + /** + * 清空数据 + */ + CLEAN, +} diff --git a/api-admin/src/main/java/com/glxp/api/admin/constant/Constant.java b/api-admin/src/main/java/com/glxp/api/admin/constant/Constant.java index fe2a8dbe..caa5b2f8 100644 --- a/api-admin/src/main/java/com/glxp/api/admin/constant/Constant.java +++ b/api-admin/src/main/java/com/glxp/api/admin/constant/Constant.java @@ -42,5 +42,38 @@ public class Constant { public static final String passwordReg = "^(?![a-zA-Z]+$)(?![A-Z0-9]+$)(?![A-Z\\W_.*%@!]+$)(?![a-z0-9]+$)(?![a-z\\W_.;*%@!]+$)(?![0-9\\W_.;*%@!]+$)[a-zA-Z0-9\\W_.;*%@!]{8,20}$"; + /** + * 通用失败标识 + */ + public static final String FAIL = "1"; + /** + * 通用成功标识 + */ + public static final String SUCCESS = "0"; + + + /** + * 登录成功 + */ + public static final String LOGIN_SUCCESS = "Success"; + + /** + * 注销 + */ + public static final String LOGOUT = "Logout"; + + /** + * 注册 + */ + public static final String REGISTER = "Register"; + + /** + * 登录失败 + */ + public static final String LOGIN_FAIL = "Error"; + + + public static final String UTF8 = "UTF-8"; + public static final String GBK = "GBK"; } diff --git a/api-admin/src/main/java/com/glxp/api/admin/constant/OperatorType.java b/api-admin/src/main/java/com/glxp/api/admin/constant/OperatorType.java new file mode 100644 index 00000000..f36dabfc --- /dev/null +++ b/api-admin/src/main/java/com/glxp/api/admin/constant/OperatorType.java @@ -0,0 +1,21 @@ +package com.glxp.api.admin.constant; + +/** + * 操作人类别 + */ +public enum OperatorType { + /** + * 其它 + */ + OTHER, + + /** + * 后台用户 + */ + MANAGE, + + /** + * 手机端用户 + */ + MOBILE +} diff --git a/api-admin/src/main/java/com/glxp/api/admin/dao/monitor/SysLogininforMapper.java b/api-admin/src/main/java/com/glxp/api/admin/dao/monitor/SysLogininforMapper.java new file mode 100644 index 00000000..c02511e9 --- /dev/null +++ b/api-admin/src/main/java/com/glxp/api/admin/dao/monitor/SysLogininforMapper.java @@ -0,0 +1,36 @@ +package com.glxp.api.admin.dao.monitor; + +import com.glxp.api.admin.entity.monitor.SysLogininfor; +import com.glxp.api.admin.req.monitor.FilterLoginLogRequest; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 系统访问日志情况信息 数据层 + */ +@Mapper +public interface SysLogininforMapper { + + + int insert(SysLogininfor sysLogininfor); + + int deleteBatchIds(@Param("infoIds") List infoIds); + + List selectLogininforList(FilterLoginLogRequest logininforRequest); + + /** + * 新增系统登录日志 + * + * @param logininfor 访问日志对象 + */ + void insertLogininfor(SysLogininfor logininfor); + + /** + * 根据此日期之前的数据(包括当前时间) + * + * @param date 日期 + */ + void deleteByDate(@Param("date") String date); +} diff --git a/api-admin/src/main/java/com/glxp/api/admin/dao/monitor/SysOperLogMapper.java b/api-admin/src/main/java/com/glxp/api/admin/dao/monitor/SysOperLogMapper.java new file mode 100644 index 00000000..b7ebbac0 --- /dev/null +++ b/api-admin/src/main/java/com/glxp/api/admin/dao/monitor/SysOperLogMapper.java @@ -0,0 +1,29 @@ +package com.glxp.api.admin.dao.monitor; + + +import com.glxp.api.admin.entity.monitor.SysOperLog; +import com.glxp.api.admin.req.monitor.FilterOperLogRequest; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 操作日志 数据层 + */ +@Mapper +public interface SysOperLogMapper { + + List selectList(FilterOperLogRequest filterOperLogRequest); + + int insert(SysOperLog sysOperLog); + + int deleteBatchIds(@Param("ids") List ids); + + int delete(Long id); + + SysOperLog selectById(Long id); + + + void deleteByDate(@Param("date") String date); +} diff --git a/api-admin/src/main/java/com/glxp/api/admin/entity/monitor/OperLogDTO.java b/api-admin/src/main/java/com/glxp/api/admin/entity/monitor/OperLogDTO.java new file mode 100644 index 00000000..eb7d2727 --- /dev/null +++ b/api-admin/src/main/java/com/glxp/api/admin/entity/monitor/OperLogDTO.java @@ -0,0 +1,99 @@ +package com.glxp.api.admin.entity.monitor; + +import lombok.Data; + +import java.util.Date; + +/** + * 通用操作日志实体 + */ + +@Data +public class OperLogDTO { + + /** + * 日志主键 + */ + private Long operId; + + /** + * 操作模块 + */ + private String title; + + /** + * 业务类型(0其它 1新增 2修改 3删除) + */ + private Integer businessType; + + /** + * 业务类型数组 + */ + private Integer[] businessTypes; + + /** + * 请求方法 + */ + private String method; + + /** + * 请求方式 + */ + private String requestMethod; + + /** + * 操作类别(0其它 1后台用户 2手机端用户) + */ + private Integer operatorType; + + /** + * 操作人员 + */ + private String operName; + + /** + * 部门名称 + */ + private String deptName; + + /** + * 请求url + */ + private String operUrl; + + /** + * 操作地址 + */ + private String operIp; + + /** + * 操作地点 + */ + private String operLocation; + + /** + * 请求参数 + */ + private String operParam; + + /** + * 返回参数 + */ + private String jsonResult; + + /** + * 操作状态(0正常 1异常) + */ + private Integer status; + + /** + * 错误消息 + */ + private String errorMsg; + + /** + * 操作时间 + */ + private Date operTime; + +} diff --git a/api-admin/src/main/java/com/glxp/api/admin/entity/monitor/SysLogininfor.java b/api-admin/src/main/java/com/glxp/api/admin/entity/monitor/SysLogininfor.java new file mode 100644 index 00000000..ec8d13b0 --- /dev/null +++ b/api-admin/src/main/java/com/glxp/api/admin/entity/monitor/SysLogininfor.java @@ -0,0 +1,66 @@ +package com.glxp.api.admin.entity.monitor; + +import lombok.Data; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * 系统访问记录表 sys_logininfor + */ + +@Data +public class SysLogininfor { + + /** + * ID + */ + private Long infoId; + + /** + * 用户账号 + */ + private String userName; + + /** + * 登录状态 0成功 1失败 + */ + private String status; + + /** + * 登录IP地址 + */ + private String ipaddr; + + /** + * 登录地点 + */ + private String loginLocation; + + /** + * 浏览器类型 + */ + private String browser; + + /** + * 操作系统 + */ + private String os; + + /** + * 提示消息 + */ + private String msg; + + /** + * 访问时间 + */ + private Date loginTime; + + /** + * 请求参数 + */ + private Map params = new HashMap<>(); + +} diff --git a/api-admin/src/main/java/com/glxp/api/admin/entity/monitor/SysOperLog.java b/api-admin/src/main/java/com/glxp/api/admin/entity/monitor/SysOperLog.java new file mode 100644 index 00000000..30d1a5de --- /dev/null +++ b/api-admin/src/main/java/com/glxp/api/admin/entity/monitor/SysOperLog.java @@ -0,0 +1,108 @@ +package com.glxp.api.admin.entity.monitor; + +import lombok.Data; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * 操作日志记录表 oper_log + */ + +@Data +public class SysOperLog { + + /** + * 日志主键 + */ + private Long operId; + + /** + * 操作模块 + */ + private String title; + + /** + * 业务类型(0其它 1新增 2修改 3删除) + */ + private Integer businessType; + + /** + * 业务类型数组 + */ + private Integer[] businessTypes; + + /** + * 请求方法 + */ + private String method; + + /** + * 请求方式 + */ + private String requestMethod; + + /** + * 操作类别(0其它 1后台用户 2手机端用户) + */ + private Integer operatorType; + + /** + * 操作人员 + */ + private String operName; + + /** + * 部门名称 + */ + private String deptName; + + /** + * 请求url + */ + private String operUrl; + + /** + * 操作地址 + */ + private String operIp; + + /** + * 操作地点 + */ + private String operLocation; + + /** + * 请求参数 + */ + private String operParam; + + /** + * 返回参数 + */ + private String jsonResult; + + /** + * 操作状态(0正常 1异常) + */ + private Integer status; + + /** + * 错误消息 + */ + private String errorMsg; + + /** + * 操作时间 + */ + private Date operTime; + + /** + * 请求参数 + */ + private Map params = new HashMap<>(); + + private String operUserName; + +} diff --git a/api-admin/src/main/java/com/glxp/api/admin/req/monitor/FilterLoginLogRequest.java b/api-admin/src/main/java/com/glxp/api/admin/req/monitor/FilterLoginLogRequest.java new file mode 100644 index 00000000..f317bbb4 --- /dev/null +++ b/api-admin/src/main/java/com/glxp/api/admin/req/monitor/FilterLoginLogRequest.java @@ -0,0 +1,19 @@ +package com.glxp.api.admin.req.monitor; + +import com.glxp.api.admin.req.ListPageRequest; +import lombok.Data; + +@Data +public class FilterLoginLogRequest extends ListPageRequest { + + private Long infoId; + + private String userName; + + private String status; + + private String ipaddr; + + private String loginLocation; + +} diff --git a/api-admin/src/main/java/com/glxp/api/admin/req/monitor/FilterOperLogRequest.java b/api-admin/src/main/java/com/glxp/api/admin/req/monitor/FilterOperLogRequest.java new file mode 100644 index 00000000..c1fcb158 --- /dev/null +++ b/api-admin/src/main/java/com/glxp/api/admin/req/monitor/FilterOperLogRequest.java @@ -0,0 +1,32 @@ +package com.glxp.api.admin.req.monitor; + +import com.glxp.api.admin.req.ListPageRequest; +import lombok.Data; + +@Data +public class FilterOperLogRequest extends ListPageRequest { + + private Long operId; + + private String title; + + private Integer businessType; + + private String method; + + private String requestMethod; + + private Integer operatorType; + + private String operName; + + private String deptName; + + private String operUrl; + + private String operIp; + + private Integer status; + + +} diff --git a/api-admin/src/main/java/com/glxp/api/admin/service/monitor/ISysLogininforService.java b/api-admin/src/main/java/com/glxp/api/admin/service/monitor/ISysLogininforService.java new file mode 100644 index 00000000..bbb58294 --- /dev/null +++ b/api-admin/src/main/java/com/glxp/api/admin/service/monitor/ISysLogininforService.java @@ -0,0 +1,35 @@ +package com.glxp.api.admin.service.monitor; + + +import com.glxp.api.admin.entity.monitor.SysLogininfor; +import com.glxp.api.admin.req.monitor.FilterLoginLogRequest; + +import java.util.List; + +/** + * 系统访问日志情况信息 服务层 + * + * @author Lion Li + */ +public interface ISysLogininforService { + + + List selectLogininforList(FilterLoginLogRequest logininforRequest); + + /** + * 新增系统登录日志 + * + * @param logininfor 访问日志对象 + */ + void insertLogininfor(SysLogininfor logininfor); + + + /** + * 批量删除系统登录日志 + * + * @param infoIds 需要删除的登录日志ID + * @return 结果 + */ + int deleteLogininforByIds(List infoIds); + +} diff --git a/api-admin/src/main/java/com/glxp/api/admin/service/monitor/ISysOperLogService.java b/api-admin/src/main/java/com/glxp/api/admin/service/monitor/ISysOperLogService.java new file mode 100644 index 00000000..9fe21a65 --- /dev/null +++ b/api-admin/src/main/java/com/glxp/api/admin/service/monitor/ISysOperLogService.java @@ -0,0 +1,47 @@ +package com.glxp.api.admin.service.monitor; + + + +import com.glxp.api.admin.entity.monitor.SysOperLog; +import com.glxp.api.admin.req.monitor.FilterOperLogRequest; + +import java.util.List; + +/** + * 操作日志 服务层 + */ +public interface ISysOperLogService { + + + /** + * 新增操作日志 + * + * @param operLog 操作日志对象 + */ + void insertOperlog(SysOperLog operLog); + + /** + * 查询系统操作日志集合 + * + * @param operLog 操作日志对象 + * @return 操作日志集合 + */ + List selectOperLogList(FilterOperLogRequest filterOperLogRequest); + + /** + * 批量删除系统操作日志 + * + * @param operIds 需要删除的操作日志ID + * @return 结果 + */ + int deleteOperLogByIds(Long[] operIds); + + /** + * 查询操作日志详细 + * + * @param operId 操作ID + * @return 操作日志对象 + */ + SysOperLog selectOperLogById(Long operId); + +} diff --git a/api-admin/src/main/java/com/glxp/api/admin/service/monitor/LogininforService.java b/api-admin/src/main/java/com/glxp/api/admin/service/monitor/LogininforService.java new file mode 100644 index 00000000..3b9a5300 --- /dev/null +++ b/api-admin/src/main/java/com/glxp/api/admin/service/monitor/LogininforService.java @@ -0,0 +1,12 @@ +package com.glxp.api.admin.service.monitor; + +import javax.servlet.http.HttpServletRequest; + +/** + * 通用 系统访问日志 + */ +public interface LogininforService { + + void recordLogininfor(String username, String status, String message, + HttpServletRequest request, Object... args); +} diff --git a/api-admin/src/main/java/com/glxp/api/admin/service/monitor/OperLogService.java b/api-admin/src/main/java/com/glxp/api/admin/service/monitor/OperLogService.java new file mode 100644 index 00000000..8cab4bf8 --- /dev/null +++ b/api-admin/src/main/java/com/glxp/api/admin/service/monitor/OperLogService.java @@ -0,0 +1,13 @@ +package com.glxp.api.admin.service.monitor; + +import com.glxp.api.admin.entity.monitor.OperLogDTO; +import org.springframework.scheduling.annotation.Async; + +/** + * 通用 操作日志 + */ +public interface OperLogService { + + @Async + void recordOper(OperLogDTO operLogDTO); +} diff --git a/api-admin/src/main/java/com/glxp/api/admin/service/monitor/impl/SysLogininforServiceImpl.java b/api-admin/src/main/java/com/glxp/api/admin/service/monitor/impl/SysLogininforServiceImpl.java new file mode 100644 index 00000000..8d0026b6 --- /dev/null +++ b/api-admin/src/main/java/com/glxp/api/admin/service/monitor/impl/SysLogininforServiceImpl.java @@ -0,0 +1,115 @@ +package com.glxp.api.admin.service.monitor.impl; + +import cn.hutool.http.useragent.UserAgent; +import cn.hutool.http.useragent.UserAgentUtil; +import com.github.pagehelper.PageHelper; +import com.glxp.api.admin.constant.Constant; +import com.glxp.api.admin.dao.monitor.SysLogininforMapper; +import com.glxp.api.admin.entity.monitor.SysLogininfor; +import com.glxp.api.admin.req.monitor.FilterLoginLogRequest; +import com.glxp.api.admin.service.monitor.ISysLogininforService; +import com.glxp.api.admin.service.monitor.LogininforService; +import com.glxp.api.admin.util.AddressUtils; +import com.glxp.api.admin.util.ServletUtils; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.servlet.http.HttpServletRequest; +import java.util.Date; +import java.util.List; + +/** + * 系统访问日志情况信息 服务层处理 + */ +@RequiredArgsConstructor +@Slf4j +@Service +@Transactional(rollbackFor = Exception.class) +public class SysLogininforServiceImpl implements ISysLogininforService, LogininforService { + + private final SysLogininforMapper baseMapper; + + /** + * 记录登录信息 + * + * @param username 用户名 + * @param status 状态 + * @param message 消息 + * @param args 列表 + */ + @Async + @Override + public void recordLogininfor(final String username, final String status, final String message, + HttpServletRequest request, final Object... args) { + final UserAgent userAgent = UserAgentUtil.parse(request.getHeader("User-Agent")); + final String ip = ServletUtils.getClientIP(request); + + String address = AddressUtils.getRealAddressByIP(ip); + StringBuilder s = new StringBuilder(); + s.append(getBlock(ip)); + s.append(address); + s.append(getBlock(username)); + s.append(getBlock(status)); + s.append(getBlock(message)); + // 打印信息到日志 + log.info(s.toString(), args); + // 获取客户端操作系统 + String os = userAgent.getOs().getName(); + // 获取客户端浏览器 + String browser = userAgent.getBrowser().getName(); + // 封装对象 + SysLogininfor logininfor = new SysLogininfor(); + logininfor.setUserName(username); + logininfor.setIpaddr(ip); + logininfor.setLoginLocation(address); + logininfor.setBrowser(browser); + logininfor.setOs(os); + logininfor.setMsg(message); + // 日志状态 + if (StringUtils.equalsAny(status, Constant.LOGIN_SUCCESS, Constant.LOGOUT, Constant.REGISTER)) { + logininfor.setStatus(Constant.SUCCESS); + } else if (Constant.LOGIN_FAIL.equals(status)) { + logininfor.setStatus(Constant.FAIL); + } + // 插入数据 + insertLogininfor(logininfor); + } + + private String getBlock(Object msg) { + if (msg == null) { + msg = ""; + } + return "[" + msg.toString() + "]"; + } + + @Override + public List selectLogininforList(FilterLoginLogRequest logininfor) { + if (logininfor.getPage() != null) { + int offset = (logininfor.getPage() - 1) * logininfor.getLimit(); + PageHelper.offsetPage(offset, logininfor.getLimit()); + } + return baseMapper.selectLogininforList(logininfor); + } + + /** + * 新增系统登录日志 + * + * @param logininfor 访问日志对象 + */ + @Override + public void insertLogininfor(SysLogininfor logininfor) { + logininfor.setLoginTime(new Date()); + baseMapper.insert(logininfor); + } + + @Override + public int deleteLogininforByIds(List infoIds) { + return baseMapper.deleteBatchIds(infoIds); + } + + +} diff --git a/api-admin/src/main/java/com/glxp/api/admin/service/monitor/impl/SysOperLogServiceImpl.java b/api-admin/src/main/java/com/glxp/api/admin/service/monitor/impl/SysOperLogServiceImpl.java new file mode 100644 index 00000000..eda2a97c --- /dev/null +++ b/api-admin/src/main/java/com/glxp/api/admin/service/monitor/impl/SysOperLogServiceImpl.java @@ -0,0 +1,88 @@ +package com.glxp.api.admin.service.monitor.impl; + +import cn.hutool.core.bean.BeanUtil; +import com.github.pagehelper.PageHelper; +import com.glxp.api.admin.dao.monitor.SysOperLogMapper; +import com.glxp.api.admin.entity.monitor.OperLogDTO; +import com.glxp.api.admin.entity.monitor.SysOperLog; +import com.glxp.api.admin.req.monitor.FilterOperLogRequest; +import com.glxp.api.admin.service.monitor.ISysOperLogService; +import com.glxp.api.admin.service.monitor.OperLogService; +import com.glxp.api.admin.util.AddressUtils; +import lombok.RequiredArgsConstructor; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +/** + * 操作日志 服务层处理 + */ +@RequiredArgsConstructor +@Service +@Transactional(rollbackFor = Exception.class) +public class SysOperLogServiceImpl implements ISysOperLogService, OperLogService { + + private final SysOperLogMapper baseMapper; + + /** + * 操作日志记录 + * + * @param operLogDTO 操作日志信息 + */ + @Async + @Override + public void recordOper(final OperLogDTO operLogDTO) { + SysOperLog operLog = BeanUtil.toBean(operLogDTO, SysOperLog.class); + // 远程查询操作地点 + operLog.setOperLocation(AddressUtils.getRealAddressByIP(operLog.getOperIp())); + insertOperlog(operLog); + } + + @Override + public List selectOperLogList(FilterOperLogRequest filterOperLogRequest) { + if (filterOperLogRequest.getPage() != null) { + int offset = (filterOperLogRequest.getPage() - 1) * filterOperLogRequest.getLimit(); + PageHelper.offsetPage(offset, filterOperLogRequest.getLimit()); + } + return baseMapper.selectList(filterOperLogRequest); + } + + /** + * 新增操作日志 + * + * @param operLog 操作日志对象 + */ + @Override + public void insertOperlog(SysOperLog operLog) { + operLog.setOperTime(new Date()); + baseMapper.insert(operLog); + } + + + /** + * 批量删除系统操作日志 + * + * @param operIds 需要删除的操作日志ID + * @return 结果 + */ + @Override + public int deleteOperLogByIds(Long[] operIds) { + return baseMapper.deleteBatchIds(Arrays.asList(operIds)); + } + + /** + * 查询操作日志详细 + * + * @param operId 操作ID + * @return 操作日志对象 + */ + @Override + public SysOperLog selectOperLogById(Long operId) { + return baseMapper.selectById(operId); + } + +} diff --git a/api-admin/src/main/java/com/glxp/api/admin/util/AddressUtils.java b/api-admin/src/main/java/com/glxp/api/admin/util/AddressUtils.java new file mode 100644 index 00000000..a9dddbde --- /dev/null +++ b/api-admin/src/main/java/com/glxp/api/admin/util/AddressUtils.java @@ -0,0 +1,55 @@ +package com.glxp.api.admin.util; + +import cn.hutool.core.lang.Dict; +import cn.hutool.core.net.NetUtil; +import cn.hutool.http.HtmlUtil; +import cn.hutool.http.HttpUtil; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + +/** + * 获取地址类 + */ +@Slf4j +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class AddressUtils { + + // IP地址查询 + public static final String IP_URL = "http://whois.pconline.com.cn/ipJson.jsp"; + + // 未知地址 + public static final String UNKNOWN = "XX XX"; + + public static String getRealAddressByIP(String ip) { + String address = UNKNOWN; + if (StringUtils.isBlank(ip)) { + return address; + } + // 内网不查询 + ip = "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : HtmlUtil.cleanHtmlTag(ip); + if (NetUtil.isInnerIP(ip)) { + return "内网IP"; + } +// if (RuoYiConfig.isAddressEnabled()) { +// try { +// String rspStr = HttpUtil.createGet(IP_URL) +// .body("ip=" + ip + "&json=true", Constant.GBK) +// .execute() +// .body(); +// if (StringUtils.isEmpty(rspStr)) { +// log.error("获取地理位置异常 {}", ip); +// return UNKNOWN; +// } +// Dict obj = JsonUtils.parseMap(rspStr); +// String region = obj.getStr("pro"); +// String city = obj.getStr("city"); +// return String.format("%s %s", region, city); +// } catch (Exception e) { +// log.error("获取地理位置异常 {}", ip); +// } +// } + return UNKNOWN; + } +} diff --git a/api-admin/src/main/java/com/glxp/api/admin/util/JsonUtils.java b/api-admin/src/main/java/com/glxp/api/admin/util/JsonUtils.java new file mode 100644 index 00000000..a02af7cd --- /dev/null +++ b/api-admin/src/main/java/com/glxp/api/admin/util/JsonUtils.java @@ -0,0 +1,110 @@ +package com.glxp.api.admin.util; + +import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjectUtil; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.exc.MismatchedInputException; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.apache.commons.lang3.StringUtils; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * JSON 工具类 + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class JsonUtils { + + private static final ObjectMapper OBJECT_MAPPER = SpringUtils.getBean(ObjectMapper.class); + + public static ObjectMapper getObjectMapper() { + return OBJECT_MAPPER; + } + + public static String toJsonString(Object object) { + if (ObjectUtil.isNull(object)) { + return null; + } + try { + return OBJECT_MAPPER.writeValueAsString(object); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + public static T parseObject(String text, Class clazz) { + if (StringUtils.isEmpty(text)) { + return null; + } + try { + return OBJECT_MAPPER.readValue(text, clazz); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static T parseObject(byte[] bytes, Class clazz) { + if (ArrayUtil.isEmpty(bytes)) { + return null; + } + try { + return OBJECT_MAPPER.readValue(bytes, clazz); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static T parseObject(String text, TypeReference typeReference) { + if (StringUtils.isBlank(text)) { + return null; + } + try { + return OBJECT_MAPPER.readValue(text, typeReference); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static Dict parseMap(String text) { + if (StringUtils.isBlank(text)) { + return null; + } + try { + return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructType(Dict.class)); + } catch (MismatchedInputException e) { + // 类型不匹配说明不是json + return null; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static List parseArrayMap(String text) { + if (StringUtils.isBlank(text)) { + return null; + } + try { + return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, Dict.class)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static List parseArray(String text, Class clazz) { + if (StringUtils.isEmpty(text)) { + return new ArrayList<>(); + } + try { + return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, clazz)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/api-admin/src/main/java/com/glxp/api/admin/util/ServletUtils.java b/api-admin/src/main/java/com/glxp/api/admin/util/ServletUtils.java new file mode 100644 index 00000000..0a29c3ef --- /dev/null +++ b/api-admin/src/main/java/com/glxp/api/admin/util/ServletUtils.java @@ -0,0 +1,173 @@ +package com.glxp.api.admin.util; + +import cn.hutool.core.convert.Convert; +import cn.hutool.extra.servlet.ServletUtil; +import cn.hutool.http.HttpStatus; +import com.glxp.api.admin.constant.Constant; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.MediaType; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + +/** + * 客户端工具类 + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class ServletUtils extends ServletUtil { + + /** + * 获取String参数 + */ + public static String getParameter(String name) { + return getRequest().getParameter(name); + } + + /** + * 获取String参数 + */ + public static String getParameter(String name, String defaultValue) { + return Convert.toStr(getRequest().getParameter(name), defaultValue); + } + + /** + * 获取Integer参数 + */ + public static Integer getParameterToInt(String name) { + return Convert.toInt(getRequest().getParameter(name)); + } + + /** + * 获取Integer参数 + */ + public static Integer getParameterToInt(String name, Integer defaultValue) { + return Convert.toInt(getRequest().getParameter(name), defaultValue); + } + + /** + * 获取Boolean参数 + */ + public static Boolean getParameterToBool(String name) { + return Convert.toBool(getRequest().getParameter(name)); + } + + /** + * 获取Boolean参数 + */ + public static Boolean getParameterToBool(String name, Boolean defaultValue) { + return Convert.toBool(getRequest().getParameter(name), defaultValue); + } + + /** + * 获取request + */ + public static HttpServletRequest getRequest() { + return getRequestAttributes().getRequest(); + } + + /** + * 获取response + */ + public static HttpServletResponse getResponse() { + return getRequestAttributes().getResponse(); + } + + /** + * 获取session + */ + public static HttpSession getSession() { + return getRequest().getSession(); + } + + public static ServletRequestAttributes getRequestAttributes() { + RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); + return (ServletRequestAttributes) attributes; + } + + /** + * 将字符串渲染到客户端 + * + * @param response 渲染对象 + * @param string 待渲染的字符串 + */ + public static void renderString(HttpServletResponse response, String string) { + try { + response.setStatus(HttpStatus.HTTP_OK); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setCharacterEncoding(StandardCharsets.UTF_8.toString()); + response.getWriter().print(string); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * 是否是Ajax异步请求 + * + * @param request + */ + public static boolean isAjaxRequest(HttpServletRequest request) { + + String accept = request.getHeader("accept"); + if (accept != null && accept.contains(MediaType.APPLICATION_JSON_VALUE)) { + return true; + } + + String xRequestedWith = request.getHeader("X-Requested-With"); + if (xRequestedWith != null && xRequestedWith.contains("XMLHttpRequest")) { + return true; + } + + String uri = request.getRequestURI(); + if (StringUtils.equalsAnyIgnoreCase(uri, ".json", ".xml")) { + return true; + } + + String ajax = request.getParameter("__ajax"); + return StringUtils.equalsAnyIgnoreCase(ajax, "json", "xml"); + } + + public static String getClientIP() { + return getClientIP(getRequest()); + } + + /** + * 内容编码 + * + * @param str 内容 + * @return 编码后的内容 + */ + public static String urlEncode(String str) { + try { + return URLEncoder.encode(str, Constant.UTF8); + } catch (UnsupportedEncodingException e) { + return StringUtils.EMPTY; + } + } + + /** + * 内容解码 + * + * @param str 内容 + * @return 解码后的内容 + */ + public static String urlDecode(String str) { + try { + return URLDecoder.decode(str, Constant.UTF8); + } catch (UnsupportedEncodingException e) { + return StringUtils.EMPTY; + } + } + +} diff --git a/api-admin/src/main/java/com/glxp/api/admin/util/SpringUtils.java b/api-admin/src/main/java/com/glxp/api/admin/util/SpringUtils.java new file mode 100644 index 00000000..161e7572 --- /dev/null +++ b/api-admin/src/main/java/com/glxp/api/admin/util/SpringUtils.java @@ -0,0 +1,63 @@ +package com.glxp.api.admin.util; + +import cn.hutool.extra.spring.SpringUtil; +import org.springframework.aop.framework.AopContext; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.stereotype.Component; + +/** + * spring工具类 + */ +@Component +public final class SpringUtils extends SpringUtil { + + /** + * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true + * + * @param name + * @return boolean + */ + public static boolean containsBean(String name) { + return getBeanFactory().containsBean(name); + } + + /** + * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 + * 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException) + * + * @param name + * @return boolean + */ + public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException { + return getBeanFactory().isSingleton(name); + } + + /** + * @param name + * @return Class 注册对象的类型 + */ + public static Class getType(String name) throws NoSuchBeanDefinitionException { + return getBeanFactory().getType(name); + } + + /** + * 如果给定的bean名字在bean定义中有别名,则返回这些别名 + * + * @param name + */ + public static String[] getAliases(String name) throws NoSuchBeanDefinitionException { + return getBeanFactory().getAliases(name); + } + + /** + * 获取aop代理对象 + * + * @param invoker + * @return + */ + @SuppressWarnings("unchecked") + public static T getAopProxy(T invoker) { + return (T) AopContext.currentProxy(); + } + +} diff --git a/api-admin/src/main/resources/mybatis/mapper/monitor/SysLogininforMapper.xml b/api-admin/src/main/resources/mybatis/mapper/monitor/SysLogininforMapper.xml new file mode 100644 index 00000000..197dc41d --- /dev/null +++ b/api-admin/src/main/resources/mybatis/mapper/monitor/SysLogininforMapper.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + INSERT INTO monitor_login_log( `user_name`, `status`, ipaddr, `login_location`, browser, `os`, msg + , login_time) + values (#{userName}, + #{status}, #{ipaddr}, + #{loginLocation}, #{browser}, #{os}, #{msg}, #{loginTime}) + + + + delete + from monitor_login_log + where info_id = #{id} + + + + + delete + from monitor_login_log + where info_id in + + #{item} + + + + + delete + from monitor_login_log + where date_format(#{date}, '%Y-%m-%d') >= date_format(login_time, '%Y-%m-%d') + + diff --git a/api-admin/src/main/resources/mybatis/mapper/monitor/SysOperLogMapper.xml b/api-admin/src/main/resources/mybatis/mapper/monitor/SysOperLogMapper.xml new file mode 100644 index 00000000..8c5ff408 --- /dev/null +++ b/api-admin/src/main/resources/mybatis/mapper/monitor/SysOperLogMapper.xml @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + INSERT INTO monitor_oper_log(`oper_id`, `title`, business_type, `method`, `request_method`, operator_type + , oper_name, dept_name, oper_url, oper_ip, oper_location, oper_param, json_result, status, + error_msg, oper_time) + values ( #{operId}, + #{title}, #{businessType}, #{method}, #{requestMethod}, #{operatorType}, #{operName}, #{deptName} + , #{operUrl}, #{operIp}, #{operLocation}, #{operParam}, #{jsonResult}, #{status} + , #{errorMsg}, #{operTime}) + + + + delete + from monitor_oper_log + where oper_id = #{id} + + + + + delete + from monitor_oper_log + where oper_id in + + #{item} + + + + + delete + from monitor_oper_log + where date_format(#{date}, '%Y-%m-%d') >= date_format(oper_time, '%Y-%m-%d') + + diff --git a/api-admin/src/main/resources/schemas/schema_v2.1.sql b/api-admin/src/main/resources/schemas/schema_v2.1.sql index e209d3a2..4178fbcb 100644 --- a/api-admin/src/main/resources/schemas/schema_v2.1.sql +++ b/api-admin/src/main/resources/schemas/schema_v2.1.sql @@ -58,3 +58,45 @@ INSERT ignore INTO `sys_param_config`(`id`, `parentId`, `paramName`, `paramKey`, `paramType`, `paramExplain`) VALUES (20019, 0, '集采单据是否拆单', 'gpropBuySplit', '1', 1, 0, '1:是,0:否'); +CREATE TABLE if not exists `monitor_login_log` +( + `info_id` bigint NOT NULL AUTO_INCREMENT COMMENT '访问ID', + `user_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '用户账号', + `ipaddr` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '登录IP地址', + `login_location` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '登录地点', + `browser` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '浏览器类型', + `os` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '操作系统', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '0' COMMENT '登录状态(0成功 1失败)', + `msg` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '提示消息', + `login_time` datetime(0) NULL DEFAULT NULL COMMENT '访问时间', + PRIMARY KEY (`info_id`) USING BTREE +) ENGINE = InnoDB + AUTO_INCREMENT = 19 + CHARACTER SET = utf8mb4 + COLLATE = utf8mb4_0900_ai_ci COMMENT = '系统访问记录' + ROW_FORMAT = DYNAMIC; + +CREATE TABLE if not exists `monitor_oper_log` +( + `oper_id` bigint NOT NULL AUTO_INCREMENT COMMENT '日志主键', + `title` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '模块标题', + `business_type` int NULL DEFAULT 0 COMMENT '业务类型(0其它 1新增 2修改 3删除)', + `method` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '方法名称', + `request_method` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '请求方式', + `operator_type` int NULL DEFAULT 0 COMMENT '操作类别(0其它 1后台用户 2手机端用户)', + `oper_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '操作人员', + `dept_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '部门名称', + `oper_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '请求URL', + `oper_ip` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '主机地址', + `oper_location` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '操作地点', + `oper_param` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '请求参数', + `json_result` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '返回参数', + `status` int NULL DEFAULT 0 COMMENT '操作状态(0正常 1异常)', + `error_msg` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '错误消息', + `oper_time` datetime(0) NULL DEFAULT NULL COMMENT '操作时间', + PRIMARY KEY (`oper_id`) USING BTREE +) ENGINE = InnoDB + AUTO_INCREMENT = 74 + CHARACTER SET = utf8mb4 + COLLATE = utf8mb4_0900_ai_ci COMMENT = '操作日志记录' + ROW_FORMAT = DYNAMIC;