团队项目安全管理子系统-历史回溯

要求:此模块支持风险项历史版本追溯与对比,详细记录更新人、更新时间、原值/新值)等。

1、创建两个实体类SafetyRisk和SafetyRiskHistory

SafetyRisk

package com.lianxi.safety7.entity;

import cn.hutool.core.annotation.Alias;
import jakarta.persistence.*;
import lombok.Data;

import java.util.Date;

@Entity
@Data // 自动生成getter/setter/tostring/hashcode/equals方法
@Table(name="safetyrisk")
public class SafetyRisk {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Alias("编号")
    private Integer id; // 编号
    @Alias("风险编码")
    private String risk_code; // 风险编码
    @Alias("责任部门/分区")
    private String department; // 责任部门/工区
    @Alias("专业系统")
    private String professional_system; // 专业系统
    @Alias("风险类别")
    private String risk_category; // 风险类别
    @Alias("风险项目")
    private String risk_item; // 风险项目
    @Alias("风险项点")
    private String risk_point; // 风险项点
    @Alias("风险等级")
    private String risk_level; // 风险等级
    @Alias("危害程度")
    private String harm_degree; // 危害程度
    @Alias("管控措施")
    private String control_measures; // 管控措施
    @Alias("管控岗位")
    private String control_position; // 管控岗位
    @Alias("管控人员")
    private String control_personnel; // 管控人员
    @Alias("量化要求")
    private String quantified_requirements; // 量化要求
    @Alias("开始录入时间")
    private Date entry_start_date; // 开始录入日期
    @Alias("结束录入时间")
    private Date entry_end_date; // 结束录入日期
    @Alias("审核状态")
    private String review_status; // 审核状态
    @Alias("审核日期")
    private Date review_date; // 审核日期

    private String updateUser; // 新增字段

    private String entry_start_date_str;
    private String entry_end_date_str;
    private String review_date_str;


    public String getUpdateUser() {
        return updateUser;
    }

    public void setUpdateUser(String updateUser) {
        this.updateUser = updateUser;
    }

    public String getEntry_start_date_str() {
        return entry_start_date_str;
    }

    public void setEntry_start_date_str(String entry_start_date_str) {
        this.entry_start_date_str = entry_start_date_str;
    }

    public String getEntry_end_date_str() {
        return entry_end_date_str;
    }

    public void setEntry_end_date_str(String entry_end_date_str) {
        this.entry_end_date_str = entry_end_date_str;
    }

    public String getReview_date_str() {
        return review_date_str;
    }

    public void setReview_date_str(String review_date_str) {
        this.review_date_str = review_date_str;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getRisk_code() {
        return risk_code;
    }

    public void setRisk_code(String risk_code) {
        this.risk_code = risk_code;
    }

    public String getDepartment() {
        return department;
    }

    public void setDepartment(String department) {
        this.department = department;
    }

    public String getProfessional_system() {
        return professional_system;
    }

    public void setProfessional_system(String professional_system) {
        this.professional_system = professional_system;
    }

    public String getRisk_category() {
        return risk_category;
    }

    public void setRisk_category(String risk_category) {
        this.risk_category = risk_category;
    }

    public String getRisk_item() {
        return risk_item;
    }

    public void setRisk_item(String risk_item) {
        this.risk_item = risk_item;
    }

    public String getRisk_point() {
        return risk_point;
    }

    public void setRisk_point(String risk_point) {
        this.risk_point = risk_point;
    }

    public String getRisk_level() {
        return risk_level;
    }

    public void setRisk_level(String risk_level) {
        this.risk_level = risk_level;
    }

    public String getHarm_degree() {
        return harm_degree;
    }

    public void setHarm_degree(String harm_degree) {
        this.harm_degree = harm_degree;
    }

    public String getControl_measures() {
        return control_measures;
    }

    public void setControl_measures(String control_measures) {
        this.control_measures = control_measures;
    }

    public String getControl_position() {
        return control_position;
    }

    public void setControl_position(String control_position) {
        this.control_position = control_position;
    }

    public String getControl_personnel() {
        return control_personnel;
    }

    public void setControl_personnel(String control_personnel) {
        this.control_personnel = control_personnel;
    }

    public String getQuantified_requirements() {
        return quantified_requirements;
    }

    public void setQuantified_requirements(String quantified_requirements) {
        this.quantified_requirements = quantified_requirements;
    }

    public Date getEntry_start_date() {
        return entry_start_date;
    }

    public void setEntry_start_date(Date entry_start_date) {
        this.entry_start_date = entry_start_date;
    }

    public Date getEntry_end_date() {
        return entry_end_date;
    }

    public void setEntry_end_date(Date entry_end_date) {
        this.entry_end_date = entry_end_date;
    }

    public String getReview_status() {
        return review_status;
    }

    public void setReview_status(String review_status) {
        this.review_status = review_status;
    }

    public Date getReview_date() {
        return review_date;
    }

    public void setReview_date(Date review_date) {
        this.review_date = review_date;
    }
}

SafetyRiskHistory

package com.lianxi.safety7.entity;

import jakarta.persistence.*;
import java.util.Date;

@Entity
@Table(name = "safetyriskhistory")
public class SafetyRiskHistory {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "history_id")
    private int historyId;

    @ManyToOne
    @JoinColumn(name = "risk_id", nullable = false)
    private SafetyRisk safetyRisk;

    @Column(name = "update_user", nullable = false, length = 100)
    private String updateUser;

    @Column(name = "update_time", nullable = false)
    private Date updateTime;

    @Column(name = "old_value", columnDefinition = "LONGTEXT")
    private String oldValue;

    @Column(name = "new_value", columnDefinition = "LONGTEXT")
    private String newValue;

    private String updateTime_str;
    private String oldValue_str;
    private String newValue_str;

    public String getUpdateTime_str() {
        return updateTime_str;
    }

    public void setUpdateTime_str(String updateTime_str) {
        this.updateTime_str = updateTime_str;
    }

    public String getOldValue_str() {
        return oldValue_str;
    }

    public void setOldValue_str(String oldValue_str) {
        this.oldValue_str = oldValue_str;
    }

    public String getNewValue_str() {
        return newValue_str;
    }

    public void setNewValue_str(String newValue_str) {
        this.newValue_str = newValue_str;
    }

    // Getters and Setters

    public int getHistoryId() {
        return historyId;
    }

    public void setHistoryId(int historyId) {
        this.historyId = historyId;
    }

    public SafetyRisk getSafetyRisk() {
        return safetyRisk;
    }

    public void setSafetyRisk(SafetyRisk safetyRisk) {
        this.safetyRisk = safetyRisk;
    }

    public String getUpdateUser() {
        return updateUser;
    }

    public void setUpdateUser(String updateUser) {
        this.updateUser = updateUser;
    }

    public Date getUpdateTime() {
        return updateTime;
    }

    public void setUpdateTime(Date updateTime) {
        this.updateTime = updateTime;
    }

    public String getOldValue() {
        return oldValue;
    }

    public void setOldValue(String oldValue) {
        this.oldValue = oldValue;
    }

    public String getNewValue() {
        return newValue;
    }

    public void setNewValue(String newValue) {
        this.newValue = newValue;
    }



}

2、controller

SafetyriskController

package com.lianxi.safety7.controller;

import com.lianxi.safety7.entity.SafetyRisk;
import com.lianxi.safety7.entity.SafetyRiskHistory;
import com.lianxi.safety7.service.SafetyRiskHistoryService;
import com.lianxi.safety7.service.SafetyRiskService;
import org.springframework.beans.BeanUtils; // 添加这行导入 BeanUtils 类
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.data.domain.Page;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.Map;

@RestController
@RequestMapping("/safetyrisk")
@CrossOrigin
public class SafetyriskController {

    @Autowired
    private SafetyRiskService safetyRiskService;

    @Autowired
    private SafetyRiskHistoryService safetyRiskHistoryService;

    @PostMapping
    public ResponseEntity<SafetyRisk> saveSafetyRisk(@RequestBody SafetyRisk safetyRisk) {
        SafetyRisk savedRisk = safetyRiskService.saveSafetyRisk(safetyRisk);
        return ResponseEntity.ok(savedRisk);
    }

