---数据服务操作增加操作对象生命周期

This commit is contained in:
2025-10-30 20:44:15 +08:00
parent e5e4cba0fc
commit ce6bc91e91
15 changed files with 733 additions and 227 deletions

View File

@ -0,0 +1,7 @@
package com.xjrsoft.module.dev.dto;
import com.xjrsoft.common.page.PageInput;
public class Testfrom3ModalDto extends PageInput {
}

View File

@ -13,6 +13,7 @@ import com.xjrsoft.common.page.PageOutput;
import com.xjrsoft.common.model.result.R;
import com.xjrsoft.common.utils.VoToColumnUtil;
import com.xjrsoft.module.dev.dto.AddTestfrom3Dto;
import com.xjrsoft.module.dev.dto.Testfrom3ModalDto;
import com.xjrsoft.module.dev.dto.UpdateTestfrom3Dto;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.alibaba.excel.EasyExcel;
@ -34,6 +35,8 @@ import com.xjrsoft.module.dev.vo.Testfrom3Vo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import shade.powerjob.com.google.common.collect.Lists;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@ -73,6 +76,41 @@ public class Testfrom3Controller {
return R.ok(pageOutput);
}
@GetMapping(value = "/modal")
@ApiOperation(value="Testfrom3模态框接口")
public R modal(@Valid Testfrom3ModalDto dto){
Object resultData = null;
List<Testfrom3PageVo> listData = null;
LambdaQueryWrapper<Testfrom3> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper
.like(StrUtil.isNotBlank(dto.getDanXingWenBen8163()),Testfrom3::getDanXingWenBen8163,dto.getDanXingWenBen8163())
.like(StrUtil.isNotBlank(dto.getDanXingWenBen3634()),Testfrom3::getDanXingWenBen3634,dto.getDanXingWenBen3634())
.like(StrUtil.isNotBlank(dto.getDanXingWenBen5097()),Testfrom3::getDanXingWenBen5097,dto.getDanXingWenBen5097())
.orderByDesc(Testfrom3::getId)
.select(Testfrom3.class,x -> VoToColumnUtil.fieldsToColumns(Testfrom3PageVo.class).contains(x.getProperty()));
if(dto.isPage()) {
IPage<Testfrom3> page = testfrom3Service.page(ConventPage.getPage(dto), queryWrapper);
PageOutput<Testfrom3PageVo> pageOutput = ConventPage.getPageOutput(page, Testfrom3PageVo.class);
resultData = pageOutput;
listData = pageOutput.getList();
}else {
List<Testfrom3> list = testfrom3Service.list(queryWrapper);
if(list==null) {
list = Lists.newArrayList();
}
listData = BeanUtil.copyToList(list,Testfrom3PageVo.class);
resultData = listData;
}
if(dto.getId()!=null && dto.getId().longValue() > 0) {
Testfrom3 selected = testfrom3Service.getById(dto.getId());
listData.add(BeanUtil.toBean(selected,Testfrom3PageVo.class));
}
return R.ok(resultData);
}
@GetMapping(value = "/info")
@ApiOperation(value="根据id查询Testfrom3信息")
@SaCheckPermission("testfrom3:detail")
@ -144,4 +182,5 @@ public class Testfrom3Controller {
return R.fileStream(resultBot.toByteArray(), "Testfrom3" + ExcelTypeEnum.XLSX.getValue());
}
}

View File

@ -0,0 +1,49 @@
package com.xjrsoft.module.dev.dto;
import com.xjrsoft.common.page.PageInput;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @title: 分页查询入参
* @Author 管理员
* @Date: 2025-09-25
* @Version 1.0
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class Testfrom3ModalDto extends PageInput {
/**
* @Fields {todo}(用一句话描述这个变量表示什么)
*/
private static final long serialVersionUID = 1L;
/**
* 单行文本
*/
@ApiModelProperty("是否分页")
private boolean page = true;
@ApiModelProperty("选中数据Id")
private Long id;
/**
* 单行文本
*/
@ApiModelProperty("单行文本")
private String danXingWenBen8163;
/**
* 单行文本
*/
@ApiModelProperty("单行文本")
private String danXingWenBen3634;
/**
* 单行文本
*/
@ApiModelProperty("单行文本")
private String danXingWenBen5097;
}

View File

@ -4,5 +4,5 @@ package com.xjrsoft.module.datalog.vo;
* 操作类型枚举
*/
public enum OperationType {
INSERT, UPDATE, DELETE
INSERT, UPDATE, DELETE,DISABLE,ENABLE
}

View File

@ -0,0 +1,64 @@
package com.pictc.datalog;
import com.xjrsoft.module.datalog.vo.OperationType;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
/**
* @author 张福财
* @date 2025年10月30日 上午9:27:17
* @Description: 数据操作上下文
*/
@Getter
@Setter
@Accessors(chain = true)
public class DataOperationContent<T> {
private LogTableInfo tabInfo;
private OperationType type;
private T obj;
private T oldObj;
private Throwable error;
private DataOperationContent(LogTableInfo tabInfo,OperationType type, T obj, T oldObj) {
super();
this.tabInfo = tabInfo;
this.type = type;
this.obj = obj;
this.oldObj = oldObj;
}
public static <T>DataOperationContent<T> of(LogTableInfo tabInfo,OperationType type, T obj, T oldObj) {
return new DataOperationContent<T>(tabInfo, type, obj,oldObj);
}
public boolean isError() {
return error!=null;
}
public boolean success() {
return error==null;
}
public String getTableName() {
return tabInfo.getTableName();
}
public long getIdValue() {
return tabInfo.getIdValue(obj);
}
}

View File

@ -0,0 +1,35 @@
package com.pictc.datalog;
/**
* @author 张福财
* @date 2025年10月30日 上午9:22:22
* @Description: 数据的生命周期接口
*/
public interface DataOperationListener<T> {
/**
* @Description: 操作后
* @param content 操作上下文
* @return T 返回类型
*/
T before(DataOperationContent<T> content);
/**
* @Description: 操作前
* @param obj
* @return T 返回类型
*/
T after(DataOperationContent<T> content);
/**
* @Description: TODO(这里用一句话描述这个方法的作用)
* @param e
* @return void 返回类型
*/
void onError(DataOperationContent<T> content);
void onFinally(DataOperationContent<T> content);
}

