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

news/2025/3/14 8:49:28/

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

方案一、使用切面类

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

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

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

选择建议

切面类:

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

工厂加策略模式

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

策略接口类
java">
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);
}
策略实现类
java">
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 {@Resourceprivate PxTrainLogMapper pxTrainLogMapper;@Resourceprivate PxTrainUserMapper pxTrainUserMapper;@Resourceprivate CompareObjectsDiffUtils compareObjectsDiffUtils;@Resourceprivate PxTrainApplyMapper pxTrainApplyMapper;/** @Author QH* @Description * @Date 10:03 2025/3/11* @Param logTye 1报名列表新增 2培训班新增 3申请新增* @return **/@Overridepublic 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 "未知日志类型,无法生成日志描述";}}
}
工厂类
java">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<>();@Autowiredpublic 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);}
}
新增/修改 使用
java">    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();}
对比并获取到哪些字段进行了修改
java">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 {@Autowiredprivate 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();}
}
通过注解来获取映射的字段名称及是否需要忽略该字段
注解类
java">
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";
}
实体类部分代码
java">    /*** 姓名*/@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


http://www.ppmy.cn/news/1578996.html

相关文章

【机械视觉】C#+VisionPro联合编程———【四、检测彩色保险丝实例,以及C#+VisionPro的两种写法】

【机械视觉】C#VisionPro联合编程———【四、检测彩色保险丝实例&#xff0c;以及C#VisionPro的两种写法】 在机械视觉C#VisionPro联合编程编程中&#xff0c;在处理业务逻辑时通常会有两种写法&#xff0c;一种是将逻辑代码编写在visionPro中然后再使用C#将visionPro工具加载…

Ollama有安全漏洞! 国家网络安全通报中心紧急通报

最新消息&#xff01;国家网络安全通报中心昨夜发布紧急通告&#xff1a;全球超火的AI神器Ollama惊现重大漏洞&#xff01;正在用DeepSeek、Llama的你&#xff0c;赶紧自查&#xff01; &#x1f6d1; 你正美滋滋用Ollama跑着大模型&#xff0c;殊不知黑客正像逛超市一样随意进…

C++ std::list超详细指南:基础实践(手搓list)

目录 一.核心特性 1.双向循环链表结构 2.头文件&#xff1a;#include 3.时间复杂度 4.内存特性 二.构造函数 三.list iterator的使用 1.学习list iterator之前我们要知道iterator的区分 ​编辑 2.begin()end() 3. rbegin()rend() 四.list关键接口 1.empty() 2. size…

【微知】tmux如何在某个会话session中创建多个窗口?如何切换?(Ctrl+b + c创建;Ctrl+b + 数字 切换;Ctrl+b + 关闭)

创建窗口 创建新窗口&#xff1a;Ctrlb c 切换窗口&#xff1a; 切换到下一个窗口&#xff1a;Ctrlb n 切换到上一个窗口&#xff1a;Ctrlb p 切换到指定窗口&#xff1a;Ctrlb 数字&#xff08;窗口编号&#xff09; 重命名窗口&#xff1a;Ctrlb ,&#xff08;逗号&a…

使用 ESP32 和 Python 进行手势识别

使用手势控制 LED 这个 ESP32 项目是一种使用手势控制 LED 的令人兴奋的交互式方式。我们将使用 ESP32 开发板、Python、MediaPipe 和 OpenCV 创建一个系统,该系统可以检测特定的手势并将其转换为控制 LED 的作。MediaPipe 将用于识别手势,而 OpenCV 将捕获来自网络摄像头的…

Win11 + cherry studio deepseek本地部署,保姆级教程

目录 1.API申请 2.API调用 3.添加本地知识库 4.内容检索 1.API申请 我们首先需要申请一个账号&#xff0c;注册后可以直接获取2000万免费tokens&#xff0c;新增我们的秘钥用于后续使用&#xff0c;申请方法如下。 账号登录地址&#xff1a; 硅基流动统一登录 点击秘钥&am…

FX-std::map

std::map 是 C 标准库中的一个关联容器&#xff0c;用于存储键值对&#xff08;key-value pairs&#xff09;&#xff0c;并根据键自动排序。它基于红黑树实现&#xff0c;具有对数时间复杂度的插入、删除和查找操作。 基本用法 1. 包含头文件 使用 std::map 需要包含头文件…

Python刷题:Python基础

今天刷的是PythonTip的Python 入门挑战中的题&#xff0c;整体难度不高&#xff0c;适合小白练手以及巩固知识点。下面会进行详细讲解。 每日一句 梦想不会发光&#xff0c;发光的是追逐梦想的我们。 只要你真的愿意为自己的梦想去努力&#xff0c; 最差的结果&#xff0c;…