    @PostMapping("/revert/{riskCode}")
    public ResponseEntity<?> revertSafetyRisk(
            @PathVariable String riskCode,
            @RequestBody Map<String, Object> requestData) {

        try {
            // 验证风险代码格式
            if (riskCode.contains(":")) {
                return ResponseEntity.badRequest().body("风险代码格式不正确");
            }

            // 获取现有风险记录
            SafetyRisk existingRisk = safetyRiskService.getByRiskCode(riskCode)
                    .orElseThrow(() -> new IllegalArgumentException("找不到风险代码: " + riskCode));

            // 创建要保存的对象
            SafetyRisk revertedRisk = new SafetyRisk();

            // 复制所有可识别属性
            BeanUtils.copyProperties(existingRisk, revertedRisk); // 先复制现有值

            // 从请求数据更新字段
            if (requestData.containsKey("department")) {
                revertedRisk.setDepartment((String) requestData.get("department"));
            }
            // 为所有字段添加类似逻辑...

            // 确保关键字段不被覆盖
            revertedRisk.setId(existingRisk.getId());
            revertedRisk.setRisk_code(riskCode);

            // 保存数据
            SafetyRisk savedRisk = safetyRiskService.saveSafetyRisk(revertedRisk);

            // 记录历史
            SafetyRiskHistory history = new SafetyRiskHistory();
            history.setSafetyRisk(savedRisk);
            history.setUpdateUser("system-revert");
            history.setUpdateTime(new Date());
            history.setOldValue(convertRiskToJson(existingRisk));
            history.setNewValue(convertRiskToJson(savedRisk));
            safetyRiskHistoryService.saveSafetyRiskHistory(history);

            return ResponseEntity.ok(savedRisk);

        } catch (IllegalArgumentException e) {
            return ResponseEntity.badRequest().body(e.getMessage());
        } catch (Exception e) {
            return ResponseEntity.internalServerError().body("服务器内部错误");
        }
    }

    @GetMapping("/all")
    public ResponseEntity<Page<SafetyRisk>> getAllSafetyRisks(
            @RequestParam(defaultValue = "1") int page,
            @RequestParam(defaultValue = "10") int size) {
        Page<SafetyRisk> pageResult = safetyRiskService.getAllSafetyRisks(page, size);
        pageResult.getContent().forEach(this::beautifyDates);
        return ResponseEntity.ok(pageResult);
    }

    @GetMapping("/byRiskCode")
    public ResponseEntity<Page<SafetyRisk>> getSafetyRisksByRiskCode(
            @RequestParam String riskCode,
            @RequestParam(defaultValue = "1") int page,
            @RequestParam(defaultValue = "10") int size
    ) {
        Page<SafetyRisk> pageResult = safetyRiskService.getSafetyRisksByRiskCode(riskCode, page, size);
        pageResult.getContent().forEach(this::beautifyDates);
        return ResponseEntity.ok(pageResult);
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteSafetyRisk(@PathVariable int id) {
        safetyRiskService.deleteSafetyRisk(id);
        return ResponseEntity.noContent().build();
    }

    @GetMapping
    public ResponseEntity<Page<SafetyRisk>> getSafetyRisksByQuery(
            @RequestParam(required = false) Integer id,
            @RequestParam(required = false) String risk_code,
            @RequestParam(required = false) String department,
            @RequestParam(required = false) String professional_system,
            @RequestParam(required = false) String risk_category,
            @RequestParam(required = false) String risk_item,
            @RequestParam(required = false) String risk_point,
            @RequestParam(required = false) String risk_level,
            @RequestParam(required = false) String harm_degree,
            @RequestParam(required = false) String control_measures,
            @RequestParam(required = false) String control_position,
            @RequestParam(required = false) String control_personnel,
            @RequestParam(required = false) String quantified_requirements,
            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date entry_start_date,
            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date entry_end_date,
            @RequestParam(required = false) String review_status,
            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date review_date,
            @RequestParam(defaultValue = "1") int page,
            @RequestParam(defaultValue = "10") int size
    ) {
        Page<SafetyRisk> pageResult = safetyRiskService.getSafetyRisksByQuery(
                id, risk_code, department, professional_system, risk_category,
                risk_item, risk_point, risk_level, harm_degree, control_measures,
                control_position, control_personnel, quantified_requirements,
                entry_start_date, entry_end_date, review_status, review_date,
                page, size
        );
        pageResult.getContent().forEach(this::beautifyDates);
        return ResponseEntity.ok(pageResult);
    }

    @PutMapping("/{id}")
    public ResponseEntity<SafetyRisk> update(@PathVariable("id") int id, @RequestBody SafetyRisk updatedRisk) {
        SafetyRisk existingRisk = safetyRiskService.getById(id)
                .orElseThrow(() -> new IllegalArgumentException("安全风险记录ID [" + id + "] 不存在"));

        existingRisk.setRisk_code(updatedRisk.getRisk_code());
        existingRisk.setDepartment(updatedRisk.getDepartment());
        existingRisk.setProfessional_system(updatedRisk.getProfessional_system());
        existingRisk.setRisk_category(updatedRisk.getRisk_category());
        existingRisk.setRisk_item(updatedRisk.getRisk_item());
        existingRisk.setRisk_point(updatedRisk.getRisk_point());
        existingRisk.setRisk_level(updatedRisk.getRisk_level());
        existingRisk.setHarm_degree(updatedRisk.getHarm_degree());
        existingRisk.setControl_measures(updatedRisk.getControl_measures());
        existingRisk.setControl_position(updatedRisk.getControl_position());
        existingRisk.setControl_personnel(updatedRisk.getControl_personnel());
        existingRisk.setQuantified_requirements(updatedRisk.getQuantified_requirements());
        existingRisk.setEntry_start_date(updatedRisk.getEntry_start_date());
        existingRisk.setEntry_end_date(updatedRisk.getEntry_end_date());
        existingRisk.setReview_status(updatedRisk.getReview_status());
        existingRisk.setReview_date(updatedRisk.getReview_date());

        SafetyRisk savedRisk = safetyRiskService.saveSafetyRisk(existingRisk);
        beautifyDates(savedRisk);
        return ResponseEntity.ok(savedRisk);
    }

    private void beautifyDates(SafetyRisk risk) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        if (risk.getEntry_start_date() != null) {
            LocalDateTime localDateTime = risk.getEntry_start_date().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
            risk.setEntry_start_date_str(localDateTime.format(formatter));
        }
        if (risk.getEntry_end_date() != null) {
            LocalDateTime localDateTime = risk.getEntry_end_date().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
            risk.setEntry_end_date_str(localDateTime.format(formatter));
        }
        if (risk.getReview_date() != null) {
            LocalDateTime localDateTime = risk.getReview_date().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
            risk.setReview_date_str(localDateTime.format(formatter));
        }
    }

    private String convertRiskToJson(SafetyRisk risk) {
        if (risk == null) {
            return "{}";
        }
        return String.format(
                "{\"id\":%d,\"risk_code\":\"%s\",\"department\":\"%s\",\"professional_system\":\"%s\"," +
                        "\"risk_category\":\"%s\",\"risk_item\":\"%s\",\"risk_point\":\"%s\",\"risk_level\":\"%s\"," +
                        "\"harm_degree\":\"%s\",\"control_measures\":\"%s\",\"control_position\":\"%s\"," +
                        "\"control_personnel\":\"%s\",\"quantified_requirements\":\"%s\",\"entry_start_date\":\"%s\"," +
                        "\"entry_end_date\":\"%s\",\"review_status\":\"%s\",\"review_date\":\"%s\"}",
                risk.getId(),
                escapeJson(risk.getRisk_code()),
                escapeJson(risk.getDepartment()),
                escapeJson(risk.getProfessional_system()),
                escapeJson(risk.getRisk_category()),
                escapeJson(risk.getRisk_item()),
                escapeJson(risk.getRisk_point()),
                escapeJson(risk.getRisk_level()),
                escapeJson(risk.getHarm_degree()),
                escapeJson(risk.getControl_measures()),
                escapeJson(risk.getControl_position()),
                escapeJson(risk.getControl_personnel()),
                escapeJson(risk.getQuantified_requirements()),
                risk.getEntry_start_date() != null ? risk.getEntry_start_date().toString() : "",
                risk.getEntry_end_date() != null ? risk.getEntry_end_date().toString() : "",
                escapeJson(risk.getReview_status()),
                risk.getReview_date() != null ? risk.getReview_date().toString() : ""
        );
    }

    private String escapeJson(String str) {
        if (str == null) {
            return "";
        }
        return str.replace("\\", "\\\\")
                .replace("\"", "\\\"")
                .replace("\b", "\\b")
                .replace("\f", "\\f")
                .replace("\n", "\\n")
                .replace("\r", "\\r")
                .replace("\t", "\\t");
    }
}

2、SafetyRiskHistoryController

package com.lianxi.safety7.controller;

