提交 87935891 authored 作者: 窦馨雨's avatar 窦馨雨

合并分支 'dxy' 到 'qa'

增加日志记录 查看合并请求 !112
package com.wangxiaolu.promotion.annotation;
import com.wangxiaolu.promotion.enums.log.BusinessType;
import com.wangxiaolu.promotion.enums.log.OperatorType;
import java.lang.annotation.*;
/**
* 自定义操作日志记录注解
*
* @author ruoyi
*
*/
@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log
{
/**
* 模块
*/
public String title() default "";
/**
* 功能
*/
public BusinessType businessType() default BusinessType.OTHER;
/**
* 操作人类别
*/
public OperatorType operatorType() default OperatorType.MANAGE;
/**
* 是否保存请求的参数
*/
public boolean isSaveRequestData() default true;
/**
* 是否保存响应的参数
*/
public boolean isSaveResponseData() default true;
/**
* 排除指定的请求参数
*/
public String[] excludeParamNames() default {};
}
package com.wangxiaolu.promotion.aspect;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.filter.SimplePropertyPreFilter;
import com.wangxiaolu.promotion.annotation.Log;
import com.wangxiaolu.promotion.domain.log.dao.ISysOperLogDao;
import com.wangxiaolu.promotion.domain.log.mapper.entity.SysOperLog;
import com.wangxiaolu.promotion.enums.log.BusinessStatus;
import com.wangxiaolu.promotion.utils.AuthUtils;
import com.wangxiaolu.promotion.utils.ServletUtils;
import org.apache.commons.lang3.ArrayUtils;
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.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.NamedThreadLocal;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Collection;
import java.util.Date;
import java.util.Map;
/**
* 操作日志记录处理(@Log 注解切面实现)
* 与原版 RuoYi LogAspect 功能一致,依赖替换为项目现有类
*/
@Aspect
@Component
public class LogAspect {
private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
/** 排除敏感属性字段 */
public static final String[] EXCLUDE_PROPERTIES = {"password", "oldPassword", "newPassword", "confirmPassword"};
/** 计算操作消耗时间 */
private static final ThreadLocal<Long> TIME_THREADLOCAL = new NamedThreadLocal<>("Cost Time");
@Autowired
private ISysOperLogDao sysOperLogDao;
@Before(value = "@annotation(controllerLog)")
public void boBefore(JoinPoint joinPoint, Log controllerLog) {
TIME_THREADLOCAL.set(System.currentTimeMillis());
}
@AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult) {
handleLog(joinPoint, controllerLog, null, jsonResult);
}
@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 {
SysOperLog operLog = new SysOperLog();
operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
// 获取 IP(自己实现 getIpAddr 逻辑)
String ip = getIpAddr(ServletUtils.getRequest());
operLog.setOperIp(ip);
operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255));
// 获取当前用户名(从 token 中解析,需根据实际认证方式调整)
try {
// 如果已有 SecurityUtils 可替换此处;当前用 AuthUtils 解析 token
String token = AuthUtils.getToken();
if (StringUtils.isNotBlank(token)) {
String userId = AuthUtils.getUserId(token);
operLog.setOperName(userId);
}
} catch (Exception ex) {
log.debug("获取用户名失败: {}", ex.getMessage());
}
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);
// 设置消耗时间
Long startTime = TIME_THREADLOCAL.get();
if (startTime != null) {
operLog.setCostTime(System.currentTimeMillis() - startTime);
}
// 操作时间
operLog.setOperTime(new Date());
// 保存数据库
sysOperLogDao.insertOperlog(operLog);
} catch (Exception exp) {
log.error("操作日志记录异常: {}", exp.getMessage(), exp);
} finally {
TIME_THREADLOCAL.remove();
}
}
/**
* 获取注解中对方法的描述信息(用于 Controller 层注解)
*/
public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog, Object jsonResult) throws Exception {
// 设置业务类型
operLog.setBusinessType(log.businessType().ordinal());
// 设置标题
operLog.setTitle(log.title());
// 设置操作人类别
operLog.setOperatorType(log.operatorType().ordinal());
// 是否需要保存请求参数
if (log.isSaveRequestData()) {
setRequestValue(joinPoint, operLog, log.excludeParamNames());
}
// 是否需要保存响应结果
if (log.isSaveResponseData() && jsonResult != null) {
operLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000));
}
}
/**
* 获取请求的参数,放到 log 中
*/
private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog, String[] excludeParamNames) throws Exception {
String requestMethod = operLog.getRequestMethod();
Map<?, ?> paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest());
if ((paramsMap == null || paramsMap.isEmpty())
&& (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)))
{
// PUT/POST 且没有 query 参数 → 从 body 中获取
String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames);
operLog.setOperParam(StringUtils.substring(params, 0, 2000));
} else {
// 有 query 参数 → 序列化 query 参数(排除敏感字段)
operLog.setOperParam(StringUtils.substring(
JSON.toJSONString(paramsMap, excludePropertyPreFilter(excludeParamNames)), 0, 2000));
}
}
/**
* 参数拼装(排除敏感属性和框架对象)
*/
private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames)
{
String params = "";
if (paramsArray != null && paramsArray.length > 0)
{
for (Object o : paramsArray)
{
if (o != null && !isFilterObject(o))
{
try
{
String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter(excludeParamNames));
params += jsonObj + " ";
}
catch (Exception e)
{
}
}
}
}
return params.trim();
}
/**
* 排除敏感属性的 Fastjson 过滤器
*/
public SimplePropertyPreFilter excludePropertyPreFilter(String[] excludeParamNames) {
SimplePropertyPreFilter filter = new SimplePropertyPreFilter();
for (String exclude : ArrayUtils.addAll(EXCLUDE_PROPERTIES, excludeParamNames)) {
filter.getExcludes().add(exclude);
}
return filter;
}
/**
* 判断是否需要过滤的对象(MultipartFile、Request、Response、BindingResult)
*/
@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;
}
/**
* 获取 IP 地址(原版 IpUtils.getIpAddr 的实现)
*/
private String getIpAddr(HttpServletRequest request) {
if (request == null) {
return "unknown";
}
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Real-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
// 多个代理时取第一个 IP
if (ip != null && ip.contains(",")) {
ip = ip.split(",")[0].trim();
}
return ip;
}
}
\ No newline at end of file
package com.wangxiaolu.promotion.controller.activityplanv2;
import com.alibaba.fastjson2.JSONObject;
import com.wangxiaolu.promotion.annotation.Log;
import com.wangxiaolu.promotion.common.excel.FileUtils;
import com.wangxiaolu.promotion.enums.log.BusinessType;
import com.wangxiaolu.promotion.exception.DataException;
import com.wangxiaolu.promotion.exception.ParamException;
import com.wangxiaolu.promotion.pojo.activity.manage.vo.ActivityPlanVo;
......@@ -45,6 +47,7 @@ public class PromPlanCoreController {
* 城市经理 - 上传计划(新增)
* 当月只能上传次月的新增(当月需要新增需要交由职能角色上传)
*/
@Log(title = "CP计划", businessType = BusinessType.INSERT)
@PostMapping("/self/upload")
public R selfPlan(@RequestBody ActivityPlanVo activityPlanVo) {
// 判断当前账号是否是城市经理
......@@ -82,6 +85,8 @@ public class PromPlanCoreController {
/**
* 职能角色 - 上传计划(新增)
*/
@Log(title = "CP计划", businessType = BusinessType.INSERT)
@PostMapping("/auth/upload")
public R authPlan(@RequestBody ActivityPlanVo activityPlanVo) {
boolean isAuth = manageEmployeeQueryService.isAuth(activityPlanVo.getEmployeeNo());
......@@ -126,6 +131,7 @@ public class PromPlanCoreController {
/**
* 促销计划修改
*/
@Log(title = "CP计划", businessType = BusinessType.UPDATE)
@PutMapping("/put")
public R planPut(@RequestBody ActivityPlanVo activityPlanVo) {
// 判断当前账号是否正常
......@@ -156,6 +162,7 @@ public class PromPlanCoreController {
/**
* 删除计划 计划日期大于今日可直接删除,如果计划日期是今日则必需是10点之前,包含过去日期不可删除
*/
@Log(title = "CP计划", businessType = BusinessType.DELETE)
@DeleteMapping("/delete")
public R deletePlan(@RequestBody ActivityPlanVo activityPlanVo){
if (Collections.isEmpty(activityPlanVo.getPlanIds())){
......@@ -172,6 +179,7 @@ public class PromPlanCoreController {
return R.success();
}
@Log(title = "CP计划", businessType = BusinessType.INSERT)
@PostMapping("/save")
public R saveWebActivityPlan(@RequestBody ActivityPlanOperVo operVo){
boolean oneSelf = manageEmployeeQueryService.isOneSelf(operVo.getEmployeeNo());
......@@ -218,6 +226,7 @@ public class PromPlanCoreController {
/**
* 修改计划
*/
@Log(title = "CP计划", businessType = BusinessType.UPDATE)
@PutMapping("/one")
public R putActivityPlan(@RequestBody ActivityPlanOperVo operVo){
if (Objects.isNull(operVo.getId())){
......@@ -251,6 +260,7 @@ public class PromPlanCoreController {
/**
* 变更归属人
*/
@Log(title = "CP计划", businessType = BusinessType.UPDATE)
@PutMapping("/more")
public R putActivityPlans(@RequestBody ActivityPlanOperVo operVo){
if (Objects.isNull(operVo.getEmployeeId()) || Collections.isEmpty(operVo.getPlanIds())){
......
package com.wangxiaolu.promotion.domain.log.dao;
import com.wangxiaolu.promotion.domain.log.mapper.entity.SysOperLog;
import com.wangxiaolu.promotion.pojo.PageInfo;
/**
* 操作日志 服务层
*
* @author ruoyi
*/
public interface ISysOperLogDao
{
/**
* 新增操作日志
*
* @param operLog 操作日志对象
* @return 结果
*/
public int insertOperlog(SysOperLog operLog);
/**
* 批量删除系统操作日志
*
* @param operIds 需要删除的操作日志ID
* @return 结果
*/
public int deleteOperLogByIds(Long[] operIds);
/**
* 查询操作日志详细
*
* @param operId 操作ID
* @return 操作日志对象
*/
public SysOperLog selectOperLogById(Long operId);
/**
* 清空操作日志
*/
public void cleanOperLog();
}
package com.wangxiaolu.promotion.domain.log.dao.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.wangxiaolu.promotion.domain.log.dao.ISysOperLogDao;
import com.wangxiaolu.promotion.domain.log.mapper.SysOperLogMapper;
import com.wangxiaolu.promotion.domain.log.mapper.entity.SysOperLog;
import com.wangxiaolu.promotion.pojo.PageInfo;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Objects;
/**
* 操作日志 服务层处理
*
* @author ruoyi
*/
@Service
public class SysOperLogDaoImpl implements ISysOperLogDao
{
@Autowired
private SysOperLogMapper operLogMapper;
/**
* 新增操作日志
*
* @param operLog 操作日志对象
* @return 结果
*/
@Override
public int insertOperlog(SysOperLog operLog)
{
return operLogMapper.insertOperlog(operLog);
}
private LambdaQueryWrapper<SysOperLog> buildWrapper(SysOperLog operLog) {
LambdaQueryWrapper<SysOperLog> qw =new LambdaQueryWrapper<>();
if (Objects.nonNull(operLog.getBusinessType())){
qw.eq(SysOperLog::getBusinessType,operLog.getBusinessType());
}
if (Objects.nonNull(operLog.getStatus())){
qw.eq(SysOperLog::getStatus,operLog.getStatus());
}
if (Objects.nonNull(operLog.getBusinessTypes())){
qw.in(SysOperLog::getBusinessType,operLog.getBusinessTypes());
}
if (StringUtils.isNotBlank(operLog.getOperIp())){
qw.like(SysOperLog::getOperIp,operLog.getOperIp());
}
if (StringUtils.isNotBlank(operLog.getTitle())){
qw.like(SysOperLog::getTitle,operLog.getTitle());
}
if (StringUtils.isNotBlank(operLog.getOperName())){
qw.like(SysOperLog::getOperName,operLog.getOperName());
}
if (Objects.nonNull(operLog.getBeginTime())&&Objects.nonNull(operLog.getEndTime())){
qw.between(SysOperLog::getOperTime, operLog.getBeginTime(),operLog.getEndTime());
}
qw.orderByDesc(SysOperLog::getOperId);
return qw;
}
/**
* 批量删除系统操作日志
*
* @param operIds 需要删除的操作日志ID
* @return 结果
*/
@Override
public int deleteOperLogByIds(Long[] operIds)
{
return operLogMapper.deleteOperLogByIds(operIds);
}
/**
* 查询操作日志详细
*
* @param operId 操作ID
* @return 操作日志对象
*/
@Override
public SysOperLog selectOperLogById(Long operId)
{
return operLogMapper.selectOperLogById(operId);
}
/**
* 清空操作日志
*/
@Override
public void cleanOperLog()
{
operLogMapper.cleanOperLog();
}
}
package com.wangxiaolu.promotion.domain.log.mapper;
import java.util.List;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.wangxiaolu.promotion.domain.log.mapper.entity.SysOperLog;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
/**
* 操作日志 数据层
*
* @author ruoyi
*/
@Repository
@Mapper
public interface SysOperLogMapper extends BaseMapper<SysOperLog>
{
/**
* 新增操作日志
*
* @param operLog 操作日志对象
*/
public int insertOperlog(SysOperLog operLog);
/**
* 查询系统操作日志集合
*
* @param operLog 操作日志对象
* @return 操作日志集合
*/
public List<SysOperLog> selectOperLogList(SysOperLog operLog);
/**
* 批量删除系统操作日志
*
* @param operIds 需要删除的操作日志ID
* @return 结果
*/
public int deleteOperLogByIds(Long[] operIds);
/**
* 查询操作日志详细
*
* @param operId 操作ID
* @return 操作日志对象
*/
public SysOperLog selectOperLogById(Long operId);
/**
* 清空操作日志
*/
public void cleanOperLog();
}
package com.wangxiaolu.promotion.domain.log.mapper.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
/**
* 操作日志记录表 oper_log
*
* @author ruoyi
*/
@NoArgsConstructor
@AllArgsConstructor
@Data
@TableName(value = "sys_oper_log")
public class SysOperLog implements Serializable
{
private static final long serialVersionUID = 1L;
/** 日志主键 */
@TableId(type = IdType.AUTO)
private Long operId;
/** 操作模块 */
private String title;
/** 业务类型(0其它 1新增 2修改 3删除) */
private Integer businessType;
/** 业务类型数组 */
@TableField(exist = false)
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 operParam;
/** 返回参数 */
private String jsonResult;
/** 操作状态(0正常 1异常) */
private Integer status;
/** 错误消息 */
private String errorMsg;
/** 操作时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date operTime;
/** 消耗时间 */
private Long costTime;
/** 操作开始时间 **/
@TableField(exist = false)
private Date beginTime;
/** 操作结束时间 **/
@TableField(exist = false)
private Date endTime;
}
package com.wangxiaolu.promotion.enums.log;
/**
* 操作状态
*
* @author ruoyi
*
*/
public enum BusinessStatus
{
/**
* 成功
*/
SUCCESS,
/**
* 失败
*/
FAIL,
}
package com.wangxiaolu.promotion.enums.log;
/**
* 业务操作类型
*
* @author ruoyi
*/
public enum BusinessType
{
/**
* 其它
*/
OTHER,
/**
* 新增
*/
INSERT,
/**
* 修改
*/
UPDATE,
/**
* 删除
*/
DELETE,
/**
* 授权
*/
GRANT,
/**
* 导出
*/
EXPORT,
/**
* 导入
*/
IMPORT,
/**
* 强退
*/
FORCE,
/**
* 生成代码
*/
GENCODE,
/**
* 清空数据
*/
CLEAN,
}
package com.wangxiaolu.promotion.enums.log;
/**
* 操作人类别
*
* @author ruoyi
*/
public enum OperatorType
{
/**
* 其它
*/
OTHER,
/**
* 后台用户
*/
MANAGE,
/**
* 手机端用户
*/
MOBILE
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wangxiaolu.promotion.domain.log.dao.ISysOperLogDao">
<resultMap type="com.wangxiaolu.promotion.domain.log.mapper.entity.SysOperLog" id="SysOperLogResult">
<id property="operId" column="oper_id" />
<result property="title" column="title" />
<result property="businessType" column="business_type" />
<result property="method" column="method" />
<result property="requestMethod" column="request_method" />
<result property="operatorType" column="operator_type" />
<result property="operName" column="oper_name" />
<result property="deptName" column="dept_name" />
<result property="operUrl" column="oper_url" />
<result property="operIp" column="oper_ip" />
<result property="operParam" column="oper_param" />
<result property="jsonResult" column="json_result" />
<result property="status" column="status" />
<result property="errorMsg" column="error_msg" />
<result property="operTime" column="oper_time" />
<result property="costTime" column="cost_time" />
</resultMap>
<sql id="selectOperLogVo">
select oper_id, title, business_type, method, request_method, operator_type, oper_name, dept_name, oper_url, oper_ip, oper_param, json_result, status, error_msg, oper_time, cost_time
from sys_oper_log
</sql>
<insert id="insertOperlog" parameterType="com.wangxiaolu.promotion.domain.log.mapper.entity.SysOperLog">
insert into sys_oper_log(title, business_type, method, request_method, operator_type, oper_name, dept_name, oper_url, oper_ip, oper_param, json_result, status, error_msg, cost_time, oper_time)
values (#{title}, #{businessType}, #{method}, #{requestMethod}, #{operatorType}, #{operName}, #{deptName}, #{operUrl}, #{operIp}, #{operParam}, #{jsonResult}, #{status}, #{errorMsg}, #{costTime}, sysdate())
</insert>
<select id="selectOperLogById" parameterType="Long" resultMap="SysOperLogResult">
<include refid="selectOperLogVo"/>
where oper_id = #{operId}
</select>
<update id="cleanOperLog">
truncate table sys_oper_log
</update>
</mapper>
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论