使用工厂加策略模式实现操作日志记录

需求:1.培训班管理;2.报名列表管理;3.申请信息变更;4.申请发布;5.申请审批
以上是本次需求中的5个功能菜单,根据客户需求,
要求在上述功能操作中的每一步都要进行日志的记录,分别记录登录人信息,IP地址,操作时间,如果是修改 要求记录为“修改姓名:张三→张小三”共有8中日志操作类型。
日志实现的方案:

方案一、使用切面类

思路:切面类适合在不改变原有业务逻辑的基础上,对特定的方法进行增强。在这个场景中,可以定义一个切面,在目标方法执行前后进行拦截,根据不同的日志类型记录相应的日志信息。对于修改和申请变更的情况,可以在方法执行前后获取对象的状态,对比字段值的变化并记录。

方案二、使用工厂加策略模式实现

思路:工厂加策略模式适合根据不同的日志类型,动态选择不同的日志记录策略。可以定义一个日志策略接口,为每种日志类型实现具体的策略类,然后使用工厂类根据日志类型创建相应的策略对象。对于修改和申请变更的情况,在具体的策略类中实现字段值对比和记录的逻辑。

选择建议

切面类:

如果你的主要需求是在不改变现有业务代码的基础上,对特定方法进行统一的日志记录增强,且希望代码的侵入性最小,那么使用切面类是一个不错的选择。

工厂加策略模式:

如果后续可能会有更多的日志类型和复杂的日志记录逻辑,需要更灵活的扩展和管理不同的日志记录策略,那么工厂加策略模式会更合适。它将日志记录的逻辑封装在不同的策略类中,便于维护和扩展。
根据本次需求已经上面人不想动现有的日志管理,且这次的日志管理只是用于这5个功能模块,所以本次选择了工厂加策略模式的方式实现,
废话不多说直接上代码

策略接口类

import java.util.List;

public interface LoggingStrategy {

    /*
    * @Author 作者本人
    * @Description 
    * @Date 14:31 2025/3/11
    * @Param args 本次新增、修改、发布等变动 的对象;
    * list 导入的数据;
    * operName:操作人;
    * operIp:ip地址;
    * logTye: 根据此值 选择不同的策略实现类 
    * method:日志操作类型 按照字典值维护 (新增操作、导入操作、删除操作,编辑操作,审批、发布等操作)
    * @return 
    **/
    void saveLog(Object args, List<Object> list, String operName, String operIp, String logTye, String method, Integer applyType);
}
策略实现类

import com.bms.common.utils.StringUtils;
import com.bms.common.utils.bean.BeanUtils;
import com.bms.common.utils.bean.CompareObjectsDiffUtils;
import com.bms.common.utils.uuid.SnowFlake;
import com.bms.project.govern.entity.train.PxTrain;
import com.bms.project.govern.entity.train.PxTrainApply;
import com.bms.project.govern.entity.train.PxTrainLog;
import com.bms.project.govern.entity.train.PxTrainUser;
import com.bms.project.govern.entity.train.dto.PxTrainUserDTO;
import com.bms.project.govern.mapper.train.PxTrainApplyMapper;
import com.bms.project.govern.mapper.train.PxTrainLogMapper;
import com.bms.project.govern.mapper.train.PxTrainUserMapper;
import com.bms.project.govern.service.train.strategy.LoggingStrategy;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @className: InsertLogStrategy
 * @author: qh
 * @date: 2025/3/11 9:49
 * @Version: 1.0
 * @description: 培训班、报名列表、申请变更新增策略实现类
 */
@Service
public class InsertLogStrategy implements LoggingStrategy {

