26 changed files with 2397 additions and 156 deletions
@ -0,0 +1,3 @@ |
|||
{ |
|||
"git.ignoreLimitWarning": true |
|||
} |
|||
@ -0,0 +1,99 @@ |
|||
package com.ruoyi.web.controller; |
|||
|
|||
import java.util.List; |
|||
|
|||
import org.springframework.security.access.prepost.PreAuthorize; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.web.bind.annotation.GetMapping; |
|||
import org.springframework.web.bind.annotation.PostMapping; |
|||
import org.springframework.web.bind.annotation.RequestMapping; |
|||
import org.springframework.web.bind.annotation.RequestParam; |
|||
import org.springframework.web.bind.annotation.RestController; |
|||
|
|||
import com.ruoyi.common.core.controller.BaseController; |
|||
import com.ruoyi.common.core.domain.AjaxResult; |
|||
import com.ruoyi.common.core.page.TableDataInfo; |
|||
import com.ruoyi.system.domain.ObjectOperationLog; |
|||
import com.ruoyi.system.service.IObjectOperationLogService; |
|||
|
|||
/** |
|||
* 对象级操作日志(航线/航点/平台):分页查询、回滚 |
|||
*/ |
|||
@RestController |
|||
@RequestMapping("/system/object-log") |
|||
public class ObjectOperationLogController extends BaseController { |
|||
|
|||
@Autowired |
|||
private IObjectOperationLogService objectOperationLogService; |
|||
|
|||
/** |
|||
* 分页查询对象级操作日志(按房间) |
|||
*/ |
|||
@PreAuthorize("@ss.hasPermi('system:routes:list')") |
|||
@GetMapping("/list") |
|||
public TableDataInfo list( |
|||
@RequestParam(required = false) Long roomId, |
|||
@RequestParam(required = false) String operatorName, |
|||
@RequestParam(required = false) Integer operationType, |
|||
@RequestParam(required = false) String objectType) { |
|||
startPage(); |
|||
ObjectOperationLog query = new ObjectOperationLog(); |
|||
query.setRoomId(roomId); |
|||
query.setOperatorName(operatorName); |
|||
query.setOperationType(operationType); |
|||
query.setObjectType(objectType); |
|||
List<ObjectOperationLog> list = objectOperationLogService.selectPage(query); |
|||
return getDataTable(list); |
|||
} |
|||
|
|||
/** |
|||
* 回滚到指定操作(数据库 + Redis 同步) |
|||
*/ |
|||
@PreAuthorize("@ss.hasPermi('system:routes:edit')") |
|||
@PostMapping("/rollback") |
|||
public AjaxResult rollback(@RequestParam Long id) { |
|||
ObjectOperationLog origin = objectOperationLogService.selectById(id); |
|||
if (origin == null) { |
|||
return error("回滚失败:原始日志不存在"); |
|||
} |
|||
boolean ok = objectOperationLogService.rollback(id); |
|||
if (!ok) { |
|||
return error("回滚失败:无快照或对象类型不支持"); |
|||
} |
|||
|
|||
// 记录一条“回滚”操作日志,便于审计
|
|||
ObjectOperationLog rollbackLog = new ObjectOperationLog(); |
|||
rollbackLog.setRoomId(origin.getRoomId()); |
|||
rollbackLog.setOperatorId(getUserId()); |
|||
rollbackLog.setOperatorName(getUsername()); |
|||
rollbackLog.setOperationType(ObjectOperationLog.TYPE_ROLLBACK); |
|||
rollbackLog.setObjectType(origin.getObjectType()); |
|||
rollbackLog.setObjectId(origin.getObjectId()); |
|||
rollbackLog.setObjectName(origin.getObjectName()); |
|||
rollbackLog.setDetail("回滚操作:基于日志ID=" + origin.getId() + " 的" + toOpText(origin.getOperationType())); |
|||
// 简要记录被回滚日志的快照,方便排查(可选)
|
|||
rollbackLog.setSnapshotBefore(origin.getSnapshotBefore()); |
|||
rollbackLog.setSnapshotAfter(origin.getSnapshotAfter()); |
|||
objectOperationLogService.saveLog(rollbackLog); |
|||
|
|||
return success(); |
|||
} |
|||
|
|||
private String toOpText(Integer type) { |
|||
if (type == null) return ""; |
|||
switch (type) { |
|||
case ObjectOperationLog.TYPE_INSERT: |
|||
return "新增"; |
|||
case ObjectOperationLog.TYPE_UPDATE: |
|||
return "修改"; |
|||
case ObjectOperationLog.TYPE_DELETE: |
|||
return "删除"; |
|||
case ObjectOperationLog.TYPE_SELECT: |
|||
return "选择"; |
|||
case ObjectOperationLog.TYPE_ROLLBACK: |
|||
return "回滚"; |
|||
default: |
|||
return ""; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,88 @@ |
|||
package com.ruoyi.system.domain; |
|||
|
|||
import java.util.Date; |
|||
import com.fasterxml.jackson.annotation.JsonFormat; |
|||
import com.ruoyi.common.core.domain.BaseEntity; |
|||
|
|||
/** |
|||
* 对象级操作日志(航线、航点、平台等,支持回滚) |
|||
* |
|||
* @author ruoyi |
|||
*/ |
|||
public class ObjectOperationLog extends BaseEntity { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
/** 操作类型:新增 */ |
|||
public static final int TYPE_INSERT = 1; |
|||
/** 操作类型:修改 */ |
|||
public static final int TYPE_UPDATE = 2; |
|||
/** 操作类型:删除 */ |
|||
public static final int TYPE_DELETE = 3; |
|||
/** 操作类型:选择 */ |
|||
public static final int TYPE_SELECT = 4; |
|||
/** 操作类型:回滚 */ |
|||
public static final int TYPE_ROLLBACK = 5; |
|||
|
|||
/** 对象类型:航线 */ |
|||
public static final String OBJ_ROUTE = "route"; |
|||
/** 对象类型:航点 */ |
|||
public static final String OBJ_WAYPOINT = "waypoint"; |
|||
/** 对象类型:平台 */ |
|||
public static final String OBJ_PLATFORM = "platform"; |
|||
|
|||
/** 主键 */ |
|||
private Long id; |
|||
/** 房间ID */ |
|||
private Long roomId; |
|||
/** 操作人用户ID */ |
|||
private Long operatorId; |
|||
/** 操作人姓名 */ |
|||
private String operatorName; |
|||
/** 操作类型 1新增 2修改 3删除 4选择 */ |
|||
private Integer operationType; |
|||
/** 操作对象类型 route/waypoint/platform */ |
|||
private String objectType; |
|||
/** 业务对象ID */ |
|||
private String objectId; |
|||
/** 对象显示名 */ |
|||
private String objectName; |
|||
/** 详细操作描述 */ |
|||
private String detail; |
|||
/** 操作前快照JSON,用于回滚 */ |
|||
private String snapshotBefore; |
|||
/** 操作后快照JSON */ |
|||
private String snapshotAfter; |
|||
/** 相对时间 K+00:00:00 */ |
|||
private String kTime; |
|||
/** 创建时间 */ |
|||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
|||
private Date createdAt; |
|||
|
|||
public Long getId() { return id; } |
|||
public void setId(Long id) { this.id = id; } |
|||
public Long getRoomId() { return roomId; } |
|||
public void setRoomId(Long roomId) { this.roomId = roomId; } |
|||
public Long getOperatorId() { return operatorId; } |
|||
public void setOperatorId(Long operatorId) { this.operatorId = operatorId; } |
|||
public String getOperatorName() { return operatorName; } |
|||
public void setOperatorName(String operatorName) { this.operatorName = operatorName; } |
|||
public Integer getOperationType() { return operationType; } |
|||
public void setOperationType(Integer operationType) { this.operationType = operationType; } |
|||
public String getObjectType() { return objectType; } |
|||
public void setObjectType(String objectType) { this.objectType = objectType; } |
|||
public String getObjectId() { return objectId; } |
|||
public void setObjectId(String objectId) { this.objectId = objectId; } |
|||
public String getObjectName() { return objectName; } |
|||
public void setObjectName(String objectName) { this.objectName = objectName; } |
|||
public String getDetail() { return detail; } |
|||
public void setDetail(String detail) { this.detail = detail; } |
|||
public String getSnapshotBefore() { return snapshotBefore; } |
|||
public void setSnapshotBefore(String snapshotBefore) { this.snapshotBefore = snapshotBefore; } |
|||
public String getSnapshotAfter() { return snapshotAfter; } |
|||
public void setSnapshotAfter(String snapshotAfter) { this.snapshotAfter = snapshotAfter; } |
|||
public String getkTime() { return kTime; } |
|||
public void setkTime(String kTime) { this.kTime = kTime; } |
|||
public Date getCreatedAt() { return createdAt; } |
|||
public void setCreatedAt(Date createdAt) { this.createdAt = createdAt; } |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
package com.ruoyi.system.mapper; |
|||
|
|||
import java.util.List; |
|||
import com.ruoyi.system.domain.ObjectOperationLog; |
|||
|
|||
/** |
|||
* 对象级操作日志 Mapper |
|||
* |
|||
* @author ruoyi |
|||
*/ |
|||
public interface ObjectOperationLogMapper { |
|||
|
|||
int insert(ObjectOperationLog log); |
|||
|
|||
ObjectOperationLog selectById(Long id); |
|||
|
|||
List<ObjectOperationLog> selectPage(ObjectOperationLog query); |
|||
|
|||
int deleteById(Long id); |
|||
|
|||
/** 删除某条之后的所有日志(回滚时清理后续日志,可选策略) */ |
|||
int deleteByRoomIdAfterId(Long roomId, Long afterId); |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
package com.ruoyi.system.service; |
|||
|
|||
import java.util.List; |
|||
import com.ruoyi.system.domain.ObjectOperationLog; |
|||
|
|||
/** |
|||
* 对象级操作日志 服务接口 |
|||
* |
|||
* @author ruoyi |
|||
*/ |
|||
public interface IObjectOperationLogService { |
|||
|
|||
/** |
|||
* 记录一条操作日志(同时写库并推送到 Redis 缓存) |
|||
*/ |
|||
void saveLog(ObjectOperationLog log); |
|||
|
|||
/** |
|||
* 分页查询(优先从 Redis 取第一页近期数据以减轻数据库压力) |
|||
*/ |
|||
List<ObjectOperationLog> selectPage(ObjectOperationLog query); |
|||
|
|||
/** |
|||
* 根据ID查询(回滚时需要快照) |
|||
*/ |
|||
ObjectOperationLog selectById(Long id); |
|||
|
|||
/** |
|||
* 回滚到指定日志:用 snapshot_before 恢复数据,并同步 Redis 缓存 |
|||
* @return 是否成功 |
|||
*/ |
|||
boolean rollback(Long logId); |
|||
} |
|||
@ -0,0 +1,219 @@ |
|||
package com.ruoyi.system.service.impl; |
|||
|
|||
import java.util.List; |
|||
import java.util.concurrent.TimeUnit; |
|||
|
|||
import com.ruoyi.system.domain.PlatformLib; |
|||
import com.ruoyi.system.domain.RouteWaypoints; |
|||
import com.ruoyi.system.domain.Routes; |
|||
import com.ruoyi.system.mapper.PlatformLibMapper; |
|||
import com.ruoyi.system.mapper.RouteWaypointsMapper; |
|||
import com.ruoyi.system.mapper.RoutesMapper; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.beans.factory.annotation.Qualifier; |
|||
import org.springframework.data.redis.core.RedisTemplate; |
|||
import org.springframework.stereotype.Service; |
|||
import org.springframework.transaction.annotation.Transactional; |
|||
|
|||
import com.alibaba.fastjson2.JSON; |
|||
import com.ruoyi.system.domain.ObjectOperationLog; |
|||
import com.ruoyi.system.mapper.ObjectOperationLogMapper; |
|||
import com.ruoyi.system.service.IObjectOperationLogService; |
|||
import com.ruoyi.system.service.IRouteWaypointsService; |
|||
|
|||
/** |
|||
* 对象级操作日志 服务实现:写库 + Redis 缓存,回滚时同步 DB 与 Redis |
|||
*/ |
|||
@Service |
|||
public class ObjectOperationLogServiceImpl implements IObjectOperationLogService { |
|||
|
|||
private static final String REDIS_KEY_PREFIX = "object_log:room:"; |
|||
private static final int REDIS_LIST_MAX = 500; |
|||
private static final long REDIS_EXPIRE_HOURS = 24; |
|||
|
|||
@Autowired |
|||
private ObjectOperationLogMapper objectOperationLogMapper; |
|||
|
|||
@Autowired |
|||
@Qualifier("stringObjectRedisTemplate") |
|||
private RedisTemplate<String, Object> redisTemplate; |
|||
|
|||
@Autowired |
|||
private RoutesMapper routesMapper; |
|||
|
|||
@Autowired |
|||
private RouteWaypointsMapper routeWaypointsMapper; |
|||
|
|||
@Autowired |
|||
private IRouteWaypointsService routeWaypointsService; |
|||
|
|||
@Autowired |
|||
private PlatformLibMapper platformLibMapper; |
|||
|
|||
@Override |
|||
@Transactional(rollbackFor = Exception.class) |
|||
public void saveLog(ObjectOperationLog log) { |
|||
objectOperationLogMapper.insert(log); |
|||
if (log.getRoomId() != null) { |
|||
String key = REDIS_KEY_PREFIX + log.getRoomId(); |
|||
redisTemplate.opsForList().rightPush(key, log); |
|||
redisTemplate.opsForList().trim(key, -REDIS_LIST_MAX, -1); |
|||
redisTemplate.expire(key, REDIS_EXPIRE_HOURS, TimeUnit.HOURS); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public List<ObjectOperationLog> selectPage(ObjectOperationLog query) { |
|||
return objectOperationLogMapper.selectPage(query); |
|||
} |
|||
|
|||
@Override |
|||
public ObjectOperationLog selectById(Long id) { |
|||
return objectOperationLogMapper.selectById(id); |
|||
} |
|||
|
|||
@Override |
|||
@Transactional(rollbackFor = Exception.class) |
|||
public boolean rollback(Long logId) { |
|||
ObjectOperationLog log = objectOperationLogMapper.selectById(logId); |
|||
if (log == null) return false; |
|||
Integer opType = log.getOperationType(); |
|||
if (opType == null) return false; |
|||
if (ObjectOperationLog.TYPE_INSERT == opType) { |
|||
return rollbackInsert(log); |
|||
} |
|||
if (ObjectOperationLog.TYPE_DELETE == opType) { |
|||
return rollbackDelete(log); |
|||
} |
|||
if (ObjectOperationLog.TYPE_UPDATE == opType && log.getSnapshotBefore() != null && !log.getSnapshotBefore().isEmpty()) { |
|||
return rollbackUpdate(log); |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
private boolean rollbackUpdate(ObjectOperationLog log) { |
|||
String objectType = log.getObjectType(); |
|||
if (ObjectOperationLog.OBJ_ROUTE.equals(objectType)) return rollbackRouteUpdate(log); |
|||
if (ObjectOperationLog.OBJ_WAYPOINT.equals(objectType)) return rollbackWaypointUpdate(log); |
|||
if (ObjectOperationLog.OBJ_PLATFORM.equals(objectType)) { |
|||
return rollbackPlatformUpdate(log); |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
private boolean rollbackDelete(ObjectOperationLog log) { |
|||
if (log.getSnapshotBefore() == null || log.getSnapshotBefore().isEmpty()) return false; |
|||
String objectType = log.getObjectType(); |
|||
if (ObjectOperationLog.OBJ_ROUTE.equals(objectType)) return rollbackRouteReinsert(log); |
|||
if (ObjectOperationLog.OBJ_WAYPOINT.equals(objectType)) return rollbackWaypointReinsert(log); |
|||
if (ObjectOperationLog.OBJ_PLATFORM.equals(objectType)) { |
|||
return rollbackPlatformReinsert(log); |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
private boolean rollbackInsert(ObjectOperationLog log) { |
|||
if (log.getSnapshotAfter() == null || log.getSnapshotAfter().isEmpty()) return false; |
|||
String objectType = log.getObjectType(); |
|||
if (ObjectOperationLog.OBJ_ROUTE.equals(objectType)) { |
|||
Routes r = JSON.parseObject(log.getSnapshotAfter(), Routes.class); |
|||
if (r != null && r.getId() != null) { |
|||
routeWaypointsService.deleteRouteWaypointsByRouteId(r.getId()); |
|||
routesMapper.deleteRoutesById(r.getId()); |
|||
invalidateRoomCache(log.getRoomId()); |
|||
return true; |
|||
} |
|||
} |
|||
if (ObjectOperationLog.OBJ_WAYPOINT.equals(objectType)) { |
|||
RouteWaypoints wp = JSON.parseObject(log.getSnapshotAfter(), RouteWaypoints.class); |
|||
if (wp != null && wp.getId() != null) { |
|||
routeWaypointsMapper.deleteRouteWaypointsById(wp.getId()); |
|||
invalidateRoomCache(log.getRoomId()); |
|||
return true; |
|||
} |
|||
} |
|||
if (ObjectOperationLog.OBJ_PLATFORM.equals(objectType)) { |
|||
PlatformLib lib = JSON.parseObject(log.getSnapshotAfter(), PlatformLib.class); |
|||
if (lib != null && lib.getId() != null) { |
|||
platformLibMapper.deletePlatformLibById(lib.getId()); |
|||
invalidateRoomCache(log.getRoomId()); |
|||
return true; |
|||
} |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
private boolean rollbackRouteUpdate(ObjectOperationLog log) { |
|||
Routes before = JSON.parseObject(log.getSnapshotBefore(), Routes.class); |
|||
if (before == null || before.getId() == null) return false; |
|||
routeWaypointsService.deleteRouteWaypointsByRouteId(before.getId()); |
|||
routesMapper.updateRoutes(before); |
|||
if (before.getWaypoints() != null && !before.getWaypoints().isEmpty()) { |
|||
for (RouteWaypoints wp : before.getWaypoints()) { |
|||
wp.setRouteId(before.getId()); |
|||
routeWaypointsMapper.insertRouteWaypoints(wp); |
|||
} |
|||
} |
|||
invalidateRoomCache(log.getRoomId()); |
|||
return true; |
|||
} |
|||
|
|||
private boolean rollbackWaypointUpdate(ObjectOperationLog log) { |
|||
RouteWaypoints before = JSON.parseObject(log.getSnapshotBefore(), RouteWaypoints.class); |
|||
if (before == null || before.getId() == null) return false; |
|||
routeWaypointsMapper.updateRouteWaypoints(before); |
|||
invalidateRoomCache(log.getRoomId()); |
|||
return true; |
|||
} |
|||
|
|||
private boolean rollbackRouteReinsert(ObjectOperationLog log) { |
|||
Routes before = JSON.parseObject(log.getSnapshotBefore(), Routes.class); |
|||
if (before == null) return false; |
|||
before.setId(null); |
|||
routesMapper.insertRoutes(before); |
|||
if (before.getWaypoints() != null && !before.getWaypoints().isEmpty()) { |
|||
for (RouteWaypoints wp : before.getWaypoints()) { |
|||
wp.setId(null); |
|||
wp.setRouteId(before.getId()); |
|||
routeWaypointsMapper.insertRouteWaypoints(wp); |
|||
} |
|||
} |
|||
invalidateRoomCache(log.getRoomId()); |
|||
return true; |
|||
} |
|||
|
|||
private boolean rollbackWaypointReinsert(ObjectOperationLog log) { |
|||
RouteWaypoints before = JSON.parseObject(log.getSnapshotBefore(), RouteWaypoints.class); |
|||
if (before == null) return false; |
|||
before.setId(null); |
|||
routeWaypointsMapper.insertRouteWaypoints(before); |
|||
invalidateRoomCache(log.getRoomId()); |
|||
return true; |
|||
} |
|||
|
|||
private boolean rollbackPlatformUpdate(ObjectOperationLog log) { |
|||
PlatformLib before = JSON.parseObject(log.getSnapshotBefore(), PlatformLib.class); |
|||
if (before == null || before.getId() == null) return false; |
|||
platformLibMapper.updatePlatformLib(before); |
|||
invalidateRoomCache(log.getRoomId()); |
|||
return true; |
|||
} |
|||
|
|||
private boolean rollbackPlatformReinsert(ObjectOperationLog log) { |
|||
PlatformLib before = JSON.parseObject(log.getSnapshotBefore(), PlatformLib.class); |
|||
if (before == null || before.getId() == null) return false; |
|||
// 如果该 ID 已经存在(例如之前已手动恢复过),则视为已回滚成功,避免主键冲突
|
|||
PlatformLib existed = platformLibMapper.selectPlatformLibById(before.getId()); |
|||
if (existed == null) { |
|||
platformLibMapper.insertPlatformLibWithId(before); |
|||
} |
|||
invalidateRoomCache(log.getRoomId()); |
|||
return true; |
|||
} |
|||
|
|||
private void invalidateRoomCache(Long roomId) { |
|||
if (roomId == null) return; |
|||
String key = REDIS_KEY_PREFIX + roomId; |
|||
redisTemplate.delete(key); |
|||
} |
|||
} |
|||
@ -0,0 +1,56 @@ |
|||
<?xml version="1.0" encoding="UTF-8" ?> |
|||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
|||
<mapper namespace="com.ruoyi.system.mapper.ObjectOperationLogMapper"> |
|||
|
|||
<resultMap type="ObjectOperationLog" id="ObjectOperationLogResult"> |
|||
<id property="id" column="id" /> |
|||
<result property="roomId" column="room_id" /> |
|||
<result property="operatorId" column="operator_id" /> |
|||
<result property="operatorName" column="operator_name" /> |
|||
<result property="operationType" column="operation_type"/> |
|||
<result property="objectType" column="object_type" /> |
|||
<result property="objectId" column="object_id" /> |
|||
<result property="objectName" column="object_name" /> |
|||
<result property="detail" column="detail" /> |
|||
<result property="snapshotBefore" column="snapshot_before"/> |
|||
<result property="snapshotAfter" column="snapshot_after" /> |
|||
<result property="kTime" column="k_time" /> |
|||
<result property="createdAt" column="created_at" /> |
|||
</resultMap> |
|||
|
|||
<sql id="selectVo"> |
|||
select id, room_id, operator_id, operator_name, operation_type, object_type, object_id, object_name, |
|||
detail, snapshot_before, snapshot_after, k_time, created_at |
|||
from object_operation_log |
|||
</sql> |
|||
|
|||
<insert id="insert" parameterType="ObjectOperationLog" useGeneratedKeys="true" keyProperty="id"> |
|||
insert into object_operation_log (room_id, operator_id, operator_name, operation_type, object_type, object_id, object_name, detail, snapshot_before, snapshot_after, k_time) |
|||
values (#{roomId}, #{operatorId}, #{operatorName}, #{operationType}, #{objectType}, #{objectId}, #{objectName}, #{detail}, #{snapshotBefore}, #{snapshotAfter}, #{kTime}) |
|||
</insert> |
|||
|
|||
<select id="selectById" resultMap="ObjectOperationLogResult"> |
|||
<include refid="selectVo"/> where id = #{id} |
|||
</select> |
|||
|
|||
<select id="selectPage" parameterType="ObjectOperationLog" resultMap="ObjectOperationLogResult"> |
|||
<include refid="selectVo"/> |
|||
<where> |
|||
<if test="roomId != null"> and room_id = #{roomId} </if> |
|||
<if test="operatorName != null and operatorName != ''"> and operator_name like concat('%', #{operatorName}, '%') </if> |
|||
<if test="operationType != null"> and operation_type = #{operationType} </if> |
|||
<if test="objectType != null and objectType != ''"> and object_type = #{objectType} </if> |
|||
<if test="params != null and params.beginTime != null and params.beginTime != ''"> and created_at >= #{params.beginTime} </if> |
|||
<if test="params != null and params.endTime != null and params.endTime != ''"> and created_at <= #{params.endTime} </if> |
|||
</where> |
|||
order by created_at desc |
|||
</select> |
|||
|
|||
<delete id="deleteById" parameterType="Long"> |
|||
delete from object_operation_log where id = #{id} |
|||
</delete> |
|||
|
|||
<delete id="deleteByRoomIdAfterId"> |
|||
delete from object_operation_log where room_id = #{roomId} and id > #{afterId} |
|||
</delete> |
|||
</mapper> |
|||
@ -0,0 +1,23 @@ |
|||
import request from '@/utils/request' |
|||
|
|||
/** |
|||
* 对象级操作日志分页查询(按房间、操作人、类型等) |
|||
*/ |
|||
export function listObjectLog(params) { |
|||
return request({ |
|||
url: '/system/object-log/list', |
|||
method: 'get', |
|||
params |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 回滚到指定操作(数据库 + Redis 同步) |
|||
*/ |
|||
export function rollbackObjectLog(id) { |
|||
return request({ |
|||
url: '/system/object-log/rollback', |
|||
method: 'post', |
|||
params: { id } |
|||
}) |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,19 @@ |
|||
-- 对象级操作日志表(航线、航点、平台等,支持回滚) |
|||
-- 执行前请根据实际库名修改 |
|||
CREATE TABLE IF NOT EXISTS object_operation_log ( |
|||
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', |
|||
room_id BIGINT DEFAULT NULL COMMENT '房间ID,非房间维度可为空', |
|||
operator_id BIGINT DEFAULT NULL COMMENT '操作人用户ID', |
|||
operator_name VARCHAR(64) DEFAULT '' COMMENT '操作人姓名', |
|||
operation_type TINYINT NOT NULL COMMENT '操作类型:1新增 2修改 3删除 4选择', |
|||
object_type VARCHAR(32) NOT NULL COMMENT '操作对象类型:route/waypoint/platform', |
|||
object_id VARCHAR(64) DEFAULT NULL COMMENT '业务对象ID(如航线ID、航点ID)', |
|||
object_name VARCHAR(255) DEFAULT '' COMMENT '对象显示名(如呼号、航点名)', |
|||
detail VARCHAR(500) DEFAULT '' COMMENT '详细操作描述', |
|||
snapshot_before TEXT DEFAULT NULL COMMENT '操作前快照JSON,用于回滚', |
|||
snapshot_after TEXT DEFAULT NULL COMMENT '操作后快照JSON', |
|||
k_time VARCHAR(32) DEFAULT NULL COMMENT '相对时间如 K+00:45:23', |
|||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', |
|||
PRIMARY KEY (id), |
|||
KEY idx_room_created (room_id, created_at DESC) |
|||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='对象级操作日志(支持回滚)'; |
|||
Loading…
Reference in new issue