import com.lianxi.safety7.entity.SafetyRisk;
import com.lianxi.safety7.entity.SafetyRiskHistory;
import com.lianxi.safety7.service.SafetyRiskHistoryService;
import com.lianxi.safety7.service.SafetyRiskService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.data.domain.Page;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.Map;

@RestController
@RequestMapping("/safetyriskhistory")
@CrossOrigin
public class SafetyRiskHistoryController {

    @Autowired
    private SafetyRiskHistoryService safetyRiskHistoryService;

    @Autowired
    private SafetyRiskService safetyRiskService;

    // 获取系统自动更新的历史记录
    @GetMapping("/system")
    public ResponseEntity<Page<SafetyRiskHistory>> getSystemGeneratedHistories(
            @RequestParam(defaultValue = "1") int page,
            @RequestParam(defaultValue = "10") int size) {
        Page<SafetyRiskHistory> pageResult = safetyRiskHistoryService.getSystemGeneratedHistories(page, size);
        beautifyPageData(pageResult);
        return ResponseEntity.ok(pageResult);
    }

    // 按更新时间范围查询系统记录
    @GetMapping("/system/byUpdateTime")
    public ResponseEntity<Page<SafetyRiskHistory>> getSystemHistoriesByUpdateTime(
            @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") Date startTime,
            @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") Date endTime,
            @RequestParam(defaultValue = "1") int page,
            @RequestParam(defaultValue = "10") int size) {

        if (startTime.after(endTime)) {
            throw new IllegalArgumentException("开始时间不能晚于结束时间");
        }

        Page<SafetyRiskHistory> pageResult = safetyRiskHistoryService.getSystemHistoriesByUpdateTime(
                startTime, endTime, page, size);
        beautifyPageData(pageResult);
        return ResponseEntity.ok(pageResult);
    }

    // 回溯到指定历史记录
    @PostMapping("/revert/{riskCode}")
    public ResponseEntity<?> revertSafetyRisk(
            @PathVariable String riskCode,
            @RequestBody Map<String, Object> requestData) {

        try {
            // 1. 验证风险代码并获取现有记录
            if (riskCode == null || riskCode.isEmpty()) {
                throw new IllegalArgumentException("风险代码不能为空");
            }

            SafetyRisk existingRisk = safetyRiskService.getByRiskCode(riskCode)
                    .orElseThrow(() -> new IllegalArgumentException("找不到风险代码: " + riskCode));

            // 2. 创建要保存的对象并复制属性
            SafetyRisk revertedRisk = new SafetyRisk();
            BeanUtils.copyProperties(existingRisk, revertedRisk);

            // 3. 从请求数据更新所有字段
            updateRiskFromRequestData(revertedRisk, requestData);

            // 4. 确保关键字段不被覆盖
            revertedRisk.setId(existingRisk.getId());
            revertedRisk.setRisk_code(riskCode);

            // 5. 保存数据
            SafetyRisk savedRisk = safetyRiskService.saveSafetyRisk(revertedRisk);

            // 6. 记录历史
            recordHistory(existingRisk, savedRisk);

            return ResponseEntity.ok(Map.of(
                    "success", true,
                    "data", savedRisk
            ));

        } catch (Exception e) {
            return ResponseEntity.badRequest().body(Map.of(
                    "success", false,
                    "message", e.getMessage()
            ));
        }
    }

    // 从请求数据更新风险对象
    private void updateRiskFromRequestData(SafetyRisk risk, Map<String, Object> requestData) {
        if (requestData.containsKey("department")) {
            risk.setDepartment((String) requestData.get("department"));
        }
        if (requestData.containsKey("professional_system")) {
            risk.setProfessional_system((String) requestData.get("professional_system"));
        }
        if (requestData.containsKey("risk_category")) {
            risk.setRisk_category((String) requestData.get("risk_category"));
        }
        if (requestData.containsKey("risk_item")) {
            risk.setRisk_item((String) requestData.get("risk_item"));
        }
        if (requestData.containsKey("risk_point")) {
            risk.setRisk_point((String) requestData.get("risk_point"));
        }
        if (requestData.containsKey("risk_level")) {
            risk.setRisk_level((String) requestData.get("risk_level"));
        }
        if (requestData.containsKey("harm_degree")) {
            risk.setHarm_degree((String) requestData.get("harm_degree"));
        }
        if (requestData.containsKey("control_measures")) {
            risk.setControl_measures((String) requestData.get("control_measures"));
        }
        if (requestData.containsKey("control_position")) {
            risk.setControl_position((String) requestData.get("control_position"));
        }
        if (requestData.containsKey("control_personnel")) {
            risk.setControl_personnel((String) requestData.get("control_personnel"));
        }
        if (requestData.containsKey("quantified_requirements")) {
            risk.setQuantified_requirements((String) requestData.get("quantified_requirements"));
        }
        if (requestData.containsKey("entry_start_date")) {
            risk.setEntry_start_date(new Date((Long) requestData.get("entry_start_date")));
        }
        if (requestData.containsKey("entry_end_date")) {
            risk.setEntry_end_date(new Date((Long) requestData.get("entry_end_date")));
        }
        if (requestData.containsKey("review_status")) {
            risk.setReview_status((String) requestData.get("review_status"));
        }
        if (requestData.containsKey("review_date")) {
            risk.setReview_date(new Date((Long) requestData.get("review_date")));
        }
    }

    // 记录历史变更
    private void recordHistory(SafetyRisk oldRisk, SafetyRisk newRisk) {
        SafetyRiskHistory history = new SafetyRiskHistory();
        history.setSafetyRisk(newRisk);
        history.setUpdateUser("system-revert");
        history.setUpdateTime(new Date());
        history.setOldValue(convertRiskToJson(oldRisk));
        history.setNewValue(convertRiskToJson(newRisk));
        safetyRiskHistoryService.saveSafetyRiskHistory(history);
    }

    // 美化页面数据
    private void beautifyPageData(Page<SafetyRiskHistory> pageResult) {
        pageResult.getContent().forEach(this::beautifyDates);
        pageResult.getContent().forEach(this::beautifyValues);
    }

    // 美化日期显示
    private void beautifyDates(SafetyRiskHistory history) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        if (history.getUpdateTime() != null) {
            LocalDateTime localDateTime = history.getUpdateTime().toInstant()
                    .atZone(ZoneId.systemDefault()).toLocalDateTime();
            history.setUpdateTime_str(localDateTime.format(formatter));
        }
    }

    // 美化值显示
    private void beautifyValues(SafetyRiskHistory history) {
        if (history.getOldValue() != null) {
            history.setOldValue_str(truncateString(history.getOldValue(), 50));
        }
        if (history.getNewValue() != null) {
            history.setNewValue_str(truncateString(history.getNewValue(), 50));
        }
    }

    private String truncateString(String str, int maxLength) {
        if (str == null || str.length() <= maxLength) {
            return str;
        }
        return str.substring(0, maxLength) + "...";
    }

    private String convertRiskToJson(SafetyRisk risk) {
        if (risk == null) {
            return "{}";
        }
        return String.format(
                "{\"id\":%d,\"risk_code\":\"%s\",\"department\":\"%s\",\"professional_system\":\"%s\"," +
                        "\"risk_category\":\"%s\",\"risk_item\":\"%s\",\"risk_point\":\"%s\",\"risk_level\":\"%s\"," +
                        "\"harm_degree\":\"%s\",\"control_measures\":\"%s\",\"control_position\":\"%s\"," +
                        "\"control_personnel\":\"%s\",\"quantified_requirements\":\"%s\",\"entry_start_date\":\"%s\"," +
                        "\"entry_end_date\":\"%s\",\"review_status\":\"%s\",\"review_date\":\"%s\"}",
                risk.getId(),
                escapeJson(risk.getRisk_code()),
                escapeJson(risk.getDepartment()),
                escapeJson(risk.getProfessional_system()),
                escapeJson(risk.getRisk_category()),
                escapeJson(risk.getRisk_item()),
                escapeJson(risk.getRisk_point()),
                escapeJson(risk.getRisk_level()),
                escapeJson(risk.getHarm_degree()),
                escapeJson(risk.getControl_measures()),
                escapeJson(risk.getControl_position()),
                escapeJson(risk.getControl_personnel()),
                escapeJson(risk.getQuantified_requirements()),
                risk.getEntry_start_date() != null ? risk.getEntry_start_date().toString() : "",
                risk.getEntry_end_date() != null ? risk.getEntry_end_date().toString() : "",
                escapeJson(risk.getReview_status()),
                risk.getReview_date() != null ? risk.getReview_date().toString() : ""
        );
    }

    private String escapeJson(String str) {
        if (str == null) {
            return "";
        }
        return str.replace("\\", "\\\\")
                .replace("\"", "\\\"")
                .replace("\b", "\\b")
                .replace("\f", "\\f")
                .replace("\n", "\\n")
                .replace("\r", "\\r")
                .replace("\t", "\\t");
    }
}