    @Resource
    private PxTrainLogMapper pxTrainLogMapper;
    @Resource
    private PxTrainUserMapper pxTrainUserMapper;
    @Resource
    private CompareObjectsDiffUtils compareObjectsDiffUtils;
    @Resource
    private PxTrainApplyMapper pxTrainApplyMapper;
    /*
    * @Author QH
    * @Description 
    * @Date 10:03 2025/3/11
    * @Param logTye 1报名列表新增 2培训班新增 3申请新增
    * @return 
    **/
    @Override
    public void saveLog(Object args, List<Object> list, String operName, String operIp, String logTye,String method,Integer applyType) {
        PxTrainLog pxTrainLog = new PxTrainLog();
        pxTrainLog.setId(SnowFlake.id()+"");
        pxTrainLog.setMethod(method);
        pxTrainLog.setOperName(operName);
        pxTrainLog.setOperIp(operIp);
        pxTrainLog.setOperTime(new Date());
        if (StringUtils.equals("1",logTye)){
        	//报名新增
            PxTrainUser trainUser = (PxTrainUser) args;
            pxTrainLog = creatLog(pxTrainLog, trainUser, null, null, logTye);
            pxTrainLogMapper.jwInsert(pxTrainLog);
        }else if (StringUtils.equals("2",logTye)){
        //培训班新增
            PxTrain pxTrain = (PxTrain) args;
            pxTrainLog = creatLog(pxTrainLog, null, null, pxTrain, logTye);
            pxTrainLogMapper.jwInsert(pxTrainLog);
        }else if (StringUtils.equals("3",logTye)){
        //申请新增
            PxTrainUserDTO pxTrainUserDTO = (PxTrainUserDTO) args;
            pxTrainLog = creatLog(pxTrainLog, null, pxTrainUserDTO, null, logTye);
            pxTrainLogMapper.jwInsert(pxTrainLog);
        }else if (StringUtils.equals("4",logTye)){
        //导入新增
            List<PxTrainLog> resultList = new ArrayList<>();
            for (Object object : list) {
                for (Object item : (ArrayList<?>) object) {
                    if (item instanceof PxTrainUser) {
                PxTrainUser pxTrainUser = (PxTrainUser) item;
                PxTrainLog pxTrainLogs = new PxTrainLog();
                pxTrainLogs.setId(SnowFlake.id()+"");
                pxTrainLogs.setMethod(method);
                pxTrainLogs.setOperName(operName);
                pxTrainLogs.setOperIp(operIp);
                pxTrainLogs.setOperTime(new Date());
                pxTrainLogs.setName(pxTrainUser.getName());
                pxTrainLogs.setNumber(pxTrainUser.getNumber());
                pxTrainLogs.setIdCard(pxTrainUser.getIdCard());
                String s = assembleLogDescription(Integer.parseInt(logTye), pxTrainUser.getName(), pxTrainUser.getNumber(), null);
                pxTrainLogs.setOperParam(s);
                resultList.add(pxTrainLogs);
            }
                }
            }
            if (resultList.size()>0){
                pxTrainLogMapper.jwInsertBatchReplace(resultList);
            }
        }else if (StringUtils.equals("5",logTye)){
           
            //发布变更
           
        }else if (StringUtils.equals("6",logTye)){
            //审批 新增 编辑 删除  2 已通过 3 未通过
          
        }else if (StringUtils.equals("7",logTye)){
            // 撤回新增
     
    }

    public PxTrainLog creatLog(PxTrainLog pxTrainLog,PxTrainUser trainUser,PxTrainUserDTO pxTrainUserDTO,PxTrain pxTrain,String logTye){

        if (ObjectUtils.isNotEmpty(trainUser)){
            pxTrainLog.setName(trainUser.getName());
            pxTrainLog.setNumber(trainUser.getNumber());
            pxTrainLog.setIdCard(trainUser.getIdCard());
            String s = assembleLogDescription(Integer.parseInt(logTye), trainUser.getName(), trainUser.getNumber(), null);
            pxTrainLog.setOperParam(s);
        }else if (ObjectUtils.isNotEmpty(pxTrainUserDTO)){
            pxTrainLog.setName(pxTrainUserDTO.getName());
            pxTrainLog.setNumber(pxTrainUserDTO.getNumber());
            pxTrainLog.setIdCard(pxTrainUserDTO.getIdCard());
            String s = assembleLogDescription(Integer.parseInt(logTye), pxTrainUserDTO.getName(), pxTrainUserDTO.getNumber(), null);
            pxTrainLog.setOperParam(s);
        }else if (ObjectUtils.isNotEmpty(pxTrain)){
            pxTrainLog.setTrainName(pxTrain.getName());
            String s = assembleLogDescription(Integer.parseInt(logTye), null, null, pxTrain.getName());
            pxTrainLog.setOperParam(s);
        }
        return pxTrainLog;
    }

    /**
     * 根据日志类型和传入的值组装日志操作描述
     * @param logType 日志类型,1 表示新增报名人员,2 表示新增培训班,4 表示申请新增报名人员
     * @param name 人员姓名,如张三
     * @param hrNumber 人资编号,如 1000000
     * @param trainingClassName 培训班名称,如 2兴趣班
     * @return 组装好的日志操作描述
     */
    public static String assembleLogDescription(int logType, String name, String hrNumber, String trainingClassName) {
        switch (logType) {
            case 1:
                return String.format("新增报名人员%s(人资编号%s)", name, hrNumber);
            case 2:
                return String.format("新增培训班%s", trainingClassName);
            case 3:
                return String.format("申请新增报名人员%s(人资编号%s)", name, hrNumber);
            case 4:
                return String.format("导入报名人员%s(人资编号%s)", name, hrNumber);
            default:
                return "未知日志类型,无法生成日志描述";
        }
    }


    public static String assembleLogDescriptionApply(int logType, String name, String hrNumber) {
        switch (logType) {
            case 0:
                return String.format("发布申请新增报名人员%s(人资编号%s)", name, hrNumber);
            case 1:
                return String.format("发布申请编辑报名人员%s(人资编号%s)", name, hrNumber);
            case 2:
                return String.format("发布申请删除报名人员%s(人资编号%s)", name, hrNumber);
            case 3:
                return String.format("撤回申请新增报名人员%s(人资编号%s)", name, hrNumber);
            case 4:
                return String.format("撤回申请编辑报名人员%s(人资编号%s)", name, hrNumber);
            case 5:
                return String.format("撤回申请删除报名人员%s(人资编号%s)", name, hrNumber);
            default:
                return "未知日志类型,无法生成日志描述";
        }
    }

    public static String assembleLogDescriptionApproval(int logType, String name, String hrNumber) {
        switch (logType) {
            case 2:
                return String.format("通过新增报名人员%s(人资编号%s)", name, hrNumber);
            case 3:
                return String.format("驳回新增报名人员%s(人资编号%s)", name, hrNumber);
            case 4:
                return String.format("通过删除报名人员%s(人资编号%s)", name, hrNumber);
            case 5:
                return String.format("驳回删除报名人员%s(人资编号%s)", name, hrNumber);
            default:
                return "未知日志类型,无法生成日志描述";
        }
    }
}

工厂类
import com.bms.project.govern.service.train.strategy.LoggingStrategy;
import com.bms.project.govern.service.train.strategy.impl.DelLogStrategy;
import com.bms.project.govern.service.train.strategy.impl.EditLogStrategy;
import com.bms.project.govern.service.train.strategy.impl.InsertLogStrategy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

/**
 * @className: LoggingStrategyFactory
 * @author: qh
 * @date: 2025/3/11 9:50
 * @Version: 1.0
 * @description:
 */
@Component
public class LoggingStrategyFactory {

