需求: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