3、repository代码
SafetyRiskHistoryRepository

package com.lianxi.safety7.repository;

import com.lianxi.safety7.entity.SafetyRiskHistory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.Date;
import java.util.List;

@Repository
public interface SafetyRiskHistoryRepository extends JpaRepository<SafetyRiskHistory, Integer> {

    @Query("SELECT s FROM SafetyRiskHistory s WHERE (:historyId IS NULL OR s.historyId = :historyId) AND (:riskId IS NULL OR s.safetyRisk.id = :riskId)")
    Page<SafetyRiskHistory> findByHistoryIdAndRiskId(@Param("historyId") Integer historyId, @Param("riskId") Integer riskId, Pageable pageable);

    Page<SafetyRiskHistory> findAllByHistoryId(Integer historyId, Pageable pageable);

    Page<SafetyRiskHistory> findByUpdateTimeBetween(Date startTime, Date endTime, Pageable pageable);
    Page<SafetyRiskHistory> findByUpdateTimeAfter(Date startTime, Pageable pageable);
    Page<SafetyRiskHistory> findByUpdateTimeBefore(Date endTime, Pageable pageable);
    @Query("SELECT h FROM SafetyRiskHistory h WHERE h.safetyRisk.risk_code LIKE :riskCode")
    Page<SafetyRiskHistory> findByRiskCodeContaining(@Param("riskCode") String riskCode, Pageable pageable);

    @Query("SELECT s FROM SafetyRiskHistory s WHERE " +
            "(:historyId IS NULL OR s.historyId = :historyId) AND " +
            "(:riskId IS NULL OR s.safetyRisk.id = :riskId) AND " +
            "(:riskCode IS NULL OR s.safetyRisk.risk_code LIKE :riskCode) AND " +
            "(:updateUser IS NULL OR s.updateUser = :updateUser) AND " +
            "(:updateTime IS NULL OR s.updateTime = :updateTime) AND " +
            "(:oldValue IS NULL OR s.oldValue = :oldValue) AND " +
            "(:newValue IS NULL OR s.newValue = :newValue)")
    Page<SafetyRiskHistory> findByMultipleConditions(
            @Param("historyId") Integer historyId,
            @Param("riskId") Integer riskId,
            @Param("riskCode") String riskCode,
            @Param("updateUser") String updateUser,
            @Param("updateTime") Date updateTime,
            @Param("oldValue") String oldValue,
            @Param("newValue") String newValue,
            Pageable pageable
    );

    @Query("SELECT s FROM SafetyRiskHistory s WHERE s.safetyRisk.id = :riskId")
    Page<SafetyRiskHistory> findByRiskId(@Param("riskId") Integer riskId, Pageable pageable);

    Page<SafetyRiskHistory> findByUpdateUserIn(List<String> updateUsers, Pageable pageable);

    Page<SafetyRiskHistory> findByUpdateUserInAndUpdateTimeBetween(List<String> updateUsers, Date startTime, Date endTime, Pageable pageable);
}

SafetyRiskRepository

package com.lianxi.safety7.repository;

import com.lianxi.safety7.entity.SafetyRisk;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.Date;
import java.util.List;
import java.util.Optional;

@Repository
public interface SafetyRiskRepository extends JpaRepository<SafetyRisk, Integer> {
    @Query("SELECT s FROM SafetyRisk s WHERE (:id IS NULL OR s.id = :id) AND (:riskLevel IS NULL OR :riskLevel = '' OR s.risk_level = :riskLevel)")
    List<SafetyRisk> findByIdAndRiskLevel(@Param("id") Integer id, @Param("riskLevel") String riskLevel);

    List<SafetyRisk> findAllById(Integer id);

    @Query("SELECT s FROM SafetyRisk s WHERE (:riskCode IS NULL OR s.risk_code LIKE %:riskCode%)")
    Page<SafetyRisk> findByRiskCodeContaining(@Param("riskCode") String riskCode, Pageable pageable);

    @Query("SELECT s FROM SafetyRisk s WHERE " +
            "(:id IS NULL OR s.id = :id) AND " +
            "(:risk_code IS NULL OR s.risk_code LIKE %:risk_code%) AND " +
            "(:department IS NULL OR s.department = :department) AND " +
            "(:professional_system IS NULL OR s.professional_system = :professional_system) AND " +
            "(:risk_category IS NULL OR s.risk_category = :risk_category) AND " +
            "(:risk_item IS NULL OR s.risk_item = :risk_item) AND " +
            "(:risk_point IS NULL OR s.risk_point = :risk_point) AND " +
            "(:risk_level IS NULL OR s.risk_level = :risk_level) AND " +
            "(:harm_degree IS NULL OR s.harm_degree = :harm_degree) AND " +
            "(:control_measures IS NULL OR s.control_measures = :control_measures) AND " +
            "(:control_position IS NULL OR s.control_position = :control_position) AND " +
            "(:control_personnel IS NULL OR s.control_personnel = :control_personnel) AND " +
            "(:quantified_requirements IS NULL OR s.quantified_requirements = :quantified_requirements) AND " +
            "(:entry_start_date IS NULL OR s.entry_start_date = :entry_start_date) AND " +
            "(:entry_end_date IS NULL OR s.entry_end_date = :entry_end_date) AND " +
            "(:review_status IS NULL OR s.review_status = :review_status) AND " +
            "(:review_date IS NULL OR s.review_date = :review_date)")
    Page<SafetyRisk> findByMultipleConditions(
            @Param("id") Integer id,
            @Param("risk_code") String risk_code,
            @Param("department") String department,
            @Param("professional_system") String professional_system,
            @Param("risk_category") String risk_category,
            @Param("risk_item") String risk_item,
            @Param("risk_point") String risk_point,
            @Param("risk_level") String risk_level,
            @Param("harm_degree") String harm_degree,
            @Param("control_measures") String control_measures,
            @Param("control_position") String control_position,
            @Param("control_personnel") String control_personnel,
            @Param("quantified_requirements") String quantified_requirements,
            @Param("entry_start_date") Date entry_start_date,
            @Param("entry_end_date") Date entry_end_date,
            @Param("review_status") String review_status,
            @Param("review_date") Date review_date,
            Pageable pageable
    );

    @Query("SELECT s FROM SafetyRisk s WHERE s.risk_level = :riskLevel")
    List<SafetyRisk> findByRiskLevel(@Param("riskLevel") String riskLevel);

    @Query("SELECT s FROM SafetyRisk s " +
            "WHERE (:id IS NULL OR s.id = :id) AND " +
            "(:risk_code IS NULL OR s.risk_code LIKE %:risk_code%) AND " +
            "(:department IS NULL OR s.department = :department) AND " +
            "(:professional_system IS NULL OR s.professional_system = :professional_system) AND " +
            "(:risk_category IS NULL OR s.risk_category = :risk_category) AND " +
            "(:risk_item IS NULL OR s.risk_item = :risk_item) AND " +
            "(:risk_point IS NULL OR s.risk_point = :risk_point) AND " +
            "(:risk_level IS NULL OR s.risk_level = :risk_level) AND " +
            "(:harm_degree IS NULL OR s.harm_degree = :harm_degree) AND " +
            "(:control_measures IS NULL OR s.control_measures = :control_measures) AND " +
            "(:control_position IS NULL OR s.control_position = :control_position) AND " +
            "(:control_personnel IS NULL OR s.control_personnel = :control_personnel) AND " +
            "(:quantified_requirements IS NULL OR s.quantified_requirements = :quantified_requirements) AND " +
            "(:entry_start_date IS NULL OR s.entry_start_date = :entry_start_date) AND " +
            "(:entry_end_date IS NULL OR s.entry_end_date = :entry_end_date) AND " +
            "(:review_status IS NULL OR s.review_status = :review_status) AND " +
            "(:review_date IS NULL OR s.review_date = :review_date)")
    List<SafetyRisk> findByMultipleConditions(
            @Param("id") Integer id,
            @Param("risk_code") String risk_code,
            @Param("department") String department,
            @Param("professional_system") String professional_system,
            @Param("risk_category") String risk_category,
            @Param("risk_item") String risk_item,
            @Param("risk_point") String risk_point,
            @Param("risk_level") String risk_level,
            @Param("harm_degree") String harm_degree,
            @Param("control_measures") String control_measures,
            @Param("control_position") String control_position,
            @Param("control_personnel") String control_personnel,
            @Param("quantified_requirements") String quantified_requirements,
            @Param("entry_start_date") Date entry_start_date,
            @Param("entry_end_date") Date entry_end_date,
            @Param("review_status") String review_status,
            @Param("review_date") Date review_date
    );

