diff --git a/src/com/engine/salary/elog/annotation/Elog.java b/src/com/engine/salary/elog/annotation/Elog.java new file mode 100644 index 000000000..7e00ca25e --- /dev/null +++ b/src/com/engine/salary/elog/annotation/Elog.java @@ -0,0 +1,23 @@ +package com.engine.salary.elog.annotation; + +import java.lang.annotation.*; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface Elog { + + String module(); + + String function(); + + String operateType(); + + String operateTypeName(); + + String sql() default ""; + + boolean isLocal() default true; + + String infoMethod() default ""; +} diff --git a/src/com/engine/salary/elog/annotation/ElogDetailField.java b/src/com/engine/salary/elog/annotation/ElogDetailField.java new file mode 100644 index 000000000..129c57da8 --- /dev/null +++ b/src/com/engine/salary/elog/annotation/ElogDetailField.java @@ -0,0 +1,14 @@ +package com.engine.salary.elog.annotation; + +import java.lang.annotation.*; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD}) +public @interface ElogDetailField { + + String fieldType() default "varchar"; + String length() default "50"; + String fieldName(); + String desc() default "自定义字段"; +} diff --git a/src/com/engine/salary/elog/annotation/ElogDetailTable.java b/src/com/engine/salary/elog/annotation/ElogDetailTable.java new file mode 100644 index 000000000..d5054b7c1 --- /dev/null +++ b/src/com/engine/salary/elog/annotation/ElogDetailTable.java @@ -0,0 +1,13 @@ +package com.engine.salary.elog.annotation; + +import java.lang.annotation.*; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface ElogDetailTable { + + String module(); + + String function() default "common"; +} diff --git a/src/com/engine/salary/elog/annotation/ElogField.java b/src/com/engine/salary/elog/annotation/ElogField.java new file mode 100644 index 000000000..3b5df99c2 --- /dev/null +++ b/src/com/engine/salary/elog/annotation/ElogField.java @@ -0,0 +1,23 @@ +package com.engine.salary.elog.annotation; + +import com.engine.salary.elog.dto.DateTypeEnum; + +import java.lang.annotation.*; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD}) +public @interface ElogField { + + DateTypeEnum dataType() default DateTypeEnum.VARCHAR; + + int length() default 50; + + String comment() default "自定义字段"; + + String defaultValue() default ""; + + boolean isNull() default true; + + boolean isKey() default false; +} diff --git a/src/com/engine/salary/elog/annotation/ElogPrimaryKey.java b/src/com/engine/salary/elog/annotation/ElogPrimaryKey.java new file mode 100644 index 000000000..291f82562 --- /dev/null +++ b/src/com/engine/salary/elog/annotation/ElogPrimaryKey.java @@ -0,0 +1,9 @@ +package com.engine.salary.elog.annotation; + +import java.lang.annotation.*; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER}) +public @interface ElogPrimaryKey { +} diff --git a/src/com/engine/salary/elog/annotation/ElogTable.java b/src/com/engine/salary/elog/annotation/ElogTable.java new file mode 100644 index 000000000..28f5f853b --- /dev/null +++ b/src/com/engine/salary/elog/annotation/ElogTable.java @@ -0,0 +1,16 @@ +package com.engine.salary.elog.annotation; + +import org.springframework.stereotype.Component; + +import java.lang.annotation.*; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +@Component +public @interface ElogTable { + + String module(); + + String function() default "common"; +} diff --git a/src/com/engine/salary/elog/annotation/LoggerTarget.java b/src/com/engine/salary/elog/annotation/LoggerTarget.java new file mode 100644 index 000000000..c1e337ca3 --- /dev/null +++ b/src/com/engine/salary/elog/annotation/LoggerTarget.java @@ -0,0 +1,24 @@ +package com.engine.salary.elog.annotation; + +import org.springframework.core.annotation.AliasFor; + +import java.lang.annotation.*; + +/** + * @ClassName: LoggerTarget + * @Description 日志构造器-自定义注解 + * @Author tanghj + * @Date 2021/2/10 14:18 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface LoggerTarget { + @AliasFor("module") + String value() default ""; + + @AliasFor("value") + String module() default ""; + + String function() default "common"; +} diff --git a/src/com/engine/salary/elog/annotation/handle/ElogHandler.java b/src/com/engine/salary/elog/annotation/handle/ElogHandler.java new file mode 100644 index 000000000..815191606 --- /dev/null +++ b/src/com/engine/salary/elog/annotation/handle/ElogHandler.java @@ -0,0 +1,177 @@ +package com.engine.salary.elog.annotation.handle; + +import com.weaver.common.async.producer.client.AsyncClient; +import com.weaver.common.distribution.genid.IdGenerator; +import com.weaver.common.elog.annotation.Elog; +import com.weaver.common.elog.annotation.ElogPrimaryKey; +import com.weaver.common.elog.dao.QueryCurretValusMapper; +import com.weaver.common.elog.dto.LoggerContext; +import com.weaver.common.elog.util.LoggerTemplate; +import org.apache.commons.lang3.StringUtils; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.Signature; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; +import org.springframework.util.ReflectionUtils; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Date; +import java.util.List; +import java.util.Map; + + +/** + * @ClassName: LoggerTargetHandler + * @Description 从Spring扫描到的类中获取到Elog自定义注解类设置function属性 + * @Author tanghj + * @Date 2021/2/10 14:18 + */ +@Aspect +@Component +public class ElogHandler { + + @Autowired + ApplicationContext applicationContext; + + @Autowired + protected AsyncClient asyncClient; + + @Autowired + QueryCurretValusMapper queryCurretValusMapper; + + /** + * 切面写入日志 + */ + @Pointcut("@annotation(com.weaver.common.elog.annotation.Elog)" ) + public void writeLog(){} + @Around("writeLog()") + public Object writeLog(ProceedingJoinPoint pjp){ + Object[] args = pjp.getArgs(); + Signature signature = pjp.getSignature(); + MethodSignature methodSignature = (MethodSignature) signature; + Method method = methodSignature.getMethod(); + + Elog elog = method.getAnnotation(Elog.class); + String moduleName = elog.module(); + String funtionName = elog.function(); + String operateType = elog.operateType(); + String operateTypeName = elog.operateTypeName(); + boolean isLocal = elog.isLocal(); + Annotation[][] annos = method.getParameterAnnotations(); + + Object id = null; + int keyPosition = -1; + int index = 0; + + Class idClass = null; + // 获取主键id注解 + for(Annotation[] anno : annos) { + if(anno.length > 0) { + for(Annotation annotation : anno) { + if(annotation instanceof ElogPrimaryKey) { + idClass = method.getParameters()[index].getType(); + id = args[index]; + if(StringUtils.isEmpty(id+"")) { + id = idClass.cast(IdGenerator.generate() + ""); + } + keyPosition = index; + break; + } + } + } + index ++; + } + + LoggerContext loggerContext = null; + // 获取日志实体类 + for(Object arg: args) { + if(arg instanceof LoggerContext) { + loggerContext = (LoggerContext) arg; + break; + } + } + + if(loggerContext == null) { + loggerContext = new LoggerContext(); + } + + // 日志实体类的初始化 + // loggerContext.setOperateType("UPDATE"); + loggerContext.setFunctionName(funtionName); + loggerContext.setModuleName(moduleName); + loggerContext.setOperateType(operateType); + loggerContext.setOperateTypeName(operateTypeName); + loggerContext.setDate(new Date()); + loggerContext.setDevice("IOS"); + + String sql = elog.sql(); + String infoMethod = elog.infoMethod(); + + boolean isSql = false; + boolean isMethod = false; + Object currentClass = null; + Method infoMtd = null; + if(StringUtils.isNotEmpty(id+"")) { + if(StringUtils.isNotEmpty(sql)) { + isSql = true; + Map oldValue = queryCurretValusMapper.queryValues(String.format(sql, id)); + loggerContext.setOldValues(oldValue); + } else if(StringUtils.isNotEmpty(infoMethod)){ + isMethod = true; + currentClass = applicationContext.getBean(pjp.getTarget().getClass()); + // 获取方法 + infoMtd = ReflectionUtils.findMethod(pjp.getTarget().getClass(),infoMethod, idClass); + + // todo 为空的情况加异常提醒 + // 反射执行方法 + Object res= ReflectionUtils.invokeMethod(infoMtd,currentClass, id); + if(res != null) { + if(res instanceof List) { + loggerContext.setOldValueList((List) res); + } else { + loggerContext.setOldValues(res); + } + + } + } + } + + LoggerTemplate loggerTemplate = new LoggerTemplate(); + loggerTemplate.setFunction(funtionName); + loggerTemplate.setModule(moduleName); + loggerTemplate.setAsyncClient(asyncClient); + + Object result = null; + try { + args[keyPosition] = id; + result = pjp.proceed(args); + } catch (Throwable throwable) { + throwable.printStackTrace(); + } + if(isSql) { + Map oldValue = queryCurretValusMapper.queryValues(String.format(sql, id)); + loggerContext.setNewValues(oldValue); + } else if(isMethod) { + Object res= ReflectionUtils.invokeMethod(infoMtd,currentClass, id); + if(res != null) { + if(res instanceof List) { + loggerContext.setNewValueList((List) res); + } else { + loggerContext.setNewValues(res); + } + } + } + if(isLocal) + loggerTemplate.write(loggerContext); + else + loggerTemplate.write(loggerContext,false); + return result; + + } +} diff --git a/src/com/engine/salary/elog/annotation/handle/ElogTableScanner.java b/src/com/engine/salary/elog/annotation/handle/ElogTableScanner.java new file mode 100644 index 000000000..2be2f35b6 --- /dev/null +++ b/src/com/engine/salary/elog/annotation/handle/ElogTableScanner.java @@ -0,0 +1,172 @@ +package com.engine.salary.elog.annotation.handle; + +import com.engine.salary.elog.annotation.ElogField; +import com.engine.salary.elog.annotation.ElogTable; +import com.engine.salary.elog.dto.TableColumnBean; +import com.engine.salary.elog.util.ElogUtils; +import com.weaver.common.elog.dao.TableCheckerMapper; +import com.weaver.common.elog.dto.DateTypeEnum; +import lombok.extern.slf4j.Slf4j; +import org.reflections.Reflections; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.aop.support.AopUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; + +import javax.annotation.Resource; +import java.lang.reflect.Field; +import java.util.*; +import java.util.stream.Collectors; + +/** + * @ClassName: ElogTableScanner + * @Description 日志操作表扫描 + * @Author tanghj + * @Date 2021/3/12 13:31 + */ +@Slf4j +public class ElogTableScanner { + private Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Resource + private ApplicationContext applicationContext; + + @Autowired + private TableCheckerMapper tableCheckerMapper; + + @Override + public void run(String... args) throws Exception { + + // todo 需要考虑集群下,控制一台机器来跑 + scanElogTable(); + + } + + private void scanElogTable() { + // todo 是否还需要扫描Elog(因为可能不需要本地存储) ELogDetailTable, + Map tableBeans = this.applicationContext.getBeansWithAnnotation(ElogTable.class); + Reflections reflections = new Reflections("com.example.controller"); + Set> restController = reflections.getTypesAnnotatedWith(RestController.class); + + List baseColumns = new ArrayList<>(); + + Map> tableColumns = elogTableHandle(tableBeans,baseColumns); + + for(String tableName : tableColumns.keySet()) { + + // todo 需要处理明细表,如果没有直接初始化原始明细表,如果有加上自定义的 + List columns = tableColumns.get(tableName); + if(columns == null) { + columns = baseColumns; + } else { + columns.addAll(baseColumns); + } + tableCheck(tableName, columns); + } + + } + + private Map> elogTableHandle(Map tableBeans, List baseColumns) { + + + Map> tableMap = new HashMap<>(); + + + for (Map.Entry entry : tableBeans.entrySet()) {//遍历每个controller层 + + List list = new ArrayList<>(); + System.out.println(entry.getKey());//demo1Controller + Object value = entry.getValue(); + Class aClass = AopUtils.getTargetClass(value);//获取class + + ElogTable elogTable = aClass.getAnnotation(ElogTable.class); + + List fields = Arrays.asList(aClass.getDeclaredFields());//获取方法 + for (Field f : fields) { + + ElogField field = f.getAnnotation(ElogField.class); + if(field == null) { + continue; + } + + TableColumnBean tableColumnBean = new TableColumnBean(); + + tableColumnBean.setColumnName(f.getName()); + tableColumnBean.setColumnComment(field.comment()); + tableColumnBean.setColumnDefault(field.defaultValue()); + tableColumnBean.setFieldLength(field.length()); + tableColumnBean.setDataType(field.dataType()); + tableColumnBean.setNullable(field.isNull()); + list.add(tableColumnBean); + } + if(!ElogUtils.BASE_TABLE.equals(elogTable.module())) { + + tableMap.put(ElogUtils.getTableName(elogTable.module(), elogTable.function()), list); + } else { + baseColumns.addAll(list); + } + } + + return tableMap; + } + + private void tableCheck(String tableName, List columns) { + + List oldColumns = tableCheckerMapper.getTableStructure(tableName); + + // 表不存在 + if(oldColumns == null || oldColumns.size() == 0) { + createTable(tableName,columns ); + } else { + Map newcolMap = new HashMap<>(); + Map oldcolMap = new HashMap<>(); + + columns.stream().forEach(tableColumnBean -> newcolMap.put(tableColumnBean.getColumnName().toLowerCase(), tableColumnBean)); + oldColumns.stream().forEach(tableColumnBean -> { + tableColumnBean.setDataType(ElogUtils.getEnumFromString(DateTypeEnum.class, tableColumnBean.getDataTypeStr())); + tableColumnBean.setNullable("YES".equalsIgnoreCase(tableColumnBean.getIsNullableStr())); + oldcolMap.put(tableColumnBean.getColumnName().toLowerCase(), tableColumnBean); + }); + // 只增加或者修改,不删除字段 + for(String key : newcolMap.keySet()) { + if(oldcolMap.containsKey(key)) { + // 字段变动则修改 + if(!(newcolMap.get(key).toSql()).equals(oldcolMap.get(key).toSql())) + this.modifyColumn(tableName, newcolMap.get(key)); + } else { + this.addColumn(tableName, newcolMap.get(key)); + } + } + } + + } + + private void createTable(String tableName, List columns) { + StringBuilder sb = new StringBuilder("create table ").append(tableName).append(" ( "); + sb.append(columns.stream().map( bean -> bean.toSql()).collect(Collectors.joining(","))); + sb.append(")"); + logger.info("创建sql:{}",sb.toString()); + tableCheckerMapper.createElogTable(sb.toString()); + } + + private void addColumn(String tableName, TableColumnBean tableColumnBean) { + StringBuilder sb = new StringBuilder("alter table ") + .append(tableName).append(" ") + .append(" add ") + .append(" column ") + .append(tableColumnBean.toSql()); + logger.info("新增字段sql:{}",sb.toString()); + tableCheckerMapper.createElogTable(sb.toString()); + } + + private void modifyColumn(String tableName, TableColumnBean tableColumnBean) { + StringBuilder sb = new StringBuilder("alter table ") + .append(tableName).append(" ") + .append(" modify ") + .append(" column ") + .append(tableColumnBean.toSql()); + logger.info("修改字段sql:{}",sb.toString()); + tableCheckerMapper.createElogTable(sb.toString()); + } +} diff --git a/src/com/engine/salary/elog/annotation/handle/LoggerTargetHandler.java b/src/com/engine/salary/elog/annotation/handle/LoggerTargetHandler.java new file mode 100644 index 000000000..4a5c44d7b --- /dev/null +++ b/src/com/engine/salary/elog/annotation/handle/LoggerTargetHandler.java @@ -0,0 +1,40 @@ +package com.engine.salary.elog.annotation.handle; + +import com.weaver.common.elog.annotation.LoggerTarget; +import com.weaver.common.elog.util.LoggerTemplate; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; + +import java.util.Map; + + +/** + * @ClassName: LoggerTargetHandler + * @Description 从Spring扫描到的类中获取到LoggerTarget自定义注解类设置function属性 + * @Author tanghj + * @Date 2021/2/10 14:18 + */ + +@Component +public class LoggerTargetHandler implements CommandLineRunner { + + @Autowired + ApplicationContext applicationContext; + + @Override + public void run(String... args) throws Exception { + Map loggtemplateMap = applicationContext.getBeansWithAnnotation(LoggerTarget.class); + + for(Object obj : loggtemplateMap.values()) { + if(obj instanceof LoggerTemplate) { + LoggerTarget loggerTarget = obj.getClass().getAnnotation(LoggerTarget.class); + ((LoggerTemplate) obj).setFunction(loggerTarget.function()); + ((LoggerTemplate) obj).setModule(StringUtils.isNotEmpty(loggerTarget.value()) ? loggerTarget.value() : loggerTarget.module()); + } + + } + } +} diff --git a/src/com/engine/salary/elog/dto/CancelContext.java b/src/com/engine/salary/elog/dto/CancelContext.java new file mode 100644 index 000000000..1c9771b69 --- /dev/null +++ b/src/com/engine/salary/elog/dto/CancelContext.java @@ -0,0 +1,25 @@ +package com.engine.salary.elog.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; + +/** + * @ClassName: CancelContext + * @Description 撤销实体类 + * @Author tanghj + * @Date 2021/2/10 14:18 + */ +@ApiModel("撤销实体类") +public class CancelContext { + + @ApiModelProperty("撤销参数") + private T cancleParams; + + public T getCancleParams() { + return cancleParams; + } + + public void setCancleParams(T cancleParams) { + this.cancleParams = cancleParams; + } +} diff --git a/src/com/engine/salary/elog/dto/DateTypeEnum.java b/src/com/engine/salary/elog/dto/DateTypeEnum.java new file mode 100644 index 000000000..9833cfa86 --- /dev/null +++ b/src/com/engine/salary/elog/dto/DateTypeEnum.java @@ -0,0 +1,16 @@ +package com.engine.salary.elog.dto; + +public enum DateTypeEnum { + VARCHAR, + BIGINT, + INT, + DATETIME, + TEXT, + LONGTEXT, + DOUBLE, + DECIMAL, + TINYINT, + FLOAT; + + +} diff --git a/src/com/engine/salary/elog/dto/LoggerContext.java b/src/com/engine/salary/elog/dto/LoggerContext.java new file mode 100644 index 000000000..248ce45d8 --- /dev/null +++ b/src/com/engine/salary/elog/dto/LoggerContext.java @@ -0,0 +1,408 @@ +package com.engine.salary.elog.dto; + +import com.engine.salary.elog.annotation.ElogField; +import com.engine.salary.elog.annotation.ElogTable; +import com.engine.salary.elog.util.ElogUtils; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * @ClassName: LoggerContext + * @Description 日志实体类。支持通过泛型扩展日志字段 + * @Author tanghj + * @Date 2021/2/10 14:18 + */ +@ElogTable(module = ElogUtils.BASE_TABLE) +@ApiModel("日志实体类") +public class LoggerContext { + @ElogField(comment = "ID", dataType = DateTypeEnum.VARCHAR, isKey = true, isNull = false) + @ApiModelProperty("日志ID") + private long id; + + @ElogField(dataType = DateTypeEnum.VARCHAR, length = 36,comment = "日志UUID") + @ApiModelProperty("日志UUID") + private String uuid; + + @ApiModelProperty("自定义日志字段信息") + private T customInfo; + @ElogField(dataType = DateTypeEnum.DATETIME,comment = "操作时间") + @ApiModelProperty("操作时间") + private Date date; + + @ElogField(dataType = DateTypeEnum.VARCHAR,length = 50,comment = "终端信息") + @ApiModelProperty("终端信息") + private String device; + + @ElogField(dataType = DateTypeEnum.VARCHAR,length = 50,comment = "操作人") + @ApiModelProperty("操作人") + private String operator; + + @ElogField(dataType = DateTypeEnum.VARCHAR,length = 100,comment = "操作人姓名") + @ApiModelProperty("操作人姓名") + private String operatorName; + + @ElogField(dataType = DateTypeEnum.VARCHAR,length = 10,comment = "租户id") + @ApiModelProperty("租户id") + private String tenant_key; + + /** + * 要操作的对象在表中的主键值 + */ + @ElogField(dataType = DateTypeEnum.VARCHAR,length = 50,comment = "操作目标id") + @ApiModelProperty("操作目标id") + private String targetId; + + @ElogField(dataType = DateTypeEnum.TEXT ,comment = "操作目标名称") + @ApiModelProperty("操作目标名称(用于显示)") + private String targetName; + + @ElogField(dataType = DateTypeEnum.VARCHAR, length = 100,comment = "模块") + @ApiModelProperty("目标对象类型(大分类,模块,服务)") + private String moduleName;// 模块 + + /** + * 目标对象类型(小分类,模块/服务下的子功能。子项目) + * 数据存储是以模块名_子项目名作为最基本的存储单元 + * 如果是子项目下的子项目 命名为:子项目名_子项目名_子项目名(全部小写) + */ + @ElogField(dataType = DateTypeEnum.VARCHAR, length = 100,comment = "服务(方法)") + @ApiModelProperty("目标对象类型(小分类,模块/服务下的子功能。子项目)") + private String functionName; + + @ElogField(dataType = DateTypeEnum.VARCHAR, length = 100,comment = "访问接口名") + @ApiModelProperty("访问接口名") + private String interfaceName; + + @ElogField(dataType = DateTypeEnum.VARCHAR, length = 50,comment = "操作类型") + @ApiModelProperty("操作类型(增删改查等)") + private String operateType; + + @ElogField(dataType = DateTypeEnum.TEXT,comment = "操作类型") + @ApiModelProperty("操作类型名称") + private String operateTypeName; + + /** + * 每个TableChangeBean 为一张表,支持记录多张表的前后值 + */ + @ApiModelProperty("修改前、后的值") + private List changeValues;// 操作表名,[字段名,值] + + @ElogField(dataType = DateTypeEnum.TEXT,comment = "操作类型") + @ApiModelProperty("操作详细说明") + private String operatedesc; + + @ApiModelProperty("涉及的相关参数") + private Map params; + + /*@ApiModelProperty("主日志") + private String mainId;//当作为主表,belongMainId不赋值 + + @ApiModelProperty("从表日志") + private String belongMainId;*/ + + @ElogField(dataType = DateTypeEnum.VARCHAR, length = 50,comment = "操作IP") + @ApiModelProperty("操作IP") + private String clientIp; + + @ElogField(dataType = DateTypeEnum.VARCHAR, length = 50,comment = "分组") + @ApiModelProperty("分组") + private String groupId; + + /*@ApiModelProperty("是否明显表") + private boolean isDetail;*/ + + @ElogField(dataType = DateTypeEnum.TEXT, comment = "分组标题") + @ApiModelProperty("分组标题") + private int groupNameLabel; + + @ApiModelProperty("重做业务接口") + private String redoService; + + @ElogField(dataType = DateTypeEnum.LONGTEXT, comment = "重做参数") + @ApiModelProperty("重做参数") + private RedoContext redoContext; + + @ElogField(dataType = DateTypeEnum.VARCHAR, length = 200, comment = "撤销业务接口") + @ApiModelProperty("撤销业务接口") + private String cancelService; + + @ElogField(dataType = DateTypeEnum.LONGTEXT, comment = "撤销参数") + @ApiModelProperty("撤销参数") + private CancelContext cancelContext; + + @ApiModelProperty("日志明细列表(值变化列表-自动赋值、或者自定义字段)") + private List detailContexts; + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public T getCustomInfo() { + return customInfo; + } + + public void setCustomInfo(T customInfo) { + this.customInfo = customInfo; + } + + public Date getDate() { + return date; + } + + public void setDate(Date date) { + this.date = date; + } + + public String getDevice() { + return device; + } + + public void setDevice(String device) { + this.device = device; + } + + public String getOperator() { + return operator; + } + + public void setOperator(String operator) { + this.operator = operator; + } + + public String getTenant_key() { + return tenant_key; + } + + public void setTenant_key(String tenant_key) { + this.tenant_key = tenant_key; + } + + public String getTargetId() { + return targetId; + } + + public void setTargetId(String targetId) { + this.targetId = targetId; + } + + public String getTargetName() { + return targetName; + } + + public void setTargetName(String targetName) { + this.targetName = targetName; + } + + public String getModuleName() { + return moduleName; + } + + public void setModuleName(String moduleName) { + this.moduleName = moduleName; + } + + public String getFunctionName() { + return functionName; + } + + public void setFunctionName(String functionName) { + this.functionName = functionName; + } + + public String getInterfaceName() { + return interfaceName; + } + + public void setInterfaceName(String interfaceName) { + this.interfaceName = interfaceName; + } + + public String getOperateType() { + return operateType; + } + + public void setOperateType(String operateType) { + this.operateType = operateType; + } + + public List getChangeValues() { + return changeValues; + } + + public void setChangeValues(List changeValues) { + this.changeValues = changeValues; + } + + public String getOperatedesc() { + return operatedesc; + } + + public void setOperatedesc(String operatedesc) { + this.operatedesc = operatedesc; + } + + public Map getParams() { + return params; + } + + public void setParams(Map params) { + this.params = params; + } + + /*public String getMainId() { + return mainId; + } + + public void setMainId(String mainId) { + this.mainId = mainId; + } + + public String getBelongMainId() { + return belongMainId; + } + + public void setBelongMainId(String belongMainId) { + this.belongMainId = belongMainId; + }*/ + + public String getClientIp() { + return clientIp; + } + + public void setClientIp(String clientIp) { + this.clientIp = clientIp; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + +/* public boolean isDetail() { + return isDetail; + } + + public void setDetail(boolean detail) { + isDetail = detail; + }*/ + + public int getGroupNameLabel() { + return groupNameLabel; + } + + public void setGroupNameLabel(int groupNameLabel) { + this.groupNameLabel = groupNameLabel; + } + + public String getRedoService() { + return redoService; + } + + public void setRedoService(String redoService) { + this.redoService = redoService; + } + + public RedoContext getRedoContext() { + return redoContext; + } + + public void setRedoContext(RedoContext redoContext) { + this.redoContext = redoContext; + } + + public CancelContext getCancelContext() { + return cancelContext; + } + + public void setCancelContext(CancelContext cancelContext) { + this.cancelContext = cancelContext; + } + + public String getCancelService() { + return cancelService; + } + public void setCancelService(String cancelService) { + this.cancelService = cancelService; + } + + public List getDetailContexts() { + return detailContexts; + } + + public void setDetailContexts(List detailContexts) { + this.detailContexts = detailContexts; + } + + public String getOperatorName() { + return operatorName; + } + + public void setOperatorName(String operatorName) { + this.operatorName = operatorName; + } + + public void setOldValues(Object object) { + TableChangeBean bean = new TableChangeBean(); + bean.setOldValue(object); + getChangeList().add(bean); + } + + public void setOldValueList(List list) { + if(list != null) + list.stream().forEach(obj -> setOldValues(obj)); + } + + public void setNewValueList(List list) { + if(list != null) + list.stream().forEach(obj -> setNewValues(obj)); + } + + public void setNewValues(Object object) { + List list = getChangeList(); + + boolean handled = false; + for(TableChangeBean bean : list) { + if(bean.getNewValue() == null) { + bean.setNewValue(object); + handled = true; + break; + } + } + if(!handled) { + TableChangeBean bean = new TableChangeBean(); + bean.setOldValue(object); + list.add(bean); + } + } + + public List getChangeList() { + + if(this.changeValues == null) { + this.changeValues = new ArrayList<>(); + } + + return this.changeValues; + + } + + public String getOperateTypeName() { + return operateTypeName; + } + + public void setOperateTypeName(String operateTypeName) { + this.operateTypeName = operateTypeName; + } +} + diff --git a/src/com/engine/salary/elog/dto/LoggerDetailContext.java b/src/com/engine/salary/elog/dto/LoggerDetailContext.java new file mode 100644 index 000000000..8dd6ec7d9 --- /dev/null +++ b/src/com/engine/salary/elog/dto/LoggerDetailContext.java @@ -0,0 +1,102 @@ +package com.engine.salary.elog.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; + +/** + * @ClassName: ValueChangeBean + * @Description 值变化实体类(自动将表字段值变更类转换为该类) + * @Author tanghj + * @Date 2021/3/9 11:06 + */ +@ApiModel("值变化实体类") +public class LoggerDetailContext { + + @ApiModelProperty("同一个bean转换的数据,uuid相同,作为区分标识") + private String uuid; + + @ApiModelProperty("表名") + private String tableName; + + @ApiModelProperty("字段名") + private String fieldName; + + @ApiModelProperty("更新后的值") + private String newValue; + + @ApiModelProperty("更新前的值") + private String oldValue; + + @ApiModelProperty("字段描述") + private String fieldDesc; + + @ApiModelProperty("字段展示顺序") + private int showorder; + + @ApiModelProperty("自定义字段") + private T customDetailInfo; + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getTableName() { + return tableName; + } + + public void setTableName(String tableName) { + this.tableName = tableName; + } + + public String getFieldName() { + return fieldName; + } + + public void setFieldName(String fieldName) { + this.fieldName = fieldName; + } + + public String getNewValue() { + return newValue; + } + + public void setNewValue(String newValue) { + this.newValue = newValue; + } + + public String getOldValue() { + return oldValue; + } + + public void setOldValue(String oldValue) { + this.oldValue = oldValue; + } + + public String getFieldDesc() { + return fieldDesc; + } + + public void setFieldDesc(String fieldDesc) { + this.fieldDesc = fieldDesc; + } + + public int getShoworder() { + return showorder; + } + + public void setShoworder(int showorder) { + this.showorder = showorder; + } + + public T getCustomDetailInfo() { + return customDetailInfo; + } + + public void setCustomDetailInfo(T customDetailInfo) { + this.customDetailInfo = customDetailInfo; + } +} diff --git a/src/com/engine/salary/elog/dto/RedoContext.java b/src/com/engine/salary/elog/dto/RedoContext.java new file mode 100644 index 000000000..749643aed --- /dev/null +++ b/src/com/engine/salary/elog/dto/RedoContext.java @@ -0,0 +1,24 @@ +package com.engine.salary.elog.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; + +/** + * @ClassName: RedoContext + * @Description 重做实体类 + * @Author tanghj + * @Date 2021/2/10 14:18 + */ +@ApiModel("重做实体类") +public class RedoContext { + @ApiModelProperty("重做参数") + private T redoParams; + + public T getRedoParams() { + return redoParams; + } + + public void setRedoParams(T redoParams) { + this.redoParams = redoParams; + } +} diff --git a/src/com/engine/salary/elog/dto/TableChangeBean.java b/src/com/engine/salary/elog/dto/TableChangeBean.java new file mode 100644 index 000000000..86cfdb0c3 --- /dev/null +++ b/src/com/engine/salary/elog/dto/TableChangeBean.java @@ -0,0 +1,53 @@ +package com.engine.salary.elog.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; + +/** + * @ClassName: TableChangeBean + * @Description 表更新前后值类 + * @Author tanghj + * @Date 2021/3/8 15:58 + */ +@ApiModel("表更新前后值类") +public class TableChangeBean { + + @ApiModelProperty("表名") + private String tableName; + + /** + * 泛型必须是具体的实体类 + */ + @ApiModelProperty("更新前的值") + private T oldValue; + + /** + * 泛型必须是具体的实体类 + */ + @ApiModelProperty("更新后的值") + private T newValue; + + public String getTableName() { + return tableName; + } + + public void setTableName(String tableName) { + this.tableName = tableName; + } + + public T getOldValue() { + return oldValue; + } + + public void setOldValue(T oldValue) { + this.oldValue = oldValue; + } + + public T getNewValue() { + return newValue; + } + + public void setNewValue(T newValue) { + this.newValue = newValue; + } +} diff --git a/src/com/engine/salary/elog/dto/TableColumnBean.java b/src/com/engine/salary/elog/dto/TableColumnBean.java new file mode 100644 index 000000000..e4c635cd0 --- /dev/null +++ b/src/com/engine/salary/elog/dto/TableColumnBean.java @@ -0,0 +1,155 @@ +package com.engine.salary.elog.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; + +/** + * @ClassName: TableColumnBean + * @Description 表字段类 + * @Author tanghj + * @Date 2021/3/12 11:44 + */ +@ApiModel("表字段类") +public class TableColumnBean { + + @ApiModelProperty("列名") + private String columnName; + + /** + * varchar(20) + */ + @ApiModelProperty("数据类型") + private String columnType; + + @ApiModelProperty("字段类型-字符串") + private String dataTypeStr; + + @ApiModelProperty("字段类型") + private DateTypeEnum dataType; + + @ApiModelProperty("长度") + private long fieldLength; + + @ApiModelProperty("是否为空") + private boolean isNullable; + + @ApiModelProperty("是否为空-字符串") + private String isNullableStr; + + @ApiModelProperty("默认值") + private Object columnDefault; + + @ApiModelProperty("备注") + private String columnComment; + + public String getColumnName() { + return columnName; + } + + public void setColumnName(String columnName) { + this.columnName = columnName; + } + + public String getColumnType() { + return columnType; + } + + public void setColumnType(String columnType) { + this.columnType = columnType; + } + + public DateTypeEnum getDataType() { + return dataType; + } + + public void setDataType(DateTypeEnum dataType) { + this.dataType = dataType; + } + + public long getFieldLength() { + return fieldLength; + } + + public void setFieldLength(long fieldLength) { + this.fieldLength = fieldLength; + } + + public boolean isNullable() { + return isNullable; + } + + public void setNullable(boolean nullable) { + isNullable = nullable; + } + + public Object getColumnDefault() { + return columnDefault; + } + + public void setColumnDefault(Object columnDefault) { + this.columnDefault = columnDefault; + } + + public String getColumnComment() { + return columnComment; + } + + public void setColumnComment(String columnComment) { + this.columnComment = columnComment; + } + + public String getDataTypeStr() { + return dataTypeStr; + } + + public void setDataTypeStr(String dataTypeStr) { + this.dataTypeStr = dataTypeStr; + } + + public String getIsNullableStr() { + return isNullableStr; + } + + public void setIsNullableStr(String isNullableStr) { + this.isNullableStr = isNullableStr; + } + + public boolean equals(TableColumnBean tableColumnBean) { + + return this.toSql().equals(tableColumnBean.toSql()); + } + + public String toSql() { + + StringBuilder sb = new StringBuilder(this.columnName.toLowerCase()).append(" "); + + if (this.dataType == null) { + return "类型为空"; + } + switch (this.dataType) { + case BIGINT: + case INT: + case TEXT: + case LONGTEXT: + case DATETIME: + case FLOAT: + case TINYINT: + case DOUBLE: + sb.append(this.dataType.name().toLowerCase()).append(" "); + break; + case VARCHAR: + sb.append(this.dataType.name().toLowerCase()).append("(").append(this.fieldLength).append(") "); + break; + case DECIMAL: + long length = (this.fieldLength < 0 || this.fieldLength > 38) ? 0 : this.fieldLength; + sb.append(this.dataType.name().toLowerCase()).append("(").append(38 - length).append(",").append(length).append(") "); + break; + default: + sb.append("varchar").append("(").append(10).append(") "); + } + + sb.append("comment ").append("'").append(this.columnComment).append("' "); + + return sb.toString(); + } +} diff --git a/src/com/engine/salary/elog/util/ElogUtils.java b/src/com/engine/salary/elog/util/ElogUtils.java new file mode 100644 index 000000000..d2203f7ab --- /dev/null +++ b/src/com/engine/salary/elog/util/ElogUtils.java @@ -0,0 +1,50 @@ +package com.engine.salary.elog.util; + +import com.engine.salary.elog.dto.DateTypeEnum; + +/** + * @ClassName: ElogUtils + * @Description TODO + * @Author tanghj + * @Date 2021/3/12 14:17 + */ +public class ElogUtils { + + private static final String TABLE_SPACER = "_"; + private static final String TABLE_SUFFIX = "logs"; + private static final String DETAIL_TABLE_SUFFIX = "detail"; + public static final String BASE_TABLE = "BASE_ELOG_TABLE"; + + + public static String getTableName(String module, String function) { + return getTableName(module, function, false); + } + + public static String getTableName(String module, String function, boolean isDetail){ + + return module + TABLE_SPACER + function + TABLE_SUFFIX + (isDetail ? DETAIL_TABLE_SUFFIX : ""); + } + + /** + * String 转枚举 + * @param c + * @param string + * @param + * @return + */ + public static > T getEnumFromString(Class c, String string) { + if (c != null && string != null) { + try { + return Enum.valueOf(c, string.trim().toUpperCase()); + } catch (IllegalArgumentException ex) { + } + } + return null; + } + + public static void main(String[] args) { + System.out.println(DateTypeEnum.BIGINT.name()); + DateTypeEnum columnTypeEnum = getEnumFromString(DateTypeEnum.class, "varchar"); + System.out.println(columnTypeEnum.equals(DateTypeEnum.VARCHAR)); + } +} diff --git a/src/com/engine/salary/elog/util/LoggerTemplate.java b/src/com/engine/salary/elog/util/LoggerTemplate.java new file mode 100644 index 000000000..1041e9375 --- /dev/null +++ b/src/com/engine/salary/elog/util/LoggerTemplate.java @@ -0,0 +1,164 @@ +package com.engine.salary.elog.util; + +import com.alibaba.fastjson.JSONObject; +import com.weaver.common.async.bean.AsyncBean; +import com.weaver.common.async.producer.client.AsyncClient; +import com.weaver.common.elog.dto.LoggerContext; +import com.weaver.common.elog.dto.LoggerDetailContext; +import com.weaver.common.elog.dto.TableChangeBean; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import org.springframework.beans.factory.annotation.Autowired; + +import java.lang.reflect.Field; +import java.util.*; + +/** + * @ClassName: LoggerTemplate + * @Description 日志基本功能类 + * @Author tanghj + * @Date 2021/2/10 14:18 + */ +public class LoggerTemplate { + protected String function = "common"; + + protected String module; + + protected String logCenterQueue = "Elog_cneterlogQueue"; + + protected String localQueue; + + @Autowired + protected AsyncClient asyncClient; + /** + * 写入日志消息队列 + * @param context 日志实体 + */ + public void write(LoggerContext context){ + this.write(context, true); + } + + /** + * 写入日志消息队列 + * @param context 日志实体 + * @param isLocal true 存储本地, false不存储本地 + */ + public void write(LoggerContext context, boolean isLocal){ + AsyncBean asyncBean = new AsyncBean<>(); + handleContext(context); + asyncBean.setMessage(context); + asyncBean.setQueue(logCenterQueue); + asyncClient.send(asyncBean); + if(isLocal) { + asyncBean.setQueue(localQueue); + asyncClient.send(asyncBean); + } + } + + private void handleContext(LoggerContext context) { + context.setModuleName(module); + context.setFunctionName(function); + + List changeBeans = context.getChangeValues(); + + List valueChangeList = new ArrayList<>(); + int showOrder = 0; + if(changeBeans != null) + for(TableChangeBean changeBean : changeBeans) { + if(changeBean != null && (changeBean.getNewValue() != null || changeBean.getOldValue() != null)) { + ApiModel apiModel = null; + JSONObject newJo = new JSONObject(); + JSONObject oldJo = new JSONObject(); + JSONObject valueChange = JSONObject.parseObject(JSONObject.toJSONString(changeBean)); + if(changeBean.getNewValue() != null) { + apiModel = changeBean.getNewValue().getClass().getAnnotation(ApiModel.class); + newJo = valueChange.getJSONObject("newValue"); + } else { + apiModel = changeBean.getOldValue().getClass().getAnnotation(ApiModel.class); + } + if(changeBean.getOldValue() != null) { + oldJo = valueChange.getJSONObject("oldValue"); + } + if(apiModel != null) { + Field[] fields = changeBean.getNewValue().getClass().getDeclaredFields(); + for(Field field : fields) { + ApiModelProperty apiModelProperty = field.getAnnotation(ApiModelProperty.class); + LoggerDetailContext valueChangeBean = new LoggerDetailContext(); + valueChangeBean.setTableName(changeBean.getTableName()); + valueChangeBean.setFieldName(field.getName()); + valueChangeBean.setFieldDesc(apiModelProperty.value()); + valueChangeBean.setNewValue(newJo.getString(field.getName())); + valueChangeBean.setOldValue(oldJo.getString(field.getName())); + valueChangeBean.setShoworder(showOrder++); + valueChangeList.add(valueChangeBean); + } + } else { + Set keys = new HashSet<>(); + for(String key : newJo.keySet()) { + keys.add(key); + LoggerDetailContext valueChangeBean = new LoggerDetailContext(); + valueChangeBean.setTableName(changeBean.getTableName()); + valueChangeBean.setFieldName(key); + valueChangeBean.setFieldDesc(key); + valueChangeBean.setNewValue(newJo.getString(key)); + valueChangeBean.setOldValue(oldJo.getString(key)); + valueChangeBean.setShoworder(showOrder++); + valueChangeList.add(valueChangeBean); + } + for(String key : oldJo.keySet()) { + if(keys.contains(key)) + continue; + keys.add(key); + LoggerDetailContext valueChangeBean = new LoggerDetailContext(); + valueChangeBean.setTableName(changeBean.getTableName()); + valueChangeBean.setFieldName(key); + valueChangeBean.setFieldDesc(key); + valueChangeBean.setNewValue(newJo.getString(key)); + valueChangeBean.setOldValue(oldJo.getString(key)); + valueChangeBean.setShoworder(showOrder++); + valueChangeList.add(valueChangeBean); + } + } + } + } + + if(valueChangeList.size() > 0) + context.setDetailContexts(valueChangeList); + } + + public String getFunction() { + return function; + } + + public void setFunction(String function) { + this.function = function; + } + + public String getModule() { + return module; + } + + /** + * 获取日志实体类 + * @return + */ + public LoggerContext getContext() { + + LoggerContext loggerContext = new LoggerContext(); + loggerContext.setUuid(UUID.randomUUID().toString().replace("-","")); + loggerContext.setModuleName(this.module); + loggerContext.setFunctionName(this.function); + + return loggerContext; + + } + + public void setModule(String module) { + this.localQueue = module + "LocalQueue"; + this.module = module; + } + + public void setAsyncClient(AsyncClient asyncClient) { + this.asyncClient = asyncClient; + } +} diff --git a/src/com/engine/salary/elog/util/LoggerTemplateBuilder.java b/src/com/engine/salary/elog/util/LoggerTemplateBuilder.java new file mode 100644 index 000000000..14ae29945 --- /dev/null +++ b/src/com/engine/salary/elog/util/LoggerTemplateBuilder.java @@ -0,0 +1,21 @@ +package com.engine.salary.elog.util; + +/** + * @ClassName: LoggerTemplateBuilder + * @Description 日志基本类构造器 + * @Author tanghj + * @Date 2021/2/10 14:18 + */ +public class LoggerTemplateBuilder { + + public static LoggerTemplate build(String module){ + return build(module, "common"); + } + public static LoggerTemplate build(String module, String function){ + LoggerTemplate loggerTemplate = new LoggerTemplate(); + loggerTemplate.setFunction(function); + loggerTemplate.setModule(module); + + return loggerTemplate; + } +}