✨ 新增认证记录不可变快照数据模型与迁移脚本
按 ADR-0001/0002 建立司机与车辆共用的认证记录表 blade_transport_cert_record (biz_type 区分、载荷存 JSON、检索字段提升为真实列),并为司机/车辆表补生效快照指针。 不变式结构性保证: - I1 同对象至多一条待审:生成列 pending_biz_id + 唯一索引,数据库层拦截; 生成列同时排除 is_deleted,避免逻辑删除的待审记录永久占用唯一键。 - I3 快照不可变:snapshot_json 等 5 列标 FieldStrategy.NEVER,永不进入 UPDATE。 - status 承载审核状态(0审核中/1已通过/2已驳回/3已作废),新增时必须显式设置: 框架 resolveEntity 会把 null 的 status 静默填成 1(即「已通过」), 且继承来的 changeStatus 可直接写 status,两条通道均已封堵。 其他:司机表补身份证有效期三列并订正排序规则为 utf8mb4_general_ci (原先为 0900_ai_ci,与车辆表 JOIN 已发生过 Illegal mix of collations); 生效快照指针加 @JsonProperty(READ_ONLY),防止以 /submit 请求体自行「认证」自己; 同步基线建表脚本与认证审核菜单/权限码。本票不含任何业务接口。
This commit is contained in:
+13
-1
@@ -46,6 +46,12 @@
|
||||
<result column="emergency_contact_name" property="emergencyContactName"/>
|
||||
<result column="emergency_contact_mobile" property="emergencyContactMobile"/>
|
||||
<result column="remark" property="remark"/>
|
||||
<result column="id_card_valid_start" property="idCardValidStart"/>
|
||||
<result column="id_card_valid_end" property="idCardValidEnd"/>
|
||||
<result column="id_card_long_term" property="idCardLongTerm"/>
|
||||
<result column="certification_status" property="certificationStatus"/>
|
||||
<result column="certification_reject_reason" property="certificationRejectReason"/>
|
||||
<result column="effective_cert_record_id" property="effectiveCertRecordId"/>
|
||||
</resultMap>
|
||||
|
||||
<sql id="BaseColumn">
|
||||
@@ -70,6 +76,9 @@
|
||||
posts,
|
||||
id_card_front,
|
||||
id_card_back,
|
||||
id_card_valid_start,
|
||||
id_card_valid_end,
|
||||
id_card_long_term,
|
||||
head_photo,
|
||||
driving_type,
|
||||
driving_license_no,
|
||||
@@ -91,7 +100,10 @@
|
||||
organization_name,
|
||||
emergency_contact_name,
|
||||
emergency_contact_mobile,
|
||||
remark
|
||||
remark,
|
||||
certification_status,
|
||||
certification_reject_reason,
|
||||
effective_cert_record_id
|
||||
</sql>
|
||||
|
||||
<select id="selectByIdCardNoIncludingDeleted" resultType="org.springblade.transport.pojo.entity.Driver">
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* 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.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.springblade.transport.pojo.entity.TransportCertRecord;
|
||||
|
||||
/**
|
||||
* 认证记录 Mapper 接口
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public interface TransportCertRecordMapper extends BaseMapper<TransportCertRecord> {
|
||||
|
||||
}
|
||||
+2
@@ -48,6 +48,7 @@
|
||||
<result column="remark" property="remark"/>
|
||||
<result column="certification_status" property="certificationStatus"/>
|
||||
<result column="certification_reject_reason" property="certificationRejectReason"/>
|
||||
<result column="effective_cert_record_id" property="effectiveCertRecordId"/>
|
||||
</resultMap>
|
||||
|
||||
<sql id="BaseColumn">
|
||||
@@ -96,6 +97,7 @@
|
||||
remark
|
||||
, certification_status
|
||||
, certification_reject_reason
|
||||
, effective_cert_record_id
|
||||
</sql>
|
||||
|
||||
<sql id="ExpiryWithin30Condition">
|
||||
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* 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.service;
|
||||
|
||||
import org.springblade.core.mp.base.BaseService;
|
||||
import org.springblade.transport.pojo.entity.TransportCertRecord;
|
||||
import org.springblade.transport.pojo.vo.TransportCertRecordVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 认证记录 服务类
|
||||
* <p>
|
||||
* 只承载认证记录本身的读写与<b>数据模型不变式</b>的守护;
|
||||
* 提交(02/05)、审核(03)、可用性门槛(04)等业务链路在各自的票中实现。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public interface ITransportCertRecordService extends BaseService<TransportCertRecord> {
|
||||
|
||||
/**
|
||||
* 某认证对象的认证记录列表,按提交时间倒序
|
||||
*
|
||||
* @param bizType 认证对象类型:driver-司机,vehicle-车辆
|
||||
* @param bizId 认证对象主键
|
||||
* @return 认证记录列表
|
||||
*/
|
||||
List<TransportCertRecordVO> selectRecordList(String bizType, Long bizId);
|
||||
|
||||
/**
|
||||
* 取某认证对象当前的待审快照(至多一条)
|
||||
*
|
||||
* @param bizType 认证对象类型:driver-司机,vehicle-车辆
|
||||
* @param bizId 认证对象主键
|
||||
* @return 待审快照,无则返回 null
|
||||
*/
|
||||
TransportCertRecord selectPendingRecord(String bizType, Long bizId);
|
||||
|
||||
}
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
/**
|
||||
* 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.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.mp.base.BaseServiceImpl;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.transport.mapper.TransportCertRecordMapper;
|
||||
import org.springblade.transport.pojo.entity.TransportCertRecord;
|
||||
import org.springblade.transport.pojo.vo.TransportCertRecordVO;
|
||||
import org.springblade.transport.service.ITransportCertRecordService;
|
||||
import org.springblade.transport.wrapper.TransportCertRecordWrapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 认证记录 服务实现类
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Service
|
||||
public class TransportCertRecordServiceImpl extends BaseServiceImpl<TransportCertRecordMapper, TransportCertRecord> implements ITransportCertRecordService {
|
||||
|
||||
/**
|
||||
* 合法的审核状态取值
|
||||
*/
|
||||
private static final Set<Integer> VALID_STATUS = Set.of(
|
||||
TransportCertRecord.STATUS_PENDING,
|
||||
TransportCertRecord.STATUS_APPROVED,
|
||||
TransportCertRecord.STATUS_REJECTED,
|
||||
TransportCertRecord.STATUS_SUPERSEDED
|
||||
);
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean save(TransportCertRecord transportCertRecord) {
|
||||
validateRecord(transportCertRecord);
|
||||
return super.save(transportCertRecord);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean saveBatch(Collection<TransportCertRecord> entityList, int batchSize) {
|
||||
entityList.forEach(this::validateRecord);
|
||||
return super.saveBatch(entityList, batchSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean saveOrUpdateBatch(Collection<TransportCertRecord> entityList, int batchSize) {
|
||||
entityList.forEach(this::validateRecord);
|
||||
return super.saveOrUpdateBatch(entityList, batchSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁用继承自 {@code BaseService} 的「修改状态」
|
||||
* <p>
|
||||
* ⚠️ 本表的 {@code status} 是<b>审核状态</b>,不是「启用 / 停用」。
|
||||
* 而 {@code BaseServiceImpl#changeStatus} 会绕过 {@link #validateRecord} 直接写 status,
|
||||
* 调用 {@code changeStatus(ids, 1)} 即等于<b>批量通过审核</b>——
|
||||
* 审核人、审核时间、驳回原因全部缺失,既破坏 I3 的留痕,也会凭空造出生效快照。
|
||||
* 审核必须走 03 的审核接口(作用于单条记录、带留痕)。
|
||||
*
|
||||
* @param ids 主键集合
|
||||
* @param status 状态
|
||||
* @return 永不返回,直接抛出
|
||||
*/
|
||||
@Override
|
||||
public boolean changeStatus(List<Long> ids, Integer status) {
|
||||
throw new ServiceException("认证记录不支持直接修改状态,请使用审核接口");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TransportCertRecordVO> selectRecordList(String bizType, Long bizId) {
|
||||
return list(recordQuery(bizType, bizId)).stream()
|
||||
.map(TransportCertRecordWrapper.build()::entityVO)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportCertRecord selectPendingRecord(String bizType, Long bizId) {
|
||||
validateBizTarget(bizType, bizId);
|
||||
return getOne(Wrappers.<TransportCertRecord>lambdaQuery()
|
||||
.eq(TransportCertRecord::getBizType, bizType)
|
||||
.eq(TransportCertRecord::getBizId, bizId)
|
||||
.eq(TransportCertRecord::getStatus, TransportCertRecord.STATUS_PENDING)
|
||||
.last("LIMIT 1"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 认证对象检索条件:按提交时间倒序
|
||||
* <p>
|
||||
* 排序用 {@code submit_time} 而非框架默认的 {@code create_time},是刻意为之:
|
||||
* 「认证记录」的业务时间轴是<b>提交</b>,ticket 02 明确要求
|
||||
* 「查询某辆车的认证记录列表:按提交时间倒序」。
|
||||
*
|
||||
* @param bizType 认证对象类型
|
||||
* @param bizId 认证对象主键
|
||||
* @return 检索条件
|
||||
*/
|
||||
private LambdaQueryWrapper<TransportCertRecord> recordQuery(String bizType, Long bizId) {
|
||||
validateBizTarget(bizType, bizId);
|
||||
return Wrappers.<TransportCertRecord>lambdaQuery()
|
||||
.eq(TransportCertRecord::getBizType, bizType)
|
||||
.eq(TransportCertRecord::getBizId, bizId)
|
||||
.orderByDesc(TransportCertRecord::getSubmitTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 认证对象标识校验
|
||||
*
|
||||
* @param bizType 认证对象类型
|
||||
* @param bizId 认证对象主键
|
||||
*/
|
||||
private void validateBizTarget(String bizType, Long bizId) {
|
||||
if (Func.isEmpty(bizType) || Func.isEmpty(bizId)) {
|
||||
throw new ServiceException("认证对象类型与主键不能为空");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录落库前的强制校验
|
||||
* <p>
|
||||
* ⚠️ 这里防的是框架的一个<b>静默</b>行为:{@code BaseServiceImpl#resolveEntity} 在插入时
|
||||
* 若 {@code status} 为 {@code null},会填充 {@code BladeConstant.DB_STATUS_NORMAL}(值 1),
|
||||
* 而 1 在本表恰好是 {@link TransportCertRecord#STATUS_APPROVED}。
|
||||
* 那会让一条「忘了设状态」的提交凭空变成「已通过」,进而可能被当成生效快照。
|
||||
* 故此处对空状态与非法取值直接拒绝,不依赖调用方自觉。
|
||||
*
|
||||
* @param transportCertRecord 待落库的认证记录
|
||||
*/
|
||||
private void validateRecord(TransportCertRecord transportCertRecord) {
|
||||
if (Func.isEmpty(transportCertRecord)) {
|
||||
throw new ServiceException("认证记录不能为空");
|
||||
}
|
||||
if (Func.isEmpty(transportCertRecord.getBizType())) {
|
||||
throw new ServiceException("认证对象类型不能为空");
|
||||
}
|
||||
if (Func.isEmpty(transportCertRecord.getBizId())) {
|
||||
throw new ServiceException("认证对象主键不能为空");
|
||||
}
|
||||
if (Func.isEmpty(transportCertRecord.getSnapshotJson())) {
|
||||
throw new ServiceException("认证内容快照不能为空");
|
||||
}
|
||||
Integer status = transportCertRecord.getStatus();
|
||||
if (Func.isEmpty(status)) {
|
||||
throw new ServiceException("认证记录状态不能为空,不允许依赖框架默认值(默认值 1 会被误判为已通过)");
|
||||
}
|
||||
if (!VALID_STATUS.contains(status)) {
|
||||
throw new ServiceException("认证记录状态取值不正确");
|
||||
}
|
||||
if (TransportCertRecord.STATUS_REJECTED.equals(status) && Func.isEmpty(transportCertRecord.getRejectReason())) {
|
||||
throw new ServiceException("请输入认证驳回原因");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 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 org.springblade.core.mp.support.BaseEntityWrapper;
|
||||
import org.springblade.core.tool.utils.BeanUtil;
|
||||
import org.springblade.system.cache.UserCache;
|
||||
import org.springblade.transport.pojo.entity.TransportCertRecord;
|
||||
import org.springblade.transport.pojo.vo.TransportCertRecordVO;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 认证记录包装类
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public class TransportCertRecordWrapper extends BaseEntityWrapper<TransportCertRecord, TransportCertRecordVO> {
|
||||
|
||||
public static TransportCertRecordWrapper build() {
|
||||
return new TransportCertRecordWrapper();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportCertRecordVO entityVO(TransportCertRecord transportCertRecord) {
|
||||
TransportCertRecordVO transportCertRecordVO = Objects.requireNonNull(BeanUtil.copyProperties(transportCertRecord, TransportCertRecordVO.class));
|
||||
transportCertRecordVO.setCreateUserName(UserCache.getUserRealName(transportCertRecord.getCreateUser()));
|
||||
transportCertRecordVO.setUpdateUserName(UserCache.getUserRealName(transportCertRecord.getUpdateUser()));
|
||||
transportCertRecordVO.setSubmitUserName(UserCache.getUserRealName(transportCertRecord.getSubmitUser()));
|
||||
transportCertRecordVO.setAuditUserName(UserCache.getUserRealName(transportCertRecord.getAuditUser()));
|
||||
return transportCertRecordVO;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user