    // 添加 findByRiskCode 方法
    @Query("SELECT s FROM SafetyRisk s WHERE s.risk_code = :riskCode")
    Optional<SafetyRisk> findByRiskCode(@Param("riskCode") String riskCode);
}

4、service

SafetyRiskHistoryService

package com.lianxi.safety7.service;

import com.lianxi.safety7.entity.SafetyRisk;
import com.lianxi.safety7.entity.SafetyRiskHistory;
import com.lianxi.safety7.repository.SafetyRiskHistoryRepository;
import com.lianxi.safety7.repository.SafetyRiskRepository;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Optional;

@Service
public class SafetyRiskHistoryService {
    @PersistenceContext
    private EntityManager entityManager;

    @Autowired
    private SafetyRiskHistoryRepository safetyRiskHistoryRepository;

    @Autowired
    private SafetyRiskRepository safetyRiskRepository;

    public SafetyRiskHistory saveSafetyRiskHistory(SafetyRiskHistory safetyRiskHistory) {
        if (safetyRiskHistory.getSafetyRisk() != null && safetyRiskHistory.getSafetyRisk().getId() != null) {
            Optional<SafetyRisk> safetyRiskOptional = safetyRiskRepository.findById(safetyRiskHistory.getSafetyRisk().getId());
            if (safetyRiskOptional.isPresent()) {
                safetyRiskHistory.setSafetyRisk(safetyRiskOptional.get());
            } else {
                throw new IllegalArgumentException("关联的风险记录不存在,id: " + safetyRiskHistory.getSafetyRisk().getId());
            }
        } else {
            throw new IllegalArgumentException("关联的风险记录不能为空");
        }

        Integer historyId = safetyRiskHistory.getHistoryId();
        if (historyId == null) {
            return safetyRiskHistoryRepository.save(safetyRiskHistory);
        } else {
            return safetyRiskHistoryRepository.findById(historyId)
                    .map(existingHistory -> {
                        existingHistory.setSafetyRisk(safetyRiskHistory.getSafetyRisk());
                        existingHistory.setUpdateUser(safetyRiskHistory.getUpdateUser());
                        existingHistory.setUpdateTime(safetyRiskHistory.getUpdateTime());
                        existingHistory.setOldValue(safetyRiskHistory.getOldValue());
                        existingHistory.setNewValue(safetyRiskHistory.getNewValue());
                        return safetyRiskHistoryRepository.save(existingHistory);
                    })
                    .orElseGet(() -> {
                        System.out.println("未找到 ID 为 " + historyId + " 的记录,将创建新记录");
                        return safetyRiskHistoryRepository.save(safetyRiskHistory);
                    });
        }
    }

    public Page<SafetyRiskHistory> getAllSafetyRiskHistories(int page, int size) {
        Pageable pageable = PageRequest.of(page - 1, size);
        System.out.println("获取所有风险历史数据,页码: " + page + ", 每页数量: " + size);
        Page<SafetyRiskHistory> result = safetyRiskHistoryRepository.findAll(pageable);
        System.out.println("查询结果数量: " + result.getTotalElements());
        return result;
    }

    public void deleteSafetyRiskHistory(int id) {
        safetyRiskHistoryRepository.deleteById(id);
    }

    public Page<SafetyRiskHistory> getSafetyRiskHistoriesByQuery(
            Integer historyId,
            Integer riskId,
            String riskCode,
            String updateUser,
            Date updateTime,
            String oldValue,
            String newValue,
            int page,
            int size
    ) {
        Pageable pageable = PageRequest.of(page - 1, size);
        System.out.println("查询条件: historyId=" + historyId + ", riskId=" + riskId + ", riskCode=" + riskCode +
                ", updateUser=" + updateUser + ", updateTime=" + updateTime + ", oldValue=" + oldValue +
                ", newValue=" + newValue + ", 页码: " + page + ", 每页数量: " + size);
        Page<SafetyRiskHistory> result = safetyRiskHistoryRepository.findByMultipleConditions(
                historyId, riskId, riskCode, updateUser, updateTime, oldValue, newValue, pageable
        );
        System.out.println("查询结果数量: " + result.getTotalElements());
        return result;
    }

    public Page<SafetyRiskHistory> getSafetyRiskHistoriesByRiskCode(String riskCode, int page, int size) {
        Pageable pageable = PageRequest.of(page - 1, size);
        return safetyRiskHistoryRepository.findByRiskCodeContaining(riskCode, pageable);
    }

    // 新增方法:按照更新时间范围查询
    public Page<SafetyRiskHistory> getSafetyRiskHistoriesByUpdateTime(Date startTime, Date endTime, int page, int size) {
        Pageable pageable = PageRequest.of(page - 1, size);
        System.out.println("按更新时间范围查询: startTime=" + startTime + ", endTime=" + endTime +
                ", 页码: " + page + ", 每页数量: " + size);
        return safetyRiskHistoryRepository.findByUpdateTimeBetween(startTime, endTime, pageable);
    }

    // 新增方法:灵活的时间范围查询
    public Page<SafetyRiskHistory> getSafetyRiskHistoriesByUpdateTimeRange(Date startTime, Date endTime, int page, int size) {
        Pageable pageable = PageRequest.of(page - 1, size);
        System.out.println("按灵活更新时间范围查询: startTime=" + startTime + ", endTime=" + endTime +
                ", 页码: " + page + ", 每页数量: " + size);

        if (startTime != null && endTime != null) {
            return safetyRiskHistoryRepository.findByUpdateTimeBetween(startTime, endTime, pageable);
        } else if (startTime != null) {
            return safetyRiskHistoryRepository.findByUpdateTimeAfter(startTime, pageable);
        } else if (endTime != null) {
            return safetyRiskHistoryRepository.findByUpdateTimeBefore(endTime, pageable);
        } else {
            return safetyRiskHistoryRepository.findAll(pageable);
        }
    }

    public List<SafetyRiskHistory> list() {
        return safetyRiskHistoryRepository.findAll();
    }

    @Transactional
    public void saveBatch(List<SafetyRiskHistory> historyList) {
        try {
            for (SafetyRiskHistory history : historyList) {
                saveSafetyRiskHistory(history);
            }
        } catch (Exception e) {
            System.out.println("数据批量保存失败:" + e.getMessage());
            e.printStackTrace();
            throw new RuntimeException("数据批量保存失败", e);
        }
    }

    public Optional<SafetyRiskHistory> getById(Integer id) {
        return safetyRiskHistoryRepository.findById(id);
    }

    // 新增方法
    public Page<SafetyRiskHistory> getSystemGeneratedHistories(int page, int size) {
        Pageable pageable = PageRequest.of(page - 1, size);
        return safetyRiskHistoryRepository.findByUpdateUserIn(Arrays.asList("system", "root@localhost"), pageable);
    }

    // 新增方法 getSystemHistoriesByUpdateTime
    public Page<SafetyRiskHistory> getSystemHistoriesByUpdateTime(Date startTime, Date endTime, int page, int size) {
        Pageable pageable = PageRequest.of(page - 1, size);
        return safetyRiskHistoryRepository.findByUpdateUserInAndUpdateTimeBetween(
                Arrays.asList("system", "root@localhost"), startTime, endTime, pageable
        );
    }
}

SafetyRiskService

package com.lianxi.safety7.service;

import com.lianxi.safety7.entity.SafetyRisk;
import com.lianxi.safety7.entity.SafetyRiskHistory;
import com.lianxi.safety7.repository.SafetyRiskRepository;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;
import java.util.Optional;

@Service
public class SafetyRiskService {

    @PersistenceContext
    private EntityManager entityManager;

    @Autowired
    private SafetyRiskRepository safetyRiskRepository;

    @Autowired
    private SafetyRiskHistoryService safetyRiskHistoryService;