    private final Map<Integer, LoggingStrategy> strategyMap = new HashMap<>();

    @Autowired
    public LoggingStrategyFactory(InsertLogStrategy insertLogStrategy,
                                  EditLogStrategy editLogStrategy,
                                  DelLogStrategy delLogStrategy
                                 ) {
        strategyMap.put(1, insertLogStrategy);
        strategyMap.put(2, editLogStrategy);
        strategyMap.put(3, delLogStrategy);

        // 这里可以继续添加其他日志类型对应的策略
    }

    public LoggingStrategy getStrategy(int logType) {
        return strategyMap.get(logType);
    }
}
新增/修改 使用
    public AjaxResult singUpTrain(PxTrainUser data) {
        String username = getUsername();
        String ipAddr = IpUtils.getIpAddr();
        data.setUpdateTime(new Date());
        data.setUpdateBy(getUsername());
        if (ObjectUtils.isEmpty(data.getId())){
        //新增
            data.setId(SnowFlake.id() + "");
            data.setCreateBy(getUsername());
            data.setCreateTime(new Date());
            LoggingStrategy strategy = loggingStrategyFactory.getStrategy(1);
            strategy.saveLog(data,null,username,ipAddr,"1","1",null);
        }else {
        //修改
            LoggingStrategy strategy = loggingStrategyFactory.getStrategy(2);
            strategy.saveLog(data,null,username,ipAddr,"1","2",null);
        }
        Integer i = pxTrainUserMapper.jwInsertReplace(data);
        return i>0?AjaxResult.success():AjaxResult.error();
    }
对比并获取到哪些字段进行了修改


import com.bms.framework.anno.ChineseFieldName;
import com.bms.framework.redis.RedisCache;
import com.bms.project.govern.entity.train.dto.TrainPeriodDTO;
import com.bms.project.govern.mapper.train.PxTrainUserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;

/**
 * @className: CompareObjectsDiffUtils
 * @author: qh
 * @date: 2025/3/11 9:16
 * @Version: 1.0
 * @description:
 */
@Component
public class CompareObjectsDiffUtils {

