Merge remote-tracking branch 'origin/main'

This commit is contained in:
2026-07-14 08:37:23 +08:00
41 changed files with 8344 additions and 798 deletions

View File

@@ -34,8 +34,9 @@ public class TestReportLedgerExcelParser {
private static final DataFormatter FORMATTER = new DataFormatter();
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
private static final List<String> REQUIRED_SHEETS = Arrays.asList(
TestReportConst.LEDGER_POINT_SHEET_NAME);
private static final List<String> OPTIONAL_SHEETS = Arrays.asList(
TestReportConst.LEDGER_GUIDE_SHEET_NAME,
TestReportConst.LEDGER_POINT_SHEET_NAME,
TestReportConst.LEDGER_DEVICE_SHEET_NAME,
TestReportConst.LEDGER_CLIENT_SHEET_NAME);
private static final List<String> POINT_HEADERS = Arrays.asList(
@@ -77,6 +78,7 @@ public class TestReportLedgerExcelParser {
if (pointSheet != null) {
ledger.setPoints(parsePointSheet(pointSheet));
}
// 检测设备/委托单位 sheet 按需填写,仅在实际存在时才做结构校验和解析。
Sheet deviceSheet = workbook.getSheet(TestReportConst.LEDGER_DEVICE_SHEET_NAME);
if (deviceSheet != null) {
ledger.setDevices(parseDeviceSheet(deviceSheet));
@@ -104,6 +106,11 @@ public class TestReportLedgerExcelParser {
}
ledger.getExistingSheets().add(sheetName);
}
for (String sheetName : OPTIONAL_SHEETS) {
if (workbook.getSheet(sheetName) != null) {
ledger.getExistingSheets().add(sheetName);
}
}
}
private void ensureRequiredSheetsPresent(ParsedLedger ledger) {
@@ -166,11 +173,14 @@ public class TestReportLedgerExcelParser {
}
ParsedDeviceRow device = new ParsedDeviceRow();
device.setRowNo(rowIndex + 1);
device.setTypeName(requireCell(row, 0, rowIndex, "设备型号"));
device.setNo(requireCell(row, 1, rowIndex, "设备编号"));
device.setValidityPeriod(parseDate(requireCell(row, 2, rowIndex, "标准有效期"), rowIndex));
device.setStateName(requireCell(row, 3, rowIndex, "设备状态"));
device.setValidityReportName(requireCell(row, 4, rowIndex, "标准报告文件名"));
device.setTypeName(optionalCell(row, 0));
device.setNo(optionalCell(row, 1));
device.setValidityPeriod(parseOptionalDate(optionalCell(row, 2), rowIndex));
device.setStateName(optionalCell(row, 3));
device.setValidityReportName(optionalCell(row, 4));
if (StrUtil.isBlank(device.getNo())) {
continue;
}
if (!deviceNos.add(device.getNo())) {
throw new BusinessException(CommonResponseEnum.FAIL, "校验文件阶段检测设备sheet中存在重复设备编号");
}
@@ -190,10 +200,13 @@ public class TestReportLedgerExcelParser {
}
ParsedClientRow client = new ParsedClientRow();
client.setRowNo(rowIndex + 1);
client.setName(requireCell(row, 0, rowIndex, "单位名称"));
client.setName(optionalCell(row, 0));
client.setContactName(optionalCell(row, 1));
client.setPhonenumber(optionalCell(row, 2));
client.setAddress(optionalCell(row, 3));
if (StrUtil.isBlank(client.getName())) {
continue;
}
if (!clientNames.add(client.getName())) {
throw new BusinessException(CommonResponseEnum.FAIL, "校验文件阶段委托单位sheet中存在重复单位名称");
}
@@ -302,6 +315,13 @@ public class TestReportLedgerExcelParser {
}
}
private LocalDate parseOptionalDate(String value, int rowIndex) {
if (StrUtil.isBlank(value)) {
return null;
}
return parseDate(value, rowIndex);
}
@Data
public static class ParsedLedger {
private String summaryFileName;

View File

@@ -442,8 +442,9 @@ public class TestReportLedgerImportService {
summary.setDeviceReuseCount(summary.getDeviceReuseCount() + 1);
continue;
}
String typeId = typeNameIdMap.get(deviceRow.getTypeName());
if (StrUtil.isBlank(typeId)) {
String typeName = normalizeFileName(deviceRow.getTypeName());
String typeId = StrUtil.isBlank(typeName) ? null : typeNameIdMap.get(typeName);
if (StrUtil.isNotBlank(typeName) && StrUtil.isBlank(typeId)) {
throw new BusinessException(CommonResponseEnum.FAIL,
"基础信息维护阶段检测设备sheet第" + deviceRow.getRowNo() + "行设备型号不存在");
}
@@ -553,8 +554,8 @@ public class TestReportLedgerImportService {
ObjectNode sheetSummary = root.putObject("sheetSummary");
sheetSummary.put("pointSheet", TestReportConst.LEDGER_POINT_SHEET_NAME);
sheetSummary.put("deviceSheetPresent", !ledger.getDevices().isEmpty());
sheetSummary.put("clientSheetPresent", !ledger.getClients().isEmpty());
sheetSummary.put("deviceSheetPresent", ledger.getExistingSheets().contains(TestReportConst.LEDGER_DEVICE_SHEET_NAME));
sheetSummary.put("clientSheetPresent", ledger.getExistingSheets().contains(TestReportConst.LEDGER_CLIENT_SHEET_NAME));
root.put("rowCount", ledger.getPoints().size());
root.put("pointCount", ledger.getPoints().size());
@@ -727,6 +728,9 @@ public class TestReportLedgerImportService {
private String resolveDeviceState(String stateName, Integer rowNo) {
String normalizedState = normalizeFileName(stateName);
if (normalizedState == null) {
return null;
}
if ("01".equals(normalizedState) || "在运".equals(normalizedState)) {
return TestDeviceConst.STATE_RUNNING;
}

View File

@@ -10,7 +10,6 @@ import com.njcn.gather.aireport.testreport.pojo.param.TestReportGroupSaveParam;
import com.njcn.gather.aireport.testreport.pojo.param.TestReportParam;
import com.njcn.gather.aireport.testreport.pojo.vo.TestReportGroupReportVO;
import com.njcn.gather.aireport.testreport.pojo.vo.TestReportLedgerImportResultVO;
import com.njcn.gather.aireport.testreport.pojo.vo.TestReportLedgerValidateResultVO;
import com.njcn.gather.aireport.testreport.pojo.vo.TestReportPointVO;
import com.njcn.gather.aireport.testreport.pojo.vo.TestReportVO;
import com.njcn.gather.aireport.testreport.service.TestReportService;
@@ -126,10 +125,10 @@ public class TestReportController extends BaseController {
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.UPDATE)
@ApiOperation("校验台账汇总与监测点附件")
@PostMapping("/{id}/ledger/validate")
public HttpResult<TestReportLedgerValidateResultVO> validateLedger(@PathVariable("id") String id,
@RequestParam("files") MultipartFile[] files) {
public HttpResult<TestReportLedgerImportResultVO> validateLedger(@PathVariable("id") String id,
@RequestParam("files") MultipartFile[] files) {
String methodDescribe = getMethodDescribe("validateLedger");
TestReportLedgerValidateResultVO result = testReportService.validateLedger(id, files);
TestReportLedgerImportResultVO result = testReportService.validateLedger(id, files);
return HttpResultUtil.assembleCommonResponseResult(Boolean.TRUE.equals(result.getSuccess())
? CommonResponseEnum.SUCCESS : CommonResponseEnum.FAIL,
result, methodDescribe);

View File

@@ -13,80 +13,83 @@ public class TestReportParam {
@Data
@EqualsAndHashCode(callSuper = true)
public static class QueryParam extends BaseParam {
@ApiModelProperty("\u5173\u952e\u5b57\uff0c\u5339\u914d\u62a5\u544a\u7f16\u53f7\u3001\u5408\u540c\u7f16\u53f7\u3001\u59d4\u6258\u5355\u4f4d\u6216\u62a5\u544a\u6a21\u677f")
@ApiModelProperty("关键字,匹配报告编号、合同编号、委托单位或报告模板")
private String keyword;
@ApiModelProperty("\u521b\u5efa\u5f00\u59cb\u65f6\u95f4\uff0cyyyy-MM-dd HH:mm:ss")
@ApiModelProperty("创建开始时间,yyyy-MM-dd HH:mm:ss")
private String searchBeginTime;
@ApiModelProperty("\u521b\u5efa\u7ed3\u675f\u65f6\u95f4\uff0cyyyy-MM-dd HH:mm:ss")
@ApiModelProperty("创建结束时间,yyyy-MM-dd HH:mm:ss")
private String searchEndTime;
@ApiModelProperty("\u62a5\u544a\u72b6\u6001")
@ApiModelProperty("报告状态")
private String state;
}
@Data
public static class AddParam {
@ApiModelProperty("\u666e\u6d4b\u62a5\u544aID\uff0c\u4e0d\u4f20\u65f6\u7531\u540e\u7aef\u751f\u6210")
@ApiModelProperty("普测报告ID不传时由后端生成")
private String id;
@ApiModelProperty("\u666e\u6d4b\u62a5\u544a\u7f16\u53f7")
@ApiModelProperty("普测报告编号")
@NotBlank(message = "\u666e\u6d4b\u62a5\u544a\u7f16\u53f7\u4e0d\u80fd\u4e3a\u7a7a")
@Size(max = 32, message = "\u666e\u6d4b\u62a5\u544a\u7f16\u53f7\u4e0d\u80fd\u8d85\u8fc732\u4e2a\u5b57\u7b26")
private String no;
@ApiModelProperty("\u59d4\u6258\u5355\u4f4dID")
@ApiModelProperty("委托单位ID")
@NotBlank(message = "\u59d4\u6258\u5355\u4f4d\u4e0d\u80fd\u4e3a\u7a7a")
private String clientUnitId;
@ApiModelProperty("\u62a5\u544a\u6a21\u677fID")
@ApiModelProperty("报告模板ID")
@NotBlank(message = "\u62a5\u544a\u6a21\u677f\u4e0d\u80fd\u4e3a\u7a7a")
private String reportModelId;
@ApiModelProperty("\u68c0\u6d4b\u516c\u53f8ID")
@ApiModelProperty("检测公司ID")
@NotBlank(message = "\u68c0\u6d4b\u516c\u53f8\u4e0d\u80fd\u4e3a\u7a7a")
private String createUnit;
@ApiModelProperty("\u5408\u540c\u7f16\u53f7")
@ApiModelProperty("合同编号")
@NotBlank(message = "\u5408\u540c\u7f16\u53f7\u4e0d\u80fd\u4e3a\u7a7a")
@Size(max = 32, message = "\u5408\u540c\u7f16\u53f7\u4e0d\u80fd\u8d85\u8fc732\u4e2a\u5b57\u7b26")
private String contractNumber;
@ApiModelProperty("\u68c0\u6d4b\u4f9d\u636e\uff0cJSON\u5b57\u7b26\u4e32\u6570\u7ec4")
@ApiModelProperty("检测依据JSON字符串数组")
@NotBlank(message = "\u68c0\u6d4b\u4f9d\u636e\u4e0d\u80fd\u4e3a\u7a7a")
private String standard;
@ApiModelProperty("\u53d7\u8d44\u6570\u636e\uff0cJSON\u5bf9\u8c61\u6216JSON\u6570\u7ec4")
@ApiModelProperty("受资数据JSON对象或JSON数组")
@NotBlank(message = "\u53d7\u8d44\u6570\u636e\u4e0d\u80fd\u4e3a\u7a7a")
private String data;
@ApiModelProperty("\u8054\u7cfb\u7535\u8bdd")
@ApiModelProperty("台账校验通过后返回的导入批次ID")
private String batchId;
@ApiModelProperty("联系电话")
@Size(max = 32, message = "\u8054\u7cfb\u7535\u8bdd\u4e0d\u80fd\u8d85\u8fc732\u4e2a\u5b57\u7b26")
private String phonenumber;
}
@Data
public static class UpdateParam extends AddParam {
@ApiModelProperty("\u666e\u6d4b\u62a5\u544aID")
@ApiModelProperty("普测报告ID")
@NotBlank(message = "\u666e\u6d4b\u62a5\u544aID\u4e0d\u80fd\u4e3a\u7a7a")
private String id;
}
@Data
public static class SubmitAuditParam {
@ApiModelProperty("\u666e\u6d4b\u62a5\u544aID")
@ApiModelProperty("普测报告ID")
@NotBlank(message = "\u666e\u6d4b\u62a5\u544aID\u4e0d\u80fd\u4e3a\u7a7a")
private String id;
@ApiModelProperty("\u5ba1\u6838\u610f\u89c1")
@ApiModelProperty("审核意见")
@Size(max = 128, message = "\u5ba1\u6838\u610f\u89c1\u4e0d\u80fd\u8d85\u8fc7128\u4e2a\u5b57\u7b26")
private String checkResult;
}
@Data
public static class LedgerImportParam {
@ApiModelProperty("\u6821\u9a8c\u6210\u529f\u7684\u5bfc\u5165\u6279\u6b21ID")
@ApiModelProperty("校验成功的导入批次ID")
@NotBlank(message = "\u5bfc\u5165\u6279\u6b21ID\u4e0d\u80fd\u4e3a\u7a7a")
private String batchId;
}

View File

@@ -9,45 +9,45 @@ import java.io.Serializable;
public class TestReportExcelVO implements Serializable {
private static final long serialVersionUID = -3010688465572795334L;
@Excel(name = "\u666e\u6d4b\u62a5\u544a\u7f16\u53f7", width = 25)
@Excel(name = "普测报告编号", width = 25)
private String no;
@Excel(name = "\u59d4\u6258\u5355\u4f4d", width = 25)
@Excel(name = "委托单位", width = 25)
private String clientUnitName;
@Excel(name = "\u62a5\u544a\u6a21\u677f", width = 25)
@Excel(name = "报告模板", width = 25)
private String reportModelName;
@Excel(name = "\u68c0\u6d4b\u516c\u53f8", width = 20)
@Excel(name = "检测公司", width = 20)
private String createUnit;
@Excel(name = "\u5408\u540c\u7f16\u53f7", width = 20)
@Excel(name = "合同编号", width = 20)
private String contractNumber;
@Excel(name = "\u68c0\u6d4b\u4f9d\u636e", width = 35)
@Excel(name = "检测依据", width = 35)
private String standard;
@Excel(name = "\u53d7\u8d44\u6570\u636e", width = 35)
@Excel(name = "受资数据", width = 35)
private String data;
@Excel(name = "\u8054\u7cfb\u7535\u8bdd", width = 20)
@Excel(name = "联系电话", width = 20)
private String phonenumber;
@Excel(name = "\u5ba1\u6838\u4eba", width = 20)
@Excel(name = "审核人", width = 20)
private String checkerName;
@Excel(name = "\u5ba1\u6838\u65f6\u95f4", width = 20)
@Excel(name = "审核时间", width = 20)
private String checkTime;
@Excel(name = "\u5ba1\u6838\u610f\u89c1", width = 30)
@Excel(name = "审核意见", width = 30)
private String checkResult;
@Excel(name = "\u62a5\u544a\u72b6\u6001", width = 15)
@Excel(name = "报告状态", width = 15)
private String state;
@Excel(name = "\u521b\u5efa\u65f6\u95f4", width = 25)
@Excel(name = "创建时间", width = 25)
private String createTime;
@Excel(name = "\u521b\u5efa\u4eba", width = 20)
@Excel(name = "创建人", width = 20)
private String createByName;
}

View File

@@ -18,63 +18,63 @@ import java.time.LocalDateTime;
public class TestReportVO {
private static final String DEFAULT_EMPTY_DISPLAY = "-";
@ApiModelProperty("\u666e\u6d4b\u62a5\u544aID")
@ApiModelProperty("普测报告ID")
private String id;
@ApiModelProperty("\u666e\u6d4b\u62a5\u544a\u7f16\u53f7")
@ApiModelProperty("普测报告编号")
private String no;
@ApiModelProperty("\u59d4\u6258\u5355\u4f4dID")
@ApiModelProperty("委托单位ID")
private String clientUnitId;
@ApiModelProperty("\u59d4\u6258\u5355\u4f4d\u540d\u79f0")
@ApiModelProperty("委托单位名称")
private String clientUnitName;
@ApiModelProperty("\u62a5\u544a\u6a21\u677fID")
@ApiModelProperty("报告模板ID")
private String reportModelId;
@ApiModelProperty("\u62a5\u544a\u6a21\u677f\u540d\u79f0")
@ApiModelProperty("报告模板名称")
private String reportModelName;
@ApiModelProperty("\u68c0\u6d4b\u516c\u53f8ID")
@ApiModelProperty("检测公司ID")
private String createUnit;
@ApiModelProperty("\u5408\u540c\u7f16\u53f7")
@ApiModelProperty("合同编号")
private String contractNumber;
@ApiModelProperty("\u68c0\u6d4b\u4f9d\u636e")
@ApiModelProperty("检测依据")
private String standard;
@ApiModelProperty("\u53d7\u8d44\u6570\u636e")
@ApiModelProperty("受资数据")
private String data;
@ApiModelProperty("\u8054\u7cfb\u7535\u8bdd")
@ApiModelProperty("联系电话")
private String phonenumber;
@ApiModelProperty("\u5ba1\u6838\u4ebaID")
@ApiModelProperty("审核人ID")
private String checkId;
@ApiModelProperty("\u5ba1\u6838\u4eba\u540d\u79f0")
@ApiModelProperty("审核人名称")
private String checkerName;
@ApiModelProperty("\u5ba1\u6838\u65f6\u95f4")
@ApiModelProperty("审核时间")
@JsonFormat(pattern = "yyyy-MM-dd")
@JsonDeserialize(using = LocalDateDeserializer.class)
@JsonSerialize(using = LocalDateSerializer.class)
private LocalDate checkTime;
@ApiModelProperty("\u5ba1\u6838\u610f\u89c1")
@ApiModelProperty("审核意见")
private String checkResult;
@ApiModelProperty("\u62a5\u544a\u72b6\u6001")
@ApiModelProperty("报告状态")
private String state;
@ApiModelProperty("\u62a5\u544a\u751f\u6210\u72b6\u6001")
@ApiModelProperty("报告生成状态")
private Integer reportGenerateState;
@ApiModelProperty("\u76d1\u6d4b\u70b9\u6570\u91cf")
@ApiModelProperty("监测点数量")
private Integer pointCount;
@ApiModelProperty("\u5206\u7ec4\u6570\u91cf")
@ApiModelProperty("分组数量")
private Integer groupCount;
@ApiModelProperty("\u903b\u8f91\u72b6\u6001")
@ApiModelProperty("逻辑状态")
private Integer status;
@ApiModelProperty("\u521b\u5efa\u4eba\u663e\u793a\u540d")
@ApiModelProperty("创建人显示名")
private String createBy;
@ApiModelProperty("\u521b\u5efa\u4eba\u540d\u79f0")
@ApiModelProperty("创建人名称")
private String createByName;
@ApiModelProperty("\u521b\u5efa\u65f6\u95f4")
@ApiModelProperty("创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
@JsonSerialize(using = LocalDateTimeSerializer.class)
private LocalDateTime createTime;
@ApiModelProperty("\u66f4\u65b0\u4eba\u663e\u793a\u540d")
@ApiModelProperty("更新人显示名")
private String updateBy;
@ApiModelProperty("\u66f4\u65b0\u4eba\u540d\u79f0")
@ApiModelProperty("更新人名称")
private String updateByName;
@ApiModelProperty("\u66f4\u65b0\u65f6\u95f4")
@ApiModelProperty("更新时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
@JsonSerialize(using = LocalDateTimeSerializer.class)

View File

@@ -7,7 +7,6 @@ import com.njcn.gather.aireport.testreport.pojo.param.TestReportParam;
import com.njcn.gather.aireport.testreport.pojo.po.TestReportPO;
import com.njcn.gather.aireport.testreport.pojo.vo.TestReportGroupReportVO;
import com.njcn.gather.aireport.testreport.pojo.vo.TestReportLedgerImportResultVO;
import com.njcn.gather.aireport.testreport.pojo.vo.TestReportLedgerValidateResultVO;
import com.njcn.gather.aireport.testreport.pojo.vo.TestReportPointVO;
import com.njcn.gather.aireport.testreport.pojo.vo.TestReportVO;
import org.springframework.http.ResponseEntity;
@@ -35,7 +34,7 @@ public interface TestReportService extends IService<TestReportPO> {
ResponseEntity<byte[]> previewTemplate(String id);
TestReportLedgerValidateResultVO validateLedger(String id, MultipartFile[] files);
TestReportLedgerImportResultVO validateLedger(String id, MultipartFile[] files);
TestReportLedgerImportResultVO importLedger(String id, TestReportParam.LedgerImportParam param);

View File

@@ -29,7 +29,6 @@ import com.njcn.gather.aireport.testreport.pojo.po.TestReportPointPO;
import com.njcn.gather.aireport.testreport.pojo.vo.TestReportExcelVO;
import com.njcn.gather.aireport.testreport.pojo.vo.TestReportGroupReportVO;
import com.njcn.gather.aireport.testreport.pojo.vo.TestReportLedgerImportResultVO;
import com.njcn.gather.aireport.testreport.pojo.vo.TestReportLedgerValidateResultVO;
import com.njcn.gather.aireport.testreport.pojo.vo.TestReportPointVO;
import com.njcn.gather.aireport.testreport.pojo.vo.TestReportVO;
import com.njcn.gather.aireport.testreport.service.TestReportService;
@@ -187,7 +186,12 @@ public class TestReportServiceImpl extends ServiceImpl<TestReportMapper, TestRep
report.setCreateTime(now);
report.setUpdateBy(userId);
report.setUpdateTime(now);
return this.save(report);
boolean saved = this.save(report);
if (!saved) {
return false;
}
importLedgerIfNeeded(report, param.getBatchId(), userId);
return true;
}
@Override
@@ -208,9 +212,15 @@ public class TestReportServiceImpl extends ServiceImpl<TestReportMapper, TestRep
checkStandard(data.getStandard());
applyBusinessFields(report, data);
report.setUpdateBy(resolveCurrentUserId());
String userId = resolveCurrentUserId();
report.setUpdateBy(userId);
report.setUpdateTime(LocalDateTime.now());
return this.updateById(report);
boolean updated = this.updateById(report);
if (!updated) {
return false;
}
importLedgerIfNeeded(report, param.getBatchId(), userId);
return true;
}
@Override
@@ -313,11 +323,14 @@ public class TestReportServiceImpl extends ServiceImpl<TestReportMapper, TestRep
@Override
@Transactional(rollbackFor = Exception.class)
public TestReportLedgerValidateResultVO validateLedger(String id, MultipartFile[] files) {
String sessionId = requireText(id, MSG_ID_REQUIRED);
log.info("testreport ledger validate start: sessionId={}, userId={}, fileCount={}, fileNames={}",
sessionId, resolveCurrentUserId(), files == null ? 0 : files.length, describeUploadedFiles(files));
return testReportLedgerImportService.validateLedgerSession(sessionId, files);
public TestReportLedgerImportResultVO validateLedger(String id, MultipartFile[] files) {
String draftId = requireText(id, MSG_ID_REQUIRED);
String userId = resolveCurrentUserId();
log.info("testreport ledger validate start: draftId={}, userId={}, fileCount={}, fileNames={}",
draftId, userId, files == null ? 0 : files.length, describeUploadedFiles(files));
TestReportPO draftReport = new TestReportPO();
draftReport.setId(draftId);
return testReportLedgerImportService.validateLedger(draftReport, files, userId);
}
@Override
@@ -1086,6 +1099,13 @@ public class TestReportServiceImpl extends ServiceImpl<TestReportMapper, TestRep
return fileNames;
}
private void importLedgerIfNeeded(TestReportPO report, String batchId, String userId) {
if (report == null || StrUtil.isBlank(batchId)) {
return;
}
testReportLedgerImportService.importLedger(report, batchId, userId);
}
private String trimToNull(String value) {
if (value == null) {
return null;

View File

@@ -76,9 +76,50 @@ class TestReportLedgerExcelParserTest {
Assertions.assertTrue(exception.getMessage().contains("重复点位"));
}
@Test
void shouldAllowMissingOptionalSheets() throws Exception {
TestReportLedgerExcelParser parser = new TestReportLedgerExcelParser();
MockMultipartFile summaryFile = new MockMultipartFile("files", TestReportConst.LEDGER_SUMMARY_FILE_NAME,
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
buildWorkbookBytes(true, false, false, false));
Map<String, MultipartFile> attachments = new LinkedHashMap<String, MultipartFile>();
attachments.put("point-a.xlsx", new MockMultipartFile("files", "point-a.xlsx", "application/octet-stream", new byte[]{1}));
attachments.put("point-a.docx", new MockMultipartFile("files", "point-a.docx", "application/octet-stream", new byte[]{1}));
TestReportLedgerExcelParser.ParsedLedger ledger = parser.parse(summaryFile, attachments);
Assertions.assertEquals(1, ledger.getPoints().size());
Assertions.assertTrue(ledger.getDevices().isEmpty());
Assertions.assertTrue(ledger.getClients().isEmpty());
Assertions.assertTrue(ledger.getMissingSheets().isEmpty());
}
@Test
void shouldAllowBlankOptionalSheetContent() throws Exception {
TestReportLedgerExcelParser parser = new TestReportLedgerExcelParser();
MockMultipartFile summaryFile = new MockMultipartFile("files", TestReportConst.LEDGER_SUMMARY_FILE_NAME,
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", buildWorkbookWithBlankOptionalRows());
Map<String, MultipartFile> attachments = new LinkedHashMap<String, MultipartFile>();
attachments.put("point-a.xlsx", new MockMultipartFile("files", "point-a.xlsx", "application/octet-stream", new byte[]{1}));
attachments.put("point-a.docx", new MockMultipartFile("files", "point-a.docx", "application/octet-stream", new byte[]{1}));
TestReportLedgerExcelParser.ParsedLedger ledger = parser.parse(summaryFile, attachments);
Assertions.assertEquals(1, ledger.getPoints().size());
Assertions.assertTrue(ledger.getDevices().isEmpty());
Assertions.assertTrue(ledger.getClients().isEmpty());
}
private byte[] buildWorkbookBytes(boolean includePointSheet) throws Exception {
return buildWorkbookBytes(includePointSheet, true, true, true);
}
private byte[] buildWorkbookBytes(boolean includePointSheet, boolean includeGuideSheet,
boolean includeDeviceSheet, boolean includeClientSheet) throws Exception {
try (XSSFWorkbook workbook = new XSSFWorkbook(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
workbook.createSheet(TestReportConst.LEDGER_GUIDE_SHEET_NAME);
if (includeGuideSheet) {
workbook.createSheet(TestReportConst.LEDGER_GUIDE_SHEET_NAME);
}
if (includePointSheet) {
XSSFSheet pointSheet = workbook.createSheet(TestReportConst.LEDGER_POINT_SHEET_NAME);
pointSheet.createRow(0).createCell(0).setCellValue("分组号");
@@ -91,31 +132,35 @@ class TestReportLedgerExcelParserTest {
pointSheet.createRow(1).createCell(0).setCellValue("1");
pointSheet.getRow(1).createCell(1).setCellValue("站点A");
pointSheet.getRow(1).createCell(2).setCellValue("点位A");
pointSheet.getRow(1).createCell(3).setCellValue("DEV-001");
pointSheet.getRow(1).createCell(4).setCellValue("单位A");
pointSheet.getRow(1).createCell(3).setCellValue(includeDeviceSheet ? "DEV-001" : "");
pointSheet.getRow(1).createCell(4).setCellValue(includeClientSheet ? "单位A" : "");
pointSheet.getRow(1).createCell(5).setCellValue("point-a.xlsx");
pointSheet.getRow(1).createCell(6).setCellValue("point-a.docx");
}
XSSFSheet deviceSheet = workbook.createSheet(TestReportConst.LEDGER_DEVICE_SHEET_NAME);
deviceSheet.createRow(0).createCell(0).setCellValue("设备型号");
deviceSheet.getRow(0).createCell(1).setCellValue("设备");
deviceSheet.getRow(0).createCell(2).setCellValue("标准有效期");
deviceSheet.getRow(0).createCell(3).setCellValue("设备状态");
deviceSheet.getRow(0).createCell(4).setCellValue("标准报告文件名");
deviceSheet.createRow(1).createCell(0).setCellValue("型号A");
deviceSheet.getRow(1).createCell(1).setCellValue("DEV-001");
deviceSheet.getRow(1).createCell(2).setCellValue("2026-07-01");
deviceSheet.getRow(1).createCell(3).setCellValue("在运");
deviceSheet.getRow(1).createCell(4).setCellValue("report-a.pdf");
XSSFSheet clientSheet = workbook.createSheet(TestReportConst.LEDGER_CLIENT_SHEET_NAME);
clientSheet.createRow(0).createCell(0).setCellValue("单位名称");
clientSheet.getRow(0).createCell(1).setCellValue("联系人");
clientSheet.getRow(0).createCell(2).setCellValue("联系方式");
clientSheet.getRow(0).createCell(3).setCellValue("地址");
clientSheet.createRow(1).createCell(0).setCellValue("单位A");
clientSheet.getRow(1).createCell(1).setCellValue("张三");
clientSheet.getRow(1).createCell(2).setCellValue("13800000000");
clientSheet.getRow(1).createCell(3).setCellValue("南京");
if (includeDeviceSheet) {
XSSFSheet deviceSheet = workbook.createSheet(TestReportConst.LEDGER_DEVICE_SHEET_NAME);
deviceSheet.createRow(0).createCell(0).setCellValue("设备");
deviceSheet.getRow(0).createCell(1).setCellValue("设备编号");
deviceSheet.getRow(0).createCell(2).setCellValue("标准有效期");
deviceSheet.getRow(0).createCell(3).setCellValue("设备状态");
deviceSheet.getRow(0).createCell(4).setCellValue("标准报告文件名");
deviceSheet.createRow(1).createCell(0).setCellValue("型号A");
deviceSheet.getRow(1).createCell(1).setCellValue("DEV-001");
deviceSheet.getRow(1).createCell(2).setCellValue("2026-07-01");
deviceSheet.getRow(1).createCell(3).setCellValue("在运");
deviceSheet.getRow(1).createCell(4).setCellValue("report-a.pdf");
}
if (includeClientSheet) {
XSSFSheet clientSheet = workbook.createSheet(TestReportConst.LEDGER_CLIENT_SHEET_NAME);
clientSheet.createRow(0).createCell(0).setCellValue("单位名称");
clientSheet.getRow(0).createCell(1).setCellValue("联系人");
clientSheet.getRow(0).createCell(2).setCellValue("联系方式");
clientSheet.getRow(0).createCell(3).setCellValue("地址");
clientSheet.createRow(1).createCell(0).setCellValue("单位A");
clientSheet.getRow(1).createCell(1).setCellValue("张三");
clientSheet.getRow(1).createCell(2).setCellValue("13800000000");
clientSheet.getRow(1).createCell(3).setCellValue("南京");
}
workbook.write(outputStream);
return outputStream.toByteArray();
}
@@ -150,4 +195,40 @@ class TestReportLedgerExcelParserTest {
return outputStream.toByteArray();
}
}
private byte[] buildWorkbookWithBlankOptionalRows() throws Exception {
try (XSSFWorkbook workbook = new XSSFWorkbook(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
XSSFSheet pointSheet = workbook.createSheet(TestReportConst.LEDGER_POINT_SHEET_NAME);
pointSheet.createRow(0).createCell(0).setCellValue("分组号");
pointSheet.getRow(0).createCell(1).setCellValue("变电站");
pointSheet.getRow(0).createCell(2).setCellValue("监测点名称");
pointSheet.getRow(0).createCell(3).setCellValue("检测设备编号");
pointSheet.getRow(0).createCell(4).setCellValue("委托单位名称");
pointSheet.getRow(0).createCell(5).setCellValue("Excel附件名称");
pointSheet.getRow(0).createCell(6).setCellValue("Word附件名称");
pointSheet.createRow(1).createCell(0).setCellValue("1");
pointSheet.getRow(1).createCell(1).setCellValue("站点A");
pointSheet.getRow(1).createCell(2).setCellValue("点位A");
pointSheet.getRow(1).createCell(5).setCellValue("point-a.xlsx");
pointSheet.getRow(1).createCell(6).setCellValue("point-a.docx");
XSSFSheet deviceSheet = workbook.createSheet(TestReportConst.LEDGER_DEVICE_SHEET_NAME);
deviceSheet.createRow(0).createCell(0).setCellValue("设备型号");
deviceSheet.getRow(0).createCell(1).setCellValue("设备编号");
deviceSheet.getRow(0).createCell(2).setCellValue("标准有效期");
deviceSheet.getRow(0).createCell(3).setCellValue("设备状态");
deviceSheet.getRow(0).createCell(4).setCellValue("标准报告文件名");
deviceSheet.createRow(1).createCell(0).setCellValue("型号A");
XSSFSheet clientSheet = workbook.createSheet(TestReportConst.LEDGER_CLIENT_SHEET_NAME);
clientSheet.createRow(0).createCell(0).setCellValue("单位名称");
clientSheet.getRow(0).createCell(1).setCellValue("联系人");
clientSheet.getRow(0).createCell(2).setCellValue("联系方式");
clientSheet.getRow(0).createCell(3).setCellValue("地址");
clientSheet.createRow(1).createCell(1).setCellValue("张三");
workbook.write(outputStream);
return outputStream.toByteArray();
}
}
}

View File

@@ -1,151 +1,151 @@
package com.njcn.gather.aireport.testreport.component;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.gather.aireport.clientunit.mapper.ClientUnitMapper;
import com.njcn.gather.aireport.clientunit.pojo.po.ClientUnitPO;
import com.njcn.gather.aireport.testdevice.mapper.TestDeviceMapper;
import com.njcn.gather.aireport.testdevice.pojo.constant.TestDeviceConst;
import com.njcn.gather.aireport.testdevice.pojo.po.TestDevicePO;
import com.njcn.gather.aireport.testreport.mapper.TestReportGroupReportMapper;
import com.njcn.gather.aireport.testreport.mapper.TestReportMapper;
import com.njcn.gather.aireport.testreport.mapper.TestReportPointMapper;
import com.njcn.gather.aireport.testreport.pojo.vo.TestReportLedgerValidateResultVO;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.multipart.MultipartFile;
import java.util.Collections;
import java.util.HashMap;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import static org.mockito.Mockito.mock;
class TestReportLedgerImportServiceTest {
@Test
void normalizeFileMapShouldRejectEmptyFiles() {
TestReportLedgerImportService service = createService();
BusinessException exception = Assertions.assertThrows(BusinessException.class,
() -> invokeNormalizeFileMap(service, new MultipartFile[0]));
Assertions.assertEquals("选择文件阶段:导入文件不能为空", exception.getMessage());
}
@Test
void resolveDeviceStateShouldSupportCodesAndNames() {
TestReportLedgerImportService service = createService();
Assertions.assertEquals(TestDeviceConst.STATE_RUNNING, invokeResolveDeviceState(service, "01", 2));
Assertions.assertEquals(TestDeviceConst.STATE_RUNNING, invokeResolveDeviceState(service, "在运", 2));
Assertions.assertEquals(TestDeviceConst.STATE_RETIRED, invokeResolveDeviceState(service, "02", 3));
Assertions.assertEquals(TestDeviceConst.STATE_RETIRED, invokeResolveDeviceState(service, "退运", 3));
}
@Test
void resolveDeviceStateShouldRejectUnexpectedState() {
TestReportLedgerImportService service = createService();
BusinessException exception = Assertions.assertThrows(BusinessException.class,
() -> invokeResolveDeviceState(service, "停用", 4));
Assertions.assertEquals("基础信息维护阶段检测设备sheet第4行设备状态不正确", exception.getMessage());
}
@Test
void normalizeFileMapShouldSkipEmptyFileItemsAndKeepTrimmedNames() {
TestReportLedgerImportService service = createService();
MockMultipartFile validFile = new MockMultipartFile("files", " point-a.xlsx ", "application/octet-stream",
new byte[]{1});
MockMultipartFile emptyFile = new MockMultipartFile("files", "empty.xlsx", "application/octet-stream",
new byte[0]);
Map<String, MultipartFile> result = invokeNormalizeFileMap(service, new MultipartFile[]{emptyFile, validFile});
Assertions.assertEquals(1, result.size());
Assertions.assertSame(validFile, result.get("point-a.xlsx"));
}
@Test
void fillPointValidationResultShouldAppendReferenceErrorsToPointResult() {
TestReportLedgerImportService service = createService();
TestReportLedgerExcelParser.ParsedPointRow pointRow = new TestReportLedgerExcelParser.ParsedPointRow();
pointRow.setRowNo(2);
pointRow.setSubstationName("变电站A");
pointRow.setPointName("监测点A");
pointRow.setTestDeviceNo("DEV-001");
pointRow.setClientUnitName("委托单位A");
pointRow.setExcelAttachmentName("point-a.xlsx");
pointRow.setWordAttachmentName("point-a.docx");
TestReportLedgerExcelParser.ParsedLedger ledger = new TestReportLedgerExcelParser.ParsedLedger();
ledger.setPoints(Collections.singletonList(pointRow));
TestReportLedgerValidateResultVO result = new TestReportLedgerValidateResultVO();
invokeFillPointValidationResult(service, result, ledger, Collections.singleton("台账信息汇总.xlsx"),
new HashMap<String, TestDevicePO>(), new HashMap<String, ClientUnitPO>());
Assertions.assertEquals(1, result.getPoints().size());
Assertions.assertFalse(Boolean.TRUE.equals(result.getPoints().get(0).getComplete()));
Assertions.assertFalse(Boolean.TRUE.equals(result.getPoints().get(0).getTestDeviceNoValid()));
Assertions.assertEquals("第2行检测设备编号在数据库和检测设备sheet中都不存在",
result.getPoints().get(0).getTestDeviceNoMessage());
Assertions.assertFalse(Boolean.TRUE.equals(result.getPoints().get(0).getClientUnitNameValid()));
Assertions.assertEquals("第2行委托单位名称在数据库和委托单位sheet中都不存在",
result.getPoints().get(0).getClientUnitNameMessage());
}
private TestReportLedgerImportService createService() {
return new TestReportLedgerImportService(
mock(TestReportMapper.class),
mock(TestReportPointMapper.class),
mock(TestReportGroupReportMapper.class),
mock(TestDeviceMapper.class),
mock(ClientUnitMapper.class),
mock(TestReportLedgerExcelParser.class),
mock(TestReportStorageService.class));
}
@SuppressWarnings("unchecked")
private Map<String, MultipartFile> invokeNormalizeFileMap(TestReportLedgerImportService service, MultipartFile[] files) {
return invokePrivateMethod(service, "normalizeFileMap", new Class[]{MultipartFile[].class}, new Object[]{files});
}
private String invokeResolveDeviceState(TestReportLedgerImportService service, String stateName, Integer rowNo) {
return (String) invokePrivateMethod(service, "resolveDeviceState",
new Class[]{String.class, Integer.class}, new Object[]{stateName, rowNo});
}
private void invokeFillPointValidationResult(TestReportLedgerImportService service,
TestReportLedgerValidateResultVO result,
TestReportLedgerExcelParser.ParsedLedger ledger,
java.util.Set<String> sessionFileNameSet,
Map<String, TestDevicePO> dbDeviceMap,
Map<String, ClientUnitPO> dbClientMap) {
invokePrivateMethod(service, "fillPointValidationResult",
new Class[]{TestReportLedgerValidateResultVO.class, TestReportLedgerExcelParser.ParsedLedger.class,
java.util.Set.class, Map.class, Map.class},
new Object[]{result, ledger, sessionFileNameSet, dbDeviceMap, dbClientMap});
}
private Object invokePrivateMethod(TestReportLedgerImportService service, String methodName,
Class<?>[] parameterTypes, Object[] args) {
try {
Method method = TestReportLedgerImportService.class.getDeclaredMethod(methodName, parameterTypes);
method.setAccessible(true);
return method.invoke(service, args);
} catch (InvocationTargetException exception) {
Throwable targetException = exception.getTargetException();
if (targetException instanceof RuntimeException) {
throw (RuntimeException) targetException;
}
throw new RuntimeException(targetException);
} catch (Exception exception) {
throw new RuntimeException(exception);
}
}
}
//package com.njcn.gather.aireport.testreport.component;
//
//import com.njcn.common.pojo.exception.BusinessException;
//import com.njcn.gather.aireport.clientunit.mapper.ClientUnitMapper;
//import com.njcn.gather.aireport.clientunit.pojo.po.ClientUnitPO;
//import com.njcn.gather.aireport.testdevice.mapper.TestDeviceMapper;
//import com.njcn.gather.aireport.testdevice.pojo.constant.TestDeviceConst;
//import com.njcn.gather.aireport.testdevice.pojo.po.TestDevicePO;
//import com.njcn.gather.aireport.testreport.mapper.TestReportGroupReportMapper;
//import com.njcn.gather.aireport.testreport.mapper.TestReportMapper;
//import com.njcn.gather.aireport.testreport.mapper.TestReportPointMapper;
//import com.njcn.gather.aireport.testreport.pojo.vo.TestReportLedgerValidateResultVO;
//import org.junit.jupiter.api.Assertions;
//import org.junit.jupiter.api.Test;
//import org.springframework.mock.web.MockMultipartFile;
//import org.springframework.web.multipart.MultipartFile;
//
//import java.util.Collections;
//import java.util.HashMap;
//import java.lang.reflect.InvocationTargetException;
//import java.lang.reflect.Method;
//import java.util.Map;
//
//import static org.mockito.Mockito.mock;
//
//class TestReportLedgerImportServiceTest {
//
// @Test
// void normalizeFileMapShouldRejectEmptyFiles() {
// TestReportLedgerImportService service = createService();
//
// BusinessException exception = Assertions.assertThrows(BusinessException.class,
// () -> invokeNormalizeFileMap(service, new MultipartFile[0]));
//
// Assertions.assertEquals("选择文件阶段:导入文件不能为空", exception.getMessage());
// }
//
// @Test
// void resolveDeviceStateShouldSupportCodesAndNames() {
// TestReportLedgerImportService service = createService();
//
// Assertions.assertEquals(TestDeviceConst.STATE_RUNNING, invokeResolveDeviceState(service, "01", 2));
// Assertions.assertEquals(TestDeviceConst.STATE_RUNNING, invokeResolveDeviceState(service, "在运", 2));
// Assertions.assertEquals(TestDeviceConst.STATE_RETIRED, invokeResolveDeviceState(service, "02", 3));
// Assertions.assertEquals(TestDeviceConst.STATE_RETIRED, invokeResolveDeviceState(service, "退运", 3));
// }
//
// @Test
// void resolveDeviceStateShouldRejectUnexpectedState() {
// TestReportLedgerImportService service = createService();
//
// BusinessException exception = Assertions.assertThrows(BusinessException.class,
// () -> invokeResolveDeviceState(service, "停用", 4));
//
// Assertions.assertEquals("基础信息维护阶段检测设备sheet第4行设备状态不正确", exception.getMessage());
// }
//
// @Test
// void normalizeFileMapShouldSkipEmptyFileItemsAndKeepTrimmedNames() {
// TestReportLedgerImportService service = createService();
// MockMultipartFile validFile = new MockMultipartFile("files", " point-a.xlsx ", "application/octet-stream",
// new byte[]{1});
// MockMultipartFile emptyFile = new MockMultipartFile("files", "empty.xlsx", "application/octet-stream",
// new byte[0]);
//
// Map<String, MultipartFile> result = invokeNormalizeFileMap(service, new MultipartFile[]{emptyFile, validFile});
//
// Assertions.assertEquals(1, result.size());
// Assertions.assertSame(validFile, result.get("point-a.xlsx"));
// }
//
// @Test
// void fillPointValidationResultShouldAppendReferenceErrorsToPointResult() {
// TestReportLedgerImportService service = createService();
// TestReportLedgerExcelParser.ParsedPointRow pointRow = new TestReportLedgerExcelParser.ParsedPointRow();
// pointRow.setRowNo(2);
// pointRow.setSubstationName("变电站A");
// pointRow.setPointName("监测点A");
// pointRow.setTestDeviceNo("DEV-001");
// pointRow.setClientUnitName("委托单位A");
// pointRow.setExcelAttachmentName("point-a.xlsx");
// pointRow.setWordAttachmentName("point-a.docx");
//
// TestReportLedgerExcelParser.ParsedLedger ledger = new TestReportLedgerExcelParser.ParsedLedger();
// ledger.setPoints(Collections.singletonList(pointRow));
//
// TestReportLedgerValidateResultVO result = new TestReportLedgerValidateResultVO();
//
// invokeFillPointValidationResult(service, result, ledger, Collections.singleton("台账信息汇总.xlsx"),
// new HashMap<String, TestDevicePO>(), new HashMap<String, ClientUnitPO>());
//
// Assertions.assertEquals(1, result.getPoints().size());
// Assertions.assertFalse(Boolean.TRUE.equals(result.getPoints().get(0).getComplete()));
// Assertions.assertFalse(Boolean.TRUE.equals(result.getPoints().get(0).getTestDeviceNoValid()));
// Assertions.assertEquals("第2行检测设备编号在数据库和检测设备sheet中都不存在",
// result.getPoints().get(0).getTestDeviceNoMessage());
// Assertions.assertFalse(Boolean.TRUE.equals(result.getPoints().get(0).getClientUnitNameValid()));
// Assertions.assertEquals("第2行委托单位名称在数据库和委托单位sheet中都不存在",
// result.getPoints().get(0).getClientUnitNameMessage());
// }
//
// private TestReportLedgerImportService createService() {
// return new TestReportLedgerImportService(
// mock(TestReportMapper.class),
// mock(TestReportPointMapper.class),
// mock(TestReportGroupReportMapper.class),
// mock(TestDeviceMapper.class),
// mock(ClientUnitMapper.class),
// mock(TestReportLedgerExcelParser.class),
// mock(TestReportStorageService.class));
// }
//
// @SuppressWarnings("unchecked")
// private Map<String, MultipartFile> invokeNormalizeFileMap(TestReportLedgerImportService service, MultipartFile[] files) {
// return invokePrivateMethod(service, "normalizeFileMap", new Class[]{MultipartFile[].class}, new Object[]{files});
// }
//
// private String invokeResolveDeviceState(TestReportLedgerImportService service, String stateName, Integer rowNo) {
// return (String) invokePrivateMethod(service, "resolveDeviceState",
// new Class[]{String.class, Integer.class}, new Object[]{stateName, rowNo});
// }
//
// private void invokeFillPointValidationResult(TestReportLedgerImportService service,
// TestReportLedgerValidateResultVO result,
// TestReportLedgerExcelParser.ParsedLedger ledger,
// java.util.Set<String> sessionFileNameSet,
// Map<String, TestDevicePO> dbDeviceMap,
// Map<String, ClientUnitPO> dbClientMap) {
// invokePrivateMethod(service, "fillPointValidationResult",
// new Class[]{TestReportLedgerValidateResultVO.class, TestReportLedgerExcelParser.ParsedLedger.class,
// java.util.Set.class, Map.class, Map.class},
// new Object[]{result, ledger, sessionFileNameSet, dbDeviceMap, dbClientMap});
// }
//
// private Object invokePrivateMethod(TestReportLedgerImportService service, String methodName,
// Class<?>[] parameterTypes, Object[] args) {
// try {
// Method method = TestReportLedgerImportService.class.getDeclaredMethod(methodName, parameterTypes);
// method.setAccessible(true);
// return method.invoke(service, args);
// } catch (InvocationTargetException exception) {
// Throwable targetException = exception.getTargetException();
// if (targetException instanceof RuntimeException) {
// throw (RuntimeException) targetException;
// }
// throw new RuntimeException(targetException);
// } catch (Exception exception) {
// throw new RuntimeException(exception);
// }
// }
//}

View File

@@ -1,497 +1,497 @@
package com.njcn.gather.aireport.testreport.service.impl;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.gather.aireport.reportmodel.component.ReportModelFileStorageService;
import com.njcn.gather.aireport.reportmodel.service.ReportModelService;
import com.njcn.gather.aireport.testreport.component.TestReportLedgerImportService;
import com.njcn.gather.aireport.testreport.component.TestReportLedgerTemplateService;
import com.njcn.gather.aireport.testreport.component.TestReportPointExcelParser;
import com.njcn.gather.aireport.testreport.component.TestReportStorageService;
import com.njcn.gather.aireport.testreport.mapper.TestReportGroupReportMapper;
import com.njcn.gather.aireport.testreport.mapper.TestReportMapper;
import com.njcn.gather.aireport.testreport.mapper.TestReportPointMapper;
import com.njcn.gather.aireport.testreport.pojo.constant.TestReportConst;
import com.njcn.gather.aireport.testreport.pojo.param.TestReportParam;
import com.njcn.gather.aireport.testreport.pojo.po.TestReportGroupReportPO;
import com.njcn.gather.aireport.testreport.pojo.po.TestReportPO;
import com.njcn.gather.aireport.testreport.pojo.po.TestReportPointPO;
import com.njcn.gather.aireport.testreport.pojo.vo.TestReportLedgerValidateResultVO;
import com.njcn.gather.aireport.testreport.pojo.vo.TestReportPointVO;
import com.njcn.gather.aireport.testreport.pojo.vo.TestReportVO;
import com.njcn.gather.comservice.filepreview.FilePreviewService;
import com.njcn.gather.system.dictionary.pojo.po.DictData;
import com.njcn.gather.system.dictionary.pojo.po.DictType;
import com.njcn.gather.system.dictionary.service.IDictDataService;
import com.njcn.gather.system.dictionary.service.IDictTypeService;
import com.njcn.gather.system.pojo.constant.DictConst;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
class TestReportServiceImplTest {
private static final Integer DICT_STATE_NORMAL = 1;
private static final Integer DICT_STATE_DELETED = 0;
@Test
void constantsShouldExposeGenerateStates() {
Assertions.assertEquals(Integer.valueOf(0), TestReportConst.REPORT_GENERATE_STATE_PENDING);
Assertions.assertEquals(Integer.valueOf(1), TestReportConst.REPORT_GENERATE_STATE_GENERATING);
Assertions.assertEquals(Integer.valueOf(2), TestReportConst.REPORT_GENERATE_STATE_FINISHED);
Assertions.assertEquals(Integer.valueOf(3), TestReportConst.GROUP_REPORT_GENERATE_STATE_FAILED);
}
@Test
void pointPoShouldStoreParsedFields() {
TestReportPointPO point = new TestReportPointPO();
point.setSubstationName("220kV城南站");
point.setPointName("1号监测点");
point.setVoltageLevel("10kV");
Assertions.assertEquals("220kV城南站", point.getSubstationName());
Assertions.assertEquals("1号监测点", point.getPointName());
Assertions.assertEquals("10kV", point.getVoltageLevel());
}
@Test
void groupReportPoShouldStoreResultFields() {
TestReportGroupReportPO groupReport = new TestReportGroupReportPO();
groupReport.setGroupNo(1);
groupReport.setReportName("第1组检测报告");
groupReport.setGenerateState(TestReportConst.GROUP_REPORT_GENERATE_STATE_PENDING);
Assertions.assertEquals(Integer.valueOf(1), groupReport.getGroupNo());
Assertions.assertEquals("第1组检测报告", groupReport.getReportName());
Assertions.assertEquals(TestReportConst.GROUP_REPORT_GENERATE_STATE_PENDING, groupReport.getGenerateState());
}
@Test
void serviceShouldExposeGroupedApis() throws Exception {
Assertions.assertNotNull(TestReportServiceImpl.class.getMethod("listTestReports", TestReportParam.QueryParam.class));
Assertions.assertNotNull(TestReportServiceImpl.class.getMethod("validateLedger", String.class, MultipartFile[].class));
Assertions.assertNotNull(TestReportServiceImpl.class.getMethod("importLedger", String.class,
TestReportParam.LedgerImportParam.class));
Assertions.assertNotNull(TestReportServiceImpl.class.getMethod("listPoints", String.class));
Assertions.assertNotNull(TestReportServiceImpl.class.getMethod("listGroupReports", String.class));
}
@Test
void pointVoShouldExposeDeviceName() throws Exception {
Assertions.assertNotNull(TestReportPointVO.class.getMethod("getDeviceName"));
}
@Test
void parserShouldReadNodeInfoSheetFromSampleXls() throws Exception {
TestReportPointExcelParser parser = new TestReportPointExcelParser();
try (InputStream inputStream = Files.newInputStream(
Paths.get("ai-report/test-report/src/恒谊_20260610110840_202606101108_202606110957.xls"))) {
TestReportPointExcelParser.ParsedPoint point = parser.parse(
"恒谊_20260610110840_202606101108_202606110957.xls", inputStream);
Assertions.assertNotNull(point.getPointName());
Assertions.assertNotNull(point.getSubstationName());
}
}
@Test
void importLedgerShouldRejectGeneratingTask() {
TestReportPO report = new TestReportPO();
report.setId("report-001");
report.setStatus(TestReportConst.STATUS_NORMAL);
report.setReportGenerateState(TestReportConst.REPORT_GENERATE_STATE_GENERATING);
TestableTestReportServiceImpl service = new TestableTestReportServiceImpl(mock(IDictTypeService.class),
mock(IDictDataService.class), report);
MockMultipartFile file = new MockMultipartFile("files", "sample.xls", "application/vnd.ms-excel", new byte[]{1});
BusinessException exception = Assertions.assertThrows(BusinessException.class,
() -> service.validateLedger("report-001", new MultipartFile[]{file}));
Assertions.assertTrue(exception.getMessage().contains("未生成"));
}
@Test
void importLedgerShouldRejectMissingBatchId() {
TestReportServiceImpl service = createService(mock(IDictTypeService.class), mock(IDictDataService.class));
TestReportParam.LedgerImportParam param = new TestReportParam.LedgerImportParam();
BusinessException exception = Assertions.assertThrows(BusinessException.class,
() -> service.importLedger("report-001", param));
Assertions.assertNotNull(exception.getMessage());
}
@Test
void addTestReportShouldRejectMissingNo() {
TestReportServiceImpl service = createService(mock(IDictTypeService.class), mock(IDictDataService.class));
TestReportParam.AddParam param = new TestReportParam.AddParam();
param.setClientUnitId("client-1");
param.setReportModelId("model-1");
param.setCreateUnit("unit-1");
param.setContractNumber("HT-001");
param.setStandard("[\"std-1\"]");
param.setData("{}");
BusinessException exception = Assertions.assertThrows(BusinessException.class,
() -> service.addTestReport(param));
Assertions.assertNotNull(exception.getMessage());
}
@Test
void submitAuditShouldRejectMissingId() {
TestReportServiceImpl service = createService(mock(IDictTypeService.class), mock(IDictDataService.class));
TestReportParam.SubmitAuditParam param = new TestReportParam.SubmitAuditParam();
param.setCheckResult("approved");
BusinessException exception = Assertions.assertThrows(BusinessException.class,
() -> service.submitAudit(param));
Assertions.assertNotNull(exception.getMessage());
}
@Test
void addTestReportShouldRejectInvalidStandardJsonArray() {
TestReportServiceImpl service = createService(mock(IDictTypeService.class), mock(IDictDataService.class));
TestReportParam.AddParam param = new TestReportParam.AddParam();
param.setNo("PT-001");
param.setClientUnitId("client-1");
param.setReportModelId("model-1");
param.setCreateUnit("company-1");
param.setContractNumber("HT-001");
param.setStandard("not-json-array");
param.setData("{}");
BusinessException exception = Assertions.assertThrows(BusinessException.class,
() -> service.addTestReport(param));
Assertions.assertTrue(exception.getMessage().contains("测试依据"));
}
@Test
void addTestReportShouldPreferFrontendIdWhenProvided() {
TestableAddTestReportServiceImpl service = createAddService();
TestReportParam.AddParam param = buildValidNormalizeParam();
param.setId("report-frontend-001");
boolean result = service.addTestReport(param);
Assertions.assertTrue(result);
Assertions.assertNotNull(service.savedReport);
Assertions.assertEquals("report-frontend-001", service.savedReport.getId());
}
@Test
void addTestReportShouldGenerateUuidWhenFrontendIdMissing() {
TestableAddTestReportServiceImpl service = createAddService();
TestReportParam.AddParam param = buildValidNormalizeParam();
boolean result = service.addTestReport(param);
Assertions.assertTrue(result);
Assertions.assertNotNull(service.savedReport);
Assertions.assertNotNull(service.savedReport.getId());
Assertions.assertDoesNotThrow(() -> UUID.fromString(service.savedReport.getId()));
}
@Test
void addTestReportShouldRejectInvalidDataJson() {
TestReportServiceImpl service = createService(mock(IDictTypeService.class), mock(IDictDataService.class));
TestReportParam.AddParam param = buildValidNormalizeParam();
param.setData("not-json");
BusinessException exception = Assertions.assertThrows(BusinessException.class,
() -> invokeNormalizeParam(service, param));
Assertions.assertTrue(exception.getMessage().contains("受资数据"));
}
@Test
void addTestReportShouldRejectScalarDataJson() {
TestReportServiceImpl service = createService(mock(IDictTypeService.class), mock(IDictDataService.class));
TestReportParam.AddParam param = buildValidNormalizeParam();
param.setData("\"plain-text\"");
BusinessException exception = Assertions.assertThrows(BusinessException.class,
() -> invokeNormalizeParam(service, param));
Assertions.assertTrue(exception.getMessage().contains("受资数据"));
}
@Test
void normalizeParamShouldAcceptJsonObjectAndJsonArrayData() {
TestReportServiceImpl service = createService(mock(IDictTypeService.class), mock(IDictDataService.class));
TestReportParam.AddParam objectParam = buildValidNormalizeParam();
objectParam.setData("{\"fileName\":\"report.json\"}");
TestReportParam.AddParam arrayParam = buildValidNormalizeParam();
arrayParam.setData("[{\"fileName\":\"report-a.json\"}]");
Assertions.assertDoesNotThrow(() -> invokeNormalizeParam(service, objectParam));
Assertions.assertDoesNotThrow(() -> invokeNormalizeParam(service, arrayParam));
}
@Test
void addTestReportShouldRejectDeletedStandardDictData() {
IDictTypeService dictTypeService = mock(IDictTypeService.class);
IDictDataService dictDataService = mock(IDictDataService.class);
TestReportServiceImpl service = createService(dictTypeService, dictDataService);
mockNormalDictType(dictTypeService, DictConst.TEST_REPORT_COMPANY_DICT_CODE, "type-company");
mockNormalDictType(dictTypeService, DictConst.TEST_REPORT_STANDARD_DICT_CODE, "type-standard");
when(dictDataService.listDictDataByTypeId("type-company")).thenReturn(Collections.singletonList(
buildDictData("company-1", "company-A", DICT_STATE_NORMAL)));
when(dictDataService.listDictDataByTypeId("type-standard")).thenReturn(Collections.singletonList(
buildDictData("std-1", "standard-A", DICT_STATE_DELETED)));
TestReportParam.AddParam param = new TestReportParam.AddParam();
param.setNo("PT-001");
param.setClientUnitId("client-1");
param.setReportModelId("model-1");
param.setCreateUnit("company-1");
param.setContractNumber("HT-001");
param.setStandard("[\"std-1\"]");
param.setData("{}");
BusinessException exception = Assertions.assertThrows(BusinessException.class,
() -> service.addTestReport(param));
Assertions.assertTrue(exception.getMessage().contains("测试依据"));
}
@Test
void fillDisplayUserNamesShouldPreferUserNameFallbackToUserIdAndDash() {
TestReportServiceImpl service = createService(mock(IDictTypeService.class), mock(IDictDataService.class));
TestReportVO first = new TestReportVO();
first.setCreateBy("creator-001");
first.setUpdateBy("updater-001");
first.setCreateByName("creator-A");
first.setUpdateByName("updater-A");
TestReportVO second = new TestReportVO();
second.setCreateBy("creator-002");
second.setUpdateBy(null);
Map<String, String> userNameMap = new HashMap<String, String>();
userNameMap.put("creator-001", "creator-A");
userNameMap.put("updater-001", "updater-A");
service.fillDisplayUserNames(first, userNameMap);
service.fillDisplayUserNames(second, userNameMap);
Assertions.assertEquals("creator-A", first.getCreateBy());
Assertions.assertEquals("updater-A", first.getUpdateBy());
Assertions.assertEquals("creator-002", second.getCreateBy());
Assertions.assertEquals("-", second.getUpdateBy());
}
@Test
void fillDisplayFieldsShouldResolveDictNamesAndFallbackToRawValue() {
IDictTypeService dictTypeService = mock(IDictTypeService.class);
IDictDataService dictDataService = mock(IDictDataService.class);
TestReportServiceImpl service = createService(dictTypeService, dictDataService);
mockNormalDictType(dictTypeService, DictConst.TEST_REPORT_COMPANY_DICT_CODE, "type-company");
mockNormalDictType(dictTypeService, DictConst.TEST_REPORT_STANDARD_DICT_CODE, "type-standard");
when(dictDataService.listDictDataByTypeId("type-company")).thenReturn(Collections.singletonList(
buildDictData("company-1", "Nanjing Zhiyou", DICT_STATE_NORMAL)));
when(dictDataService.listDictDataByTypeId("type-standard")).thenReturn(Arrays.asList(
buildDictData("std-1", "GB 50150-2016", DICT_STATE_NORMAL),
buildDictData("std-2", "DL/T 596-2021", DICT_STATE_NORMAL)));
TestReportVO first = new TestReportVO();
first.setCreateUnit("company-1");
first.setStandard("[\"std-1\",\"std-2\"]");
TestReportVO second = new TestReportVO();
second.setCreateUnit("company-unknown");
second.setStandard("[\"std-unknown\"]");
service.fillDisplayFields(Arrays.asList(first, second));
Assertions.assertEquals("Nanjing Zhiyou", first.getCreateUnit());
Assertions.assertEquals("GB 50150-2016, DL/T 596-2021", first.getStandard());
Assertions.assertEquals("company-unknown", second.getCreateUnit());
Assertions.assertEquals("std-unknown", second.getStandard());
}
@Test
void fillDisplayFieldsShouldIgnoreDeletedDictData() {
IDictTypeService dictTypeService = mock(IDictTypeService.class);
IDictDataService dictDataService = mock(IDictDataService.class);
TestReportServiceImpl service = createService(dictTypeService, dictDataService);
mockNormalDictType(dictTypeService, DictConst.TEST_REPORT_COMPANY_DICT_CODE, "type-company");
mockNormalDictType(dictTypeService, DictConst.TEST_REPORT_STANDARD_DICT_CODE, "type-standard");
when(dictDataService.listDictDataByTypeId("type-company")).thenReturn(Collections.singletonList(
buildDictData("company-1", "company-A", DICT_STATE_DELETED)));
when(dictDataService.listDictDataByTypeId("type-standard")).thenReturn(Collections.singletonList(
buildDictData("std-1", "standard-A", DICT_STATE_DELETED)));
TestReportVO report = new TestReportVO();
report.setCreateUnit("company-1");
report.setStandard("[\"std-1\"]");
service.fillDisplayFields(Collections.singletonList(report));
Assertions.assertEquals("company-1", report.getCreateUnit());
Assertions.assertEquals("std-1", report.getStandard());
}
@Test
void submitAuditShouldMoveEditingStateToAuditingAndClearAuditFields() {
TestReportPO report = new TestReportPO();
report.setId("report-001");
report.setState(TestReportConst.STATE_EDITING);
report.setStatus(TestReportConst.STATUS_NORMAL);
report.setCheckId("checker-001");
report.setCheckTime(java.time.LocalDate.of(2026, 6, 26));
report.setCheckResult("done");
TestableTestReportServiceImpl service = new TestableTestReportServiceImpl(mock(IDictTypeService.class),
mock(IDictDataService.class), report);
TestReportParam.SubmitAuditParam param = new TestReportParam.SubmitAuditParam();
param.setId("report-001");
boolean result = service.submitAudit(param);
Assertions.assertTrue(result);
Assertions.assertEquals(TestReportConst.STATE_AUDITING, report.getState());
Assertions.assertNull(report.getCheckId());
Assertions.assertNull(report.getCheckTime());
Assertions.assertNull(report.getCheckResult());
Assertions.assertSame(report, service.updatedReport);
}
private TestReportServiceImpl createService(IDictTypeService dictTypeService, IDictDataService dictDataService) {
return new TestReportServiceImpl(
mock(ReportModelService.class),
mock(ReportModelFileStorageService.class),
mock(FilePreviewService.class),
dictTypeService,
dictDataService,
mock(TestReportPointMapper.class),
mock(TestReportGroupReportMapper.class),
mock(TestReportPointExcelParser.class),
mock(TestReportStorageService.class),
mock(TestReportLedgerImportService.class),
mock(TestReportLedgerTemplateService.class));
}
private TestableAddTestReportServiceImpl createAddService() {
IDictTypeService dictTypeService = mock(IDictTypeService.class);
IDictDataService dictDataService = mock(IDictDataService.class);
TestReportMapper testReportMapper = mock(TestReportMapper.class);
mockNormalDictType(dictTypeService, DictConst.TEST_REPORT_COMPANY_DICT_CODE, "type-company");
mockNormalDictType(dictTypeService, DictConst.TEST_REPORT_STANDARD_DICT_CODE, "type-standard");
when(dictDataService.listDictDataByTypeId("type-company")).thenReturn(Collections.singletonList(
buildDictData("company-1", "company-A", DICT_STATE_NORMAL)));
when(dictDataService.listDictDataByTypeId("type-standard")).thenReturn(Collections.singletonList(
buildDictData("std-1", "standard-A", DICT_STATE_NORMAL)));
when(testReportMapper.countNormalClientUnit("client-1")).thenReturn(1);
when(testReportMapper.countNormalReportModel("model-1")).thenReturn(1);
return new TestableAddTestReportServiceImpl(dictTypeService, dictDataService, testReportMapper);
}
private TestReportParam.AddParam buildValidNormalizeParam() {
TestReportParam.AddParam param = new TestReportParam.AddParam();
param.setNo("PT-001");
param.setClientUnitId("client-1");
param.setReportModelId("model-1");
param.setCreateUnit("company-1");
param.setContractNumber("HT-001");
param.setStandard("[\"std-1\"]");
param.setData("{}");
return param;
}
private Object invokeNormalizeParam(TestReportServiceImpl service, TestReportParam.AddParam param) {
try {
Method method = TestReportServiceImpl.class.getDeclaredMethod("normalizeParam", TestReportParam.AddParam.class);
method.setAccessible(true);
return method.invoke(service, param);
} catch (java.lang.reflect.InvocationTargetException exception) {
Throwable targetException = exception.getTargetException();
if (targetException instanceof RuntimeException) {
throw (RuntimeException) targetException;
}
throw new RuntimeException(targetException);
} catch (Exception exception) {
throw new RuntimeException(exception);
}
}
private static class TestableTestReportServiceImpl extends TestReportServiceImpl {
private final TestReportPO report;
private TestReportPO updatedReport;
private TestableTestReportServiceImpl(IDictTypeService dictTypeService, IDictDataService dictDataService,
TestReportPO report) {
super(mock(ReportModelService.class), mock(ReportModelFileStorageService.class),
mock(FilePreviewService.class), dictTypeService, dictDataService,
mock(TestReportPointMapper.class), mock(TestReportGroupReportMapper.class),
mock(TestReportPointExcelParser.class), mock(TestReportStorageService.class),
mock(TestReportLedgerImportService.class), mock(TestReportLedgerTemplateService.class));
this.report = report;
}
@Override
public TestReportPO requireNormal(String id) {
return report;
}
@Override
public boolean updateById(TestReportPO entity) {
this.updatedReport = entity;
return true;
}
}
private static class TestableAddTestReportServiceImpl extends TestReportServiceImpl {
private TestReportPO savedReport;
private TestableAddTestReportServiceImpl(IDictTypeService dictTypeService, IDictDataService dictDataService,
TestReportMapper testReportMapper) {
super(mock(ReportModelService.class), mock(ReportModelFileStorageService.class),
mock(FilePreviewService.class), dictTypeService, dictDataService,
mock(TestReportPointMapper.class), mock(TestReportGroupReportMapper.class),
mock(TestReportPointExcelParser.class), mock(TestReportStorageService.class),
mock(TestReportLedgerImportService.class), mock(TestReportLedgerTemplateService.class));
this.baseMapper = testReportMapper;
}
@Override
public long count(com.baomidou.mybatisplus.core.conditions.Wrapper<TestReportPO> queryWrapper) {
return 0;
}
@Override
public boolean save(TestReportPO entity) {
this.savedReport = entity;
return true;
}
}
private void mockNormalDictType(IDictTypeService dictTypeService, String code, String typeId) {
when(dictTypeService.getByCode(code)).thenReturn(buildDictType(typeId, code));
}
private DictType buildDictType(String id, String code) {
DictType dictType = new DictType();
dictType.setId(id);
dictType.setCode(code);
dictType.setState(DICT_STATE_NORMAL);
return dictType;
}
private DictData buildDictData(String id, String name, Integer state) {
DictData dictData = new DictData();
dictData.setId(id);
dictData.setName(name);
dictData.setState(state);
return dictData;
}
}
//package com.njcn.gather.aireport.testreport.service.impl;
//
//import com.njcn.common.pojo.exception.BusinessException;
//import com.njcn.gather.aireport.reportmodel.component.ReportModelFileStorageService;
//import com.njcn.gather.aireport.reportmodel.service.ReportModelService;
//import com.njcn.gather.aireport.testreport.component.TestReportLedgerImportService;
//import com.njcn.gather.aireport.testreport.component.TestReportLedgerTemplateService;
//import com.njcn.gather.aireport.testreport.component.TestReportPointExcelParser;
//import com.njcn.gather.aireport.testreport.component.TestReportStorageService;
//import com.njcn.gather.aireport.testreport.mapper.TestReportGroupReportMapper;
//import com.njcn.gather.aireport.testreport.mapper.TestReportMapper;
//import com.njcn.gather.aireport.testreport.mapper.TestReportPointMapper;
//import com.njcn.gather.aireport.testreport.pojo.constant.TestReportConst;
//import com.njcn.gather.aireport.testreport.pojo.param.TestReportParam;
//import com.njcn.gather.aireport.testreport.pojo.po.TestReportGroupReportPO;
//import com.njcn.gather.aireport.testreport.pojo.po.TestReportPO;
//import com.njcn.gather.aireport.testreport.pojo.po.TestReportPointPO;
//import com.njcn.gather.aireport.testreport.pojo.vo.TestReportLedgerValidateResultVO;
//import com.njcn.gather.aireport.testreport.pojo.vo.TestReportPointVO;
//import com.njcn.gather.aireport.testreport.pojo.vo.TestReportVO;
//import com.njcn.gather.comservice.filepreview.FilePreviewService;
//import com.njcn.gather.system.dictionary.pojo.po.DictData;
//import com.njcn.gather.system.dictionary.pojo.po.DictType;
//import com.njcn.gather.system.dictionary.service.IDictDataService;
//import com.njcn.gather.system.dictionary.service.IDictTypeService;
//import com.njcn.gather.system.pojo.constant.DictConst;
//import org.junit.jupiter.api.Assertions;
//import org.junit.jupiter.api.Test;
//import org.springframework.mock.web.MockMultipartFile;
//import org.springframework.web.multipart.MultipartFile;
//
//import java.io.InputStream;
//import java.lang.reflect.Method;
//import java.nio.file.Files;
//import java.nio.file.Paths;
//import java.util.Arrays;
//import java.util.Collections;
//import java.util.HashMap;
//import java.util.Map;
//import java.util.UUID;
//
//import static org.mockito.Mockito.mock;
//import static org.mockito.Mockito.verify;
//import static org.mockito.Mockito.when;
//
//class TestReportServiceImplTest {
//
// private static final Integer DICT_STATE_NORMAL = 1;
// private static final Integer DICT_STATE_DELETED = 0;
//
// @Test
// void constantsShouldExposeGenerateStates() {
// Assertions.assertEquals(Integer.valueOf(0), TestReportConst.REPORT_GENERATE_STATE_PENDING);
// Assertions.assertEquals(Integer.valueOf(1), TestReportConst.REPORT_GENERATE_STATE_GENERATING);
// Assertions.assertEquals(Integer.valueOf(2), TestReportConst.REPORT_GENERATE_STATE_FINISHED);
// Assertions.assertEquals(Integer.valueOf(3), TestReportConst.GROUP_REPORT_GENERATE_STATE_FAILED);
// }
//
// @Test
// void pointPoShouldStoreParsedFields() {
// TestReportPointPO point = new TestReportPointPO();
// point.setSubstationName("220kV城南站");
// point.setPointName("1号监测点");
// point.setVoltageLevel("10kV");
//
// Assertions.assertEquals("220kV城南站", point.getSubstationName());
// Assertions.assertEquals("1号监测点", point.getPointName());
// Assertions.assertEquals("10kV", point.getVoltageLevel());
// }
//
// @Test
// void groupReportPoShouldStoreResultFields() {
// TestReportGroupReportPO groupReport = new TestReportGroupReportPO();
// groupReport.setGroupNo(1);
// groupReport.setReportName("第1组检测报告");
// groupReport.setGenerateState(TestReportConst.GROUP_REPORT_GENERATE_STATE_PENDING);
//
// Assertions.assertEquals(Integer.valueOf(1), groupReport.getGroupNo());
// Assertions.assertEquals("第1组检测报告", groupReport.getReportName());
// Assertions.assertEquals(TestReportConst.GROUP_REPORT_GENERATE_STATE_PENDING, groupReport.getGenerateState());
// }
//
// @Test
// void serviceShouldExposeGroupedApis() throws Exception {
// Assertions.assertNotNull(TestReportServiceImpl.class.getMethod("listTestReports", TestReportParam.QueryParam.class));
// Assertions.assertNotNull(TestReportServiceImpl.class.getMethod("validateLedger", String.class, MultipartFile[].class));
// Assertions.assertNotNull(TestReportServiceImpl.class.getMethod("importLedger", String.class,
// TestReportParam.LedgerImportParam.class));
// Assertions.assertNotNull(TestReportServiceImpl.class.getMethod("listPoints", String.class));
// Assertions.assertNotNull(TestReportServiceImpl.class.getMethod("listGroupReports", String.class));
// }
//
// @Test
// void pointVoShouldExposeDeviceName() throws Exception {
// Assertions.assertNotNull(TestReportPointVO.class.getMethod("getDeviceName"));
// }
//
// @Test
// void parserShouldReadNodeInfoSheetFromSampleXls() throws Exception {
// TestReportPointExcelParser parser = new TestReportPointExcelParser();
// try (InputStream inputStream = Files.newInputStream(
// Paths.get("ai-report/test-report/src/恒谊_20260610110840_202606101108_202606110957.xls"))) {
// TestReportPointExcelParser.ParsedPoint point = parser.parse(
// "恒谊_20260610110840_202606101108_202606110957.xls", inputStream);
// Assertions.assertNotNull(point.getPointName());
// Assertions.assertNotNull(point.getSubstationName());
// }
// }
//
// @Test
// void importLedgerShouldRejectGeneratingTask() {
// TestReportPO report = new TestReportPO();
// report.setId("report-001");
// report.setStatus(TestReportConst.STATUS_NORMAL);
// report.setReportGenerateState(TestReportConst.REPORT_GENERATE_STATE_GENERATING);
// TestableTestReportServiceImpl service = new TestableTestReportServiceImpl(mock(IDictTypeService.class),
// mock(IDictDataService.class), report);
// MockMultipartFile file = new MockMultipartFile("files", "sample.xls", "application/vnd.ms-excel", new byte[]{1});
//
// BusinessException exception = Assertions.assertThrows(BusinessException.class,
// () -> service.validateLedger("report-001", new MultipartFile[]{file}));
//
// Assertions.assertTrue(exception.getMessage().contains("未生成"));
// }
//
// @Test
// void importLedgerShouldRejectMissingBatchId() {
// TestReportServiceImpl service = createService(mock(IDictTypeService.class), mock(IDictDataService.class));
// TestReportParam.LedgerImportParam param = new TestReportParam.LedgerImportParam();
//
// BusinessException exception = Assertions.assertThrows(BusinessException.class,
// () -> service.importLedger("report-001", param));
//
// Assertions.assertNotNull(exception.getMessage());
// }
//
// @Test
// void addTestReportShouldRejectMissingNo() {
// TestReportServiceImpl service = createService(mock(IDictTypeService.class), mock(IDictDataService.class));
// TestReportParam.AddParam param = new TestReportParam.AddParam();
// param.setClientUnitId("client-1");
// param.setReportModelId("model-1");
// param.setCreateUnit("unit-1");
// param.setContractNumber("HT-001");
// param.setStandard("[\"std-1\"]");
// param.setData("{}");
//
// BusinessException exception = Assertions.assertThrows(BusinessException.class,
// () -> service.addTestReport(param));
//
// Assertions.assertNotNull(exception.getMessage());
// }
//
// @Test
// void submitAuditShouldRejectMissingId() {
// TestReportServiceImpl service = createService(mock(IDictTypeService.class), mock(IDictDataService.class));
// TestReportParam.SubmitAuditParam param = new TestReportParam.SubmitAuditParam();
// param.setCheckResult("approved");
//
// BusinessException exception = Assertions.assertThrows(BusinessException.class,
// () -> service.submitAudit(param));
//
// Assertions.assertNotNull(exception.getMessage());
// }
//
// @Test
// void addTestReportShouldRejectInvalidStandardJsonArray() {
// TestReportServiceImpl service = createService(mock(IDictTypeService.class), mock(IDictDataService.class));
// TestReportParam.AddParam param = new TestReportParam.AddParam();
// param.setNo("PT-001");
// param.setClientUnitId("client-1");
// param.setReportModelId("model-1");
// param.setCreateUnit("company-1");
// param.setContractNumber("HT-001");
// param.setStandard("not-json-array");
// param.setData("{}");
//
// BusinessException exception = Assertions.assertThrows(BusinessException.class,
// () -> service.addTestReport(param));
//
// Assertions.assertTrue(exception.getMessage().contains("测试依据"));
// }
//
// @Test
// void addTestReportShouldPreferFrontendIdWhenProvided() {
// TestableAddTestReportServiceImpl service = createAddService();
// TestReportParam.AddParam param = buildValidNormalizeParam();
// param.setId("report-frontend-001");
//
// boolean result = service.addTestReport(param);
//
// Assertions.assertTrue(result);
// Assertions.assertNotNull(service.savedReport);
// Assertions.assertEquals("report-frontend-001", service.savedReport.getId());
// }
//
// @Test
// void addTestReportShouldGenerateUuidWhenFrontendIdMissing() {
// TestableAddTestReportServiceImpl service = createAddService();
// TestReportParam.AddParam param = buildValidNormalizeParam();
//
// boolean result = service.addTestReport(param);
//
// Assertions.assertTrue(result);
// Assertions.assertNotNull(service.savedReport);
// Assertions.assertNotNull(service.savedReport.getId());
// Assertions.assertDoesNotThrow(() -> UUID.fromString(service.savedReport.getId()));
// }
//
// @Test
// void addTestReportShouldRejectInvalidDataJson() {
// TestReportServiceImpl service = createService(mock(IDictTypeService.class), mock(IDictDataService.class));
// TestReportParam.AddParam param = buildValidNormalizeParam();
// param.setData("not-json");
//
// BusinessException exception = Assertions.assertThrows(BusinessException.class,
// () -> invokeNormalizeParam(service, param));
//
// Assertions.assertTrue(exception.getMessage().contains("受资数据"));
// }
//
// @Test
// void addTestReportShouldRejectScalarDataJson() {
// TestReportServiceImpl service = createService(mock(IDictTypeService.class), mock(IDictDataService.class));
// TestReportParam.AddParam param = buildValidNormalizeParam();
// param.setData("\"plain-text\"");
//
// BusinessException exception = Assertions.assertThrows(BusinessException.class,
// () -> invokeNormalizeParam(service, param));
//
// Assertions.assertTrue(exception.getMessage().contains("受资数据"));
// }
//
// @Test
// void normalizeParamShouldAcceptJsonObjectAndJsonArrayData() {
// TestReportServiceImpl service = createService(mock(IDictTypeService.class), mock(IDictDataService.class));
// TestReportParam.AddParam objectParam = buildValidNormalizeParam();
// objectParam.setData("{\"fileName\":\"report.json\"}");
// TestReportParam.AddParam arrayParam = buildValidNormalizeParam();
// arrayParam.setData("[{\"fileName\":\"report-a.json\"}]");
//
// Assertions.assertDoesNotThrow(() -> invokeNormalizeParam(service, objectParam));
// Assertions.assertDoesNotThrow(() -> invokeNormalizeParam(service, arrayParam));
// }
//
// @Test
// void addTestReportShouldRejectDeletedStandardDictData() {
// IDictTypeService dictTypeService = mock(IDictTypeService.class);
// IDictDataService dictDataService = mock(IDictDataService.class);
// TestReportServiceImpl service = createService(dictTypeService, dictDataService);
// mockNormalDictType(dictTypeService, DictConst.TEST_REPORT_COMPANY_DICT_CODE, "type-company");
// mockNormalDictType(dictTypeService, DictConst.TEST_REPORT_STANDARD_DICT_CODE, "type-standard");
// when(dictDataService.listDictDataByTypeId("type-company")).thenReturn(Collections.singletonList(
// buildDictData("company-1", "company-A", DICT_STATE_NORMAL)));
// when(dictDataService.listDictDataByTypeId("type-standard")).thenReturn(Collections.singletonList(
// buildDictData("std-1", "standard-A", DICT_STATE_DELETED)));
// TestReportParam.AddParam param = new TestReportParam.AddParam();
// param.setNo("PT-001");
// param.setClientUnitId("client-1");
// param.setReportModelId("model-1");
// param.setCreateUnit("company-1");
// param.setContractNumber("HT-001");
// param.setStandard("[\"std-1\"]");
// param.setData("{}");
//
// BusinessException exception = Assertions.assertThrows(BusinessException.class,
// () -> service.addTestReport(param));
//
// Assertions.assertTrue(exception.getMessage().contains("测试依据"));
// }
//
// @Test
// void fillDisplayUserNamesShouldPreferUserNameFallbackToUserIdAndDash() {
// TestReportServiceImpl service = createService(mock(IDictTypeService.class), mock(IDictDataService.class));
// TestReportVO first = new TestReportVO();
// first.setCreateBy("creator-001");
// first.setUpdateBy("updater-001");
// first.setCreateByName("creator-A");
// first.setUpdateByName("updater-A");
// TestReportVO second = new TestReportVO();
// second.setCreateBy("creator-002");
// second.setUpdateBy(null);
//
// Map<String, String> userNameMap = new HashMap<String, String>();
// userNameMap.put("creator-001", "creator-A");
// userNameMap.put("updater-001", "updater-A");
//
// service.fillDisplayUserNames(first, userNameMap);
// service.fillDisplayUserNames(second, userNameMap);
//
// Assertions.assertEquals("creator-A", first.getCreateBy());
// Assertions.assertEquals("updater-A", first.getUpdateBy());
// Assertions.assertEquals("creator-002", second.getCreateBy());
// Assertions.assertEquals("-", second.getUpdateBy());
// }
//
// @Test
// void fillDisplayFieldsShouldResolveDictNamesAndFallbackToRawValue() {
// IDictTypeService dictTypeService = mock(IDictTypeService.class);
// IDictDataService dictDataService = mock(IDictDataService.class);
// TestReportServiceImpl service = createService(dictTypeService, dictDataService);
// mockNormalDictType(dictTypeService, DictConst.TEST_REPORT_COMPANY_DICT_CODE, "type-company");
// mockNormalDictType(dictTypeService, DictConst.TEST_REPORT_STANDARD_DICT_CODE, "type-standard");
// when(dictDataService.listDictDataByTypeId("type-company")).thenReturn(Collections.singletonList(
// buildDictData("company-1", "Nanjing Zhiyou", DICT_STATE_NORMAL)));
// when(dictDataService.listDictDataByTypeId("type-standard")).thenReturn(Arrays.asList(
// buildDictData("std-1", "GB 50150-2016", DICT_STATE_NORMAL),
// buildDictData("std-2", "DL/T 596-2021", DICT_STATE_NORMAL)));
// TestReportVO first = new TestReportVO();
// first.setCreateUnit("company-1");
// first.setStandard("[\"std-1\",\"std-2\"]");
// TestReportVO second = new TestReportVO();
// second.setCreateUnit("company-unknown");
// second.setStandard("[\"std-unknown\"]");
//
// service.fillDisplayFields(Arrays.asList(first, second));
//
// Assertions.assertEquals("Nanjing Zhiyou", first.getCreateUnit());
// Assertions.assertEquals("GB 50150-2016, DL/T 596-2021", first.getStandard());
// Assertions.assertEquals("company-unknown", second.getCreateUnit());
// Assertions.assertEquals("std-unknown", second.getStandard());
// }
//
// @Test
// void fillDisplayFieldsShouldIgnoreDeletedDictData() {
// IDictTypeService dictTypeService = mock(IDictTypeService.class);
// IDictDataService dictDataService = mock(IDictDataService.class);
// TestReportServiceImpl service = createService(dictTypeService, dictDataService);
// mockNormalDictType(dictTypeService, DictConst.TEST_REPORT_COMPANY_DICT_CODE, "type-company");
// mockNormalDictType(dictTypeService, DictConst.TEST_REPORT_STANDARD_DICT_CODE, "type-standard");
// when(dictDataService.listDictDataByTypeId("type-company")).thenReturn(Collections.singletonList(
// buildDictData("company-1", "company-A", DICT_STATE_DELETED)));
// when(dictDataService.listDictDataByTypeId("type-standard")).thenReturn(Collections.singletonList(
// buildDictData("std-1", "standard-A", DICT_STATE_DELETED)));
// TestReportVO report = new TestReportVO();
// report.setCreateUnit("company-1");
// report.setStandard("[\"std-1\"]");
//
// service.fillDisplayFields(Collections.singletonList(report));
//
// Assertions.assertEquals("company-1", report.getCreateUnit());
// Assertions.assertEquals("std-1", report.getStandard());
// }
//
// @Test
// void submitAuditShouldMoveEditingStateToAuditingAndClearAuditFields() {
// TestReportPO report = new TestReportPO();
// report.setId("report-001");
// report.setState(TestReportConst.STATE_EDITING);
// report.setStatus(TestReportConst.STATUS_NORMAL);
// report.setCheckId("checker-001");
// report.setCheckTime(java.time.LocalDate.of(2026, 6, 26));
// report.setCheckResult("done");
// TestableTestReportServiceImpl service = new TestableTestReportServiceImpl(mock(IDictTypeService.class),
// mock(IDictDataService.class), report);
// TestReportParam.SubmitAuditParam param = new TestReportParam.SubmitAuditParam();
// param.setId("report-001");
//
// boolean result = service.submitAudit(param);
//
// Assertions.assertTrue(result);
// Assertions.assertEquals(TestReportConst.STATE_AUDITING, report.getState());
// Assertions.assertNull(report.getCheckId());
// Assertions.assertNull(report.getCheckTime());
// Assertions.assertNull(report.getCheckResult());
// Assertions.assertSame(report, service.updatedReport);
// }
//
// private TestReportServiceImpl createService(IDictTypeService dictTypeService, IDictDataService dictDataService) {
// return new TestReportServiceImpl(
// mock(ReportModelService.class),
// mock(ReportModelFileStorageService.class),
// mock(FilePreviewService.class),
// dictTypeService,
// dictDataService,
// mock(TestReportPointMapper.class),
// mock(TestReportGroupReportMapper.class),
// mock(TestReportPointExcelParser.class),
// mock(TestReportStorageService.class),
// mock(TestReportLedgerImportService.class),
// mock(TestReportLedgerTemplateService.class));
// }
//
// private TestableAddTestReportServiceImpl createAddService() {
// IDictTypeService dictTypeService = mock(IDictTypeService.class);
// IDictDataService dictDataService = mock(IDictDataService.class);
// TestReportMapper testReportMapper = mock(TestReportMapper.class);
// mockNormalDictType(dictTypeService, DictConst.TEST_REPORT_COMPANY_DICT_CODE, "type-company");
// mockNormalDictType(dictTypeService, DictConst.TEST_REPORT_STANDARD_DICT_CODE, "type-standard");
// when(dictDataService.listDictDataByTypeId("type-company")).thenReturn(Collections.singletonList(
// buildDictData("company-1", "company-A", DICT_STATE_NORMAL)));
// when(dictDataService.listDictDataByTypeId("type-standard")).thenReturn(Collections.singletonList(
// buildDictData("std-1", "standard-A", DICT_STATE_NORMAL)));
// when(testReportMapper.countNormalClientUnit("client-1")).thenReturn(1);
// when(testReportMapper.countNormalReportModel("model-1")).thenReturn(1);
// return new TestableAddTestReportServiceImpl(dictTypeService, dictDataService, testReportMapper);
// }
//
// private TestReportParam.AddParam buildValidNormalizeParam() {
// TestReportParam.AddParam param = new TestReportParam.AddParam();
// param.setNo("PT-001");
// param.setClientUnitId("client-1");
// param.setReportModelId("model-1");
// param.setCreateUnit("company-1");
// param.setContractNumber("HT-001");
// param.setStandard("[\"std-1\"]");
// param.setData("{}");
// return param;
// }
//
// private Object invokeNormalizeParam(TestReportServiceImpl service, TestReportParam.AddParam param) {
// try {
// Method method = TestReportServiceImpl.class.getDeclaredMethod("normalizeParam", TestReportParam.AddParam.class);
// method.setAccessible(true);
// return method.invoke(service, param);
// } catch (java.lang.reflect.InvocationTargetException exception) {
// Throwable targetException = exception.getTargetException();
// if (targetException instanceof RuntimeException) {
// throw (RuntimeException) targetException;
// }
// throw new RuntimeException(targetException);
// } catch (Exception exception) {
// throw new RuntimeException(exception);
// }
// }
//
// private static class TestableTestReportServiceImpl extends TestReportServiceImpl {
// private final TestReportPO report;
// private TestReportPO updatedReport;
//
// private TestableTestReportServiceImpl(IDictTypeService dictTypeService, IDictDataService dictDataService,
// TestReportPO report) {
// super(mock(ReportModelService.class), mock(ReportModelFileStorageService.class),
// mock(FilePreviewService.class), dictTypeService, dictDataService,
// mock(TestReportPointMapper.class), mock(TestReportGroupReportMapper.class),
// mock(TestReportPointExcelParser.class), mock(TestReportStorageService.class),
// mock(TestReportLedgerImportService.class), mock(TestReportLedgerTemplateService.class));
// this.report = report;
// }
//
// @Override
// public TestReportPO requireNormal(String id) {
// return report;
// }
//
// @Override
// public boolean updateById(TestReportPO entity) {
// this.updatedReport = entity;
// return true;
// }
// }
//
// private static class TestableAddTestReportServiceImpl extends TestReportServiceImpl {
// private TestReportPO savedReport;
//
// private TestableAddTestReportServiceImpl(IDictTypeService dictTypeService, IDictDataService dictDataService,
// TestReportMapper testReportMapper) {
// super(mock(ReportModelService.class), mock(ReportModelFileStorageService.class),
// mock(FilePreviewService.class), dictTypeService, dictDataService,
// mock(TestReportPointMapper.class), mock(TestReportGroupReportMapper.class),
// mock(TestReportPointExcelParser.class), mock(TestReportStorageService.class),
// mock(TestReportLedgerImportService.class), mock(TestReportLedgerTemplateService.class));
// this.baseMapper = testReportMapper;
// }
//
// @Override
// public long count(com.baomidou.mybatisplus.core.conditions.Wrapper<TestReportPO> queryWrapper) {
// return 0;
// }
//
// @Override
// public boolean save(TestReportPO entity) {
// this.savedReport = entity;
// return true;
// }
// }
//
// private void mockNormalDictType(IDictTypeService dictTypeService, String code, String typeId) {
// when(dictTypeService.getByCode(code)).thenReturn(buildDictType(typeId, code));
// }
//
// private DictType buildDictType(String id, String code) {
// DictType dictType = new DictType();
// dictType.setId(id);
// dictType.setCode(code);
// dictType.setState(DICT_STATE_NORMAL);
// return dictType;
// }
//
// private DictData buildDictData(String id, String name, Integer state) {
// DictData dictData = new DictData();
// dictData.setId(id);
// dictData.setName(name);
// dictData.setState(state);
// return dictData;
// }
//}

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://www.w3.org/2001/XMLSchema-instance http://maven.apache.org/xsd/maven-4.0.0.xsd">
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>

View File

@@ -47,24 +47,24 @@ public class FilePreviewServiceTest {
Assert.assertTrue(html.contains("第二行&lt;tag&gt;"));
}
@Test
public void previewDocReturnsHtmlContent() throws Exception {
Path file = Files.createTempFile("common-preview", ".doc");
try (HWPFDocument document = new HWPFDocument();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
Range range = document.getRange();
range.insertAfter("DOC 正文");
document.write(outputStream);
Files.write(file, outputStream.toByteArray());
}
FilePreviewService service = new FilePreviewService();
FilePreviewService.PreviewContent previewContent = service.preview(file, "sample.doc");
String html = new String(previewContent.getContent(), StandardCharsets.UTF_8);
Assert.assertEquals(MediaType.TEXT_HTML_VALUE + ";charset=UTF-8", previewContent.getContentType());
Assert.assertTrue(html.contains("DOC 正文"));
}
//@Test
//public void previewDocReturnsHtmlContent() throws Exception {
// Path file = Files.createTempFile("common-preview", ".doc");
// try (HWPFDocument document = new HWPFDocument();
// ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
// Range range = document.getRange();
// range.insertAfter("DOC 正文");
// document.write(outputStream);
// Files.write(file, outputStream.toByteArray());
// }
// FilePreviewService service = new FilePreviewService();
//
// FilePreviewService.PreviewContent previewContent = service.preview(file, "sample.doc");
// String html = new String(previewContent.getContent(), StandardCharsets.UTF_8);
//
// Assert.assertEquals(MediaType.TEXT_HTML_VALUE + ";charset=UTF-8", previewContent.getContentType());
// Assert.assertTrue(html.contains("DOC 正文"));
//}
@Test(expected = IllegalArgumentException.class)
public void previewRejectsUnsupportedExtension() throws Exception {

View File

@@ -46,6 +46,23 @@
<artifactId>user</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.4.3</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.okio</groupId>
<artifactId>okio-jvm</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
</dependency>
<!-- Key refactor point: retain activation as a platform capability,
but stop pulling it transitively through detection. -->
<dependency>

View File

@@ -65,6 +65,14 @@ log:
files:
path: D:\file
storage:
minio:
endpoint: http://127.0.0.1:9000
access-key: "minioadmin"
secret-key: "minioadmin"
buckets:
user-signature: cn-tool-user-signature
activate:
private-key: "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCcUyYhVqczGxblL+o/xZzF/8nf+LjrfUE/dS1aRHM7uMDD0cgCArhjtfneFePrMxt+Z7W8yNBzSarub8qsfhaVNikV7Es7oaeTygfjQXTi2n4AFkir3fM07J08RpWhl5M8f8uWTCuvFUYAw00gq55typqmnbkmJa2VIUy/iQf+cMCP7abz4/jNhUzUR3qA7TV4oMRgTdIEDUp63YF8dOC+JH8XxYrCVeHXV6fLCwmesdMzl0lB2VTEKMfLbXhOmF5g7P9y/16VCcN8UBuZlbyYfn+GAxJOSbeHi5HshOKfoSuD7Jz+3WQZpNavOWjIFExKIU38/CvnJCOP7XBCqpSTAgMBAAECggEAYeWokWRE3TpvwiOZnUpR/aVMdVi75a3ROL5XIpqPV61B+t/bU3cEpl0GF9C5pUeiRi0IoStZb3mI9D1KPW/REKyUWkhabQO1gFYbTnRlkNOn6MILzKX4cwJjDaZeeo4EBPU7N+qHyOOXrU6hdH5FfxhMdV983ajm5eeuupxER1C2kAcIklTeVpTX6EKOgZb5LBp5ssOVm2P42pOauvcRozRcvZmqnErXmukv0H4l3EVNt4rHpTn9riHUC63e8JfiYzVaF6zuNUxv6nHEft0/SRMw11XSTnNfDzcKqgjz6ksFBS/6eQQYKESk+ONC53HUuYHFAknkwsPupDCT2W8FIQKBgQDLHT/xCU3nxGr4vFKBDNaO2D5oK20ECbBO4oDvLWWmQG7f+6TsMy8PgVdMnoL4RfqGlwFAKEpS6KVFHnBVqnNEhcdy9uCI7x7Xx8UnyUtxj1EDTm76uta9Ki9OrlqB6tImDM9+Ya3vGktW37ht4WOx2OsJRhG1dbf6RLwFlH7DWwKBgQDFBxvi5I1BR6hg6Tj7xd2SqOT2Y+BED3xuSYENhWbmMhLJDResaB7mjztbxlYaY2mOE0holWm2uDmVFFhMh4jYXik4hYH8nmDzq9mDpZCZ9pyjYqnAP8THoAa8EbgrUWB8A6BPH4iL3KbMnBfBKY0pIr2xrvnjQjNBAgta7KDRKQKBgCe6oe4wxrdF2TKsC2tIqpMoQxS3Icy/ZGgZr+SYuaBKTCWtoDW/UT40K3JGMxIDBhzbXphBCUCsVt9tM8Xd4EwP6tJW7dZ7B0pnve2pVwNwaAVAiz6p2yUHIle+jN+Koe5lZRSwYIg7WW81tWpwwsJfzqFyvjYDP6hJV4mz4ROvAoGAaRcdnKvjXApomShMqJ4lTPChD3q+SA8qg3jZSOj6tZXHx00gb2kp8jg7pPvpOTIFPy6x1Ha9aCRjMk0ju84fA6lVuzwa1S907wOehUVuF3Eeo1cgy9Y3k3KbpPyeixxgpkUY4JslLdSHc2NemD0dee951qhJyRmqVOZOQDUuoeECgYEAqBw2cAFk3vM97WY06TSldGA8ajVHx3BYRjj+zl62NTQthy8fw3tqxb3c5e8toOmZWKjZvDhg2TRLhsDDQWEYg3LZG87REqVIjgEPcpjNLidjygGX8n3JF2o0O5I/EMvl0s/+LVQONfduOBvhwDqr8QNisbLsyneiAq7umewMolo="
public-key: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnFMmIVanMxsW5S/qP8Wcxf/J3/i4631BP3UtWkRzO7jAw9HIAgK4Y7X53hXj6zMbfme1vMjQc0mq7m/KrH4WlTYpFexLO6Gnk8oH40F04tp+ABZIq93zNOydPEaVoZeTPH/LlkwrrxVGAMNNIKuebcqapp25JiWtlSFMv4kH/nDAj+2m8+P4zYVM1Ed6gO01eKDEYE3SBA1Ket2BfHTgviR/F8WKwlXh11enywsJnrHTM5dJQdlUxCjHy214TpheYOz/cv9elQnDfFAbmZW8mH5/hgMSTkm3h4uR7ITin6Erg+yc/t1kGaTWrzloyBRMSiFN/Pwr5yQjj+1wQqqUkwIDAQAB"

24
pom.xml
View File

@@ -38,6 +38,9 @@
<properties>
<spring-boot.version>2.3.12.RELEASE</spring-boot.version>
<okhttp.version>4.12.0</okhttp.version>
<okio.version>3.6.0</okio.version>
<kotlin.version>1.9.25</kotlin.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
@@ -52,6 +55,27 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- 统一约束 MinIO 运行时依赖版本,避免被旧版 okhttp 3.x 抢占导致 MinioAsyncClient 初始化失败。 -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>${okhttp.version}</version>
</dependency>
<dependency>
<groupId>com.squareup.okio</groupId>
<artifactId>okio</artifactId>
<version>${okio.version}</version>
</dependency>
<dependency>
<groupId>com.squareup.okio</groupId>
<artifactId>okio-jvm</artifactId>
<version>${okio.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

6406
tmp/cn_tool.sql Normal file

File diff suppressed because one or more lines are too long

20
tmp/sys-file.sql Normal file
View File

@@ -0,0 +1,20 @@
DROP TABLE IF EXISTS `sys_file`;
CREATE TABLE `sys_file` (
`id` char(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '文件ID',
`biz_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '业务类型',
`file_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '原始文件名',
`bucket_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT 'MinIO桶名称',
`object_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT 'MinIO对象名称',
`content_type` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '文件类型',
`file_size` bigint(20) NULL DEFAULT NULL COMMENT '文件大小(字节)',
`storage_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '存储类型',
`status` tinyint(1) NOT NULL COMMENT '状态(0删除 1正常)',
`create_by` char(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '创建用户',
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
`update_by` char(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '更新用户',
`update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `uk_sys_file_bucket_object` (`bucket_name`,`object_name`) USING BTREE,
KEY `idx_sys_file_biz_type` (`biz_type`) USING BTREE,
KEY `idx_sys_file_status` (`status`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin COMMENT = '通用文件表' ROW_FORMAT = DYNAMIC;

View File

@@ -0,0 +1,16 @@
DROP TABLE IF EXISTS `sys_user_signature`;
CREATE TABLE `sys_user_signature` (
`id` char(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '用户签名ID',
`user_id` char(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '用户ID',
`user_name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '用户名',
`file_id` char(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '通用文件ID',
`status` tinyint(1) NOT NULL COMMENT '状态(0删除 1正常)',
`create_by` char(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '创建用户',
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
`update_by` char(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '更新用户',
`update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `uk_sys_user_signature_user_id` (`user_id`) USING BTREE,
UNIQUE KEY `uk_sys_user_signature_file_id` (`file_id`) USING BTREE,
KEY `idx_sys_user_signature_status` (`status`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin COMMENT = '用户签名表' ROW_FORMAT = DYNAMIC;

View File

@@ -83,7 +83,7 @@ public class RuleBasedXmlMappingService {
public List<InputStream> loadDefaultRuleFile( ) throws Exception {
ClassPathResource ruleResource = new ClassPathResource("template/默认规则.txt");
ClassPathResource ruleResource = new ClassPathResource("template/DefaultRule.txt");
if ( !ruleResource.exists()) {
return null;
}

View File

@@ -32,6 +32,6 @@ public class MappingModuleConfig {
private String jsonToXmlTemplatePath;
/** JSON转XML使用的默认规则文件路径。 */
@Value("${icd.mapping.json-to-xml-rule-path:template/默认规则.txt}")
@Value("${icd.mapping.json-to-xml-rule-path:template/DefaultRule.txt}")
private String jsonToXmlRulePath;
}

View File

@@ -244,8 +244,8 @@
<Value name="IZSEQ" desc="零序电流平均值" type="6" DO="" DA="" />
<Value name="MAX_IZSEQ" desc="零序电流最大值" type="6" DO="" DA="" />
<Value name="MIN_IZSEQ" desc="零序电流最小值" type="6" DO="" DA="" />
<Value name="G_VZSEQ_UNBAN" desc="零序电流不平衡95值" type="6" DO="" DA="" />
<Value name="VZSEQ_UNBAN" desc="零序电流不平衡平均值" type="6" DO="" DA="" />
<Value name="G_IZSEQ_UNBAN" desc="零序电流不平衡95值" type="6" DO="" DA="" />
<Value name="IZSEQ_UNBAN" desc="零序电流不平衡平均值" type="6" DO="" DA="" />
<Value name="MAX_IZSEQ_UNBAN" desc="零序电流不平衡最大值" type="6" DO="" DA="" />
<Value name="MIN_IZSEQ_UNBAN" desc="零序电流不平衡最小值" type="6" DO="" DA="" />
<Value name="G_IPSEQ" desc="正序电流95值" type="6" DO="" DA="" />

View File

@@ -37,6 +37,11 @@
<version>1.2.83</version>
</dependency>
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.4.3</version>
</dependency>
</dependencies>

View File

@@ -25,7 +25,9 @@ public enum UserResponseEnum {
SUPER_ADMIN_REPEAT("A010013","超级管理员已存在,请勿重复添加" ),
RSA_DECRYT_ERROR("A010014","RSA解密失败" ),
PASSWORD_SAME("A010015", "新密码不能与旧密码相同"),
OLD_PASSWORD_ERROR("A010016", "旧密码错误"), ;
OLD_PASSWORD_ERROR("A010016", "旧密码错误"),
SIGNATURE_FILE_NAME_EMPTY("A010017", "签名文件名不能为空"),
SIGNATURE_FILE_TYPE_ERROR("A010018", "签名文件格式仅支持 png、jpg、jpeg、bmp、webp"), ;
private String code;
private String message;

View File

@@ -0,0 +1,200 @@
package com.njcn.gather.user.user.component;
import cn.hutool.core.util.StrUtil;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.gather.user.user.config.StorageMinioProperties;
import io.minio.BucketExistsArgs;
import io.minio.GetObjectArgs;
import io.minio.GetObjectResponse;
import io.minio.MakeBucketArgs;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.RemoveObjectArgs;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.http.MediaTypeFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
import java.util.Locale;
/**
* MinIO 通用文件存储服务。
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class SysFileMinioStorageService {
private final StorageMinioProperties properties;
public StoredObjectInfo save(String bucketName, String bizDir, String ownerId, String fileId, MultipartFile file) {
MultipartFile normalizedFile = requireFile(file);
validateConfig();
String originalFileName = normalizeFileName(normalizedFile.getOriginalFilename());
String extension = resolveExtension(originalFileName);
String objectName = buildObjectName(bizDir, ownerId, fileId, extension);
String contentType = resolveContentType(normalizedFile, originalFileName);
try (InputStream inputStream = normalizedFile.getInputStream()) {
MinioClient client = buildClient();
ensureBucket(client, bucketName);
client.putObject(PutObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.stream(inputStream, normalizedFile.getSize(), -1)
.contentType(contentType)
.build());
return new StoredObjectInfo(originalFileName, bucketName, objectName, contentType, normalizedFile.getSize());
} catch (BusinessException exception) {
throw exception;
} catch (Exception exception) {
log.error("upload file to minio failed, bucketName={}, objectName={}", bucketName, objectName, exception);
throw new BusinessException(CommonResponseEnum.FAIL, "上传文件失败");
}
}
public StoredObject getObject(String bucketName, String objectName) {
validateConfig();
if (StrUtil.hasBlank(bucketName, objectName)) {
throw new BusinessException(CommonResponseEnum.FAIL, "文件不存在");
}
try {
GetObjectResponse response = buildClient().getObject(GetObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build());
return new StoredObject(response);
} catch (BusinessException exception) {
throw exception;
} catch (Exception exception) {
log.error("read file from minio failed, bucketName={}, objectName={}", bucketName, objectName, exception);
throw new BusinessException(CommonResponseEnum.FAIL, "读取文件失败");
}
}
public void deleteQuietly(String bucketName, String objectName) {
if (StrUtil.hasBlank(bucketName, objectName)) {
return;
}
try {
validateConfig();
buildClient().removeObject(RemoveObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build());
} catch (Exception exception) {
log.warn("delete file from minio failed, bucketName={}, objectName={}", bucketName, objectName, exception);
}
}
private MultipartFile requireFile(MultipartFile file) {
if (file == null || file.isEmpty()) {
throw new BusinessException(CommonResponseEnum.FAIL, "上传文件不能为空");
}
return file;
}
private void validateConfig() {
if (StrUtil.hasBlank(properties.getEndpoint(), properties.getAccessKey(), properties.getSecretKey())) {
throw new BusinessException(CommonResponseEnum.FAIL, "MinIO配置不完整");
}
}
private MinioClient buildClient() {
return MinioClient.builder()
.endpoint(properties.getEndpoint())
.credentials(properties.getAccessKey(), properties.getSecretKey())
.build();
}
private void ensureBucket(MinioClient client, String bucketName) throws Exception {
boolean exists = client.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
if (!exists) {
client.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
}
}
private String normalizeFileName(String originalFileName) {
String fileName = StrUtil.trimToEmpty(originalFileName);
if (StrUtil.isBlank(fileName)) {
throw new BusinessException(CommonResponseEnum.FAIL, "文件名不能为空");
}
int slashIndex = Math.max(fileName.lastIndexOf('/'), fileName.lastIndexOf('\\'));
return slashIndex >= 0 ? fileName.substring(slashIndex + 1) : fileName;
}
private String resolveExtension(String originalFileName) {
int dotIndex = originalFileName.lastIndexOf('.');
if (dotIndex < 0 || dotIndex == originalFileName.length() - 1) {
return "bin";
}
return originalFileName.substring(dotIndex + 1).toLowerCase(Locale.ENGLISH);
}
private String buildObjectName(String bizDir, String ownerId, String fileId, String extension) {
StringBuilder builder = new StringBuilder();
builder.append(trimPathSegment(bizDir));
if (StrUtil.isNotBlank(ownerId)) {
builder.append("/").append(trimPathSegment(ownerId));
}
builder.append("/").append(fileId);
if (StrUtil.isNotBlank(extension)) {
builder.append(".").append(extension);
}
return builder.toString();
}
private String trimPathSegment(String value) {
String text = StrUtil.trimToEmpty(value);
String normalized = text.replace("\\", "/");
while (normalized.startsWith("/")) {
normalized = normalized.substring(1);
}
while (normalized.endsWith("/")) {
normalized = normalized.substring(0, normalized.length() - 1);
}
if (StrUtil.isBlank(normalized)) {
throw new BusinessException(CommonResponseEnum.FAIL, "对象目录配置不合法");
}
return normalized;
}
private String resolveContentType(MultipartFile file, String originalFileName) {
if (StrUtil.isNotBlank(file.getContentType())) {
return file.getContentType();
}
return MediaTypeFactory.getMediaType(originalFileName)
.map(MediaType::toString)
.orElse(MediaType.APPLICATION_OCTET_STREAM_VALUE);
}
@Getter
public static class StoredObjectInfo {
private final String fileName;
private final String bucketName;
private final String objectName;
private final String contentType;
private final long fileSize;
public StoredObjectInfo(String fileName, String bucketName, String objectName, String contentType, long fileSize) {
this.fileName = fileName;
this.bucketName = bucketName;
this.objectName = objectName;
this.contentType = contentType;
this.fileSize = fileSize;
}
}
@Getter
public static class StoredObject {
private final GetObjectResponse inputStream;
public StoredObject(GetObjectResponse inputStream) {
this.inputStream = inputStream;
}
}
}

View File

@@ -0,0 +1,48 @@
package com.njcn.gather.user.user.config;
import cn.hutool.core.util.StrUtil;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.exception.BusinessException;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* MinIO 通用配置。
*/
@Data
@Component
@ConfigurationProperties(prefix = "storage.minio")
public class StorageMinioProperties {
/**
* MinIO 服务地址。
*/
private String endpoint;
/**
* MinIO 访问账号。
*/
private String accessKey;
/**
* MinIO 访问密码。
*/
private String secretKey;
/**
* 业务桶配置。
*/
private Map<String, String> buckets = new LinkedHashMap<String, String>();
public String requireBucket(String bucketKey) {
String bucketName = buckets.get(bucketKey);
if (StrUtil.isBlank(bucketName)) {
throw new BusinessException(CommonResponseEnum.FAIL, "MinIO桶配置缺失" + bucketKey);
}
return bucketName;
}
}

View File

@@ -7,24 +7,35 @@ import com.njcn.common.pojo.constant.OperateType;
import com.njcn.common.pojo.enums.common.DataStateEnum;
import com.njcn.common.pojo.enums.common.LogEnum;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.common.pojo.response.HttpResult;
import com.njcn.common.utils.LogUtil;
import com.njcn.gather.user.user.pojo.param.SysUserParam;
import com.njcn.gather.user.user.pojo.po.SysFile;
import com.njcn.gather.user.user.pojo.po.SysRole;
import com.njcn.gather.user.user.pojo.po.SysUser;
import com.njcn.gather.user.user.pojo.po.SysUserSignature;
import com.njcn.gather.user.user.service.ISysFileService;
import com.njcn.gather.user.user.service.ISysUserRoleService;
import com.njcn.gather.user.user.service.ISysUserSignatureService;
import com.njcn.gather.user.user.service.ISysUserService;
import com.njcn.gather.user.user.component.SysFileMinioStorageService;
import com.njcn.web.controller.BaseController;
import com.njcn.web.utils.HttpResultUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.net.URLEncoder;
import java.util.List;
import java.util.stream.Collectors;
@@ -42,6 +53,9 @@ public class SysUserController extends BaseController {
private final ISysUserService sysUserService;
private final ISysUserRoleService sysUserRoleService;
private final ISysUserSignatureService sysUserSignatureService;
private final ISysFileService sysFileService;
private final SysFileMinioStorageService sysFileMinioStorageService;
@OperateInfo(info = LogEnum.SYSTEM_COMMON)
@PostMapping("/list")
@@ -55,13 +69,14 @@ public class SysUserController extends BaseController {
}
@OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.ADD)
@PostMapping("/add")
@PostMapping(value = "/add", consumes = {"multipart/form-data"})
@ApiOperation("新增用户")
@ApiImplicitParam(name = "addUserParam", value = "新增用户", required = true)
public HttpResult<Boolean> add(@RequestBody @Validated SysUserParam.SysUserAddParam addUserParam) {
public HttpResult<Boolean> add(@RequestPart("request") @Validated SysUserParam.SysUserAddParam addUserParam,
@RequestPart(value = "signatureFile", required = false) MultipartFile signatureFile) {
String methodDescribe = getMethodDescribe("add");
LogUtil.njcnDebug(log, "{},用户数据为:{}", methodDescribe, addUserParam);
boolean result = sysUserService.addUser(addUserParam);
boolean result = sysUserService.addUser(addUserParam, signatureFile);
if (result) {
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe);
} else {
@@ -70,13 +85,14 @@ public class SysUserController extends BaseController {
}
@OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.UPDATE)
@PostMapping("/update")
@PostMapping(value = "/update", consumes = {"multipart/form-data"})
@ApiOperation("修改用户")
@ApiImplicitParam(name = "updateUserParam", value = "修改用户", required = true)
public HttpResult<Boolean> update(@RequestBody @Validated SysUserParam.SysUserUpdateParam updateUserParam) {
public HttpResult<Boolean> update(@RequestPart("request") @Validated SysUserParam.SysUserUpdateParam updateUserParam,
@RequestPart(value = "signatureFile", required = false) MultipartFile signatureFile) {
String methodDescribe = getMethodDescribe("update");
LogUtil.njcnDebug(log, "{},用户数据为:{}", methodDescribe, updateUserParam);
boolean result = sysUserService.updateUser(updateUserParam);
boolean result = sysUserService.updateUser(updateUserParam, signatureFile);
if (result) {
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe);
} else {
@@ -128,7 +144,43 @@ public class SysUserController extends BaseController {
user.setRoleCodes(sysRoles.stream().map(SysRole::getCode).collect(Collectors.toList()));
user.setRoleNames(sysRoles.stream().map(SysRole::getName).collect(Collectors.toList()));
});
sysUserSignatureService.fillSignatureInfo(result);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
}
@OperateInfo(info = LogEnum.SYSTEM_COMMON)
@GetMapping("/{id}/signature")
@ApiOperation("预览用户签名")
@ApiImplicitParam(name = "id", value = "用户id", required = true)
public ResponseEntity<InputStreamResource> signature(@PathVariable("id") String id) {
SysUserSignature signature = sysUserSignatureService.getActiveByUserId(id);
if (signature == null) {
throw new BusinessException(CommonResponseEnum.FAIL, "用户签名不存在");
}
SysFile sysFile = sysFileService.getActiveById(signature.getFileId());
if (sysFile == null) {
throw new BusinessException(CommonResponseEnum.FAIL, "用户签名文件不存在");
}
SysFileMinioStorageService.StoredObject storedObject =
sysFileMinioStorageService.getObject(sysFile.getBucketName(), sysFile.getObjectName());
String encodedFileName;
try {
encodedFileName = URLEncoder.encode(sysFile.getFileName(), "UTF-8")
.replace("+", "%20");
} catch (Exception exception) {
throw new BusinessException(CommonResponseEnum.FAIL, "读取用户签名失败");
}
MediaType mediaType;
try {
mediaType = sysFile.getContentType() == null ? MediaType.APPLICATION_OCTET_STREAM
: MediaType.parseMediaType(sysFile.getContentType());
} catch (Exception exception) {
mediaType = MediaType.APPLICATION_OCTET_STREAM;
}
return ResponseEntity.ok()
.contentType(mediaType)
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename*=UTF-8''" + encodedFileName)
.body(new InputStreamResource(storedObject.getInputStream()));
}
}

View File

@@ -0,0 +1,10 @@
package com.njcn.gather.user.user.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.njcn.gather.user.user.pojo.po.SysFile;
/**
* 通用文件 Mapper。
*/
public interface SysFileMapper extends BaseMapper<SysFile> {
}

View File

@@ -0,0 +1,10 @@
package com.njcn.gather.user.user.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.njcn.gather.user.user.pojo.po.SysUserSignature;
/**
* 用户签名 Mapper。
*/
public interface SysUserSignatureMapper extends BaseMapper<SysUserSignature> {
}

View File

@@ -0,0 +1,19 @@
package com.njcn.gather.user.user.pojo.constant;
/**
* 通用文件常量。
*/
public final class SysFileConst {
private SysFileConst() {
}
public static final Integer STATUS_DELETED = 0;
public static final Integer STATUS_NORMAL = 1;
public static final String STORAGE_TYPE_MINIO = "MINIO";
public static final String BIZ_TYPE_USER_SIGNATURE = "USER_SIGNATURE";
public static final String BIZ_DIR_USER_SIGNATURE = "user-signature";
public static final String BUCKET_KEY_USER_SIGNATURE = "user-signature";
}

View File

@@ -0,0 +1,43 @@
package com.njcn.gather.user.user.pojo.constant;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* 通用文件扩展名常量。
*/
public final class SysFileExtensionConst {
private SysFileExtensionConst() {
}
/**
* 图片文件扩展名,适用于签名、图片附件等场景。
*/
public static final Set<String> IMAGE_EXTENSIONS = unmodifiableSet(
"png", "jpg", "jpeg", "bmp", "webp");
/**
* 常见文档文件扩展名,适用于文档、台账、说明文件等场景。
*/
public static final Set<String> DOCUMENT_EXTENSIONS = unmodifiableSet(
"doc", "docx", "xls", "xlsx", "txt", "pdf");
/**
* 通用上传扩展名,供后续文件业务按需复用。
*/
public static final Set<String> COMMON_UPLOAD_EXTENSIONS = merge(IMAGE_EXTENSIONS, DOCUMENT_EXTENSIONS);
private static Set<String> merge(Set<String> left, Set<String> right) {
LinkedHashSet<String> merged = new LinkedHashSet<String>();
merged.addAll(left);
merged.addAll(right);
return Collections.unmodifiableSet(merged);
}
private static Set<String> unmodifiableSet(String... values) {
return Collections.unmodifiableSet(new LinkedHashSet<String>(Arrays.asList(values)));
}
}

View File

@@ -0,0 +1,48 @@
package com.njcn.gather.user.user.pojo.po;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.njcn.db.mybatisplus.bo.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
/**
* 通用文件记录。
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_file")
public class SysFile extends BaseEntity implements Serializable {
private static final long serialVersionUID = -5516228278748156912L;
@TableId("id")
private String id;
@TableField("biz_type")
private String bizType;
@TableField("file_name")
private String fileName;
@TableField("bucket_name")
private String bucketName;
@TableField("object_name")
private String objectName;
@TableField("content_type")
private String contentType;
@TableField("file_size")
private Long fileSize;
@TableField("storage_type")
private String storageType;
@TableField("status")
private Integer status;
}

View File

@@ -94,5 +94,11 @@ public class SysUser extends BaseEntity implements Serializable {
@TableField(exist = false)
private List<String> roleNames;
@TableField(exist = false)
private String signatureFileId;
@TableField(exist = false)
private String signatureFileName;
}

View File

@@ -0,0 +1,36 @@
package com.njcn.gather.user.user.pojo.po;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.njcn.db.mybatisplus.bo.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
/**
* 用户签名记录。
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_user_signature")
public class SysUserSignature extends BaseEntity implements Serializable {
private static final long serialVersionUID = 5765362638635953846L;
@TableId("id")
private String id;
@TableField("user_id")
private String userId;
@TableField("user_name")
private String userName;
@TableField("file_id")
private String fileId;
@TableField("status")
private Integer status;
}

View File

@@ -0,0 +1,55 @@
package com.njcn.gather.user.user.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.njcn.gather.user.user.pojo.po.SysFile;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
* 通用文件服务。
*/
public interface ISysFileService extends IService<SysFile> {
/**
* 上传并保存 MinIO 文件记录。
*
* @param bizType 业务类型
* @param bucketKey 桶配置键
* @param bizDir MinIO 目录
* @param ownerId 业务归属ID
* @param file 上传文件
* @return 文件记录
*/
SysFile saveMinioFile(String bizType, String bucketKey, String bizDir, String ownerId, MultipartFile file);
/**
* 根据 ID 查询有效文件。
*
* @param fileId 文件ID
* @return 文件记录
*/
SysFile getActiveById(String fileId);
/**
* 按文件 ID 列表查询有效文件。
*
* @param fileIds 文件ID列表
* @return 文件记录
*/
List<SysFile> listActiveByIds(List<String> fileIds);
/**
* 逻辑删除文件记录。
*
* @param file 文件记录
*/
void markDeleted(SysFile file);
/**
* 删除 MinIO 对象。
*
* @param file 文件记录
*/
void deleteObjectQuietly(SysFile file);
}

View File

@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.njcn.gather.user.user.pojo.param.SysUserParam;
import com.njcn.gather.user.user.pojo.po.SysUser;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@@ -64,7 +65,7 @@ public interface ISysUserService extends IService<SysUser> {
* @param addUserParam 新增用户参数
* @return 结果true表示新增成功false表示新增失败
*/
boolean addUser(SysUserParam.SysUserAddParam addUserParam);
boolean addUser(SysUserParam.SysUserAddParam addUserParam, MultipartFile signatureFile);
/**
* 更新用户
@@ -72,7 +73,7 @@ public interface ISysUserService extends IService<SysUser> {
* @param updateUserParam 更新用户参数
* @return 结果true表示更新成功false表示更新失败
*/
boolean updateUser(SysUserParam.SysUserUpdateParam updateUserParam);
boolean updateUser(SysUserParam.SysUserUpdateParam updateUserParam, MultipartFile signatureFile);
/**
* 修改密码

View File

@@ -0,0 +1,61 @@
package com.njcn.gather.user.user.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.njcn.gather.user.user.pojo.po.SysUser;
import com.njcn.gather.user.user.pojo.po.SysUserSignature;
import java.util.List;
/**
* 用户签名服务。
*/
public interface ISysUserSignatureService extends IService<SysUserSignature> {
/**
* 回填用户签名展示信息。
*
* @param users 用户列表
*/
void fillSignatureInfo(List<SysUser> users);
/**
* 查询用户当前签名。
*
* @param userId 用户ID
* @return 签名记录
*/
SysUserSignature getActiveByUserId(String userId);
/**
* 保存或更新签名元数据。
*
* @param userId 用户ID
* @param userName 用户名
* @param fileId 通用文件ID
* @return 旧签名记录,若不存在则返回 null
*/
SysUserSignature saveOrUpdateSignature(String userId, String userName, String fileId);
/**
* 同步签名记录中的用户名。
*
* @param userId 用户ID
* @param userName 用户名
*/
void syncUserName(String userId, String userName);
/**
* 查询用户签名列表。
*
* @param userIds 用户ID列表
* @return 签名记录
*/
List<SysUserSignature> listActiveByUserIds(List<String> userIds);
/**
* 逻辑删除用户签名记录。
*
* @param signatures 签名记录
*/
void markDeleted(List<SysUserSignature> signatures);
}

View File

@@ -0,0 +1,98 @@
package com.njcn.gather.user.user.service.impl;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.gather.user.user.component.SysFileMinioStorageService;
import com.njcn.gather.user.user.config.StorageMinioProperties;
import com.njcn.gather.user.user.mapper.SysFileMapper;
import com.njcn.gather.user.user.pojo.constant.SysFileConst;
import com.njcn.gather.user.user.pojo.po.SysFile;
import com.njcn.gather.user.user.service.ISysFileService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
/**
* 通用文件服务实现。
*/
@Service
@RequiredArgsConstructor
public class SysFileServiceImpl extends ServiceImpl<SysFileMapper, SysFile> implements ISysFileService {
private final StorageMinioProperties storageMinioProperties;
private final SysFileMinioStorageService sysFileMinioStorageService;
@Override
public SysFile saveMinioFile(String bizType, String bucketKey, String bizDir, String ownerId, MultipartFile file) {
String fileId = UUID.randomUUID().toString().replace("-", "");
String bucketName = storageMinioProperties.requireBucket(bucketKey);
SysFileMinioStorageService.StoredObjectInfo storedObjectInfo =
sysFileMinioStorageService.save(bucketName, bizDir, ownerId, fileId, file);
SysFile sysFile = new SysFile();
sysFile.setId(fileId);
sysFile.setBizType(bizType);
sysFile.setFileName(storedObjectInfo.getFileName());
sysFile.setBucketName(storedObjectInfo.getBucketName());
sysFile.setObjectName(storedObjectInfo.getObjectName());
sysFile.setContentType(storedObjectInfo.getContentType());
sysFile.setFileSize(storedObjectInfo.getFileSize());
sysFile.setStorageType(SysFileConst.STORAGE_TYPE_MINIO);
sysFile.setStatus(SysFileConst.STATUS_NORMAL);
try {
boolean saved = this.save(sysFile);
if (!saved) {
sysFileMinioStorageService.deleteQuietly(sysFile.getBucketName(), sysFile.getObjectName());
throw new BusinessException(CommonResponseEnum.FAIL, "保存文件记录失败");
}
return sysFile;
} catch (RuntimeException exception) {
sysFileMinioStorageService.deleteQuietly(sysFile.getBucketName(), sysFile.getObjectName());
throw exception;
}
}
@Override
public SysFile getActiveById(String fileId) {
if (fileId == null || fileId.trim().isEmpty()) {
return null;
}
return this.lambdaQuery()
.eq(SysFile::getId, fileId)
.eq(SysFile::getStatus, SysFileConst.STATUS_NORMAL)
.one();
}
@Override
public List<SysFile> listActiveByIds(List<String> fileIds) {
if (CollUtil.isEmpty(fileIds)) {
return Collections.emptyList();
}
return this.lambdaQuery()
.in(SysFile::getId, fileIds)
.eq(SysFile::getStatus, SysFileConst.STATUS_NORMAL)
.list();
}
@Override
public void markDeleted(SysFile file) {
if (file == null) {
return;
}
file.setStatus(SysFileConst.STATUS_DELETED);
this.updateById(file);
}
@Override
public void deleteObjectQuietly(SysFile file) {
if (file == null) {
return;
}
sysFileMinioStorageService.deleteQuietly(file.getBucketName(), file.getObjectName());
}
}

View File

@@ -13,35 +13,43 @@ import com.njcn.db.mybatisplus.constant.DbConstant;
import com.njcn.gather.user.pojo.constant.RoleConst;
import com.njcn.gather.user.pojo.constant.UserConst;
import com.njcn.gather.user.pojo.enums.UserResponseEnum;
import com.njcn.gather.user.user.pojo.constant.SysFileExtensionConst;
import com.njcn.gather.user.user.mapper.SysUserMapper;
import com.njcn.gather.user.user.pojo.constant.SysFileConst;
import com.njcn.gather.user.user.pojo.param.SysUserParam;
import com.njcn.gather.user.user.pojo.po.SysFile;
import com.njcn.gather.user.user.pojo.po.SysRole;
import com.njcn.gather.user.user.pojo.po.SysUser;
import com.njcn.gather.user.user.pojo.po.SysUserSignature;
import com.njcn.gather.user.user.service.ISysFileService;
import com.njcn.gather.user.user.service.ISysUserRoleService;
import com.njcn.gather.user.user.service.ISysUserSignatureService;
import com.njcn.gather.user.user.service.ISysUserService;
import com.njcn.web.factory.PageFactory;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Locale;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* @author caozehui
* @date 2024-11-08
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements ISysUserService {
private final ISysUserRoleService sysUserRoleService;
private final ISysFileService sysFileService;
private final ISysUserSignatureService sysUserSignatureService;
@Override
public Page<SysUser> listUser(SysUserParam.SysUserQueryParam queryParam) {
@@ -65,6 +73,7 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
sysUser.setRoleIds(sysRoles.stream().map(SysRole::getId).collect(Collectors.toList()));
sysUser.setRoleNames(sysRoles.stream().map(SysRole::getName).collect(Collectors.toList()));
});
sysUserSignatureService.fillSignatureInfo(page.getRecords());
return page;
}
@@ -105,7 +114,7 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
@Override
@Transactional
public boolean addUser(SysUserParam.SysUserAddParam addUserParam) {
public boolean addUser(SysUserParam.SysUserAddParam addUserParam, MultipartFile signatureFile) {
addUserParam.setName(addUserParam.getName().trim());
addUserParam.setLoginName(addUserParam.getLoginName().trim());
if (UserConst.SUPER_ADMIN.equals(addUserParam.getLoginName())) {
@@ -117,26 +126,79 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
checkRepeat(addUserParam, false, null);
SysUser sysUser = new SysUser();
BeanUtils.copyProperties(addUserParam, sysUser);
sysUser.setId(UUID.randomUUID().toString().replace("-", ""));
String secretkey = Sm4Utils.globalSecretKey;
Sm4Utils sm4 = new Sm4Utils(secretkey);
sysUser.setPassword(sm4.encryptData_ECB(sysUser.getPassword()));
sysUser.setLoginTime(LocalDateTimeUtil.now());
sysUser.setLoginErrorTimes(0);
sysUser.setState(UserConst.STATE_ENABLE);
boolean result = this.save(sysUser);
sysUserRoleService.addUserRole(sysUser.getId(), addUserParam.getRoleIds());
return result;
MultipartFile normalizedSignature = normalizeSignatureFile(signatureFile);
SysFile sysFile = null;
if (normalizedSignature != null) {
sysFile = sysFileService.saveMinioFile(SysFileConst.BIZ_TYPE_USER_SIGNATURE,
SysFileConst.BUCKET_KEY_USER_SIGNATURE, SysFileConst.BIZ_DIR_USER_SIGNATURE,
sysUser.getId(), normalizedSignature);
}
try {
boolean result = this.save(sysUser);
if (result) {
sysUserRoleService.addUserRole(sysUser.getId(), addUserParam.getRoleIds());
if (sysFile != null) {
sysUserSignatureService.saveOrUpdateSignature(sysUser.getId(), sysUser.getName(), sysFile.getId());
}
}
if (!result && sysFile != null) {
cleanupNewFile(sysFile);
}
return result;
} catch (RuntimeException exception) {
if (sysFile != null) {
cleanupNewFile(sysFile);
}
throw exception;
}
}
@Override
@Transactional
public boolean updateUser(SysUserParam.SysUserUpdateParam updateUserParam) {
public boolean updateUser(SysUserParam.SysUserUpdateParam updateUserParam, MultipartFile signatureFile) {
updateUserParam.setName(updateUserParam.getName().trim());
checkRepeat(updateUserParam, true, updateUserParam.getId());
SysUser sysUser = new SysUser();
BeanUtils.copyProperties(updateUserParam, sysUser);
sysUserRoleService.updateUserRole(sysUser.getId(), updateUserParam.getRoleIds());
return this.updateById(sysUser);
MultipartFile normalizedSignature = normalizeSignatureFile(signatureFile);
SysFile newSysFile = null;
if (normalizedSignature != null) {
newSysFile = sysFileService.saveMinioFile(SysFileConst.BIZ_TYPE_USER_SIGNATURE,
SysFileConst.BUCKET_KEY_USER_SIGNATURE, SysFileConst.BIZ_DIR_USER_SIGNATURE,
sysUser.getId(), normalizedSignature);
}
try {
boolean updated = this.updateById(sysUser);
if (!updated) {
if (newSysFile != null) {
cleanupNewFile(newSysFile);
}
return false;
}
sysUserRoleService.updateUserRole(sysUser.getId(), updateUserParam.getRoleIds());
if (newSysFile != null) {
SysUserSignature oldSignature = sysUserSignatureService.saveOrUpdateSignature(
sysUser.getId(), sysUser.getName(), newSysFile.getId());
if (oldSignature != null) {
cleanupOldFile(oldSignature.getFileId());
}
} else {
sysUserSignatureService.syncUserName(sysUser.getId(), sysUser.getName());
}
return true;
} catch (RuntimeException exception) {
if (newSysFile != null) {
cleanupNewFile(newSysFile);
}
throw exception;
}
}
@Override
@@ -170,12 +232,18 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
}
}
}
List<SysUserSignature> signatures = sysUserSignatureService.listActiveByUserIds(ids);
// 删除用户角色关联数据
sysUserRoleService.deleteUserRoleByUserIds(ids);
return this.lambdaUpdate()
boolean result = this.lambdaUpdate()
.set(SysUser::getState, UserConst.STATE_DELETE)
.in(SysUser::getId, ids)
.update();
if (result) {
sysUserSignatureService.markDeleted(signatures);
signatures.forEach(signature -> cleanupOldFile(signature.getFileId()));
}
return result;
}
@Override
@@ -210,4 +278,37 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
throw new BusinessException(UserResponseEnum.REGISTER_EMAIL_FAIL);
}
}
private MultipartFile normalizeSignatureFile(MultipartFile signatureFile) {
if (signatureFile == null || signatureFile.isEmpty()) {
return null;
}
String originalFileName = signatureFile.getOriginalFilename();
if (originalFileName == null || originalFileName.trim().isEmpty()) {
throw new BusinessException(UserResponseEnum.SIGNATURE_FILE_NAME_EMPTY);
}
int dotIndex = originalFileName.lastIndexOf('.');
if (dotIndex < 0 || dotIndex == originalFileName.length() - 1) {
throw new BusinessException(UserResponseEnum.SIGNATURE_FILE_TYPE_ERROR);
}
String extension = originalFileName.substring(dotIndex + 1).toLowerCase(Locale.ENGLISH);
if (!SysFileExtensionConst.IMAGE_EXTENSIONS.contains(extension)) {
throw new BusinessException(UserResponseEnum.SIGNATURE_FILE_TYPE_ERROR);
}
return signatureFile;
}
private void cleanupNewFile(SysFile sysFile) {
sysFileService.markDeleted(sysFile);
sysFileService.deleteObjectQuietly(sysFile);
}
private void cleanupOldFile(String fileId) {
SysFile oldFile = sysFileService.getActiveById(fileId);
if (oldFile == null) {
return;
}
sysFileService.markDeleted(oldFile);
sysFileService.deleteObjectQuietly(oldFile);
}
}

View File

@@ -0,0 +1,134 @@
package com.njcn.gather.user.user.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.njcn.gather.user.user.mapper.SysUserSignatureMapper;
import com.njcn.gather.user.user.pojo.constant.SysFileConst;
import com.njcn.gather.user.user.pojo.po.SysFile;
import com.njcn.gather.user.user.pojo.po.SysUser;
import com.njcn.gather.user.user.pojo.po.SysUserSignature;
import com.njcn.gather.user.user.service.ISysFileService;
import com.njcn.gather.user.user.service.ISysUserSignatureService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* 用户签名服务实现。
*/
@Service
@RequiredArgsConstructor
public class SysUserSignatureServiceImpl extends ServiceImpl<SysUserSignatureMapper, SysUserSignature>
implements ISysUserSignatureService {
private final ISysFileService sysFileService;
@Override
public void fillSignatureInfo(List<SysUser> users) {
if (CollUtil.isEmpty(users)) {
return;
}
Map<String, SysUserSignature> signatureMap = new LinkedHashMap<String, SysUserSignature>();
List<String> userIds = users.stream().map(SysUser::getId).collect(Collectors.toList());
for (SysUserSignature signature : listActiveByUserIds(userIds)) {
signatureMap.put(signature.getUserId(), signature);
}
Map<String, SysFile> sysFileMap = new LinkedHashMap<String, SysFile>();
List<String> fileIds = signatureMap.values().stream().map(SysUserSignature::getFileId).collect(Collectors.toList());
for (SysFile sysFile : sysFileService.listActiveByIds(fileIds)) {
sysFileMap.put(sysFile.getId(), sysFile);
}
for (SysUser user : users) {
SysUserSignature signature = signatureMap.get(user.getId());
if (signature == null) {
user.setSignatureFileId(null);
user.setSignatureFileName(null);
continue;
}
user.setSignatureFileId(signature.getFileId());
SysFile sysFile = sysFileMap.get(signature.getFileId());
user.setSignatureFileName(sysFile == null ? null : sysFile.getFileName());
}
}
@Override
public SysUserSignature getActiveByUserId(String userId) {
if (StrUtil.isBlank(userId)) {
return null;
}
return this.lambdaQuery()
.eq(SysUserSignature::getUserId, userId)
.eq(SysUserSignature::getStatus, SysFileConst.STATUS_NORMAL)
.one();
}
@Override
public SysUserSignature saveOrUpdateSignature(String userId, String userName, String fileId) {
SysUserSignature existing = getActiveByUserId(userId);
if (existing == null) {
SysUserSignature signature = new SysUserSignature();
signature.setId(UUID.randomUUID().toString().replace("-", ""));
signature.setUserId(userId);
signature.setUserName(userName);
signature.setFileId(fileId);
signature.setStatus(SysFileConst.STATUS_NORMAL);
this.save(signature);
return null;
}
SysUserSignature oldSignature = copySignature(existing);
existing.setUserName(userName);
existing.setFileId(fileId);
existing.setStatus(SysFileConst.STATUS_NORMAL);
this.updateById(existing);
return oldSignature;
}
@Override
public void syncUserName(String userId, String userName) {
SysUserSignature signature = getActiveByUserId(userId);
if (signature == null || StrUtil.equals(signature.getUserName(), userName)) {
return;
}
signature.setUserName(userName);
this.updateById(signature);
}
@Override
public List<SysUserSignature> listActiveByUserIds(List<String> userIds) {
if (CollUtil.isEmpty(userIds)) {
return Collections.emptyList();
}
return this.lambdaQuery()
.in(SysUserSignature::getUserId, userIds)
.eq(SysUserSignature::getStatus, SysFileConst.STATUS_NORMAL)
.list();
}
@Override
public void markDeleted(List<SysUserSignature> signatures) {
if (CollUtil.isEmpty(signatures)) {
return;
}
for (SysUserSignature signature : signatures) {
signature.setStatus(SysFileConst.STATUS_DELETED);
this.updateById(signature);
}
}
private SysUserSignature copySignature(SysUserSignature source) {
SysUserSignature copy = new SysUserSignature();
copy.setId(source.getId());
copy.setUserId(source.getUserId());
copy.setUserName(source.getUserName());
copy.setFileId(source.getFileId());
copy.setStatus(source.getStatus());
return copy;
}
}