    @Transactional
    public SafetyRisk saveSafetyRisk(SafetyRisk safetyRisk) {
        if (safetyRisk.getId() == null) {
            return safetyRiskRepository.save(safetyRisk);
        } else {
            return safetyRiskRepository.findById(safetyRisk.getId())
                    .map(existingRisk -> {
                        // 如果是系统回溯操作,直接保存不创建历史记录
                        if (!"system-revert".equals(safetyRisk.getUpdateUser())) {
                            SafetyRiskHistory history = new SafetyRiskHistory();
                            history.setSafetyRisk(existingRisk);
                            history.setUpdateUser("system");
                            history.setUpdateTime(new Date());
                            history.setOldValue(convertRiskToJson(existingRisk));
                            history.setNewValue(convertRiskToJson(safetyRisk));

                            safetyRiskHistoryService.saveSafetyRiskHistory(history);
                        }

                        // 更新现有记录
                        existingRisk.setRisk_code(safetyRisk.getRisk_code());
                        existingRisk.setDepartment(safetyRisk.getDepartment());
                        existingRisk.setProfessional_system(safetyRisk.getProfessional_system());
                        existingRisk.setRisk_category(safetyRisk.getRisk_category());
                        existingRisk.setRisk_item(safetyRisk.getRisk_item());
                        existingRisk.setRisk_point(safetyRisk.getRisk_point());
                        existingRisk.setRisk_level(safetyRisk.getRisk_level());
                        existingRisk.setHarm_degree(safetyRisk.getHarm_degree());
                        existingRisk.setControl_measures(safetyRisk.getControl_measures());
                        existingRisk.setControl_position(safetyRisk.getControl_position());
                        existingRisk.setControl_personnel(safetyRisk.getControl_personnel());
                        existingRisk.setQuantified_requirements(safetyRisk.getQuantified_requirements());
                        existingRisk.setEntry_start_date(safetyRisk.getEntry_start_date());
                        existingRisk.setEntry_end_date(safetyRisk.getEntry_end_date());
                        existingRisk.setReview_status(safetyRisk.getReview_status());
                        existingRisk.setReview_date(safetyRisk.getReview_date());

                        return safetyRiskRepository.save(existingRisk);
                    })
                    .orElseGet(() -> {
                        System.out.println("未找到 ID 为 " + safetyRisk.getId() + " 的记录,将创建新记录");
                        return safetyRiskRepository.save(safetyRisk);
                    });
        }
    }

    private String convertRiskToJson(SafetyRisk risk) {
        if (risk == null) {
            return "{}";
        }
        return String.format(
                "{\"id\":%d,\"risk_code\":\"%s\",\"department\":\"%s\",\"professional_system\":\"%s\"," +
                        "\"risk_category\":\"%s\",\"risk_item\":\"%s\",\"risk_point\":\"%s\",\"risk_level\":\"%s\"," +
                        "\"harm_degree\":\"%s\",\"control_measures\":\"%s\",\"control_position\":\"%s\"," +
                        "\"control_personnel\":\"%s\",\"quantified_requirements\":\"%s\",\"entry_start_date\":\"%s\"," +
                        "\"entry_end_date\":\"%s\",\"review_status\":\"%s\",\"review_date\":\"%s\"}",
                risk.getId(),
                escapeJson(risk.getRisk_code()),
                escapeJson(risk.getDepartment()),
                escapeJson(risk.getProfessional_system()),
                escapeJson(risk.getRisk_category()),
                escapeJson(risk.getRisk_item()),
                escapeJson(risk.getRisk_point()),
                escapeJson(risk.getRisk_level()),
                escapeJson(risk.getHarm_degree()),
                escapeJson(risk.getControl_measures()),
                escapeJson(risk.getControl_position()),
                escapeJson(risk.getControl_personnel()),
                escapeJson(risk.getQuantified_requirements()),
                risk.getEntry_start_date() != null ? risk.getEntry_start_date().toString() : "",
                risk.getEntry_end_date() != null ? risk.getEntry_end_date().toString() : "",
                escapeJson(risk.getReview_status()),
                risk.getReview_date() != null ? risk.getReview_date().toString() : ""
        );
    }

    private String escapeJson(String str) {
        if (str == null) {
            return "";
        }
        return str.replace("\\", "\\\\")
                .replace("\"", "\\\"")
                .replace("\b", "\\b")
                .replace("\f", "\\f")
                .replace("\n", "\\n")
                .replace("\r", "\\r")
                .replace("\t", "\\t");
    }

    public Page<SafetyRisk> getAllSafetyRisks(int page, int size) {
        Pageable pageable = PageRequest.of(page - 1, size);
        System.out.println("获取所有风险数据,页码: " + page + ", 每页数量: " + size);
        Page<SafetyRisk> result = safetyRiskRepository.findAll(pageable);
        System.out.println("查询结果数量: " + result.getTotalElements());
        return result;
    }

    public void deleteSafetyRisk(int id) {
        safetyRiskRepository.deleteById(id);
    }

    public Page<SafetyRisk> getSafetyRisksByQuery(
            Integer id,
            String risk_code,
            String department,
            String professional_system,
            String risk_category,
            String risk_item,
            String risk_point,
            String risk_level,
            String harm_degree,
            String control_measures,
            String control_position,
            String control_personnel,
            String quantified_requirements,
            Date entry_start_date,
            Date entry_end_date,
            String review_status,
            Date review_date,
            int page,
            int size
    ) {
        Pageable pageable = PageRequest.of(page - 1, size);
        System.out.println("查询条件: id=" + id + ", risk_code=" + risk_code + ", department=" + department +
                ", professional_system=" + professional_system + ", risk_category=" + risk_category +
                ", risk_item=" + risk_item + ", risk_point=" + risk_point + ", risk_level=" + risk_level +
                ", harm_degree=" + harm_degree + ", control_measures=" + control_measures +
                ", control_position=" + control_position + ", control_personnel=" + control_personnel +
                ", quantified_requirements=" + quantified_requirements + ", entry_start_date=" + entry_start_date +
                ", entry_end_date=" + entry_end_date + ", review_status=" + review_status +
                ", review_date=" + review_date + ", 页码: " + page + ", 每页数量: " + size);
        Page<SafetyRisk> result = safetyRiskRepository.findByMultipleConditions(
                id, risk_code, department, professional_system, risk_category,
                risk_item, risk_point, risk_level, harm_degree, control_measures,
                control_position, control_personnel, quantified_requirements,
                entry_start_date, entry_end_date, review_status, review_date,
                pageable
        );
        System.out.println("查询结果数量: " + result.getTotalElements());
        return result;
    }

    public Page<SafetyRisk> getSafetyRisksByRiskCode(String riskCode, int page, int size) {
        Pageable pageable = PageRequest.of(page - 1, size);
        return safetyRiskRepository.findByRiskCodeContaining(riskCode, pageable);
    }

    public List<SafetyRisk> list() {
        return safetyRiskRepository.findAll();
    }

    @Transactional
    public void saveBatch(List<SafetyRisk> riskList) {
        try {
            for (SafetyRisk risk : riskList) {
                saveSafetyRisk(risk);
            }
        } catch (Exception e) {
            System.out.println("数据批量保存失败:" + e.getMessage());
            e.printStackTrace();
            throw new RuntimeException("数据批量保存失败", e);
        }
    }

    public Optional<SafetyRisk> getById(Integer id) {
        return safetyRiskRepository.findById(id);
    }

    public Optional<SafetyRisk> getByRiskCode(String riskCode) {
        return safetyRiskRepository.findByRiskCode(riskCode);
    }
}

5、前端代码
HomeView.vue

