1. 介绍
项目中好多地方需要记录日志,操作日志。以前是每一个功能新建一个表,把一些字段存进去,记录下操作信息。这样太繁琐,我这里新建一个表,里面存了多个功能的操作记录,利用bizType作为区分,content作为日志的内容,查找时按照操作时间倒序排列就可以得到对某一个功能的操作记录了。
2. 代码
2.1 代码介绍
代码的实现主要是:利用短信 模板的形式。先给定一段话,里面的变量用字符串代替。等针的有操作的时候,就替换变量,改成一段话记录下来。
变量替换的参数可以对象,map,或者是json。
2.2 模板替换代码
package com.demo.common.utils;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import java.time.temporal.TemporalAccessor;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Slf4j
public class MessageTemplateUtil {
public static void main(String[] args) {
String template = "尊敬的${name},您的验证码是 ${code},请勿泄露。";
Map<String, String> variables = new HashMap<>(); // Map.of("name", "张三", "code", "123456");
variables.put( "name", "张三");
variables.put( "code", "123456");
String smsText = replaceVariables(template, variables);
System.out.println(smsText); // 输出:'尊敬的张三,您的验证码是 123456,请勿泄露。'
}
public static String replaceVariables(String template, Object param) {
// 参数是map
if (param instanceof Map){
return replaceVariablesByMap(template, (Map<String, Object>) param);
} else if (param instanceof String) {
// 参数是json,处理方式
ObjectMapper objectMapper = new ObjectMapper();
try {
// 将JSON字符串转换为Map
Map map = objectMapper.readValue((String) param, Map.class);
return replaceVariablesByMap(template, map);
} catch (Exception e) {
log.error("参数是json处理报错--", e);
}
}
// 最后参数是对象
Map<String, Object> map = objToMap(param);
return replaceVariablesByMap(template, map);
// return replaceVariablesByObj(template, param);
}
public static String replaceVariablesByMap(String template, Map<String, Object> variables) {
Pattern pattern = Pattern.compile("\\$\\{([^}]+)\\}");
Matcher matcher = pattern.matcher(template);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
String variableName = matcher.group(1);
Object variableValue = variables.get(variableName);
if (variableValue != null) {
// 时间类型转换
if (variableValue instanceof TemporalAccessor) {
variableValue = DateUtils.YMD_HMS.format((TemporalAccessor) variableValue);
}
matcher.appendReplacement(sb, variableValue.toString());
} else {
log.error("参数没有值--" + variableName);
}
}
matcher.appendTail(sb);
return sb.toString();
}
public static String replaceVariablesByObj(String template, Object object) {
Pattern pattern = Pattern.compile("\\$\\{([^}]+)\\}");
Matcher matcher = pattern.matcher(template);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
String variableName = matcher.group(1);
String variableValue = getProperty(object, variableName);
if (variableValue != null) {
matcher.appendReplacement(sb, variableValue);
} else {
log.error("参数没有值--" + variableName);
}
}
matcher.appendTail(sb);
return sb.toString();
}
public static String getProperty(Object object, String propertyName) {
try {
// 使用反射获取属性值
String getterMethodName = "get" + capitalize(propertyName);
return object.getClass().getMethod(getterMethodName).invoke(object).toString();
} catch (Exception e) {
return null;
}
}
public static String capitalize(String str) {
if (str == null || str.isEmpty()) {
return str;
}
return Character.toUpperCase(str.charAt(0)) + str.substring(1);
}
/**
* 对象转map,只支持单个对象,不支持嵌套,对象内不能有List, map
* @param obj
* @return
*/
public static Map<String, Object> objToMap(Object obj) {
// 步骤1:对对象属性按照首字母进行排序
Map<String, Object> sortedProperties = new TreeMap<>();
try {
Arrays.stream(obj.getClass().getDeclaredFields())
.forEach(field -> {
try {
field.setAccessible(true);
Object value = field.get(obj);
if (!Objects.equals(field.getName(),"serialVersionUID") && Objects.nonNull(value)) {
if (value instanceof TemporalAccessor) {
value = DateUtils.YMD_HMS.format((TemporalAccessor) value);
}
sortedProperties.put(field.getName(), value);
}
} catch (IllegalAccessException e) {
log.error("参数是对象转换报错11--", e);
}
});
} catch (Exception e) {
log.error("参数是对象转换报错22--", e);
}
return sortedProperties;
}
}
// public static void main(String[] args) {
// String template = "尊敬的${name},您的验证码是 ${code},请勿泄露。";
// String json = "{\"name\":\"张三\",\"code\":\"123456\"}";
//import org.json.JSONObject;
// JSONObject variablesJson = new JSONObject(json);
// Map<String, String> variables = variablesJson.toMap();
//
// String smsText = replaceVariables(template, variables);
//
// System.out.println(smsText); // 输出:'尊敬的张三,您的验证码是 123456,请勿泄露。'
// }
//
// public static String replaceVariables(String template, Map<String, String> variables) {
// Pattern pattern = Pattern.compile("\\$\\{([^}]+)\\}");
// Matcher matcher = pattern.matcher(template);
// StringBuffer sb = new StringBuffer();
//
// while (matcher.find()) {
// String variableName = matcher.group(1);
// String variableValue = variables.get(variableName);
// if (variableValue != null) {
// matcher.appendReplacement(sb, variableValue);
// }
// }
//
// matcher.appendTail(sb);
//
// return sb.toString();
// }
2.3 记录表结构
CREATE TABLE `operate_record` (
`id` bigint unsigned NOT NULL DEFAULT '0' COMMENT '记录主键',
`main_type` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '0' COMMENT '主业务类别',
`biz_type` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '0' COMMENT '业务类别',
`rel_id` bigint unsigned NOT NULL DEFAULT '0' COMMENT '关联主键',
`content` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '日志内容',
`is_deleted` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '是否删除0否1是',
`create_time` datetime NOT NULL COMMENT '创建时间',
`create_by` bigint unsigned NOT NULL DEFAULT '0' COMMENT '创建人id',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`update_by` bigint unsigned DEFAULT '0' COMMENT '更新人id',
PRIMARY KEY (`id`) USING BTREE,
KEY `idx_biz_relId` (`biz_type`,`rel_id`) USING BTREE COMMENT '业务类型和关联主键作为查询'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='操作记录'
2.4 logType类
package com.demo.common.enums.log;
import com.demo.common.utils.MessageTemplateUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 操作日志内容类型枚举
*/
@Getter
@AllArgsConstructor
public enum LogTypeEnum {
/**
* ${username}于${time}开通了${companyName}运营账户
*/
OPEN_PARTNER_RECORD("新增会员:于${time}由用户:${username}开通会员", RecordMainTypeEnum.PARTNER) {
@Override
public String getContent(Object param) {
return MessageTemplateUtil.replaceVariables(this.getLabel(), param);
}
},
;
private final String label;
private final RecordMainTypeEnum mainType;
/**
* 处理模版内容
*
*/
public String getContent(Object param) {
return null;
}
public static void main(String[] args) {
// Map<String, String> variables = new HashMap<>();
// variables.put( "username", "张三");
// variables.put( "time", "2023-11-29");
// variables.put( "companyName", "大公司");
// UserA variables = new UserA();
// String handle = LogTypeEnum.OPEN_PARTNER_RECORD.getContent(variables);
// System.out.println(handle);
}
}
//@Data
//class UserA {
// private String username = "张飒";
// private LocalDateTime time= LocalDateTime.now();
// private String companyName = "栈公司";
//}
2.5主类型
为了区分日志都是哪几个大块的
package com.demo.common.enums.log;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 操作记录 - 业务大类 枚举
*
* @author
*/
@Getter
@AllArgsConstructor
public enum RecordMainTypeEnum{
PARTNER("运营"),
;
private final String label;
}
3. 使用
保存方法
/**
* 新增操作记录
*
* @param logType 操作记录信息
* @param relId 操作业务ID
* @param userId 用户主键
* @param obj 参数,可以是对象, json, map
* @author
*/
@Transactional(rollbackFor = Exception.class)
public Boolean saveRecord(LogTypeEnum logType, Long userId, Long relId, Object obj) {
// 参数转成实体PO
OperateRecordPO operateRecordPo = new OperateRecordPO()
.setContent(logType.getContent(obj))
.setCreateBy(userId)
.setMainType(logType.getMainType().name())
.setBizType(logType.name())
.setRelId(relId)
;
return iOperateRecordService.save(operateRecordPo);
}
3.1 查询记录列表
/**
* 分页查询操作记录
*
* @param params 分页查询参数
* @author
*/
public Page<OperateRecordVO> queryPage(OperateRecordPageDTO params) {
// 设置查询参数
final LambdaQueryWrapper<OperateRecordPO> queryLambda = Wrappers.<OperateRecordPO>lambdaQuery()
.eq(OperateRecordPO::getBizType, params.getBizType())
.eq(OperateRecordPO::getRelId, params.getRelId());
// 分页查询
Page<OperateRecordVO> page = PageUtils.page(params, iOperateRecordService, OperateRecordVO.class, queryLambda);
return page;
}
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.demo.mybatis.utils;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.demo.common.exception.E;
import com.demo.common.exception.SystemExceptionEnum;
import java.util.ArrayList;
import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.util.CollectionUtils;
public class PageUtils {
private static final Logger log = LoggerFactory.getLogger(PageUtils.class);
public PageUtils() {
}
public static <PO, PAGE extends IPage<VO>, VO> PAGE page(PAGE page, IService<PO> iService, Class<VO> voType) {
return page(page, iService, voType, (Wrapper)null);
}
public static <PO, PAGE extends IPage<VO>, VO> PAGE page(PAGE page, IService<PO> iService, Class<VO> voType, Wrapper<PO> queryWrapper) {
Page<PO> poPage = new Page();
BeanUtils.copyProperties(page, poPage);
Page result;
if (Objects.isNull(queryWrapper)) {
result = (Page)iService.page(poPage);
} else {
result = (Page)iService.page(poPage, queryWrapper);
}
BeanUtils.copyProperties(poPage, page);
ArrayList<VO> voArrayList = new ArrayList();
if (!CollectionUtils.isEmpty(result.getRecords())) {
result.getRecords().forEach((po) -> {
try {
VO vo = voType.newInstance();
BeanUtils.copyProperties(po, vo);
voArrayList.add(vo);
} catch (IllegalAccessException | InstantiationException var4) {
log.error("集合浅拷贝出错 -> {}", var4.getMessage(), var4);
throw E.of(SystemExceptionEnum.TYPE_CONVERSION_FAILED, true);
}
});
}
page.setRecords(voArrayList);
return page;
}
}
4. 总结
基本使用就这些,如果想更方便,还可以做成注解形式,解析参数,利用切面保存