    @Autowired
    private PxTrainUserMapper pxTrainUserMapper;
    /**
     * 比较两个对象差异
     * @param oldObj 原始对象
     * @param newObj 新对象
     * @return 变更描述列表
     */
    public  List<String> compareObjects(Object oldObj, Object newObj) {
        List<String> changes = new ArrayList<>();

        try {
            Class<?> clazz = oldObj.getClass();
            for (Field field : clazz.getDeclaredFields()) {
                field.setAccessible(true);

                String fieldName = field.getName();
                Object oldValue = field.get(oldObj);
                Object newValue = field.get(newObj);

                if (!Objects.equals(oldValue, newValue)) {
                    // 获取字段上的 ChineseFieldName 注解
                    ChineseFieldName annotation = field.getAnnotation(ChineseFieldName.class);
                    String chineseFieldName;
                    if (annotation != null) {
                        String tag = annotation.tag();
                        //如果是1 则说明该字段不需要进行对比新旧值  直接跳过进入下一个字段对比
                        if ("1".equals(tag)){
                            continue;
                        }else {
                            chineseFieldName = annotation.value();
                        }
                    } else {
                        chineseFieldName = fieldName;
                    }
                    changes.add(String.format("%s: %s → %s",
                            chineseFieldName,
                            formatValue(fieldName,oldValue),
                            formatValue(fieldName,newValue)));
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return changes;
    }

    private  String formatValue(String fieldName, Object value) {
        if (value == null) return "空";
        if (value instanceof Date) {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format((Date) value);
        }
        // 一下是对一些特殊字段的处理,我这里是对映射的字段值做了处理
        if ("periodNumber".equals(fieldName) && value instanceof Integer) {
            TrainPeriodDTO trainPeriodDTO = pxTrainUserMapper.queryTrainPeriodByNum((Integer) value);
            return trainPeriodDTO.dictLabel;
        }
        if ("periodNumbers".equals(fieldName) && value instanceof Integer) {
            TrainPeriodDTO trainPeriodDTO = pxTrainUserMapper.queryTrainPeriodByNum((Integer) value);
            return trainPeriodDTO.dictLabel;
        }
        if ("gender".equals(fieldName) && value instanceof String) {
            if ("0".equals(value)){
                return "男";
            }else if ("1".equals(value)){
                return "女";
            }else {
                return "未知";
            }
        }
        if ("closed".equals(fieldName) && value instanceof Integer) {
            if (((Integer) value).intValue() == 0){
                return "关闭";
            }else if (((Integer) value).intValue() == 1){
                return "开启";
            }
        }
        return value.toString();
    }
}

通过注解来获取映射的字段名称及是否需要忽略该字段
注解类

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * @className: ChineseFieldName
 * @author: qh
 * @date: 2025/3/11 11:16
 * @Version: 1.0
 * @description:
 */
// 注解的作用目标为字段
@Target(ElementType.FIELD)
// 注解在运行时保留
@Retention(RetentionPolicy.RUNTIME)
public @interface ChineseFieldName {
	//字段汉字名称
    String value();
    //默认是0--进行对比 1 忽略对比
    String tag() default "0";
}
实体类部分代码
    /**
     * 姓名
     */
    @Excel(name = "姓名")
    @NotEmpty(message = "姓名不能为空")
    @ChineseFieldName(value = "姓名")
    private String name;

    /**
     * 员工编号
     */
    @Excel(name = "员工编号")
    @NotEmpty(message = "员工编号不能为空")
    @ChineseFieldName(value = "员工编号")
    private String number;

    /**
     * 身份证号
     */
    @Excel(name = "身份证号")
    @NotEmpty(message = "身份证号不能为空")
    @ChineseFieldName(value = "身份证号")
    private String idCard;

    /**
     * 此处为忽略标记 不对该字段进行新旧值的对比
     */
    @ChineseFieldName(value = "", tag = "1")
    private Integer applyState;

通过上述代码可实现操作日志记录,且还存在 很大 的优化空间,但是由于要两天的时间进行开发、测试、上线,所以就这样吧 优化个der

你可能感兴趣的:(策略模式,java,spring,boot)