<template>
  <div>
    <el-button @click="add" type="primary">添加数据</el-button>

    <el-dialog v-model="dialogVisible" :title="form.id ? '编辑风险记录' : '新增风险记录'" width="50%" :before-close="handleClose">
      <el-form :label-position="labelPosition" label-width="150px" :model="form" style="max-width: 600px">
        <el-form-item v-if="form.id" label="编号">
          <el-input v-model="form.id" disabled />
        </el-form-item>

        <el-form-item label="风险代码">
          <el-input v-model="form.risk_code" />
        </el-form-item>
        <el-form-item label="部门">
          <el-input v-model="form.department" />
        </el-form-item>
        <el-form-item label="专业系统">
          <el-input v-model="form.professional_system" />
        </el-form-item>
        <el-form-item label="风险类别">
          <el-input v-model="form.risk_category" />
        </el-form-item>
        <el-form-item label="风险事项">
          <el-input v-model="form.risk_item" />
        </el-form-item>
        <el-form-item label="风险点">
          <el-input v-model="form.risk_point" type="textarea" :rows="2" />
        </el-form-item>
        <el-form-item label="风险等级">
          <el-select v-model="form.risk_level" placeholder="请选择风险等级">
            <el-option label="重大" value="MAJOR" />
            <el-option label="较大" value="LARGER" />
            <el-option label="一般" value="GENERAL" />
            <el-option label="低" value="LOW" />
          </el-select>
        </el-form-item>
        <el-form-item label="危害程度">
          <el-select v-model="form.harm_degree" placeholder="请选择危害程度">
            <el-option label="极高" value="VERY_HIGH" />
            <el-option label="高" value="HIGH" />
            <el-option label="中" value="MEDIUM" />
            <el-option label="低" value="LOW" />
          </el-select>
        </el-form-item>
        <el-form-item label="控制措施">
          <el-input v-model="form.control_measures" type="textarea" :rows="3" />
        </el-form-item>
        <el-form-item label="控制位置">
          <el-input v-model="form.control_position" />
        </el-form-item>
        <el-form-item label="控制人员">
          <el-input v-model="form.control_personnel" />
        </el-form-item>
        <el-form-item label="量化要求">
          <el-input v-model="form.quantified_requirements" type="textarea" :rows="2" />
        </el-form-item>
        <el-form-item label="录入开始日期">
          <el-date-picker
              v-model="form.entry_start_date"
              type="date"
              placeholder="选择开始日期"
              value-format="YYYY-MM-DD"
          />
        </el-form-item>
        <el-form-item label="录入结束日期">
          <el-date-picker
              v-model="form.entry_end_date"
              type="date"
              placeholder="选择结束日期"
              value-format="YYYY-MM-DD"
          />
        </el-form-item>
        <el-form-item label="审核状态">
          <el-select v-model="form.review_status" placeholder="请选择审核状态">
            <el-option label="待审核" value="PENDING" />
            <el-option label="已通过" value="APPROVED" />
            <el-option label="已拒绝" value="REJECTED" />
          </el-select>
        </el-form-item>
        <el-form-item label="审核日期">
          <el-date-picker
              v-model="form.review_date"
              type="date"
              placeholder="选择审核日期"
              value-format="YYYY-MM-DD"
          />
        </el-form-item>
      </el-form>

      <template #footer>
        <span class="dialog-footer">
          <el-button @click="dialogVisible = false">取消</el-button>
          <el-button type="primary" @click="save">确认</el-button>
        </span>
      </template>
    </el-dialog>

    <el-table :data="tableData" stripe style="width: 100%">
      <!-- 添加序号列 -->
      <el-table-column label="序号" width="60" fixed>
        <template #default="scope">
          {{ scope.$index + 1 }}
        </template>
      </el-table-column>

      <el-table-column prop="id" label="数据库ID" width="80" />
      <el-table-column prop="risk_code" label="风险代码" />
      <el-table-column prop="department" label="部门" />
      <el-table-column prop="professional_system" label="专业系统" />
      <el-table-column prop="risk_category" label="风险类别" />
      <el-table-column prop="risk_item" label="风险事项" />
      <el-table-column prop="risk_point" label="风险点" />
      <el-table-column prop="risk_level" label="风险等级">
        <template #default="scope">
          <el-tag :type="getRiskLevelTagType(scope.row.risk_level)">
            {{ formatRiskLevel(scope.row.risk_level) }}
          </el-tag>
        </template>
      </el-table-column>
      <el-table-column prop="harm_degree" label="危害程度">
        <template #default="scope">
          <el-tag :type="getHarmDegreeTagType(scope.row.harm_degree)">
            {{ formatHarmDegree(scope.row.harm_degree) }}
          </el-tag>
        </template>
      </el-table-column>
      <el-table-column prop="control_measures" label="控制措施" show-overflow-tooltip />
      <el-table-column prop="control_position" label="控制位置" />
      <el-table-column prop="control_personnel" label="控制人员" />
      <el-table-column prop="quantified_requirements" label="量化要求" show-overflow-tooltip />
      <el-table-column prop="entry_start_date" label="录入开始日期" width="120" />
      <el-table-column prop="entry_end_date" label="录入结束日期" width="120" />
      <el-table-column prop="review_status" label="审核状态">
        <template #default="scope">
          <el-tag :type="getStatusTagType(scope.row.review_status)">
            {{ formatReviewStatus(scope.row.review_status) }}
          </el-tag>
        </template>
      </el-table-column>
      <el-table-column prop="review_date" label="审核日期" width="120" />
      <el-table-column fixed="right" label="操作" width="150">
        <template #default="scope">
          <el-button type="primary" size="small" @click="handleEdit(scope.row)">编辑</el-button>
          <el-popconfirm title="确认删除?" @confirm="handleDelete(scope.row.id)">
            <template #reference>
              <el-button type="danger" size="small">删除</el-button>
            </template>
          </el-popconfirm>
        </template>
      </el-table-column>
    </el-table>
  </div>
</template>

<script>
import request from '@/utils/request'

export default {
  name: 'SafetyRiskView',
  data() {
    return {
      form: {
        id: null,
        risk_code: '',
        department: '',
        professional_system: '',
        risk_category: '',
        risk_item: '',
        risk_point: '',
        risk_level: '',
        harm_degree: '',
        control_measures: '',
        control_position: '',
        control_personnel: '',
        quantified_requirements: '',
        entry_start_date: null,
        entry_end_date: null,
        review_status: 'PENDING',
        review_date: null,
      },
      dialogVisible: false,
      tableData: [],
      labelPosition: 'right'
    }
  },
  created() {
    this.load();
  },
  methods: {
    add() {
      this.form = {
        id: null,
        risk_code: '',
        department: '',
        professional_system: '',
        risk_category: '',
        risk_item: '',
        risk_point: '',
        risk_level: '',
        harm_degree: '',
        control_measures: '',
        control_position: '',
        control_personnel: '',
        quantified_requirements: '',
        entry_start_date: null,
        entry_end_date: null,
        review_status: 'PENDING',
        review_date: null,
      };
      this.dialogVisible = true;
    },
    load() {
      request.get("http://localhost:9090/safetyrisk/all").then(res => {
        this.tableData = res.content;
      });
    },
    save() {
      const api = this.form.id
          ? `http://localhost:9090/safetyrisk/${this.form.id}`
          : "http://localhost:9090/safetyrisk";
      request[this.form.id ? 'put' : 'post'](api, this.form).then(() => {
        this.$message.success(this.form.id ? '更新成功' : '添加成功');
        this.load();
        this.dialogVisible = false;
      }).catch(error => {
        this.$message.error('操作失败: ' + (error.response?.data?.message || error.message));
      });
    },
    handleEdit(row) {
      this.form = JSON.parse(JSON.stringify(row));
      this.dialogVisible = true;
    },
    handleDelete(id) {
      request.delete(`http://localhost:9090/safetyrisk/${id}`).then(() => {
        this.$message.success('删除成功');
        this.load();
      }).catch(error => {
        this.$message.error('删除失败: ' + (error.response?.data?.message || error.message));
      });
    },
    handleClose(done) {
      done();
    },
    formatRiskLevel(level) {
      const map = {
        'MAJOR': '重大',
        'LARGER': '较大',
        'GENERAL': '一般',
        'LOW': '低'
      }
      return map[level] || level;
    },
    formatHarmDegree(degree) {
      const map = {
        'VERY_HIGH': '极高',
        'HIGH': '高',
        'MEDIUM': '中',
        'LOW': '低'
      }
      return map[degree] || degree;
    },
    formatReviewStatus(status) {
      const map = {
        'PENDING': '待审核',
        'APPROVED': '已通过',
        'REJECTED': '已拒绝'
      }
      return map[status] || status;
    },
    getRiskLevelTagType(level) {
      const map = {
        'MAJOR': 'danger',
        'LARGER': 'warning',
        'GENERAL': '',
        'LOW': 'success'
      }
      return map[level] || '';
    },
    getHarmDegreeTagType(degree) {
      const map = {
        'VERY_HIGH': 'danger',
        'HIGH': 'warning',
        'MEDIUM': '',
        'LOW': 'success'
      }
      return map[degree] || '';
    },
    getStatusTagType(status) {
      const map = {
        'PENDING': 'warning',
        'APPROVED': 'success',
        'REJECTED': 'danger'
      }
      return map[status] || '';
    }
  }
}
</script>

<style scoped>
.dialog-footer {
  display: flex;
  justify-content: flex-end;
}
</style>

AboutView.vue

