✨ 认证审核接口支持修订版内容写入
- 新增 CertAuditRequest,audit 接口原地升级为 body 优先、query 兜底,缺 id/pass 返回 400 - 通过时同事务写入 audited_snapshot_json(驳回忽略修订);修订内容先校验可反序列化与关键字段再落库 - 写回档案与司机端「当前生效内容」改为修订版优先,为空回退 snapshot_json - 读不懂的历史快照不再静默跳过,改为抛错回滚,杜绝空内容被盖章通过 - 审核详情下发 auditedSnapshotJson;司机端 VO 增加 revised 标记,快照字段不随 VO 下发
This commit is contained in:
+64
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.pojo.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 管理端认证审核请求
|
||||
* <p>
|
||||
* 这个接口原本只有 query 参数({@code id} / {@code pass} / {@code rejectReason}),
|
||||
* 新增修订能力后改为「body 优先、query 兜底」:老前端不改代码仍然能用,
|
||||
* 新前端把修订内容放进 body。两者同时给出时以 body 为准,避免审核员的修订被旧值覆盖。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "管理端认证审核请求")
|
||||
public class CertAuditRequest implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "认证记录主键")
|
||||
private Long id;
|
||||
@Schema(description = "true 通过,false 驳回")
|
||||
private Boolean pass;
|
||||
@Schema(description = "驳回原因;驳回时必填,≤200 字")
|
||||
private String rejectReason;
|
||||
/**
|
||||
* 审核员修订后的全量认证内容(JSON);空或空白表示不修订
|
||||
* <p>
|
||||
* 通过时才生效:它既是写回档案的依据,也会原样留痕到认证记录上。
|
||||
*/
|
||||
@Schema(description = "审核员修订后的全量认证内容JSON;空表示不修订")
|
||||
private String auditedSnapshotJson;
|
||||
|
||||
}
|
||||
+14
@@ -28,6 +28,7 @@ package org.springblade.transport.pojo.entity;
|
||||
import com.baomidou.mybatisplus.annotation.FieldStrategy;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
@@ -125,6 +126,19 @@ public class TransportCertRecord extends TenantEntity {
|
||||
@TableField(updateStrategy = FieldStrategy.NEVER)
|
||||
@Schema(description = "该次提交的全量认证内容JSON")
|
||||
private String snapshotJson;
|
||||
/**
|
||||
* 审核通过时确定的修订版内容(JSON)
|
||||
* <p>
|
||||
* 审核员可以在通过时直接改内容:司机提交的是 {@code snapshotJson}(永不可改),
|
||||
* 最终写回档案、司机端读到的「当前生效内容」以本字段优先,为 null 才回退到
|
||||
* {@code snapshotJson}。为 null 即「审核员没有修订」。
|
||||
* <p>
|
||||
* {@code @JsonIgnore}:司机端的 {@code TransportCertRecordVO} 继承本实体,
|
||||
* 修订版是审核过程的留痕,不该下发给司机。
|
||||
*/
|
||||
@JsonIgnore
|
||||
@Schema(description = "审核通过时确定的修订版内容JSON;null 表示未修订")
|
||||
private String auditedSnapshotJson;
|
||||
/**
|
||||
* 驳回原因,仅当审核状态为已驳回时有值
|
||||
*/
|
||||
|
||||
+7
@@ -81,6 +81,13 @@ public class CertAuditDetailVO implements Serializable {
|
||||
|
||||
@Schema(description = "待审快照:本次提交的全量内容")
|
||||
private String pendingSnapshotJson;
|
||||
/**
|
||||
* 审核员在本次审核中填写的修订版内容;未修订时为 null
|
||||
* <p>
|
||||
* 只在「通过」时才会落库,驳回不产生生效内容。审核页据此回显审核员改了什么。
|
||||
*/
|
||||
@Schema(description = "本次审核提交的修订版内容;未修订时为 null")
|
||||
private String auditedSnapshotJson;
|
||||
@Schema(description = "生效快照:该对象当前生效的全量内容;从未认证通过时为 null")
|
||||
private String effectiveSnapshotJson;
|
||||
@Schema(description = "生效快照记录ID;null 表示该对象从未认证通过")
|
||||
|
||||
+8
@@ -96,5 +96,13 @@ public class TransportCertRecordVO extends TransportCertRecord {
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "司机手机号")
|
||||
private String driverMobile;
|
||||
/**
|
||||
* 该条记录是否经过审核员修订
|
||||
* <p>
|
||||
* 由服务层按 {@code auditedSnapshotJson} 是否有内容算出,前端据此打「已修订」标记。
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "是否经过审核员修订")
|
||||
private Boolean revised;
|
||||
|
||||
}
|
||||
|
||||
+20
-4
@@ -36,6 +36,7 @@ import org.springblade.core.mp.support.Condition;
|
||||
import org.springblade.core.mp.support.Query;
|
||||
import org.springblade.core.secure.annotation.PreAuth;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.transport.pojo.dto.CertAuditRequest;
|
||||
import org.springblade.transport.pojo.vo.CertAuditCapabilityVO;
|
||||
import org.springblade.transport.pojo.vo.CertAuditDetailVO;
|
||||
import org.springblade.transport.pojo.vo.TransportCertRecordVO;
|
||||
@@ -43,6 +44,7 @@ import org.springblade.transport.service.ICertAuditService;
|
||||
import org.springblade.transport.support.CertAuditPermissions;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
@@ -92,13 +94,27 @@ public class CertificationAuditController extends BladeController {
|
||||
|
||||
@PostMapping("/audit")
|
||||
@ApiOperationSupport(order = 3)
|
||||
@Operation(summary = "审核认证记录", description = "通过使该记录成为生效快照;驳回只终结该记录,生效快照不变")
|
||||
@Operation(summary = "审核认证记录", description = "通过使该记录成为生效快照;驳回只终结该记录,生效快照不变。通过时可携带修订后的全量认证内容,它将成为新的生效内容并原样留痕")
|
||||
@PreAuth(CertAuditPermissions.AUDIT_ACTION)
|
||||
public R<Boolean> audit(
|
||||
@Parameter(description = "认证记录主键", required = true) @RequestParam Long id,
|
||||
@Parameter(description = "是否通过", required = true) @RequestParam Boolean pass,
|
||||
@Parameter(description = "审核请求体(推荐):id、pass、rejectReason、auditedSnapshotJson")
|
||||
@RequestBody(required = false) CertAuditRequest body,
|
||||
@Parameter(description = "认证记录主键(兼容老调用)") @RequestParam(required = false) Long id,
|
||||
@Parameter(description = "是否通过(兼容老调用)") @RequestParam(required = false) Boolean pass,
|
||||
@Parameter(description = "驳回原因,驳回时必填") @RequestParam(required = false) String rejectReason) {
|
||||
return R.status(certAuditService.audit(id, Boolean.TRUE.equals(pass), rejectReason));
|
||||
// 原地升级:老前端只发 query,新前端发 body。逐字段「body 优先、query 兜底」——
|
||||
// 两者都给时以 body 为准,否则审核员在表单里改的内容会被 URL 上的旧值悄悄覆盖。
|
||||
Long recordId = body != null && body.getId() != null ? body.getId() : id;
|
||||
Boolean approved = body != null && body.getPass() != null ? body.getPass() : pass;
|
||||
String reason = body != null && body.getRejectReason() != null ? body.getRejectReason() : rejectReason;
|
||||
String revision = body == null ? null : body.getAuditedSnapshotJson();
|
||||
if (recordId == null) {
|
||||
return R.fail("缺少认证记录ID");
|
||||
}
|
||||
if (approved == null) {
|
||||
return R.fail("缺少审核结果");
|
||||
}
|
||||
return R.status(certAuditService.audit(recordId, Boolean.TRUE.equals(approved), reason, revision));
|
||||
}
|
||||
|
||||
// 本接口不挂 @PreAuth;无权限时返回 false。
|
||||
|
||||
+5
-4
@@ -74,12 +74,13 @@ public interface ICertAuditService {
|
||||
/**
|
||||
* 审核一条认证记录
|
||||
*
|
||||
* @param recordId 认证记录主键
|
||||
* @param pass true 通过,false 驳回
|
||||
* @param rejectReason 驳回原因;驳回时必填,≤200 字
|
||||
* @param recordId 认证记录主键
|
||||
* @param pass true 通过,false 驳回
|
||||
* @param rejectReason 驳回原因;驳回时必填,≤200 字
|
||||
* @param auditedSnapshotJson 审核员修订后的全量认证内容;空或空白表示不修订,驳回时忽略
|
||||
* @return 是否成功
|
||||
*/
|
||||
boolean audit(Long recordId, boolean pass, String rejectReason);
|
||||
boolean audit(Long recordId, boolean pass, String rejectReason, String auditedSnapshotJson);
|
||||
|
||||
/**
|
||||
* 当前登录用户的审核能力:能否查看、能否审核
|
||||
|
||||
+75
-19
@@ -138,20 +138,24 @@ public class CertAuditServiceImpl implements ICertAuditService {
|
||||
detail.setAuditUserName(UserCache.getUserRealName(record.getAuditUser()));
|
||||
detail.setRejectReason(record.getRejectReason());
|
||||
detail.setPendingSnapshotJson(record.getSnapshotJson());
|
||||
// 审核员上次填过的修订版:详情页要能回显,否则重开页面就丢了
|
||||
detail.setAuditedSnapshotJson(record.getAuditedSnapshotJson());
|
||||
|
||||
// 生效值一侧:从未认证通过时为 null,前端按「无对比基准」处理
|
||||
detail.setEffectiveRecordId(effectiveId);
|
||||
detail.setCertified(CertificationRuleSupport.isCertified(effectiveId));
|
||||
if (Func.isNotEmpty(effectiveId)) {
|
||||
TransportCertRecord effective = certRecordService.getById(effectiveId);
|
||||
detail.setEffectiveSnapshotJson(effective == null ? null : effective.getSnapshotJson());
|
||||
// 生效一侧同样修订版优先:审核页是拿「新提交」和「当前生效内容」做 diff 的,
|
||||
// 这里取旧值会让审核员看到一份从未生效过的差异
|
||||
detail.setEffectiveSnapshotJson(CertificationRuleSupport.effectiveSnapshotJson(effective));
|
||||
}
|
||||
return detail;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean audit(Long recordId, boolean pass, String rejectReason) {
|
||||
public boolean audit(Long recordId, boolean pass, String rejectReason, String auditedSnapshotJson) {
|
||||
TransportCertRecord record = requireRecord(recordId);
|
||||
|
||||
// 终态不可再审。若允许,把指针指着的那条「已通过」改成「已驳回」,
|
||||
@@ -165,6 +169,13 @@ public class CertAuditServiceImpl implements ICertAuditService {
|
||||
+ TransportCertRecord.REJECT_REASON_MAX_LENGTH + "字)");
|
||||
}
|
||||
|
||||
// 修订版只有「通过」才可能生效:驳回不产生生效内容,传了也不该留痕,
|
||||
// 否则历史里会出现一条「已驳回却带着生效内容」的记录。
|
||||
String revision = pass ? blankToNull(auditedSnapshotJson) : null;
|
||||
// 写回档案的是「本次生效内容」:有修订用修订版,没有就用司机原提交。
|
||||
// 校验放在写库之前——格式不对就整体回滚,不能先落一条状态再报错。
|
||||
String effectiveSnapshotJson = revision == null ? record.getSnapshotJson() : requireUsableRevision(record, revision);
|
||||
|
||||
Long auditor = AuthUtil.getUserId();
|
||||
LocalDateTime auditTime = LocalDateTime.now();
|
||||
|
||||
@@ -177,11 +188,13 @@ public class CertAuditServiceImpl implements ICertAuditService {
|
||||
: CertificationRuleSupport.reasonForReject(rejectReason));
|
||||
patch.setAuditUser(auditor);
|
||||
patch.setAuditTime(auditTime);
|
||||
// 未修订时留 null:框架的 not_null 更新策略会跳过该列,库里保持 NULL 而不是空串
|
||||
patch.setAuditedSnapshotJson(revision);
|
||||
certRecordService.updateById(patch);
|
||||
|
||||
// 2) 认证对象:通过才切指针;驳回只刷展示缓存。两者合并为**一次** UPDATE,
|
||||
// 避免对同一行连续写两次(第二次会把第一次的指针覆盖掉的风险也随之消失)。
|
||||
updateCertTarget(record, pass, rejectReason);
|
||||
updateCertTarget(record, pass, rejectReason, effectiveSnapshotJson);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -212,11 +225,12 @@ public class CertAuditServiceImpl implements ICertAuditService {
|
||||
* </ul>
|
||||
* 分两次写同一行除了多一次往返,还会让「先切指针、后被展示缓存更新覆盖」成为可能。
|
||||
*
|
||||
* @param record 被审核的记录
|
||||
* @param pass 是否通过
|
||||
* @param rejectReason 驳回原因
|
||||
* @param record 被审核的记录
|
||||
* @param pass 是否通过
|
||||
* @param rejectReason 驳回原因
|
||||
* @param effectiveSnapshotJson 本次生效内容(修订版优先,否则司机原提交)
|
||||
*/
|
||||
private void updateCertTarget(TransportCertRecord record, boolean pass, String rejectReason) {
|
||||
private void updateCertTarget(TransportCertRecord record, boolean pass, String rejectReason, String effectiveSnapshotJson) {
|
||||
boolean vehicleTarget = TransportCertRecord.BIZ_TYPE_VEHICLE.equals(record.getBizType());
|
||||
Integer status = pass ? TransportCertRecord.STATUS_APPROVED : TransportCertRecord.STATUS_REJECTED;
|
||||
// 只有通过才切指针;驳回时保持 null,框架的 not_null 更新策略会跳过该列
|
||||
@@ -232,7 +246,7 @@ public class CertAuditServiceImpl implements ICertAuditService {
|
||||
// 不写回的话,审核通过之后管理端与司机端看到的仍是提交之前的旧值——
|
||||
// 状态显示「已通过」、内容却还是旧的,是最容易让人误判的一种不一致。
|
||||
// 与指针合并成同一次 UPDATE。
|
||||
applySnapshotToVehicle(vehicle, record.getSnapshotJson());
|
||||
applySnapshotToVehicle(vehicle, effectiveSnapshotJson);
|
||||
} else {
|
||||
vehicle.setCertificationRejectReason(CertificationRuleSupport.reasonForReject(rejectReason));
|
||||
}
|
||||
@@ -255,7 +269,7 @@ public class CertAuditServiceImpl implements ICertAuditService {
|
||||
// 不写回的话,司机档案里始终是管理端当初录入的旧值(甚至为空),
|
||||
// 而认证状态已是「已通过」——状态与内容不一致最容易让人误判。
|
||||
// 与指针合并成同一次 UPDATE。
|
||||
applySnapshotToDriver(driver, record.getSnapshotJson());
|
||||
applySnapshotToDriver(driver, effectiveSnapshotJson);
|
||||
} else {
|
||||
driver.setCertificationRejectReason(CertificationRuleSupport.reasonForReject(rejectReason));
|
||||
}
|
||||
@@ -332,6 +346,47 @@ public class CertAuditServiceImpl implements ICertAuditService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验审核员填写的修订版内容,通过后原样返回(作为本次生效内容)
|
||||
* <p>
|
||||
* 修订版是审核员的「最终认定」,会直接写回档案。因此必须能反序列化成当前提交结构,
|
||||
* 且带上判定「这份内容有效」的关键字段,否则写回等于什么也没写:
|
||||
* 司机看身份证号与驾驶证号,车辆看行驶证号与核定载质量。
|
||||
* <p>
|
||||
* 不满足时抛错、整体回滚——审核员填错 JSON 是操作失误,不能悄悄降级成「不修订」,
|
||||
* 那样他会以为自己的修订已经生效。
|
||||
*
|
||||
* @param record 被审核的记录
|
||||
* @param revision 已去空白的修订版内容
|
||||
* @return 同一个修订版内容
|
||||
*/
|
||||
private String requireUsableRevision(TransportCertRecord record, String revision) {
|
||||
if (TransportCertRecord.BIZ_TYPE_VEHICLE.equals(record.getBizType())) {
|
||||
VehicleCertSubmitRequest snapshot;
|
||||
try {
|
||||
snapshot = JSON.parseObject(revision, VehicleCertSubmitRequest.class);
|
||||
} catch (Exception exception) {
|
||||
throw new ServiceException("修订内容格式不正确,请检查后重试");
|
||||
}
|
||||
if (snapshot == null
|
||||
|| (Func.isEmpty(snapshot.getDrivingLicenseNo()) && Func.isEmpty(snapshot.getApprovedLoadKg()))) {
|
||||
throw new ServiceException("修订内容缺少行驶证号与核定载质量,无法写入车辆档案");
|
||||
}
|
||||
return revision;
|
||||
}
|
||||
PersonCertSubmitRequest snapshot;
|
||||
try {
|
||||
snapshot = JSON.parseObject(revision, PersonCertSubmitRequest.class);
|
||||
} catch (Exception exception) {
|
||||
throw new ServiceException("修订内容格式不正确,请检查后重试");
|
||||
}
|
||||
if (snapshot == null
|
||||
|| (Func.isEmpty(snapshot.getIdCardNo()) && Func.isEmpty(snapshot.getDriverFileNo()))) {
|
||||
throw new ServiceException("修订内容缺少身份证号与驾驶证号,无法写入司机档案");
|
||||
}
|
||||
return revision;
|
||||
}
|
||||
|
||||
/**
|
||||
* 把生效快照里的司机证件字段搬进司机档案(就地写入同一个 patch 对象)
|
||||
* <p>
|
||||
@@ -345,33 +400,34 @@ public class CertAuditServiceImpl implements ICertAuditService {
|
||||
* 快照里为 null 的字段不会写(框架的 not_null 更新策略会跳过),
|
||||
* 因此不会把管理端填过、司机端没填的字段清空。
|
||||
* <p>
|
||||
* 快照缺失、无法解析或不是当前字段定义时**只跳过写回,不阻断审核**:
|
||||
* 审核本身是独立于写回的业务动作(切指针、置状态),不该因为一份读不懂的快照而失败;
|
||||
* 何况历史快照本来就存在,阻断会让这些对象再也审不过去。
|
||||
* 申报的身份证号已落在另一名司机名下时是例外——那会把两条档案写成同一个证件号,
|
||||
* 必须阻断。
|
||||
* 快照缺失、无法解析或不是当前字段定义时**直接拒绝本次通过并整体回滚**:
|
||||
* 「通过」的语义是「把这批证件内容认定为生效值」,没有可用内容却放行,
|
||||
* 等于给一条空记录盖上已通过的章——状态与内容不一致正是这次改造要消灭的。
|
||||
* 内容确实为空的历史记录,由审核员在审核时补全后重审即可。
|
||||
* 申报的身份证号已落在另一名司机名下时同样阻断——那会把两条档案写成同一个证件号。
|
||||
*
|
||||
* @param driver 待更新的司机 patch
|
||||
* @param snapshotJson 该条记录的全量提交内容
|
||||
*/
|
||||
private void applySnapshotToDriver(Driver driver, String snapshotJson) {
|
||||
if (Func.isEmpty(snapshotJson)) {
|
||||
return;
|
||||
throw new ServiceException("该认证记录没有提交内容,无法写入司机档案;请让司机重新提交一次认证");
|
||||
}
|
||||
PersonCertSubmitRequest snapshot;
|
||||
try {
|
||||
snapshot = JSON.parseObject(snapshotJson, PersonCertSubmitRequest.class);
|
||||
} catch (Exception exception) {
|
||||
return;
|
||||
throw new ServiceException("该认证记录的提交内容无法解析,未写入司机档案;请重新提交一次认证");
|
||||
}
|
||||
if (snapshot == null) {
|
||||
return;
|
||||
throw new ServiceException("该认证记录的提交内容无法解析,未写入司机档案;请重新提交一次认证");
|
||||
}
|
||||
// 字段名对不上时 JSON 会解析成「全是 null」而不报错(历史快照就是这种情况),
|
||||
// 那样写回等于什么也没写,还会把档案里已有的值当成「没填」而跳过。
|
||||
// 故用证件内容里的关键字段判定「这份快照是否是当前格式」,不是就不写。
|
||||
// 故用证件内容里的关键字段判定「这份快照是否是当前格式」,不是就拒绝。
|
||||
if (Func.isEmpty(snapshot.getIdCardNo()) && Func.isEmpty(snapshot.getDriverFileNo())) {
|
||||
return;
|
||||
throw new ServiceException("该认证记录的提交内容与当前字段定义不匹配(可能是早期格式的历史快照),"
|
||||
+ "未写入司机档案;请重新提交一次认证");
|
||||
}
|
||||
// 身份证号要落到档案上,先确认它没有落在别人名下:重复证件号一旦落库,
|
||||
// 建档查重与按证件号识别同一人都会失效
|
||||
|
||||
+4
-2
@@ -209,9 +209,11 @@ public class DriverCertServiceImpl implements IDriverCertService {
|
||||
Driver driver = requireCurrentDriver();
|
||||
// 当前司机实体已加载过,直接读它身上的指针,不再回查一次
|
||||
TransportCertRecord effective = findEffectiveRecord(driver.getEffectiveCertRecordId());
|
||||
// 生效内容以修订版为准:审核员改过的值才是档案里真正生效的那份,
|
||||
// 仍读 snapshot_json 会让司机端拿旧值覆盖审核员的改动
|
||||
return effective == null
|
||||
? null
|
||||
: JSON.parseObject(effective.getSnapshotJson(), PersonCertSubmitRequest.class);
|
||||
: JSON.parseObject(CertificationRuleSupport.effectiveSnapshotJson(effective), PersonCertSubmitRequest.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -272,7 +274,7 @@ public class DriverCertServiceImpl implements IDriverCertService {
|
||||
TransportCertRecord effective = findEffectiveRecord(vehicle.getEffectiveCertRecordId());
|
||||
return effective == null
|
||||
? null
|
||||
: JSON.parseObject(effective.getSnapshotJson(), VehicleCertSubmitRequest.class);
|
||||
: JSON.parseObject(CertificationRuleSupport.effectiveSnapshotJson(effective), VehicleCertSubmitRequest.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+21
@@ -90,6 +90,27 @@ public final class CertificationRuleSupport {
|
||||
* @param effectiveRecordId 生效快照指针
|
||||
* @return 是否已认证
|
||||
*/
|
||||
/**
|
||||
* 取一条记录的「当前生效内容」
|
||||
* <p>
|
||||
* 审核员可以在通过时修订内容:修订版存在则以修订版为准,否则回退到司机原提交
|
||||
* ({@code snapshot_json} 永不修改,它是司机当时提交了什么的历史证据)。
|
||||
* <p>
|
||||
* 写回档案、司机端读生效值、审核详情对比三处必须用同一套取法,
|
||||
* 否则会出现「档案是新值、司机端还是旧值」这种自相矛盾。
|
||||
*
|
||||
* @param record 认证记录,可为 null
|
||||
* @return 生效内容 JSON;记录为空、或两者都为空时返回 null
|
||||
*/
|
||||
public static String effectiveSnapshotJson(TransportCertRecord record) {
|
||||
if (record == null) {
|
||||
return null;
|
||||
}
|
||||
return Func.isNotBlank(record.getAuditedSnapshotJson())
|
||||
? record.getAuditedSnapshotJson()
|
||||
: record.getSnapshotJson();
|
||||
}
|
||||
|
||||
public static boolean isCertified(Long effectiveRecordId) {
|
||||
return Func.isNotEmpty(effectiveRecordId);
|
||||
}
|
||||
|
||||
+3
@@ -27,6 +27,7 @@ package org.springblade.transport.wrapper;
|
||||
|
||||
import org.springblade.core.mp.support.BaseEntityWrapper;
|
||||
import org.springblade.core.tool.utils.BeanUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.system.cache.UserCache;
|
||||
import org.springblade.system.cache.UserDisplayCache;
|
||||
import org.springblade.transport.pojo.entity.TransportCertRecord;
|
||||
@@ -70,6 +71,8 @@ public class TransportCertRecordWrapper extends BaseEntityWrapper<TransportCertR
|
||||
transportCertRecordVO.setAuditUserName(UserCache.getUserRealName(transportCertRecordVO.getAuditUser()));
|
||||
// 状态文案统一在此翻译,避免小程序、Web、接口各处各造一套说法
|
||||
transportCertRecordVO.setStatusText(CertificationRuleSupport.statusText(transportCertRecordVO.getStatus()));
|
||||
// 是否经过审核员修订:修订版非空才算,空串与空白都不算(前端清空输入框会传空串)
|
||||
transportCertRecordVO.setRevised(Func.isNotBlank(transportCertRecordVO.getAuditedSnapshotJson()));
|
||||
return transportCertRecordVO;
|
||||
}
|
||||
|
||||
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
package org.springblade.transport.controller;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springblade.transport.service.ICertAuditService;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
* 认证审核接口的入参绑定测试。
|
||||
* <p>
|
||||
* 这个接口是「原地升级」的:老前端只发 query 参数,新前端发 JSON body 带修订内容。
|
||||
* 两种调用都必须能用,且 body 一旦给了就以 body 为准——否则审核员的修订会被 query 里的旧值覆盖。
|
||||
*/
|
||||
class CertificationAuditEndpointTest {
|
||||
|
||||
private static final Long RECORD_ID = 5001L;
|
||||
private static final String REVISION = "{\"realName\":\"李四\",\"idCardNo\":\"450100199001019999\"}";
|
||||
|
||||
private ICertAuditService certAuditService;
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
certAuditService = mock(ICertAuditService.class);
|
||||
when(certAuditService.audit(any(), anyBoolean(), any(), any())).thenReturn(true);
|
||||
mockMvc = MockMvcBuilders.standaloneSetup(new CertificationAuditController(certAuditService)).build();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("老调用:只带 query 的 id/pass/rejectReason,行为不变")
|
||||
void legacyQueryOnlyStillWorks() throws Exception {
|
||||
mockMvc.perform(post("/certification-audit/audit")
|
||||
.param("id", String.valueOf(RECORD_ID))
|
||||
.param("pass", "true"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200));
|
||||
|
||||
verify(certAuditService).audit(RECORD_ID, true, null, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("新调用:body 携带修订内容,透传到服务层")
|
||||
void bodyCarriesRevision() throws Exception {
|
||||
String body = """
|
||||
{"id": 5001, "pass": true, "auditedSnapshotJson": "{\\"realName\\":\\"李四\\",\\"idCardNo\\":\\"450100199001019999\\"}"}
|
||||
""";
|
||||
|
||||
mockMvc.perform(post("/certification-audit/audit")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(body))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200));
|
||||
|
||||
verify(certAuditService).audit(RECORD_ID, true, null, REVISION);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("两种都带:以 body 为准(query 里的旧值不得覆盖修订)")
|
||||
void bodyWinsOverQuery() throws Exception {
|
||||
String body = """
|
||||
{"id": 5001, "pass": true}
|
||||
""";
|
||||
|
||||
mockMvc.perform(post("/certification-audit/audit")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(body)
|
||||
.param("id", "6001")
|
||||
.param("pass", "false"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200));
|
||||
|
||||
verify(certAuditService).audit(RECORD_ID, true, null, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("body 只给一半时,缺的字段回退到 query")
|
||||
void partialBodyFallsBackToQuery() throws Exception {
|
||||
String body = """
|
||||
{"id": 5001, "pass": false}
|
||||
""";
|
||||
|
||||
mockMvc.perform(post("/certification-audit/audit")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(body)
|
||||
.param("rejectReason", "行驶证模糊"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200));
|
||||
|
||||
verify(certAuditService).audit(RECORD_ID, false, "行驶证模糊", null);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("缺少 id 或 pass:按业务失败返回(code=400),不能把 null 传进服务层")
|
||||
void missingIdOrPassIsRejected() throws Exception {
|
||||
mockMvc.perform(post("/certification-audit/audit").param("pass", "true"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(400));
|
||||
|
||||
mockMvc.perform(post("/certification-audit/audit").param("id", String.valueOf(RECORD_ID)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(400));
|
||||
|
||||
verify(certAuditService, never()).audit(anyLong(), anyBoolean(), any(), any());
|
||||
}
|
||||
}
|
||||
+212
-22
@@ -96,7 +96,7 @@ class CertAuditServiceImplTest {
|
||||
when(certRecordService.getById(RECORD_ID)).thenReturn(record);
|
||||
when(transportVehicleService.getById(VEHICLE_ID)).thenReturn(vehicle(null));
|
||||
|
||||
service.audit(RECORD_ID, true, null);
|
||||
service.audit(RECORD_ID, true, null, null);
|
||||
|
||||
ArgumentCaptor<TransportCertRecord> recordCaptor = ArgumentCaptor.forClass(TransportCertRecord.class);
|
||||
verify(certRecordService).updateById(recordCaptor.capture());
|
||||
@@ -118,7 +118,7 @@ class CertAuditServiceImplTest {
|
||||
when(certRecordService.getById(RECORD_ID)).thenReturn(record);
|
||||
when(transportVehicleService.getById(VEHICLE_ID)).thenReturn(vehicle(null));
|
||||
|
||||
service.audit(RECORD_ID, true, null);
|
||||
service.audit(RECORD_ID, true, null, null);
|
||||
|
||||
ArgumentCaptor<TransportVehicle> captor = ArgumentCaptor.forClass(TransportVehicle.class);
|
||||
verify(transportVehicleService).updateById(captor.capture());
|
||||
@@ -141,7 +141,7 @@ class CertAuditServiceImplTest {
|
||||
when(transportVehicleService.getById(VEHICLE_ID)).thenReturn(vehicle(null));
|
||||
|
||||
ServiceException exception = assertThrows(ServiceException.class,
|
||||
() -> service.audit(RECORD_ID, true, null));
|
||||
() -> service.audit(RECORD_ID, true, null, null));
|
||||
assertTrue(exception.getMessage().contains("字段定义不匹配"), exception.getMessage());
|
||||
verify(transportVehicleService, never()).updateById(any(TransportVehicle.class));
|
||||
}
|
||||
@@ -154,7 +154,7 @@ class CertAuditServiceImplTest {
|
||||
when(certRecordService.getById(RECORD_ID)).thenReturn(record);
|
||||
when(transportVehicleService.getById(VEHICLE_ID)).thenReturn(vehicle(oldEffectiveId));
|
||||
|
||||
service.audit(RECORD_ID, true, null);
|
||||
service.audit(RECORD_ID, true, null, null);
|
||||
|
||||
ArgumentCaptor<TransportVehicle> captor = ArgumentCaptor.forClass(TransportVehicle.class);
|
||||
verify(transportVehicleService).updateById(captor.capture());
|
||||
@@ -169,7 +169,7 @@ class CertAuditServiceImplTest {
|
||||
when(certRecordService.getById(RECORD_ID)).thenReturn(vehicleRecord(TransportCertRecord.STATUS_PENDING));
|
||||
when(transportVehicleService.getById(VEHICLE_ID)).thenReturn(vehicle(null));
|
||||
|
||||
service.audit(RECORD_ID, true, null);
|
||||
service.audit(RECORD_ID, true, null, null);
|
||||
|
||||
ArgumentCaptor<TransportVehicle> captor = ArgumentCaptor.forClass(TransportVehicle.class);
|
||||
verify(transportVehicleService).updateById(captor.capture());
|
||||
@@ -183,7 +183,7 @@ class CertAuditServiceImplTest {
|
||||
when(certRecordService.getById(RECORD_ID)).thenReturn(record);
|
||||
when(driverService.getById(DRIVER_ID)).thenReturn(driver(null));
|
||||
|
||||
service.audit(RECORD_ID, true, null);
|
||||
service.audit(RECORD_ID, true, null, null);
|
||||
|
||||
ArgumentCaptor<Driver> captor = ArgumentCaptor.forClass(Driver.class);
|
||||
verify(driverService).updateById(captor.capture());
|
||||
@@ -199,7 +199,7 @@ class CertAuditServiceImplTest {
|
||||
existing.setDrivingVehicle("桂A12345,桂B67890");
|
||||
when(driverService.getById(DRIVER_ID)).thenReturn(existing);
|
||||
|
||||
service.audit(RECORD_ID, true, null);
|
||||
service.audit(RECORD_ID, true, null, null);
|
||||
|
||||
ArgumentCaptor<Driver> captor = ArgumentCaptor.forClass(Driver.class);
|
||||
verify(driverService).updateById(captor.capture());
|
||||
@@ -216,7 +216,7 @@ class CertAuditServiceImplTest {
|
||||
when(transportVehicleService.updateById(any(TransportVehicle.class))).thenReturn(false);
|
||||
|
||||
ServiceException exception = assertThrows(ServiceException.class,
|
||||
() -> service.audit(RECORD_ID, true, null));
|
||||
() -> service.audit(RECORD_ID, true, null, null));
|
||||
assertTrue(exception.getMessage().contains("未生效"), exception.getMessage());
|
||||
}
|
||||
|
||||
@@ -228,7 +228,7 @@ class CertAuditServiceImplTest {
|
||||
when(driverService.updateById(any(Driver.class))).thenReturn(false);
|
||||
|
||||
ServiceException exception = assertThrows(ServiceException.class,
|
||||
() -> service.audit(RECORD_ID, true, null));
|
||||
() -> service.audit(RECORD_ID, true, null, null));
|
||||
assertTrue(exception.getMessage().contains("司机"), exception.getMessage());
|
||||
}
|
||||
|
||||
@@ -242,7 +242,7 @@ class CertAuditServiceImplTest {
|
||||
when(certRecordService.getById(RECORD_ID)).thenReturn(record);
|
||||
when(transportVehicleService.getById(VEHICLE_ID)).thenReturn(vehicle(existingEffectiveId));
|
||||
|
||||
service.audit(RECORD_ID, false, "证件照片模糊");
|
||||
service.audit(RECORD_ID, false, "证件照片模糊", null);
|
||||
|
||||
ArgumentCaptor<TransportCertRecord> recordCaptor = ArgumentCaptor.forClass(TransportCertRecord.class);
|
||||
verify(certRecordService).updateById(recordCaptor.capture());
|
||||
@@ -264,7 +264,7 @@ class CertAuditServiceImplTest {
|
||||
when(certRecordService.getById(RECORD_ID)).thenReturn(vehicleRecord(TransportCertRecord.STATUS_PENDING));
|
||||
when(transportVehicleService.getById(VEHICLE_ID)).thenReturn(vehicle(existingEffectiveId));
|
||||
|
||||
service.audit(RECORD_ID, false, "运输证过期");
|
||||
service.audit(RECORD_ID, false, "运输证过期", null);
|
||||
|
||||
// 指针字段没有被写成 null,也没有被写成新记录
|
||||
ArgumentCaptor<TransportVehicle> captor = ArgumentCaptor.forClass(TransportVehicle.class);
|
||||
@@ -277,8 +277,8 @@ class CertAuditServiceImplTest {
|
||||
void rejectRequiresReason() {
|
||||
when(certRecordService.getById(RECORD_ID)).thenReturn(vehicleRecord(TransportCertRecord.STATUS_PENDING));
|
||||
|
||||
assertThrows(ServiceException.class, () -> service.audit(RECORD_ID, false, null));
|
||||
assertThrows(ServiceException.class, () -> service.audit(RECORD_ID, false, " "));
|
||||
assertThrows(ServiceException.class, () -> service.audit(RECORD_ID, false, null, null));
|
||||
assertThrows(ServiceException.class, () -> service.audit(RECORD_ID, false, " ", null));
|
||||
verify(certRecordService, never()).updateById(any(TransportCertRecord.class));
|
||||
}
|
||||
|
||||
@@ -288,7 +288,7 @@ class CertAuditServiceImplTest {
|
||||
when(certRecordService.getById(RECORD_ID)).thenReturn(vehicleRecord(TransportCertRecord.STATUS_PENDING));
|
||||
|
||||
assertThrows(ServiceException.class,
|
||||
() -> service.audit(RECORD_ID, false, "长".repeat(201)));
|
||||
() -> service.audit(RECORD_ID, false, "长".repeat(201), null));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------ 终态保护
|
||||
@@ -303,9 +303,9 @@ class CertAuditServiceImplTest {
|
||||
TransportCertRecord record = vehicleRecord(terminal);
|
||||
when(certRecordService.getById(RECORD_ID)).thenReturn(record);
|
||||
|
||||
assertThrows(ServiceException.class, () -> service.audit(RECORD_ID, true, null),
|
||||
assertThrows(ServiceException.class, () -> service.audit(RECORD_ID, true, null, null),
|
||||
"状态 " + terminal + " 属终态,不应可再审");
|
||||
assertThrows(ServiceException.class, () -> service.audit(RECORD_ID, false, "驳回"),
|
||||
assertThrows(ServiceException.class, () -> service.audit(RECORD_ID, false, "驳回", null),
|
||||
"状态 " + terminal + " 属终态,不应可再审");
|
||||
}
|
||||
verify(certRecordService, never()).updateById(any(TransportCertRecord.class));
|
||||
@@ -315,13 +315,13 @@ class CertAuditServiceImplTest {
|
||||
@DisplayName("记录不存在:拒绝")
|
||||
void recordNotFound() {
|
||||
when(certRecordService.getById(RECORD_ID)).thenReturn(null);
|
||||
assertThrows(ServiceException.class, () -> service.audit(RECORD_ID, true, null));
|
||||
assertThrows(ServiceException.class, () -> service.audit(RECORD_ID, true, null, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("记录主键为空:拒绝")
|
||||
void recordIdRequired() {
|
||||
assertThrows(ServiceException.class, () -> service.audit(null, true, null));
|
||||
assertThrows(ServiceException.class, () -> service.audit(null, true, null, null));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------ 审核详情(diff 用)
|
||||
@@ -470,7 +470,7 @@ class CertAuditServiceImplTest {
|
||||
when(certRecordService.getById(RECORD_ID)).thenReturn(record);
|
||||
when(transportVehicleService.getById(VEHICLE_ID)).thenReturn(vehicle(null));
|
||||
|
||||
service.audit(RECORD_ID, true, null);
|
||||
service.audit(RECORD_ID, true, null, null);
|
||||
|
||||
ArgumentCaptor<Wrapper<TransportVehicle>> captor = ArgumentCaptor.forClass(Wrapper.class);
|
||||
verify(transportVehicleService).update(captor.capture());
|
||||
@@ -494,7 +494,7 @@ class CertAuditServiceImplTest {
|
||||
when(certRecordService.getById(RECORD_ID)).thenReturn(record);
|
||||
when(driverService.getById(DRIVER_ID)).thenReturn(driver(null));
|
||||
|
||||
service.audit(RECORD_ID, true, null);
|
||||
service.audit(RECORD_ID, true, null, null);
|
||||
|
||||
ArgumentCaptor<Wrapper<Driver>> captor = ArgumentCaptor.forClass(Wrapper.class);
|
||||
verify(driverService).update(captor.capture());
|
||||
@@ -518,7 +518,7 @@ class CertAuditServiceImplTest {
|
||||
when(certRecordService.getById(RECORD_ID)).thenReturn(record);
|
||||
when(transportVehicleService.getById(VEHICLE_ID)).thenReturn(vehicle(777L));
|
||||
|
||||
service.audit(RECORD_ID, false, " 行驶证模糊 ");
|
||||
service.audit(RECORD_ID, false, " 行驶证模糊 ", null);
|
||||
|
||||
ArgumentCaptor<TransportVehicle> captor = ArgumentCaptor.forClass(TransportVehicle.class);
|
||||
verify(transportVehicleService).updateById(captor.capture());
|
||||
@@ -527,6 +527,183 @@ class CertAuditServiceImplTest {
|
||||
verify(transportVehicleService, never()).update(any());
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------ 修订版
|
||||
|
||||
@Test
|
||||
@DisplayName("修订版:通过时写入新列,且 snapshot_json 一个字都不改")
|
||||
void approveStoresAuditedSnapshot() {
|
||||
when(certRecordService.getById(RECORD_ID)).thenReturn(vehicleRecord(TransportCertRecord.STATUS_PENDING));
|
||||
when(transportVehicleService.getById(VEHICLE_ID)).thenReturn(vehicle(null));
|
||||
String revision = vehicleRevision();
|
||||
|
||||
service.audit(RECORD_ID, true, null, revision);
|
||||
|
||||
ArgumentCaptor<TransportCertRecord> captor = ArgumentCaptor.forClass(TransportCertRecord.class);
|
||||
verify(certRecordService).updateById(captor.capture());
|
||||
assertEquals(revision, captor.getValue().getAuditedSnapshotJson(),
|
||||
"修订版必须原样留痕,供事后追责");
|
||||
assertNull(captor.getValue().getSnapshotJson(),
|
||||
"snapshot_json 是司机当时提交的内容,审核 patch 里不得出现");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("修订版:写回车辆档案的是修订内容,不是司机原提交")
|
||||
void approveAppliesRevisionToVehicleProfile() {
|
||||
when(certRecordService.getById(RECORD_ID)).thenReturn(vehicleRecord(TransportCertRecord.STATUS_PENDING));
|
||||
when(transportVehicleService.getById(VEHICLE_ID)).thenReturn(vehicle(null));
|
||||
String revision = vehicleRevision();
|
||||
|
||||
service.audit(RECORD_ID, true, null, revision);
|
||||
|
||||
ArgumentCaptor<TransportVehicle> captor = ArgumentCaptor.forClass(TransportVehicle.class);
|
||||
verify(transportVehicleService).updateById(captor.capture());
|
||||
assertEquals("修订后的所有人", captor.getValue().getOwnerName(), "档案要落修订后的内容");
|
||||
assertEquals(25000, captor.getValue().getApprovedLoadKg());
|
||||
assertEquals("450100999999", captor.getValue().getDrivingLicenseNo());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("修订版:司机档案同样以修订内容写回")
|
||||
void approveAppliesRevisionToDriverProfile() {
|
||||
TransportCertRecord record = driverRecord(TransportCertRecord.STATUS_PENDING);
|
||||
record.setSnapshotJson("{}");
|
||||
when(certRecordService.getById(RECORD_ID)).thenReturn(record);
|
||||
when(driverService.getById(DRIVER_ID)).thenReturn(driver(null));
|
||||
String revision = "{\"realName\":\"李四\",\"idCardNo\":\"450100199001019999\","
|
||||
+ "\"driverFileNo\":\"450100888899\"}";
|
||||
|
||||
service.audit(RECORD_ID, true, null, revision);
|
||||
|
||||
ArgumentCaptor<Driver> captor = ArgumentCaptor.forClass(Driver.class);
|
||||
verify(driverService).updateById(captor.capture());
|
||||
assertEquals("李四", captor.getValue().getDriverName());
|
||||
assertEquals("450100199001019999", captor.getValue().getIdCardNo());
|
||||
assertEquals("450100888899", captor.getValue().getDrivingLicenseNo());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("修订版:未提供时不写新列,生效内容仍是司机原提交")
|
||||
void approveWithoutRevisionLeavesColumnNull() {
|
||||
when(certRecordService.getById(RECORD_ID)).thenReturn(vehicleRecord(TransportCertRecord.STATUS_PENDING));
|
||||
when(transportVehicleService.getById(VEHICLE_ID)).thenReturn(vehicle(null));
|
||||
|
||||
service.audit(RECORD_ID, true, null, null);
|
||||
|
||||
ArgumentCaptor<TransportCertRecord> recordCaptor = ArgumentCaptor.forClass(TransportCertRecord.class);
|
||||
verify(certRecordService).updateById(recordCaptor.capture());
|
||||
assertNull(recordCaptor.getValue().getAuditedSnapshotJson(),
|
||||
"未修订时该列保持 NULL,不能塞一份复制品进来");
|
||||
ArgumentCaptor<TransportVehicle> vehicleCaptor = ArgumentCaptor.forClass(TransportVehicle.class);
|
||||
verify(transportVehicleService).updateById(vehicleCaptor.capture());
|
||||
assertEquals("广西现代物流集团有限公司", vehicleCaptor.getValue().getOwnerName());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("修订版:纯空白等于未修订(前端清空输入框就是这种情况)")
|
||||
void blankRevisionIsTreatedAsAbsent() {
|
||||
when(certRecordService.getById(RECORD_ID)).thenReturn(vehicleRecord(TransportCertRecord.STATUS_PENDING));
|
||||
when(transportVehicleService.getById(VEHICLE_ID)).thenReturn(vehicle(null));
|
||||
|
||||
service.audit(RECORD_ID, true, null, " ");
|
||||
|
||||
ArgumentCaptor<TransportCertRecord> captor = ArgumentCaptor.forClass(TransportCertRecord.class);
|
||||
verify(certRecordService).updateById(captor.capture());
|
||||
assertNull(captor.getValue().getAuditedSnapshotJson());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("修订版:JSON 无法解析时拒绝,且记录与档案都不写(事务回滚)")
|
||||
void approveRejectsMalformedRevision() {
|
||||
when(certRecordService.getById(RECORD_ID)).thenReturn(vehicleRecord(TransportCertRecord.STATUS_PENDING));
|
||||
when(transportVehicleService.getById(VEHICLE_ID)).thenReturn(vehicle(null));
|
||||
|
||||
ServiceException exception = assertThrows(ServiceException.class,
|
||||
() -> service.audit(RECORD_ID, true, null, "{不是JSON"));
|
||||
assertTrue(exception.getMessage().contains("修订内容格式不正确"), exception.getMessage());
|
||||
verify(certRecordService, never()).updateById(any(TransportCertRecord.class));
|
||||
verify(transportVehicleService, never()).updateById(any(TransportVehicle.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("修订版:司机缺身份证号与驾驶证号时拒绝(写回等于什么也没写)")
|
||||
void approveRejectsIncompleteDriverRevision() {
|
||||
when(certRecordService.getById(RECORD_ID)).thenReturn(driverRecord(TransportCertRecord.STATUS_PENDING));
|
||||
when(driverService.getById(DRIVER_ID)).thenReturn(driver(null));
|
||||
|
||||
ServiceException exception = assertThrows(ServiceException.class,
|
||||
() -> service.audit(RECORD_ID, true, null, "{\"realName\":\"李四\"}"));
|
||||
assertTrue(exception.getMessage().contains("身份证号"), exception.getMessage());
|
||||
verify(driverService, never()).updateById(any(Driver.class));
|
||||
verify(certRecordService, never()).updateById(any(TransportCertRecord.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("修订版:车辆缺行驶证号与核定载质量时拒绝")
|
||||
void approveRejectsIncompleteVehicleRevision() {
|
||||
when(certRecordService.getById(RECORD_ID)).thenReturn(vehicleRecord(TransportCertRecord.STATUS_PENDING));
|
||||
when(transportVehicleService.getById(VEHICLE_ID)).thenReturn(vehicle(null));
|
||||
|
||||
ServiceException exception = assertThrows(ServiceException.class,
|
||||
() -> service.audit(RECORD_ID, true, null, "{\"ownerName\":\"只改了个名字\"}"));
|
||||
assertTrue(exception.getMessage().contains("核定载质量"), exception.getMessage());
|
||||
verify(transportVehicleService, never()).updateById(any(TransportVehicle.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("驳回:修订内容完全忽略,新列保持 NULL")
|
||||
void rejectIgnoresRevision() {
|
||||
when(certRecordService.getById(RECORD_ID)).thenReturn(vehicleRecord(TransportCertRecord.STATUS_PENDING));
|
||||
when(transportVehicleService.getById(VEHICLE_ID)).thenReturn(vehicle(null));
|
||||
|
||||
service.audit(RECORD_ID, false, "行驶证模糊", vehicleRevision());
|
||||
|
||||
ArgumentCaptor<TransportCertRecord> captor = ArgumentCaptor.forClass(TransportCertRecord.class);
|
||||
verify(certRecordService).updateById(captor.capture());
|
||||
assertNull(captor.getValue().getAuditedSnapshotJson(),
|
||||
"驳回不产生生效内容,留修订版只会让人误读");
|
||||
assertEquals(TransportCertRecord.STATUS_REJECTED, captor.getValue().getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("空快照不再静默通过:司机记录没有内容、也没有修订时拒绝")
|
||||
void approveRejectsEmptyDriverSnapshot() {
|
||||
TransportCertRecord record = driverRecord(TransportCertRecord.STATUS_PENDING);
|
||||
record.setSnapshotJson(null);
|
||||
when(certRecordService.getById(RECORD_ID)).thenReturn(record);
|
||||
when(driverService.getById(DRIVER_ID)).thenReturn(driver(null));
|
||||
|
||||
ServiceException exception = assertThrows(ServiceException.class,
|
||||
() -> service.audit(RECORD_ID, true, null, null));
|
||||
assertTrue(exception.getMessage().contains("司机档案"), exception.getMessage());
|
||||
verify(driverService, never()).updateById(any(Driver.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("审核详情:修订版内容随详情下发,生效一侧同样修订版优先")
|
||||
void detailExposesAuditedSnapshot() {
|
||||
TransportCertRecord record = vehicleRecord(TransportCertRecord.STATUS_PENDING);
|
||||
record.setAuditedSnapshotJson("{\"vin\":\"REVISED\"}");
|
||||
when(certRecordService.getById(RECORD_ID)).thenReturn(record);
|
||||
|
||||
TransportCertRecord effective = new TransportCertRecord();
|
||||
effective.setId(777L);
|
||||
effective.setSnapshotJson("{\"vin\":\"OLD\"}");
|
||||
effective.setAuditedSnapshotJson("{\"vin\":\"OLD-REVISED\"}");
|
||||
when(transportVehicleService.getById(VEHICLE_ID)).thenReturn(vehicle(777L));
|
||||
when(certRecordService.getById(777L)).thenReturn(effective);
|
||||
|
||||
CertAuditDetailVO detail = service.detail(RECORD_ID);
|
||||
|
||||
assertEquals("{\"vin\":\"REVISED\"}", detail.getAuditedSnapshotJson());
|
||||
assertEquals("{\"vin\":\"OLD-REVISED\"}", detail.getEffectiveSnapshotJson(),
|
||||
"生效一侧也修订版优先,否则审核页拿旧值做对比");
|
||||
}
|
||||
|
||||
private static String vehicleRevision() {
|
||||
return "{\"drivingLicenseNo\":\"450100999999\",\"approvedLoadKg\":25000,"
|
||||
+ "\"ownerName\":\"修订后的所有人\"}";
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------ 辅助
|
||||
|
||||
private TransportCertRecord vehicleRecord(Integer status) {
|
||||
@@ -556,6 +733,19 @@ class CertAuditServiceImplTest {
|
||||
+ "\"vehicleType\":\"重型货车\",\"driveLicenseImage\":\"http://oss/xsz.jpg\"}";
|
||||
}
|
||||
|
||||
/**
|
||||
* 一份字段齐全的司机认证快照
|
||||
* <p>
|
||||
* 司机侧的空快照不再「静默跳过」而是拒审,故通过类用例不能再拿 "{}" 走流程;
|
||||
* 身份证号与驾驶证号是「这份快照是否为当前格式」的判据,缺一不可。
|
||||
*/
|
||||
static String completeDriverSnapshot() {
|
||||
return "{\"realName\":\"张三\",\"idCardNo\":\"450100199001011234\","
|
||||
+ "\"driverFileNo\":\"450100888888\",\"driverClass\":\"A2\","
|
||||
+ "\"headPhoto\":\"http://oss/head.jpg\",\"idCardFrontUrl\":\"http://oss/idcard-front.jpg\","
|
||||
+ "\"idCardBackUrl\":\"http://oss/idcard-back.jpg\"}";
|
||||
}
|
||||
|
||||
private TransportCertRecord driverRecord(Integer status) {
|
||||
TransportCertRecord record = new TransportCertRecord();
|
||||
record.setId(RECORD_ID);
|
||||
@@ -563,7 +753,7 @@ class CertAuditServiceImplTest {
|
||||
record.setBizId(DRIVER_ID);
|
||||
record.setBizKey("张三");
|
||||
record.setStatus(status);
|
||||
record.setSnapshotJson("{}");
|
||||
record.setSnapshotJson(completeDriverSnapshot());
|
||||
return record;
|
||||
}
|
||||
|
||||
|
||||
+50
@@ -358,6 +358,56 @@ class DriverCertServiceImplTest {
|
||||
assertNull(service.getEffectivePersonCert());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("读生效值:有修订版时返回修订版内容(司机端不能拿旧值覆盖审核员的改动)")
|
||||
void getEffectivePersonCertPrefersRevision() {
|
||||
Long effectiveId = 777L;
|
||||
Driver driver = driver(PLATE);
|
||||
driver.setEffectiveCertRecordId(effectiveId);
|
||||
when(driverAppService.currentDriverEntity()).thenReturn(driver);
|
||||
when(certRecordService.getById(effectiveId)).thenReturn(pointedRecord(effectiveId,
|
||||
"司机原提交", "审核员修订"));
|
||||
|
||||
PersonCertSubmitRequest result = service.getEffectivePersonCert();
|
||||
|
||||
assertEquals("审核员修订", result.getRealName());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("读生效值:车辆同理,修订版优先")
|
||||
void getEffectiveVehicleCertPrefersRevision() {
|
||||
when(driverAppService.currentDriverEntity()).thenReturn(driver(PLATE));
|
||||
when(transportVehicleService.getById(VEHICLE_ID)).thenReturn(vehicle(VEHICLE_ID, PLATE, 777L));
|
||||
when(certRecordService.getById(777L)).thenReturn(pointedVehicleRecord(777L,
|
||||
"司机原提交", "审核员修订"));
|
||||
|
||||
VehicleCertSubmitRequest result = service.getEffectiveVehicleCert(VEHICLE_ID);
|
||||
|
||||
assertEquals("审核员修订", result.getOwnerName());
|
||||
}
|
||||
|
||||
private TransportCertRecord pointedRecord(Long id, String submittedName, String revisedName) {
|
||||
PersonCertSubmitRequest submitted = new PersonCertSubmitRequest();
|
||||
submitted.setRealName(submittedName);
|
||||
PersonCertSubmitRequest revised = new PersonCertSubmitRequest();
|
||||
revised.setRealName(revisedName);
|
||||
TransportCertRecord record = record(id, TransportCertRecord.STATUS_APPROVED);
|
||||
record.setSnapshotJson(JSON.toJSONString(submitted));
|
||||
record.setAuditedSnapshotJson(JSON.toJSONString(revised));
|
||||
return record;
|
||||
}
|
||||
|
||||
private TransportCertRecord pointedVehicleRecord(Long id, String submittedName, String revisedName) {
|
||||
VehicleCertSubmitRequest submitted = new VehicleCertSubmitRequest();
|
||||
submitted.setOwnerName(submittedName);
|
||||
VehicleCertSubmitRequest revised = new VehicleCertSubmitRequest();
|
||||
revised.setOwnerName(revisedName);
|
||||
TransportCertRecord record = record(id, TransportCertRecord.STATUS_APPROVED);
|
||||
record.setSnapshotJson(JSON.toJSONString(submitted));
|
||||
record.setAuditedSnapshotJson(JSON.toJSONString(revised));
|
||||
return record;
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("重新提交后,生效值仍是旧的(核心验收点:提交不影响已认证状态)")
|
||||
void resubmitKeepsEffectiveValue() {
|
||||
|
||||
+23
-21
@@ -108,7 +108,7 @@ class PersonCertRoundTripTest {
|
||||
void submitThenApprove() {
|
||||
// 1) 先走一遍完整流程,让该司机有一个生效值
|
||||
Long firstRecordId = driverCertService.submitPersonCert(personCert("张三", "110101199001011234"));
|
||||
certAuditService.audit(firstRecordId, true, null);
|
||||
certAuditService.audit(firstRecordId, true, null, null);
|
||||
assertEquals("张三", driverCertService.getEffectivePersonCert().getRealName(),
|
||||
"审核通过后应能查到生效值");
|
||||
|
||||
@@ -120,7 +120,7 @@ class PersonCertRoundTripTest {
|
||||
"指针应仍指向原来那条已通过记录");
|
||||
|
||||
// 3) 审核通过:生效值才变成新值
|
||||
certAuditService.audit(secondRecordId, true, null);
|
||||
certAuditService.audit(secondRecordId, true, null, null);
|
||||
assertEquals("张三改名", driverCertService.getEffectivePersonCert().getRealName(),
|
||||
"审核通过后生效值应切到新记录");
|
||||
assertEquals(secondRecordId, driver.getEffectiveCertRecordId());
|
||||
@@ -130,10 +130,10 @@ class PersonCertRoundTripTest {
|
||||
@DisplayName("驳回后生效值不变(已认证对象不会被驳回下线)")
|
||||
void rejectKeepsEffectiveValue() {
|
||||
Long firstRecordId = driverCertService.submitPersonCert(personCert("张三", "110101199001011234"));
|
||||
certAuditService.audit(firstRecordId, true, null);
|
||||
certAuditService.audit(firstRecordId, true, null, null);
|
||||
|
||||
Long secondRecordId = driverCertService.submitPersonCert(personCert("张三改名", "110101199001011234"));
|
||||
certAuditService.audit(secondRecordId, false, "证件照片模糊");
|
||||
certAuditService.audit(secondRecordId, false, "证件照片模糊", null);
|
||||
|
||||
assertEquals("张三", driverCertService.getEffectivePersonCert().getRealName(),
|
||||
"驳回只终结该次提交,既得认证必须保持有效");
|
||||
@@ -149,7 +149,7 @@ class PersonCertRoundTripTest {
|
||||
assertNull(driverCertService.getEffectivePersonCert(),
|
||||
"待审期间不应有生效值");
|
||||
|
||||
certAuditService.audit(recordId, true, null);
|
||||
certAuditService.audit(recordId, true, null, null);
|
||||
assertEquals("张三", driverCertService.getEffectivePersonCert().getRealName());
|
||||
}
|
||||
|
||||
@@ -171,18 +171,18 @@ class PersonCertRoundTripTest {
|
||||
// 被作废的那条不能再被审核(否则会把已作废的记录变成生效值)
|
||||
org.junit.jupiter.api.Assertions.assertThrows(
|
||||
org.springblade.core.log.exception.ServiceException.class,
|
||||
() -> certAuditService.audit(first, true, null));
|
||||
() -> certAuditService.audit(first, true, null, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("已通过的记录不可再被审核(防止把生效快照改坏)")
|
||||
void approvedRecordCannotBeReaudited() {
|
||||
Long recordId = driverCertService.submitPersonCert(personCert("张三", "110101199001011234"));
|
||||
certAuditService.audit(recordId, true, null);
|
||||
certAuditService.audit(recordId, true, null, null);
|
||||
|
||||
org.junit.jupiter.api.Assertions.assertThrows(
|
||||
org.springblade.core.log.exception.ServiceException.class,
|
||||
() -> certAuditService.audit(recordId, false, "事后反悔"));
|
||||
() -> certAuditService.audit(recordId, false, "事后反悔", null));
|
||||
assertEquals("张三", driverCertService.getEffectivePersonCert().getRealName(),
|
||||
"重复审核被拒后,生效值不受影响");
|
||||
}
|
||||
@@ -314,7 +314,7 @@ class PersonCertRoundTripTest {
|
||||
request.setQualificationBack("http://example.com/q-back.jpg");
|
||||
|
||||
Long recordId = driverCertService.submitPersonCert(request);
|
||||
certAuditService.audit(recordId, true, null);
|
||||
certAuditService.audit(recordId, true, null, null);
|
||||
|
||||
// 档案里必须是这次提交的内容:只切指针、不写内容的话,
|
||||
// 认证状态显示「已通过」而档案仍是旧值,管理端与司机端会各看各的
|
||||
@@ -342,7 +342,7 @@ class PersonCertRoundTripTest {
|
||||
request.setAddressRegion(" ");
|
||||
request.setAddress("");
|
||||
Long recordId = driverCertService.submitPersonCert(request);
|
||||
certAuditService.audit(recordId, true, null);
|
||||
certAuditService.audit(recordId, true, null, null);
|
||||
|
||||
// 框架的 not_null 更新策略只跳过 null:写空串会把管理端填过的住址清掉
|
||||
assertEquals("广西壮族自治区柳州市柳北区", driver.getAddressRegion(),
|
||||
@@ -358,7 +358,7 @@ class PersonCertRoundTripTest {
|
||||
driver.setIdCardNo("110101199001011234");
|
||||
|
||||
Long recordId = driverCertService.submitPersonCert(personCert("张三改名", "110101199001011234"));
|
||||
certAuditService.audit(recordId, true, null);
|
||||
certAuditService.audit(recordId, true, null, null);
|
||||
|
||||
// 审核通过即认可这次申报:档案里必须也是申报的姓名,
|
||||
// 否则司机端卡片显示档案名、「重新认证」表单回填快照名,同一个页面上两个名字
|
||||
@@ -377,7 +377,7 @@ class PersonCertRoundTripTest {
|
||||
Long recordId = driverCertService.submitPersonCert(personCert("张三", "110101199001011999"));
|
||||
|
||||
ServiceException exception = assertThrows(ServiceException.class,
|
||||
() -> certAuditService.audit(recordId, true, null));
|
||||
() -> certAuditService.audit(recordId, true, null, null));
|
||||
assertTrue(exception.getMessage().contains("另一名司机档案"), exception.getMessage());
|
||||
// 真实环境下这次审核由 @Transactional 整体回滚(记录仍是待审);
|
||||
// 单测没有事务,故这里校验更直接的一点:拦截发生在写档案之前,别人的证件号没被写进来
|
||||
@@ -392,25 +392,27 @@ class PersonCertRoundTripTest {
|
||||
PersonCertSubmitRequest request = personCert("张三", "110101199001011234");
|
||||
request.setHeadPhoto("http://example.com/new-head.jpg");
|
||||
Long recordId = driverCertService.submitPersonCert(request);
|
||||
certAuditService.audit(recordId, false, "证件照片模糊");
|
||||
certAuditService.audit(recordId, false, "证件照片模糊", null);
|
||||
|
||||
assertEquals("http://example.com/original-head.jpg", driver.getHeadPhoto(),
|
||||
"驳回只终结该次提交,档案内容不得被改动");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("历史快照:读不懂就跳过写回,审核本身照常通过")
|
||||
void legacySnapshotSkipsWriteBackWithoutFailingAudit() {
|
||||
// 早期格式的快照没有 idCardNo / driverFileNo,解析出来字段全是 null;
|
||||
// 此时既不能把 null 当成「司机没填」写进档案,也不能让这次审核直接失败
|
||||
@DisplayName("历史快照:读不懂就拒绝通过并回滚(不再静默放行)")
|
||||
void legacySnapshotBlocksAuditInsteadOfPassing() {
|
||||
// 早期格式的快照没有 idCardNo / driverFileNo,解析出来字段全是 null。
|
||||
// 「通过」的语义是「把这批内容认定为生效值」,内容读不懂就无从认定,
|
||||
// 必须拒绝——静默放行等于给一条空记录盖上已通过的章。
|
||||
Long recordId = driverCertService.submitPersonCert(personCert("张三", "110101199001011234"));
|
||||
TransportCertRecord record = recordStore.get(recordId);
|
||||
record.setSnapshotJson("{\"legacyField\":\"早期格式的旧内容\"}");
|
||||
|
||||
certAuditService.audit(recordId, true, null);
|
||||
ServiceException exception = assertThrows(ServiceException.class,
|
||||
() -> certAuditService.audit(recordId, true, null, null));
|
||||
|
||||
assertEquals(TransportCertRecord.STATUS_APPROVED, recordStore.get(recordId).getStatus(),
|
||||
"快照读不懂不该阻断审核");
|
||||
assertEquals(recordId, driver.getEffectiveCertRecordId(), "指针仍应照常切换");
|
||||
assertTrue(exception.getMessage().contains("不匹配"), exception.getMessage());
|
||||
// 内存版记录表没有事务,这里只验最关键的一条:生效指针不能被切过去
|
||||
assertNull(driver.getEffectiveCertRecordId(), "审核失败不得切换生效指针");
|
||||
}
|
||||
}
|
||||
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.wrapper;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.springblade.system.cache.UserCache;
|
||||
import org.springblade.system.cache.UserDisplayCache;
|
||||
import org.springblade.transport.pojo.entity.TransportCertRecord;
|
||||
import org.springblade.transport.pojo.vo.TransportCertRecordVO;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
|
||||
/**
|
||||
* 认证记录视图对象的修订版标记与字段可见性
|
||||
* <p>
|
||||
* 两件事必须同时成立:司机端要能从 {@code revised} 看出「这条记录经过审核员修订」,
|
||||
* 而又不能顺着 VO 把整份修订内容(含身份证号、证件照地址)再下发一遍。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
class TransportCertRecordWrapperRevisionTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("修订版非空才算修订,空串与空白不算")
|
||||
void marksRevisedOnlyWhenRevisionPresent() {
|
||||
try (MockedStatic<UserDisplayCache> ignoredDisplay = mockStatic(UserDisplayCache.class);
|
||||
MockedStatic<UserCache> ignoredUser = mockStatic(UserCache.class)) {
|
||||
assertTrue(wrapper().fillDisplayFields(record("{\"realName\":\"张三\"}")).getRevised(), "有修订版应标记为已修订");
|
||||
assertFalse(wrapper().fillDisplayFields(record(null)).getRevised(), "无修订版不应标记");
|
||||
assertFalse(wrapper().fillDisplayFields(record("")).getRevised(), "空串不应标记");
|
||||
assertFalse(wrapper().fillDisplayFields(record(" ")).getRevised(), "空白不应标记");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("视图对象序列化不包含修订内容,只保留 revised 标记")
|
||||
void voJsonOmitsRevisionContent() throws Exception {
|
||||
TransportCertRecordVO vo = record("{\"realName\":\"张三\"}");
|
||||
vo.setRevised(Boolean.TRUE);
|
||||
|
||||
String json = new ObjectMapper().writeValueAsString(vo);
|
||||
|
||||
assertFalse(json.contains("auditedSnapshotJson"), "修订内容不得随 VO 下发:" + json);
|
||||
assertTrue(json.contains("\"revised\":true"), "必须下发 revised 标记:" + json);
|
||||
}
|
||||
|
||||
private static TransportCertRecordWrapper wrapper() {
|
||||
return TransportCertRecordWrapper.build();
|
||||
}
|
||||
|
||||
private static TransportCertRecordVO record(String auditedSnapshotJson) {
|
||||
TransportCertRecordVO vo = new TransportCertRecordVO();
|
||||
vo.setSnapshotJson("{\"realName\":\"李四\"}");
|
||||
vo.setAuditedSnapshotJson(auditedSnapshotJson);
|
||||
return vo;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user