View File

@ -0,0 +1,38 @@
package com.pictc.datalog;
import com.xjrsoft.module.common.db.utils.CommonCallUtils;
import com.xjrsoft.module.datalog.vo.OperationType;
public class DefaultDataOperationListener<T> implements DataOperationListener<T> {
@Override
public T before(DataOperationContent<T> content) {
if(content.getType()==OperationType.DELETE) {
CommonCallUtils.deleteBefore(content.getTableName(),content.getIdValue());
}else if(content.getType()==OperationType.DISABLE){
CommonCallUtils.deleteBefore(content.getTableName(),content.getIdValue());
}else if(content.getType()==OperationType.ENABLE){
CommonCallUtils.enableBefore(content.getTableName(),content.getIdValue());
}
return content.getObj();
}
@Override
public T after(DataOperationContent<T> content) {
if(content.getType()==OperationType.INSERT || content.getType()==OperationType.UPDATE) {
CommonCallUtils.saveAfter(content.getTableName(),content.getIdValue());
}
return content.getObj();
}
@Override
public void onError(DataOperationContent<T> content) {
// TODO Auto-generated method stub
}
@Override
public void onFinally(DataOperationContent<T> content) {
// TODO Auto-generated method stub
}
}

View File

@ -9,7 +9,6 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
@ -26,34 +25,29 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.shaded.com.google.common.collect.Lists;
import com.alibaba.nacos.shaded.com.google.common.collect.Maps;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.conditions.query.QueryChainWrapper;
import com.esotericsoftware.kryo.kryo5.minlog.Log;
import com.google.api.client.util.Sets;
import com.pictc.annotations.datalog.JoinCaseType;
import com.pictc.annotations.datalog.JoinValueType;
import com.pictc.annotations.datalog.LogJoin;
import com.pictc.annotations.datalog.LogJoinColumn;
import com.pictc.annotations.datalog.ValueDirectionType;
import com.pictc.converts.ConverUtil;
import com.pictc.datalog.DataOperationContent;
import com.pictc.datalog.DataOperationListener;
import com.pictc.datalog.DefaultDataOperationListener;
import com.pictc.datalog.LogFieldInfo;
import com.pictc.datalog.LogJoinInfo;
import com.pictc.datalog.LogTableInfo;
@ -63,12 +57,12 @@ import com.pictc.exceptions.OrmException;
import com.pictc.jdbc.JdbcContext;
import com.pictc.jdbc.ResultSetUtils;
import com.pictc.jdbc.model.JdbcParam;
import com.xjrsoft.common.enums.YesOrNoEnum;
import com.xjrsoft.common.exception.BusinessException;
import com.xjrsoft.common.utils.SecureUtil;
import com.xjrsoft.module.common.db.utils.CommonCallUtils;
import com.xjrsoft.module.datalog.entity.DataChangeLog;
import com.xjrsoft.module.datalog.entity.json.FieldChange;
import com.xjrsoft.module.datalog.service.DatalogService;
import com.xjrsoft.module.datalog.mapper.DatalogMapper;
import com.xjrsoft.module.datalog.vo.DataChangeLogVo;
import com.xjrsoft.module.datalog.vo.OperationType;
import com.xjrsoft.module.organization.dto.UserDto;
@ -91,55 +85,14 @@ public class DataLogTools {
private static Object lock = new Object();
public static final String SQL_HAS_TABLE = "select count(*) from log_table_info where tbl_name = ?";
public static final String SQL_INST_TABLE = "INSERT INTO log_table_info (tbl_name) VALUES (?);";
public static final String SQL_LOG_TABLE_LIST = "select tbl_name FROM log_table_info";
public static final String SQL_PLACEHOLDER = "\\$\\{TBL_NAME\\}";
public static final String[] SQL_CREATE_TABLE = {
"CREATE TABLE ${TBL_NAME} (\r\n" +
" id VARCHAR(32) NOT NULL,\r\n" +
" flow_id VARCHAR(32),\r\n" +
" pid VARCHAR(32) DEFAULT '#',\r\n" +
" entity_class_name VARCHAR(255),\r\n" +
" bus_name VARCHAR(100),\r\n" +
" entity_id BIGINT,\r\n" +
" field_changes TEXT,\r\n" +
" operation_type VARCHAR(20),\r\n" +
" operator_id BIGINT,\r\n" +
" operator_name VARCHAR(100),\r\n" +
" operation_time TIMESTAMPTZ,\r\n" +
" operation_ip VARCHAR(50),\r\n" +
" PRIMARY KEY (id)\r\n" +
" );",
"COMMENT ON COLUMN ${TBL_NAME}.\"flow_id\" IS '流水id';",
"COMMENT ON COLUMN ${TBL_NAME}.\"pid\" IS '父表ID,根节点#';",
"COMMENT ON COLUMN ${TBL_NAME}.\"entity_class_name\" IS '实体类名称(全类名)';",
"COMMENT ON COLUMN ${TBL_NAME}.\"bus_name\" IS '业务名称';",
"COMMENT ON COLUMN ${TBL_NAME}.\"entity_id\" IS '操作的实体ID主键值';",
"COMMENT ON COLUMN ${TBL_NAME}.\"field_changes\" IS '属性值记录JSON格式存储字段变更详情';",
"COMMENT ON COLUMN ${TBL_NAME}.\"operation_type\" IS '操作类型INSERT-新增UPDATE-修改DELETE-删除';",
"COMMENT ON COLUMN ${TBL_NAME}.\"operator_id\" IS '操作人ID';",
"COMMENT ON COLUMN ${TBL_NAME}.\"operator_name\" IS '操作人姓名';",
"COMMENT ON COLUMN ${TBL_NAME}.\"operation_time\" IS '操作时间';",
"COMMENT ON COLUMN ${TBL_NAME}.\"operation_ip\" IS '操作IP地址';",
"CREATE INDEX idx_${TBL_NAME}_enityt_id ON ${TBL_NAME} (entity_id)"
};
public static final String SQL_INSERT = "INSERT INTO ${TBL_NAME} (\r\n" +
" id, flow_id, pid, entity_class_name, bus_name, \r\n" +
" entity_id, field_changes, operation_type, operator_id, \r\n" +
" operator_name, operation_time, operation_ip\r\n" +
") VALUES (?,?,?,?,?,?,?,?,?,?,?,?);";
public static final String SQL_LIST = "SELECT * FROM ${TBL_NAME} WHERE entity_id = ? ORDER BY operation_time DESC, flow_id DESC";
public static final Set<String> excludeFields = SetUtils.of("id","tenantId","dataVersion","createUserId","createDate","modifyUserId","modifyDate","modifyDate","deleteMark");
private static DatalogMapper logDbService;
@Autowired
public void setLogDbService(DatalogMapper logDbService) {
DataLogTools.logDbService = logDbService;
}
public static DataChangeLog createLog(Class<?> klazz,OperationType type) {
DataChangeLog createLog = createLog(klazz, type, null);
@ -183,6 +136,10 @@ public class DataLogTools {
}
public static <T>T insert(T entity) {
return insert(entity, new DefaultDataOperationListener<T>());
}
public static <T>T insert(T entity,DataOperationListener<T> listener) {
Class<? extends Object> klazz = entity.getClass();
List<DataChangeLog> logs = CollectionUtils.newArrayList();
DataChangeLog datalog = createLog(klazz,OperationType.INSERT);
@ -190,108 +147,209 @@ public class DataLogTools {
initJoinValue(entity,tabInfo,null);
Long idValue = tabInfo.getIdValue(entity);
BaseMapper mapper = MybatisTools.getMapper(tabInfo.getEntityType());
mapper.insert(BeanUtil.toBean(entity,tabInfo.getEntityType()));
datalog.setEntityId(idValue);
buildFields(datalog,tabInfo,entity,null);
logs.add(datalog);
List<LogJoinInfo> joins = tabInfo.getJoins();
if(CollectionUtils.isNotEmpty(joins)) {
for (LogJoinInfo join : joins) {
if(join.getJoin().caseType()==JoinCaseType.FULL) {
buildJoins(logs,datalog,OperationType.INSERT,tabInfo,join,entity);
DataOperationContent<T> content = null;
if(listener!=null) {
content = DataOperationContent.of(tabInfo,OperationType.INSERT,entity,null);
listener.before(content);
}
try {
mapper.insert(BeanUtil.toBean(entity,tabInfo.getEntityType()));
datalog.setEntityId(idValue);
buildFields(datalog,tabInfo,entity,null);
logs.add(datalog);
List<LogJoinInfo> joins = tabInfo.getJoins();
if(CollectionUtils.isNotEmpty(joins)) {
for (LogJoinInfo join : joins) {
if(join.getJoin().caseType()==JoinCaseType.FULL) {
buildJoins(logs,datalog,OperationType.INSERT,tabInfo,join,entity);
}
}
}
if(listener!=null) {
listener.after(content);
}
saveLogs(tabInfo,logs);
} catch (Exception e) {
if(listener!=null) {
content.setError(e);
listener.onError(content);
}
throw e;
}finally {
if(listener!=null) {
listener.onFinally(content);
}
}
saveLogs(tabInfo,logs);
return entity;
}
public static <T>T update(T dto) {
return update(dto, new DefaultDataOperationListener<T>());
}
public static <T>T update(T dto,DataOperationListener<T> listener) {
Class<? extends Object> klazz = dto.getClass();
LogTableInfo tabInfo = getAnnotation(klazz);
Long idValue = tabInfo.getIdValue(dto);
BaseMapper mapper = MybatisTools.getMapper(tabInfo.getEntityType());
Object old = findById(klazz, idValue);
if(old!=null) {
List<DataChangeLog> logs = CollectionUtils.newArrayList();
DataChangeLog datalog = createLog(klazz,OperationType.UPDATE);
initJoinValue(dto,tabInfo,null);
T old = findById(klazz, idValue);
DataOperationContent<T> content = null;
if(listener!=null) {
content = DataOperationContent.of(tabInfo,OperationType.UPDATE,dto,old);
listener.before(content);
}
try {
if(old!=null) {
List<DataChangeLog> logs = CollectionUtils.newArrayList();
DataChangeLog datalog = createLog(klazz,OperationType.UPDATE);
initJoinValue(dto,tabInfo,null);
mapper.updateById(tabInfo.toEntity(dto));
datalog.setEntityId(idValue);
buildFields(datalog,tabInfo,dto,old);
logs.add(datalog);
List<LogJoinInfo> joins = tabInfo.getJoins();
if(!CollectionUtils.isNotEmpty(joins)) {
for (LogJoinInfo join : joins) {
if(join.getJoin().caseType()==JoinCaseType.FULL) {
buildJoins(logs,datalog,OperationType.UPDATE,tabInfo,join,dto);
mapper.updateById(tabInfo.toEntity(dto));
datalog.setEntityId(idValue);
buildFields(datalog,tabInfo,dto,old);
logs.add(datalog);
List<LogJoinInfo> joins = tabInfo.getJoins();
if(!CollectionUtils.isNotEmpty(joins)) {
for (LogJoinInfo join : joins) {
if(join.getJoin().caseType()==JoinCaseType.FULL) {
buildJoins(logs,datalog,OperationType.UPDATE,tabInfo,join,dto);
}
}
}
if(listener!=null) {
listener.after(content);
}
saveLogs(tabInfo,logs);
}else {
throw new OrmException("数据不存在");
}
} catch (Exception e) {
if(listener!=null) {
content.setError(e);
listener.onError(content);
}
throw e;
} finally {
if(listener!=null) {
listener.onFinally(content);
}
saveLogs(tabInfo,logs);
}else {
throw new OrmException("数据不存在");
}
return dto;
}
public static <T>T delete(T dto) {
return delete(dto, new DefaultDataOperationListener<T>());
}
public static <T>T delete(T entity) {
public static <T>T delete(T entity,DataOperationListener<T> listener) {
Class<? extends Object> klazz = entity.getClass();
List<DataChangeLog> logs = CollectionUtils.newArrayList();
LogTableInfo tabInfo = getAnnotation(klazz);
Long idValue = tabInfo.getIdValue(entity);
BaseMapper mapper = MybatisTools.getMapper(tabInfo.getEntityType());
DataChangeLog datalog = createLog(klazz,OperationType.DELETE);
datalog.setEntityId(idValue);
buildFields(datalog,tabInfo,entity,null);
logs.add(datalog);
delete(entity, tabInfo, mapper);
List<LogJoinInfo> joins = tabInfo.getJoins();
if(!CollectionUtils.isNotEmpty(joins)) {
for (LogJoinInfo join : joins) {
if(join.getJoin().caseType()==JoinCaseType.FULL) {
buildJoins(logs,datalog,OperationType.DELETE,tabInfo,join,entity);
T old = findById(klazz, idValue);
DataOperationContent<T> content = null;
if(listener!=null) {
content = DataOperationContent.of(tabInfo,OperationType.DELETE,old,null);
listener.before(content);
}
try {
DataChangeLog datalog = createLog(klazz,OperationType.DELETE);
datalog.setEntityId(idValue);
buildFields(datalog,tabInfo,entity,null);
logs.add(datalog);
delete(entity, tabInfo, mapper);
List<LogJoinInfo> joins = tabInfo.getJoins();
if(!CollectionUtils.isNotEmpty(joins)) {
for (LogJoinInfo join : joins) {
if(join.getJoin().caseType()==JoinCaseType.FULL) {
buildJoins(logs,datalog,OperationType.DELETE,tabInfo,join,entity);
}
}
}
if(listener!=null) {
listener.after(content);
}
saveLogs(tabInfo,logs);
} catch (Exception e) {
if(listener!=null) {
content.setError(e);
listener.onError(content);
}
throw e;
}finally {
if(listener!=null) {
listener.onFinally(content);
}
}
saveLogs(tabInfo,logs);
return entity;
}
public static boolean deleteByIds(Class<?> klazz, @Valid List<Long> ids) {
public static <T>boolean deleteByIds(Class<T> klazz, @Valid List<Long> ids) {
return deleteByIds(klazz, ids, new DefaultDataOperationListener<T>());
}
public static <T>boolean deleteByIds(Class<T> klazz, @Valid List<Long> ids,DataOperationListener<T> listener) {
for (Long id : ids) {
deleteById(klazz, id);
deleteById(klazz, id,listener);
}
return true;
}
public static <T>T deleteById(Class<T> klazz,Long id) {
return deleteById(klazz,id,new DefaultDataOperationListener<T>());
}
public static <T>T deleteById(Class<T> klazz,Long id,DataOperationListener<T> listener) {
LogTableInfo tabInfo = getAnnotation(klazz);
BaseMapper mapper = MybatisTools.getMapper(tabInfo.getEntityType());
T entity = findById(klazz, id);
if(entity==null) return null;
DataOperationContent<T> content = null;
if(listener!=null) {
content = DataOperationContent.of(tabInfo,OperationType.DELETE,entity,null);
listener.before(content);
}
List<DataChangeLog> logs = CollectionUtils.newArrayList();
delete(entity, tabInfo, mapper);
DataChangeLog datalog = createLog(klazz,OperationType.DELETE);
datalog.setEntityId(id);
buildFields(datalog,tabInfo,entity,null);
logs.add(datalog);
List<LogJoinInfo> joins = tabInfo.getJoins();
if(!CollectionUtils.isNotEmpty(joins)) {
for (LogJoinInfo join : joins) {
if(join.getJoin().caseType()==JoinCaseType.FULL) {
buildJoins(logs,datalog,OperationType.DELETE,tabInfo,join,entity);
try {
delete(entity, tabInfo, mapper);
DataChangeLog datalog = createLog(klazz,OperationType.DELETE);
datalog.setEntityId(id);
buildFields(datalog,tabInfo,entity,null);
logs.add(datalog);
List<LogJoinInfo> joins = tabInfo.getJoins();
if(!CollectionUtils.isNotEmpty(joins)) {
for (LogJoinInfo join : joins) {
if(join.getJoin().caseType()==JoinCaseType.FULL) {
buildJoins(logs,datalog,OperationType.DELETE,tabInfo,join,entity);
}
}
}
if(listener!=null) {
listener.after(content);
}
saveLogs(tabInfo,logs);
} catch (Exception e) {
if(listener!=null) {
content.setError(e);
listener.onError(content);
}
throw e;
}finally {
if(listener!=null) {
listener.onFinally(content);
}
}
saveLogs(tabInfo,logs);
return entity;
}
@ -304,64 +362,117 @@ public class DataLogTools {
}
}
public static boolean enable(Class<?> klazz, @Valid List<Long> ids) {
public static <T>boolean enable(Class<T> klazz,@Valid List<Long> ids) {
return enable(klazz, ids, new DefaultDataOperationListener<T>());
}
public static <T>boolean enable(Class<T> klazz, @Valid List<Long> ids,DataOperationListener<T> listener) {
for (Long id : ids) {
enableById(klazz, id);
enableById(klazz, id,listener);
}
return true;
}
public static <T>T enableById(Class<T> klazz,Long id) {
LogTableInfo tabInfo = getAnnotation(klazz);
BaseMapper mapper = MybatisTools.getMapper(tabInfo.getEntityType());
Object entity = findNativeById(klazz, id);
if(entity==null) return null;
T old = BeanUtils.newInstance(klazz);
BeanUtil.copyProperties(entity,old,true);
List<DataChangeLog> logs = CollectionUtils.newArrayList();
BeanUtils.setFieldValue("valid", YesOrNoEnum.YES.getTextCode(), entity);
mapper.updateById(entity);
T dto = (T)tabInfo.toDto(entity);
DataChangeLog datalog = createLog(klazz,OperationType.UPDATE);
datalog.setEntityId(id);
buildFields(datalog,tabInfo,dto,tabInfo.toDto(old));
logs.add(datalog);
saveLogs(tabInfo,logs);
return dto;
return enableById(klazz, id, new DefaultDataOperationListener<T>());
}
public static boolean disable(Class<?> klazz, @Valid List<Long> ids) {
public static <T>T enableById(Class<T> klazz,Long id,DataOperationListener<T> listener) {
LogTableInfo tabInfo = getAnnotation(klazz);
BaseMapper mapper = MybatisTools.getMapper(tabInfo.getEntityType());
T old = findById(klazz, id);
if(old==null) return null;
DataOperationContent<T> content = null;
if(listener!=null) {
content = DataOperationContent.of(tabInfo,OperationType.ENABLE,old,old);
listener.before(content);
}
T entity = null;
try {
List<DataChangeLog> logs = CollectionUtils.newArrayList();
CommonCallUtils.enableBefore(tabInfo.getTableName(),id);
entity = findById(klazz, id);
content.setObj(entity);
DataChangeLog datalog = createLog(klazz,OperationType.ENABLE);
datalog.setEntityId(id);
buildFields(datalog,tabInfo,entity,old);
logs.add(datalog);
if(listener!=null) {
listener.after(content);
}
saveLogs(tabInfo,logs);
} catch (Exception e) {
if(listener!=null) {
content.setError(e);
listener.onError(content);
}
throw e;
}finally {
if(listener!=null) {
listener.onFinally(content);
}
}
return entity;
}
public static <T>boolean disable(Class<T> klazz,@Valid List<Long> ids) {
return disable(klazz, ids, new DefaultDataOperationListener<T>());
}
public static <T>boolean disable(Class<T> klazz, @Valid List<Long> ids,DataOperationListener<T> listener) {
for (Long id : ids) {
disableById(klazz, id);
disableById(klazz, id,listener);
}
return true;
}
public static <T>T disableById(Class<T> klazz,Long id) {
public static <T>T disableById(Class<T> klazz,Long id,DataOperationListener<T> listener) {
LogTableInfo tabInfo = getAnnotation(klazz);
BaseMapper mapper = MybatisTools.getMapper(tabInfo.getEntityType());
Object entity = findNativeById(klazz, id);
if(entity==null) return null;
T old = BeanUtils.newInstance(klazz);
BeanUtil.copyProperties(entity,old,true);
List<DataChangeLog> logs = CollectionUtils.newArrayList();
BeanUtils.setFieldValue("valid",YesOrNoEnum.NO.getTextCode(), entity);
mapper.updateById(entity);
T dto = (T)tabInfo.toDto(entity);
DataChangeLog datalog = createLog(klazz,OperationType.UPDATE);
datalog.setEntityId(id);
buildFields(datalog,tabInfo,dto,tabInfo.toDto(old));
logs.add(datalog);
saveLogs(tabInfo,logs);
return dto;
T old = findById(klazz, id);
if(old==null) return null;
DataOperationContent<T> content = null;
if(listener!=null) {
content = DataOperationContent.of(tabInfo,OperationType.DISABLE,old,old);
listener.before(content);
}
T entity = null;
try {
List<DataChangeLog> logs = CollectionUtils.newArrayList();
CommonCallUtils.enableBefore(tabInfo.getTableName(),id);
entity = findById(klazz, id);
content.setObj(entity);
DataChangeLog datalog = createLog(klazz,OperationType.DISABLE);
datalog.setEntityId(id);
buildFields(datalog,tabInfo,entity,old);
logs.add(datalog);
if(listener!=null) {
listener.after(content);
}
saveLogs(tabInfo,logs);
} catch (Exception e) {
if(listener!=null) {
content.setError(e);
listener.onError(content);
}
throw e;
}finally {
if(listener!=null) {
listener.onFinally(content);
}
}
return entity;
}
public static <T>T findById(Class<T> klazz,Serializable id){
public static <T>T findById(Class<?> klazz,Serializable id){
return findById(klazz,id,false);
}
public static <T>T findById(Class<T> klazz,Serializable id,boolean full){
public static <T>T findById(Class<?> klazz,Serializable id,boolean full){
LogTableInfo tabInfo = getAnnotation(klazz);
BaseMapper mapper = MybatisTools.getMapper(tabInfo.getEntityType());
Object old = mapper.selectById(id);
@ -755,34 +866,35 @@ public class DataLogTools {
public static void saveLogs(LogTableInfo tableInfo,List<DataChangeLog> logs) {
String tableName = parseLogTableName(tableInfo);
initTable(tableName);
String sql = parseSql(SQL_INSERT, tableName);
List<List<JdbcParam>> batchParams = CollectionUtils.newArrayList();
Map<String, List<DataChangeLog>> pidMap = logs.stream().collect(Collectors.groupingBy(DataChangeLog::getPid));
// String sql = parseSql(SQL_INSERT, tableName);
// List<List<JdbcParam>> batchParams = CollectionUtils.newArrayList();
// Map<String, List<DataChangeLog>> pidMap = logs.stream().collect(Collectors.groupingBy(DataChangeLog::getPid));
//
// for (DataChangeLog item : logs) {
// //没有改变的属性,且没有子表修改时跳过
// if(!item.hasFieldChanges() && !hasChild(item, pidMap)) {
// continue;
// }
//
// List<JdbcParam> params = CollectionUtils.newArrayList();
// params.add(JdbcParam.of(Types.VARCHAR,String.class, item.getId()));
// params.add(JdbcParam.of(Types.VARCHAR,String.class, item.getFlowId()));
// params.add(JdbcParam.of(Types.VARCHAR,String.class, item.getPid()));
// params.add(JdbcParam.of(Types.VARCHAR,String.class, item.getEntityClassName()));
// params.add(JdbcParam.of(Types.VARCHAR,String.class, item.getBusName()));
// params.add(JdbcParam.of(Types.BIGINT,long.class, item.getEntityId()));
// params.add(JdbcParam.of(Types.VARCHAR,String.class,JSON.toJSONString(item.getFieldChanges())));
// params.add(JdbcParam.of(Types.VARCHAR,String.class,item.getOperationType().name()));
// params.add(JdbcParam.of(Types.VARCHAR,long.class,item.getOperatorId()));
// params.add(JdbcParam.of(Types.VARCHAR,String.class,item.getOperatorName()));
// params.add(JdbcParam.of(Types.TIMESTAMP,Timestamp.class,item.getOperationTime()));
// params.add(JdbcParam.of(Types.VARCHAR,String.class,item.getOperationIp()));
// batchParams.add(params);
// }
// executeBatch(sql, batchParams);
for (DataChangeLog item : logs) {
//没有改变的属性,且没有子表修改时跳过
if(!item.hasFieldChanges() && !hasChild(item, pidMap)) {
continue;
}
List<JdbcParam> params = CollectionUtils.newArrayList();
params.add(JdbcParam.of(Types.VARCHAR,String.class, item.getId()));
params.add(JdbcParam.of(Types.VARCHAR,String.class, item.getFlowId()));
params.add(JdbcParam.of(Types.VARCHAR,String.class, item.getPid()));
params.add(JdbcParam.of(Types.VARCHAR,String.class, item.getEntityClassName()));
params.add(JdbcParam.of(Types.VARCHAR,String.class, item.getBusName()));
params.add(JdbcParam.of(Types.BIGINT,long.class, item.getEntityId()));
params.add(JdbcParam.of(Types.VARCHAR,String.class,JSON.toJSONString(item.getFieldChanges())));
params.add(JdbcParam.of(Types.VARCHAR,String.class,item.getOperationType().name()));
params.add(JdbcParam.of(Types.VARCHAR,long.class,item.getOperatorId()));
params.add(JdbcParam.of(Types.VARCHAR,String.class,item.getOperatorName()));
params.add(JdbcParam.of(Types.TIMESTAMP,Timestamp.class,item.getOperationTime()));
params.add(JdbcParam.of(Types.VARCHAR,String.class,item.getOperationIp()));
batchParams.add(params);
logDbService.insertDataLog(tableName,item);
}
executeBatch(sql, batchParams);
}
/**
@ -794,11 +906,8 @@ public class DataLogTools {
synchronized (lock) {
//如果没有日志表时,重新创建
if(!hasTable(tableName)) {
for (int i = 0; i < SQL_CREATE_TABLE.length; i++) {
execute(parseSql(SQL_CREATE_TABLE[i], tableName));
}
//将表名进行记录
executeUpdate(SQL_INST_TABLE,ListUtils.ofArray(JdbcParam.ofString(tableName)));
logDbService.createDatalogTable(tableName);
logDbService.addTableInfo(tableName);
logTables.add(tableName);
log.info("新增数据日志表:{}",tableName);
return true;
@ -808,18 +917,14 @@ public class DataLogTools {
}
private static String parseSql(String sql,String tableName) {
return sql.replaceAll(SQL_PLACEHOLDER,tableName);
}
private static boolean hasTable(String tableName) {
synchronized (lock) {
if(logTables==null) {
List<String> tabs = queryListPrimitive(SQL_LOG_TABLE_LIST,String.class, null);
List<String> tabs = logDbService.getTableList();
logTables = new HashSet<String>(tabs);
}
if(logTables.contains(tableName)) return true;
Long count = queryPrimitive(SQL_HAS_TABLE,long.class,Lists.newArrayList(JdbcParam.ofString(tableName)));
Long count = logDbService.getCountByTableName(tableName);
boolean flag = count!=null && count > 0;
if(flag) {
logTables.add(tableName);
@ -851,40 +956,41 @@ public class DataLogTools {
if(initTable(tableName)) {
return null;
}
List<DataChangeLog> result = CollectionUtils.newArrayList();
String sql = parseSql(SQL_LIST, tableName);
JdbcContext context = null;
try {
context = new JdbcContext(true,true);
context.init(sql);
context.setParams(ListUtils.ofArray(JdbcParam.ofLong(dataId)));
ResultSet set = context.executeQuery();
Map<String, String> names = ResultSetUtils.getMapColumns(set);
while (set.next()) {
DataChangeLog log = new DataChangeLog();
log.setId(set.getString("id"));
log.setFlowId(set.getString("flow_id"));
log.setPid(set.getString("pid"));
log.setEntityClassName(set.getString("entity_class_name"));
log.setBusName(set.getString("bus_name"));
log.setEntityId(set.getLong("entity_id"));
log.setOperationType(OperationType.valueOf(set.getString("operation_type")));
log.setOperationIp(set.getString("operation_ip"));
log.setOperatorId(set.getLong("operator_id"));
log.setOperatorName(set.getString("operator_name"));
log.setOperationTime(ResultSetUtils.getObject(set,"operation_time",LocalDateTime.class));
String json = set.getString("field_changes");
if(StringUtils.isNotEmpty(json)) {
log.setFieldChanges(JSON.parseArray(json).toJavaList(FieldChange.class));
}
result.add(log);
}
return toVo(result);
} catch (SQLException e) {
throw context.createException(e);
} finally {
context.end();
}
List<DataChangeLog> logs = logDbService.selectByDataId(tableName,dataId);
return toVo(logs);
// String sql = parseSql(SQL_LIST, tableName);
// JdbcContext context = null;
// try {
// context = new JdbcContext(true,true);
// context.init(sql);
// context.setParams(ListUtils.ofArray(JdbcParam.ofLong(dataId)));
// ResultSet set = context.executeQuery();
// Map<String, String> names = ResultSetUtils.getMapColumns(set);
// while (set.next()) {
// DataChangeLog log = new DataChangeLog();
// log.setId(set.getString("id"));
// log.setFlowId(set.getString("flow_id"));
// log.setPid(set.getString("pid"));
// log.setEntityClassName(set.getString("entity_class_name"));
// log.setBusName(set.getString("bus_name"));
// log.setEntityId(set.getLong("entity_id"));
// log.setOperationType(OperationType.valueOf(set.getString("operation_type")));
// log.setOperationIp(set.getString("operation_ip"));
// log.setOperatorId(set.getLong("operator_id"));
// log.setOperatorName(set.getString("operator_name"));
// log.setOperationTime(ResultSetUtils.getObject(set,"operation_time",LocalDateTime.class));
// String json = set.getString("field_changes");
// if(StringUtils.isNotEmpty(json)) {
// log.setFieldChanges(JSON.parseArray(json).toJavaList(FieldChange.class));
// }
// result.add(log);
// }
// return toVo(result);
// } catch (SQLException e) {
// throw context.createException(e);
// } finally {
// context.end();
// }
}
private static List<DataChangeLogVo> toVo(List<DataChangeLog> list){

View File

@ -0,0 +1,75 @@
package com.xjrsoft.module.common.db.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.xjrsoft.module.common.db.service.CommonCallService;
/**
* @author 张福财
* @date 2025年10月30日 上午9:58:16
* @Description: TODO(这里用一句话描述这个类的作用)
*/
@Component
public class CommonCallUtils {
private static CommonCallService callService;
@Autowired
public void setCallService(CommonCallService callService) {
CommonCallUtils.callService = callService;
}
/**
* @Description:
一、新增、修改记录时,在同一个事务里执行如下过程:
1、更新表
2、调用数据库函数pc_表名.f_save(表主键)
3、函数返回非空、或者数据库异常时rollback函数返回空时commit
* @param table 表名
* @param id 表主键
* @return String 返回类型 函数返回非空、或者数据库异常时rollback函数返回空时commit
*/
public static String saveAfter(String table,long id) {
return callService.saveAfter(table, id);
}
/**
* @Description:
二、删除记录时,在同一个事务里执行如下过程:
1、调用数据库函数pc_表名.f_delete(表主键)
2、函数返回非空、或者数据库异常时rollback函数返回空时commit
* @return
* @return String 返回类型
*/
public static String deleteBefore(String table,Long id) {
return callService.deleteBefore(table, id);
}
/**
* @Description:
二、停用时,在同一个事务里执行如下过程:
1、调用数据库函数pc_表名.f_off(表主键)
2、函数返回非空、或者数据库异常时rollback函数返回空时commit
* @return
* @return String 返回类型
*/
public static String disableBefore(String table,Long id) {
return callService.disableBefore(table, id);
}
/**
* @Description:
1、调用数据库函数pc_表名.f_on(表主键)
2、函数返回非空、或者数据库异常时rollback函数返回空时commit
* @return
* @return String 返回类型
*/
public static String enableBefore(String table,Long id) {
return callService.enableBefore(table, id);
}
}

View File

@ -77,7 +77,7 @@ public class DataChangeLog implements Serializable{
/**
* 操作类型INSERT-新增UPDATE-修改DELETE-删除
*/
@ApiModelProperty("操作类型INSERT-新增UPDATE-修改DELETE-删除")
@ApiModelProperty("操作类型INSERT-新增UPDATE-修改DELETE-删除 ENABLE-启用 DISABLE-禁用")
private OperationType operationType;
/**

View File

@ -0,0 +1,80 @@
package com.xjrsoft.module.datalog.mapper;
import java.util.List;
import org.apache.ibatis.annotations.*;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xjrsoft.module.datalog.entity.DataChangeLog;
/**
* @author 张福财
* @date 2025年10月30日 下午2:17:47
* @Description: 数据日志操作
*/
@DS("datalog") //"data_log_sit"
@Mapper
public interface DatalogMapper extends BaseMapper<DataChangeLog> {
public static final String SQL_INSERT = "INSERT INTO \"data_log_sit\".${TBL_NAME} (\r\n" +
" id, flow_id, pid, entity_class_name, bus_name, \r\n" +
" entity_id, field_changes, operation_type, operator_id, \r\n" +
" operator_name, operation_time, operation_ip\r\n" +
") VALUES (#{data.id},#{data.flowId},#{data.pid},#{data.entityClassName},#{data.busName},#{data.entityId},"
+ "#{data.fieldChanges},#{data.operationType},#{data.operatorId},#{data.operatorName},#{data.operationTime},#{data.operationIp});";
public static final String SQL_CREATE_TABLE =
"CREATE TABLE \"data_log_sit\".${TBL_NAME} (\r\n" +
" id VARCHAR(32) NOT NULL,\r\n" +
" flow_id VARCHAR(32),\r\n" +
" pid VARCHAR(32) DEFAULT '#',\r\n" +
" entity_class_name VARCHAR(255),\r\n" +
" bus_name VARCHAR(100),\r\n" +
" entity_id BIGINT,\r\n" +
" field_changes TEXT,\r\n" +
" operation_type VARCHAR(20),\r\n" +
" operator_id BIGINT,\r\n" +
" operator_name VARCHAR(100),\r\n" +
" operation_time TIMESTAMPTZ,\r\n" +
" operation_ip VARCHAR(50),\r\n" +
" PRIMARY KEY (id));\r\n" +
"COMMENT ON COLUMN \"data_log_sit\".${TBL_NAME}.\"flow_id\" IS '流水id';\r\n" +
"COMMENT ON COLUMN \"data_log_sit\".${TBL_NAME}.\"pid\" IS '父表ID,根节点#';\r\n" +
"COMMENT ON COLUMN \"data_log_sit\".${TBL_NAME}.\"entity_class_name\" IS '实体类名称(全类名)';\r\n" +
"COMMENT ON COLUMN \"data_log_sit\".${TBL_NAME}.\"bus_name\" IS '业务名称';\r\n" +
"COMMENT ON COLUMN \"data_log_sit\".${TBL_NAME}.\"entity_id\" IS '操作的实体ID主键值';\r\n" +
"COMMENT ON COLUMN \"data_log_sit\".${TBL_NAME}.\"field_changes\" IS '属性值记录JSON格式存储字段变更详情';\r\n" +
"COMMENT ON COLUMN \"data_log_sit\".${TBL_NAME}.\"operation_type\" IS '操作类型INSERT-新增UPDATE-修改DELETE-删除';\r\n" +
"COMMENT ON COLUMN \"data_log_sit\".${TBL_NAME}.\"operator_id\" IS '操作人ID';\r\n" +
"COMMENT ON COLUMN \"data_log_sit\".${TBL_NAME}.\"operator_name\" IS '操作人姓名';\r\n" +
"COMMENT ON COLUMN \"data_log_sit\".${TBL_NAME}.\"operation_time\" IS '操作时间';\r\n" +
"COMMENT ON COLUMN \"data_log_sit\".${TBL_NAME}.\"operation_ip\" IS '操作IP地址';\r\n" +
"CREATE INDEX idx_${TBL_NAME}_enityt_id ON \"data_log_sit\".${TBL_NAME} (entity_id);";
@DS("datalog")
@Select("select count(*) from \"data_log_sit\".log_table_info where tbl_name = #{tableName}")
long getCountByTableName(@Param("tableName") String name);
@DS("datalog")
@Insert("INSERT INTO \"data_log_sit\".log_table_info (tbl_name) VALUES (#{tableName});")
void addTableInfo(@Param("tableName") String tableName);
@DS("datalog")
@Select("select tbl_name FROM \"data_log_sit\".log_table_info")
List<String> getTableList();
@DS("datalog")
@Insert(SQL_INSERT)
void insertDataLog(@Param("TBL_NAME") String tableName,@Param("data") DataChangeLog data);
@DS("datalog")
@Update(SQL_CREATE_TABLE)
void createDatalogTable(@Param("TBL_NAME") String tableName);
@DS("datalog")
@Select("SELECT * FROM \"data_log_sit\".${TBL_NAME} WHERE entity_id = #{id} ORDER BY operation_time DESC, flow_id DESC")
List<DataChangeLog> selectByDataId(@Param("TBL_NAME") String tableName,@Param("id")Long id);
}

View File

@ -4,6 +4,7 @@ import java.util.List;
import javax.validation.Valid;
import org.apache.seata.spring.annotation.GlobalTransactional;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -16,12 +17,14 @@ import com.xjrsoft.module.datalog.vo.DataChangeLogVo;
public class DatalogServiceImpl implements DatalogService{
@Transactional(rollbackFor = Exception.class)
@GlobalTransactional(rollbackFor = Exception.class) // Seata全局事务注解
@Override
public <T> T insert(T entity) {
return DataLogTools.insert(entity);
}
@Transactional(rollbackFor = Exception.class)
@GlobalTransactional(rollbackFor = Exception.class) // Seata全局事务注解
@Override
public <T> List<T> insertBatch(List<T> entitys) {
List<T> res = Lists.newArrayList();
@ -32,6 +35,7 @@ public class DatalogServiceImpl implements DatalogService{
}
@Transactional(rollbackFor = Exception.class)
@GlobalTransactional(rollbackFor = Exception.class) // Seata全局事务注解
@Override
public <T> boolean updateById(T entity) {
DataLogTools.update(entity);
@ -39,34 +43,42 @@ public class DatalogServiceImpl implements DatalogService{
}
@Transactional(rollbackFor = Exception.class)
@GlobalTransactional(rollbackFor = Exception.class) // Seata全局事务注解
@Override
public <T> T delete(T entity) {
return DataLogTools.delete(entity);
}
@Transactional(rollbackFor = Exception.class)
@GlobalTransactional(rollbackFor = Exception.class) // Seata全局事务注解
@Override
public <T> T deleteById(Class<T> klazz, long id) {
return DataLogTools.deleteById(klazz, id);
}
@Transactional(rollbackFor = Exception.class)
@GlobalTransactional(rollbackFor = Exception.class) // Seata全局事务注解
@Override
public <T> boolean deleteByIds(Class<T> klazz, @Valid List<Long> ids) {
return DataLogTools.deleteByIds(klazz, ids);
}
@Transactional(readOnly = true)
@GlobalTransactional(rollbackFor = Exception.class) // Seata全局事务注解
@Override
public <T> List<DataChangeLogVo> findLogsByEntityId(Class<T> klazz, long dataId) {
return DataLogTools.findLogsByEntityId(klazz, dataId);
}
@Transactional(rollbackFor = Exception.class)
@GlobalTransactional(rollbackFor = Exception.class) // Seata全局事务注解
@Override
public <T> boolean enable(Class<T> klazz, @Valid List<Long> ids) {
return DataLogTools.enable(klazz, ids);
}
@Transactional(rollbackFor = Exception.class)
@GlobalTransactional(rollbackFor = Exception.class) // Seata全局事务注解
@Override
public <T> boolean disable(Class<T> klazz, @Valid List<Long> ids) {
return DataLogTools.disable(klazz, ids);

View File

@ -102,7 +102,7 @@ public class CategoryController {
@ApiOperation(value = "启用LngBCategory")
@SaCheckPermission("category:enable")
public R enable(@Valid @RequestBody List<Long> ids){
return R.ok(categoryService.enable(ids));
return R.ok(dataService.enable(UpdateLngBCategoryDto.class,ids));
}
@PostMapping("/disable")

View File

@ -121,7 +121,6 @@ public class CountryRegionController {
@SaCheckPermission("countryRegion:delete")
public R delete(@Valid @RequestBody List<Long> ids){
return R.ok(dataService.deleteByIds(UpdateLngBRegionDto.class, ids));
}

View File

@ -1,5 +1,6 @@
package com.xjrsoft;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@ -14,6 +15,7 @@ import com.xjrsoft.common.annotation.UniqueNameGenerator;
* 主数据服务---开发使用
* 使用时集成到其它模块使用,不作为单独的服务
*/
@MapperScan("com.xjrsoft.module.*.mapper")
@SpringBootApplication
@EnableAspectJAutoProxy(exposeProxy = true)
@ComponentScan(nameGenerator = UniqueNameGenerator.class)