<template>
  <div>
    <!-- 搜索区域 - 只保留时间范围查询 -->
    <div class="search-container">
      <el-form :inline="true" :model="searchForm" class="search-form">
        <el-form-item label="系统更新时间范围">
          <el-date-picker
              v-model="searchForm.timeRange"
              type="datetimerange"
              range-separator="至"
              start-placeholder="开始时间"
              end-placeholder="结束时间"
              value-format="YYYY-MM-DD HH:mm:ss"
              style="width: 380px"
          />
        </el-form-item>

        <el-form-item>
          <el-button type="primary" @click="handleSearch">查询系统记录</el-button>
          <el-button @click="resetSearch">重置</el-button>
        </el-form-item>
      </el-form>
    </div>

    <!-- 表格展示系统历史记录 -->
    <el-table
        :data="tableData"
        stripe
        style="width: 100%"
        v-loading="loading"
    >
      <el-table-column label="序号" width="60">
        <template #default="scope">
          {{ scope.$index + 1 }}
        </template>
      </el-table-column>

      <el-table-column prop="historyId" label="记录ID" width="100" />
      <el-table-column prop="safetyRisk.risk_code" label="风险代码" />

      <!-- 操作来源列 -->
      <el-table-column label="操作来源" width="150">
        <template #default="scope">
          <div class="operator-cell">
            <el-icon class="system-icon">
              <Cpu />
            </el-icon>
            <span class="system-operator">
              {{ formatOperator(scope.row.updateUser) }}
            </span>
          </div>
        </template>
      </el-table-column>

      <el-table-column label="更新时间" width="180">
        <template #default="scope">
          {{ scope.row.updateTime_str || '无' }}
        </template>
      </el-table-column>

      <el-table-column label="值对比">
        <template #default="scope">
          <div class="value-comparison-container">
            <div class="value-header">
              <div class="value-title old-value-title">旧值</div>
              <div class="value-title new-value-title">新值</div>
            </div>
            <div class="value-diff-container">
              <div
                  v-for="(diff, index) in getValueDiffs(scope.row)"
                  :key="index"
                  class="value-row"
              >
                <div class="value-cell old-value-cell">
                  <span class="property-name">{{ diff.property }}:</span>
                  <span>{{ diff.oldValue || '无' }}</span>
                </div>
                <div class="value-cell new-value-cell">
                  <span class="property-name">{{ diff.property }}:</span>
                  <span>{{ diff.newValue || '无' }}</span>
                </div>
              </div>
            </div>
          </div>
        </template>
      </el-table-column>

      <!-- 添加回溯操作列 -->
      <el-table-column fixed="right" label="操作" width="120">
        <template #default="scope">
          <el-popconfirm
              title="确认回溯到该历史记录?"
              @confirm="handleRevert(scope.row)"
          >
            <template #reference>
              <el-button type="primary" size="small">回溯</el-button>
            </template>
          </el-popconfirm>
        </template>
      </el-table-column>
    </el-table>

    <!-- 分页组件 -->
    <div class="pagination-container">
      <el-pagination
          v-model:current-page="pagination.current"
          v-model:page-size="pagination.size"
          :total="pagination.total"
          :page-sizes="[10, 20, 50, 100]"
          layout="total, sizes, prev, pager, next, jumper"
          @size-change="handleSizeChange"
          @current-change="handleCurrentChange"
      />
    </div>
  </div>
</template>

<script setup>
/* eslint-disable */
import { ref, onMounted } from 'vue'
import { Cpu } from '@element-plus/icons-vue'
import request from '@/utils/request'
import { ElMessage } from 'element-plus'

// 数据定义
const searchForm = ref({
  timeRange: []
})

const tableData = ref([])
const loading = ref(false)

// 分页数据
const pagination = ref({
  current: 1,
  size: 10,
  total: 0
})

// 操作来源格式化
const formatOperator = (operator) => {
  const map = {
    'system': '系统自动',
    'root@localhost': '系统管理员',
    'system-revert': '系统回溯'
  }
  return map[operator] || operator
}

// 数据加载方法
const loadSystemData = async () => {
  loading.value = true
  try {
    let url = "http://localhost:9090/safetyriskhistory/system"
    let params = {
      page: pagination.value.current,
      size: pagination.value.size
    }

    // 如果有时间范围,使用时间范围查询
    if (searchForm.value.timeRange?.length === 2) {
      url = "http://localhost:9090/safetyriskhistory/system/byUpdateTime"
      params.startTime = searchForm.value.timeRange[0]
      params.endTime = searchForm.value.timeRange[1]
    }

    const res = await request.get(url, { params })
    tableData.value = res.content
    pagination.value.total = res.totalElements
  } catch (error) {
    console.error('加载系统数据失败:', error)
    ElMessage.error('加载系统数据失败')
  } finally {
    loading.value = false
  }
}

// 回溯方法
const handleRevert = async (historyRecord) => {
  try {
    loading.value = true;

    // 直接从历史记录获取风险代码
    const riskCode = historyRecord.safetyRisk?.risk_code;
    if (!riskCode) {
      throw new Error('无法从历史记录中获取风险代码');
    }

    // 解析旧值数据
    let revertData = {};
    try {
      revertData = JSON.parse(historyRecord.oldValue || '{}');
    } catch (e) {
      console.error('解析历史记录失败:', e);
      throw new Error('解析历史记录数据失败');
    }

    // 发送请求到正确的端点
    const response = await request.post(
        `/safetyriskhistory/revert/${encodeURIComponent(riskCode)}`,
        revertData,
        {
          headers: {
            'Content-Type': 'application/json'
          }
        }
    );

    ElMessage.success('追溯成功');
    loadSystemData();
  } catch (error) {
    console.error('追溯错误:', error);
    ElMessage.error(`追溯失败: ${error.response?.data?.message || error.message}`);
  } finally {
    loading.value = false;
  }
};

// 搜索相关方法
const handleSearch = () => {
  pagination.value.current = 1
  loadSystemData()
}

const resetSearch = () => {
  searchForm.value.timeRange = []
  pagination.value.current = 1
  loadSystemData()
}

// 分页方法
const handleSizeChange = (val) => {
  pagination.value.size = val
  loadSystemData()
}

const handleCurrentChange = (val) => {
  pagination.value.current = val
  loadSystemData()
}

// 值对比方法
const getValueDiffs = (row) => {
  try {
    const oldObj = JSON.parse(row.oldValue || '{}')
    const newObj = JSON.parse(row.newValue || '{}')
    const allProperties = new Set([...Object.keys(oldObj), ...Object.keys(newObj)])

    return Array.from(allProperties).map(prop => ({
      property: formatPropertyName(prop),
      oldValue: formatValue(oldObj[prop]),
      newValue: formatValue(newObj[prop])
    }))
  } catch (e) {
    return [{
      property: '原始数据',
      oldValue: row.oldValue || '无',
      newValue: row.newValue || '无'
    }]
  }
}

const formatPropertyName = (prop) => {
  const nameMap = {
    'risk_code': '风险编码',
    'department': '责任部门',
    'professional_system': '专业系统',
    'risk_category': '风险类别',
    'risk_item': '风险事项',
    'risk_point': '风险点',
    'risk_level': '风险等级',
    'harm_degree': '危害程度',
    'control_measures': '控制措施',
    'control_position': '控制位置',
    'control_personnel': '控制人员',
    'quantified_requirements': '量化要求',
    'entry_start_date': '录入开始日期',
    'entry_end_date': '录入结束日期',
    'review_status': '审核状态',
    'review_date': '审核日期'
  }
  return nameMap[prop] || prop
}

const formatValue = (value) => {
  if (value === null || value === undefined) return '无'
  return String(value)
}

// 初始化加载
onMounted(() => {
  loadSystemData()
})
</script>

<style scoped>
.search-container {
  margin-bottom: 20px;
  padding: 20px;
  background-color: #f5f7fa;
  border-radius: 4px;
}

.operator-cell {
  display: flex;
  align-items: center;
  gap: 8px;
}

.system-icon {
  color: #909399;
}

.system-operator {
  color: #606266;
  font-weight: 500;
}

.value-comparison-container {
  border: 1px solid #ebeef5;
  border-radius: 4px;
}

.value-header {
  display: flex;
  background-color: #f5f7fa;
  font-weight: bold;
}

.value-title {
  flex: 1;
  padding: 8px 12px;
  text-align: center;
}

.old-value-title {
  background-color: #fef0f0;
  color: #f56c6c;
}

.new-value-title {
  background-color: #f0f9eb;
  color: #67c23a;
}

.value-diff-container {
  max-height: 300px;
  overflow-y: auto;
}

.value-row {
  display: flex;
  border-bottom: 1px solid #ebeef5;
}

.value-cell {
  flex: 1;
  padding: 8px 12px;
}

.old-value-cell {
  border-right: 1px solid #ebeef5;
  background-color: #fef0f0;
}

.new-value-cell {
  background-color: #f0f9eb;
}

.property-name {
  font-weight: bold;
  color: #606266;
}

.pagination-container {
  margin-top: 20px;
  display: flex;
  justify-content: flex-end;
}
</style>
posted @ 2025-05-14 12:42  呓语-MSHK  阅读(17)  评论(0)    收藏  举报