Merge branch 'master' into feature/payment-apply-bfi-sync

# Conflicts:
#	blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerArchiveServiceImpl.java
This commit is contained in:
刘泉佳
2026-10-14 14:24:27 +08:00
205 changed files with 17746 additions and 907 deletions
@@ -144,6 +144,12 @@ public class Dept extends TenantEntity {
@Schema(description = "金蝶部门代码")
private String kingdeeDeptCode;
/**
* 合同编码前缀(合同编号:前缀(年份)三位流水号)
*/
@Schema(description = "合同编码前缀")
private String contractCodePrefix;
/**
* 组织简称
*/
@@ -210,4 +216,12 @@ public class Dept extends TenantEntity {
@TableField(updateStrategy = FieldStrategy.NOT_NULL)
private String oaSupSubComId;
/**
* OA上次更新时间(OA 的 modified 字段),仅同步比对使用
*/
@JsonIgnore
@Schema(description = "OA上次更新时间", hidden = true)
@TableField(updateStrategy = FieldStrategy.NOT_NULL)
private String oaModified;
}
@@ -25,6 +25,7 @@
*/
package org.springblade.system.pojo.entity;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
@@ -63,6 +64,7 @@ public class RailwayStation extends BaseEntity {
* 电报略码
*/
@Schema(description = "电报略码")
@TableField(updateStrategy = FieldStrategy.ALWAYS)
private String telegraphCode;
/**
* 车站名称
@@ -4,29 +4,34 @@ import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 部门类型枚举
* 组织类型枚举,与业务字典 organization_type 对齐
*
* @author bfhuange
* @date 2024/8/26
*/
@AllArgsConstructor
@Getter
public enum DeptCategory {
/**
* 公司
*/
COMPANY(1, "公司"),
/**
* 部门
*/
DEPT(2, "部门"),
;
/**
* 集团
*/
GROUP(1, "集团"),
/**
* 公司
*/
COMPANY(2, "公司"),
/**
* 部门
*/
DEPT(3, "部门"),
;
/**
* 部门类型编码
*/
private final Integer code;
/**
* 部门类型名称
*/
private final String name;
/**
* 组织类型编码
*/
private final Integer code;
/**
* 组织类型名称
*/
private final String name;
}
@@ -35,8 +35,12 @@ public enum UserSex {
if (name == null || name.isEmpty()) {
return null;
}
String sexName = name.trim();
if ("-1".equals(sexName) || "0".equals(sexName) || "未知".equals(sexName)) {
return null;
}
return Stream.of(values())
.filter(e -> e.getName().equals(name))
.filter(e -> e.getName().equals(sexName) || String.valueOf(e.getCode()).equals(sexName))
.map(UserSex::getCode)
.findFirst().orElse(null);
}
@@ -66,4 +66,7 @@ public class OaOrgSyncPageVO implements Serializable {
@Schema(description = "是否已到最后一页")
private Boolean finished;
@Schema(description = "增量同步使用的 OA modified 起始时间,后续分页需原样回传")
private String modifiedSince;
}
@@ -63,4 +63,7 @@ public class OaPersonSyncPageVO implements Serializable {
@Schema(description = "是否已到最后一页")
private Boolean finished;
@Schema(description = "增量同步使用的 OA modified 起始时间,后续分页需原样回传")
private String modifiedSince;
}
@@ -0,0 +1,148 @@
/**
* 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>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.transport.jackson;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import java.io.IOException;
import java.time.DateTimeException;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* LocalDate 宽松反序列化,对齐 OCR / 司机侧日期归一化规则。
* <p>
* 兼容写法:
* <ul>
* <li>ISO:{@code yyyy-MM-dd}</li>
* <li>紧凑:{@code yyyyMMdd}</li>
* <li>点分 / 斜杠 / 年月日:{@code 2018.3.9}、{@code 2026/03/17}、{@code 2030年05月20日}</li>
* <li>任意非数字分隔:年与月、月与日之间只要不是数字即可</li>
* <li>区间文本:按字段名取起期(first)或止期(last)</li>
* </ul>
*
* @author Chill
*/
public class FlexibleLocalDateDeserializer extends JsonDeserializer<LocalDate> {
/**
* 4 位年 + 1~2 位月 + 1~2 位日,中间允许任意非数字分隔(与 OcrFieldSupport 一致)
*/
private static final Pattern DATE_PATTERN = Pattern.compile("(\\d{4})\\D+(\\d{1,2})\\D+(\\d{1,2})");
private static final Pattern COMPACT_PATTERN = Pattern.compile("(\\d{4})(\\d{2})(\\d{2})");
@Override
public LocalDate deserialize(JsonParser parser, DeserializationContext context) throws IOException {
String text = parser.getValueAsString();
if (text == null) {
return null;
}
String value = text.trim();
if (value.isEmpty()) {
return null;
}
// 「长期」等非日期文案:按空值处理,避免把无法解析的字符串打成 400
if (value.contains("长期") || value.contains("永久")) {
return null;
}
boolean preferLast = isEndDateField(parser.getCurrentName());
LocalDate parsed = parseFlexible(value, preferLast);
if (parsed != null) {
return parsed;
}
return (LocalDate) context.handleWeirdStringValue(LocalDate.class, value,
"期望日期格式 yyyy-MM-dd / yyyyMMdd / yyyy年M月d日 等 OCR 常见写法");
}
/**
* 解析宽松日期文本;区间时按 preferLast 取首段或末段。
*
* @param value 原始文本
* @param preferLast true 取止期,false 取起期
* @return 合法 LocalDate;无法识别时返回 null
*/
public static LocalDate parseFlexible(String value, boolean preferLast) {
if (value == null) {
return null;
}
String text = value.trim();
if (text.isEmpty()) {
return null;
}
List<LocalDate> candidates = matchDates(text);
if (!candidates.isEmpty()) {
return preferLast ? candidates.get(candidates.size() - 1) : candidates.get(0);
}
// 去掉所有非数字后恰为 8 位时按 yyyyMMdd 处理(如 20260317、2026-0317 混杂)
String compact = text.replaceAll("\\D", "");
if (compact.length() == 8) {
return toLocalDate(compact.substring(0, 4), compact.substring(4, 6), compact.substring(6, 8));
}
Matcher compactMatcher = COMPACT_PATTERN.matcher(text);
if (compactMatcher.find()) {
return toLocalDate(compactMatcher.group(1), compactMatcher.group(2), compactMatcher.group(3));
}
return null;
}
private static List<LocalDate> matchDates(String text) {
List<LocalDate> dates = new ArrayList<>();
Matcher matcher = DATE_PATTERN.matcher(text);
while (matcher.find()) {
LocalDate date = toLocalDate(matcher.group(1), matcher.group(2), matcher.group(3));
if (date != null) {
dates.add(date);
}
}
return dates;
}
private static LocalDate toLocalDate(String yearText, String monthText, String dayText) {
try {
int year = Integer.parseInt(yearText);
int month = Integer.parseInt(monthText);
int day = Integer.parseInt(dayText);
return LocalDate.of(year, month, day);
} catch (NumberFormatException | DateTimeException exception) {
return null;
}
}
/**
* 止期字段名通常以 End / EndDate 结尾,区间文本应取最后一段日期。
*/
private static boolean isEndDateField(String fieldName) {
if (fieldName == null || fieldName.isEmpty()) {
return false;
}
return fieldName.endsWith("End") || fieldName.endsWith("EndDate")
|| fieldName.contains("EndDate") || fieldName.contains("ValidEnd");
}
}
@@ -46,4 +46,7 @@ public class ExceptionDisposalFollowRequest implements Serializable {
@Schema(description = "跟进说明")
private String followContent;
@Schema(description = "附件JSON,[{\"name\":\"...\",\"url\":\"...\"}]")
private String attachmentsJson;
}
@@ -0,0 +1,63 @@
/**
* 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>
* 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>
* 只承载上报四件套。车牌、项目、承运商、配载单号等字段由上报时从运单回填,
* 不在此列,避免编辑后与运单脱钩。
* <p>
* 不含处置状态:状态只能经跟进与完成流转,编辑不得改变它。
*
* @author Chill
*/
@Data
@Schema(description = "异常处置编辑请求")
public class ExceptionDisposalUpdateRequest implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "异常处置ID")
private Long id;
@Schema(description = "异常类型")
private String exceptionType;
@Schema(description = "异常原因")
private String exceptionReason;
@Schema(description = "上报说明")
private String reportDescription;
@Schema(description = "现场照片,逗号分隔")
private String scenePhotos;
}
@@ -30,7 +30,19 @@ public class NodeSubmitDTO implements Serializable {
@Schema(description = "定位信息")
private Location location;
@Schema(description = "凭证照片 URL 列表")
/**
* 带类型的打卡照片,类型由提交方显式给出;非空时优先于 photos。
*/
@Schema(description = "带类型的照片列表,非空时优先于 photos")
private List<PunchPhotoDTO> photoItems;
/**
* 旧版小程序提交的裸 URL 列表,类型只能按下标回推过程配置的凭证类型。
* <p>
* 与 photoItems 并存是为了不强制升级小程序:新版提交 photoItems,旧版仍走这里;
* 等旧版本覆盖率达标后由后续改造删除。
*/
@Schema(description = "凭证照片 URL 列表,类型按下标回推")
private List<String> photos;
@Schema(description = "重量(吨)")
@@ -0,0 +1,53 @@
/**
* 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;
/**
* 证件 OCR 识别请求(小程序)
*
* @author Chill
*/
@Data
@Schema(description = "证件OCR识别请求")
public class OcrRecognizeRequest implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "已上传图片的地址,必须是百度可访问的HTTP或HTTPS地址")
private String imageUrl;
@Schema(description = "证件类型:idcard-身份证,driver-驾驶证,vehicle-行驶证,road_transport-道路运输证")
private String type;
@Schema(description = "正副面:front或back,默认front")
private String side;
}
@@ -0,0 +1,98 @@
/**
* 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;
import java.time.LocalDate;
/**
* 司机端个人认证提交请求
* <p>
* 一次提交承载个人认证的**全部**内容(身份证 + 驾驶证 + 从业资格证),
* 三证一起提交、一起审核,不拆开。
*
* @author Chill
*/
@Data
@Schema(description = "司机端个人认证提交请求")
public class PersonCertSubmitRequest implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "真实姓名")
private String realName;
@Schema(description = "身份证号")
private String idCardNo;
@Schema(description = "身份证正面照")
private String idCardFrontUrl;
@Schema(description = "身份证反面照")
private String idCardBackUrl;
@Schema(description = "身份证有效期起")
private LocalDate idCardValidStart;
@Schema(description = "身份证有效期止")
private LocalDate idCardValidEnd;
@Schema(description = "身份证长期有效")
private Integer idCardLongTerm;
@Schema(description = "驾驶证档案编号")
private String driverFileNo;
@Schema(description = "准驾车型")
private String driverClass;
@Schema(description = "驾驶证主页照片")
private String driverLicenseFrontUrl;
@Schema(description = "驾驶证副页照片")
private String driverLicenseBackUrl;
@Schema(description = "驾驶证有效期起")
private LocalDate driverValidStart;
@Schema(description = "驾驶证有效期止")
private LocalDate driverValidEnd;
@Schema(description = "驾驶证长期有效")
private Integer driverLongTerm;
@Schema(description = "从业资格证类型")
private String qualificationType;
@Schema(description = "从业资格证号")
private String qualificationNo;
@Schema(description = "从业资格证封面页")
private String qualificationFront;
@Schema(description = "从业资格证内容页")
private String qualificationBack;
@Schema(description = "从业资格证有效期起")
private LocalDate qualificationValidStart;
@Schema(description = "从业资格证有效期止")
private LocalDate qualificationValidEnd;
@Schema(description = "从业资格证长期有效")
private Integer qualificationLongTerm;
@Schema(description = "大头照")
private String headPhoto;
}
@@ -0,0 +1,32 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
*/
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>
* 类型取值域是固定的「打卡照片」加该节点过程配置勾选的凭证类型,域外类型一律拒绝:
* 类型是照片分组的唯一依据,放进来路不明的类型会让回显分组错乱。
*/
@Data
@Schema(description = "带类型的打卡照片")
public class PunchPhotoDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "照片类型")
private String type;
@Schema(description = "图片URL")
private String url;
}
@@ -0,0 +1,115 @@
/**
* 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;
import java.time.LocalDate;
/**
* 司机端车辆认证提交请求
* <p>
* 一次提交承载该车辆认证的**全部**内容(行驶证 + 道路运输证),两证一起提交、一起审核。
* 提交只产生新的待审记录,不覆盖车辆当前已生效的认证内容。
*
* @author Chill
*/
@Data
@Schema(description = "司机端车辆认证提交请求")
public class VehicleCertSubmitRequest implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "车辆ID:新增时为空,重新认证时必填")
private Long id;
@Schema(description = "车牌号")
private String plateNo;
@Schema(description = "所有人:行驶证登记的单位名称,不是司机")
private String ownerName;
@Schema(description = "车牌颜色")
private String plateColor;
@Schema(description = "车辆类型")
private String vehicleType;
@Schema(description = "车辆识别代号(车架号),选填")
private String vin;
@Schema(description = "外廓长度,毫米")
private Integer outerLength;
@Schema(description = "外廓宽度,毫米")
private Integer outerWidth;
@Schema(description = "外廓高度,毫米")
private Integer outerHeight;
@Schema(description = "核定载质量,KG")
private Integer approvedLoadKg;
@Schema(description = "准牵引总质量,KG")
private Integer tractionMassKg;
@Schema(description = "能源类型")
private String energyType;
@Schema(description = "业务关系:自有 / 管理挂靠 / 业务合作")
private String businessRelation;
@Schema(description = "强制报废日期")
private LocalDate compulsoryScrapDate;
@Schema(description = "强制报废长期有效:0否 1是")
private Integer compulsoryScrapLongTerm;
@Schema(description = "行驶证档案编号")
private String drivingLicenseNo;
@Schema(description = "行驶证主页正面照片")
private String drivingLicenseImage;
@Schema(description = "行驶证主页反面照片")
private String drivingLicenseMainBack;
@Schema(description = "行驶证副页正面照片")
private String drivingLicenseViceFront;
@Schema(description = "行驶证副页反面照片")
private String drivingLicenseViceBack;
@Schema(description = "行驶证有效期起")
private LocalDate drivingLicenseStartDate;
@Schema(description = "行驶证有效期止")
private LocalDate drivingLicenseEndDate;
@Schema(description = "行驶证长期有效:0否 1是")
private Integer drivingLicenseLongTerm;
@Schema(description = "道路运输证号")
private String roadTransportCertNo;
@Schema(description = "道路运输证照片")
private String roadTransportCertImage;
@Schema(description = "道路运输证有效期起")
private LocalDate roadTransportCertStartDate;
@Schema(description = "道路运输证有效期止")
private LocalDate roadTransportCertEndDate;
@Schema(description = "道路运输证长期有效:0否 1是")
private Integer roadTransportCertLongTerm;
@Schema(description = "机动车登记编号")
private String registrationNo;
@Schema(description = "机动车登记本照片")
private String registrationImage;
}
@@ -32,6 +32,12 @@ public class WaybillImportBatchRequest {
private Long planId;
private String planName;
private String remark;
/** 列表筛选:创建人 */
private Long createUser;
/** 列表筛选:创建时间起(yyyy-MM-dd HH:mm:ss) */
private String createTimeStart;
/** 列表筛选:创建时间止(yyyy-MM-dd HH:mm:ss) */
private String createTimeEnd;
private List<Map<String, Object>> rows;
/** 分批确认导入时,是否追加到已有批次(首批为 false,后续批为 true)。 */
private Boolean append;
@@ -0,0 +1,114 @@
/**
* 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>
* 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;
import java.util.ArrayList;
import java.util.List;
/**
* 管理端打卡记录编辑请求
* <p>
* 承载时间轴上单条打卡记录的全部可编辑项,字段按过程配置取舍:
* 打卡定位(地址/经纬度)、上传货量(重量/体积/数量)、上传凭证(照片)。
* <p>
* {@code punched=false} 表示取消打卡,服务端会清除该节点的打卡流水
* ——时间轴上「未打卡」的判据就是没有流水,故不能只改状态位。
*
* @author Chill
*/
@Data
@Schema(description = "管理端打卡记录编辑请求")
public class WaybillPunchRecordSaveRequest implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "运单ID")
private Long waybillId;
@Schema(description = "打卡类型:node=过程节点打卡,enroute=在途打卡")
private String type;
@Schema(description = "过程节点 key")
private String nodeCode;
@Schema(description = "过程节点名称")
private String nodeName;
@Schema(description = "打卡状态:true 已打卡,false 未打卡")
private Boolean punched;
@Schema(description = "打卡时间 yyyy-MM-dd HH:mm:ss,为空时取当前时间")
private String punchTime;
@Schema(description = "打卡地址")
private String address;
@Schema(description = "经度")
private String longitude;
@Schema(description = "纬度")
private String latitude;
@Schema(description = "重量(吨)")
private String weight;
@Schema(description = "体积(方)")
private String volume;
@Schema(description = "数量(件)")
private String quantity;
@Schema(description = "备注")
private String remark;
@Schema(description = "打卡凭证图,按上传顺序回写凭证类型")
private List<Photo> photos = new ArrayList<>();
/**
* 打卡凭证图
* <p>
* 类型随图一起回写:展示层对「逗号分隔的裸 URL」按凭证类型下标回推标签,
* 编辑换了顺序就会串标,所以这里保留原类型。
*/
@Data
@Schema(description = "打卡凭证图")
public static class Photo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "凭证类型")
private String type;
@Schema(description = "图片URL")
private String url;
}
}
@@ -61,10 +61,10 @@ public class ContractManage extends TenantEntity {
@Schema(description = "所属项目")
private String projectName;
@Schema(description = "所属组织ID")
@Schema(description = "合同负责部门ID")
private Long organizationId;
@Schema(description = "所属组织")
@Schema(description = "合同负责部门")
private String organizationName;
@Schema(description = "合同类别")
@@ -100,6 +100,9 @@ public class ContractManage extends TenantEntity {
@Schema(description = "签订日期")
private LocalDate signDate;
@Schema(description = "申请日期(提交正式合同时写入)")
private LocalDate applyDate;
@Schema(description = "结算方式")
private String settlementMode;
@@ -107,6 +110,7 @@ public class ContractManage extends TenantEntity {
private String contractFormat;
@Schema(description = "是否需要加盖法人章")
@TableField(insertStrategy = FieldStrategy.ALWAYS, updateStrategy = FieldStrategy.ALWAYS)
private Integer legalSealFlag;
@Schema(description = "一式份数")
@@ -206,7 +210,10 @@ public class ContractManage extends TenantEntity {
@Schema(description = "终止原因")
private String terminateReason;
@Schema(description = "备注")
@Schema(description = "合同主要内容")
private String mainContent;
@Schema(description = "其他事项说明")
private String remark;
}
@@ -28,10 +28,15 @@ 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.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springblade.core.tenant.mp.TenantEntity;
import org.springblade.transport.jackson.FlexibleLocalDateDeserializer;
import java.io.Serial;
import java.time.LocalDate;
@@ -64,6 +69,7 @@ public class Driver extends TenantEntity {
* 出生年月
*/
@Schema(description = "出生年月")
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
private LocalDate birthday;
/**
* 性别
@@ -111,6 +117,23 @@ public class Driver extends TenantEntity {
*/
@Schema(description = "身份证反面照")
private String idCardBack;
/**
* 身份证有效期起
*/
@Schema(description = "身份证有效期起")
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
private LocalDate idCardValidStart;
/**
* 身份证有效期止
*/
@Schema(description = "身份证有效期止")
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
private LocalDate idCardValidEnd;
/**
* 身份证长期有效
*/
@Schema(description = "身份证长期有效")
private Integer idCardLongTerm;
/**
* 大头照
*/
@@ -130,11 +153,13 @@ public class Driver extends TenantEntity {
* 驾驶证有效期起
*/
@Schema(description = "驾驶证有效期起")
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
private LocalDate drivingLicenseStartDate;
/**
* 驾驶证有效期止
*/
@Schema(description = "驾驶证有效期止")
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
private LocalDate drivingLicenseEndDate;
/**
* 驾驶证长期有效
@@ -165,6 +190,7 @@ public class Driver extends TenantEntity {
* 从业资格证有效期止
*/
@Schema(description = "从业资格证有效期止")
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
private LocalDate qualificationEndDate;
/**
* 从业资格证长期有效
@@ -192,9 +218,10 @@ public class Driver extends TenantEntity {
@Schema(description = "手机号")
private String mobile;
/**
* 关联系统用户ID
* 关联小程序用户ID(blade_mp_user.id)
*/
@Schema(description = "关联系统用户ID")
@Schema(description = "关联小程序用户ID")
@JsonSerialize(using = ToStringSerializer.class)
private Long userId;
/**
* 与联系人关系
@@ -221,5 +248,32 @@ public class Driver extends TenantEntity {
*/
@Schema(description = "备注")
private String remark;
/**
* 最新提交状态展示缓存:0审核中 1已通过 2已驳回 3已作废,null 表示从未提交
* <p>
* 仅为列表展示而冗余,<b>不是</b>可用性判据;「是否已认证」一律读生效快照指针。
*/
@Schema(description = "最新提交状态展示缓存:0审核中 1已通过 2已驳回 3已作废,null为从未提交")
private Integer certificationStatus;
/**
* 最新一次驳回原因展示缓存
*/
@Schema(description = "最新一次驳回原因展示缓存")
private String certificationRejectReason;
/**
* 生效快照指针:指向 blade_transport_cert_record.id
* <p>
* 「该司机是否已认证」的<b>唯一</b>判据。为 null 表示从未认证通过;
* 一经设置<b>永不回退</b>——提交、驳回、作废都不得修改它,
* 因此重新提交认证不会让一个已认证的司机失去接单资格。
* <p>
* ⚠️ {@code READ_ONLY}:本字段<b>只能由审核通过时</b>写入。
* 司机档案的 {@code /submit} 直接以本实体接收请求体,若允许反序列化,
* 调用方就能自行填一个记录 ID 把自己「认证」掉,绕过审核。
*/
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "生效快照指针")
private Long effectiveCertRecordId;
}
@@ -51,6 +51,12 @@ public class ExceptionDisposal extends TenantEntity {
@Schema(description = "运单号")
private String waybillNo;
@Schema(description = "过程节点 key(为空表示未指明节点)")
private String nodeCode;
@Schema(description = "过程节点名称(上报时复制,此后不随过程配置变化)")
private String nodeName;
@Schema(description = "配载单号/总单号")
private String loadingOrMasterNo;
@@ -51,6 +51,9 @@ public class ExceptionDisposalFollowRecord extends TenantEntity {
@Schema(description = "跟进说明")
private String followContent;
@Schema(description = "附件JSON")
private String attachmentsJson;
@Schema(description = "跟进人")
private Long followUser;
@@ -70,10 +70,10 @@ public class ProjectApply extends TenantEntity {
@Schema(description = "业务部门")
private String businessDeptName;
@Schema(description = "承办部门ID")
private Long undertakeDeptId;
@Schema(description = "平台公司ID,多个逗号分隔")
private String undertakeDeptId;
@Schema(description = "承办部门")
@Schema(description = "平台公司,多个顿号分隔")
private String undertakeDeptName;
@Schema(description = "项目由来")
@@ -0,0 +1,158 @@
/**
* 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.entity;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springblade.core.tenant.mp.TenantEntity;
import java.io.Serial;
import java.time.LocalDateTime;
/**
* 认证记录实体类
* <p>
* 认证对象(司机 / 车辆)<b>每一次提交</b>所产生的、<b>不可变</b>的快照。
* 记录一经创建,其 {@code snapshotJson} 与提交元信息永不修改;被同对象的后续提交取代时
* 标记为「已作废」,而<b>不是</b>删除。判定某个对象「是否已认证」只看它持有的生效快照指针,
* 与记录表里的任何一条记录的状态都无关。
* <p>
* ⚠️ <b>{@code status} 承载的是「审核状态」,不是 BladeX 的「启用 / 停用」。</b>
* 该字段继承自 {@link TenantEntity},带来两条必须堵住的框架通道:
* 一是 {@code BaseServiceImpl#resolveEntity} 在插入时会把 {@code null} 的 {@code status}
* <b>静默填成 {@code 1}</b>(恰好是 {@link #STATUS_APPROVED});
* 二是继承来的 {@code changeStatus(ids, 1)} 会直接写 {@code status},等于批量通过审核。
* 二者都已在 {@code TransportCertRecordServiceImpl} 中封堵(前者校验抛错、后者覆写抛错)。
* 审核必须逐条、带审核人与审核时间落库,不得借道 {@code changeStatus} 批量改状态。
*
* @author Chill
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("blade_transport_cert_record")
@Schema(description = "认证记录")
public class TransportCertRecord extends TenantEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 认证对象类型:司机
*/
public static final String BIZ_TYPE_DRIVER = "driver";
/**
* 认证对象类型:车辆
*/
public static final String BIZ_TYPE_VEHICLE = "vehicle";
/**
* 审核状态:审核中(即该对象的「待审快照」)
*/
public static final Integer STATUS_PENDING = 0;
/**
* 审核状态:已通过(该对象最新一条已通过者即其「生效快照」)
*/
public static final Integer STATUS_APPROVED = 1;
/**
* 审核状态:已驳回(审核的否定结论,需带驳回原因)
*/
public static final Integer STATUS_REJECTED = 2;
/**
* 审核状态:已作废(被同对象的再次提交取代,非驳回)
*/
public static final Integer STATUS_SUPERSEDED = 3;
/**
* 驳回原因最大长度,与 {@code reject_reason} 列宽保持一致
*/
public static final int REJECT_REASON_MAX_LENGTH = 200;
/**
* 认证对象类型:driver-司机,vehicle-车辆
* <p>
* {@code NEVER}:记录一经创建,认证对象的身份不可改。
*/
@TableField(updateStrategy = FieldStrategy.NEVER)
@Schema(description = "认证对象类型:driver-司机,vehicle-车辆")
private String bizType;
/**
* 认证对象主键:司机ID 或 车辆ID(车辆按具体车辆,不是司机)
*/
@TableField(updateStrategy = FieldStrategy.NEVER)
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "认证对象主键")
private Long bizId;
/**
* 展示键:司机姓名 / 车牌号,供审核列表直接展示
*/
@Schema(description = "展示键:司机姓名 / 车牌号")
private String bizKey;
/**
* 该次提交的全量认证内容(JSON),一经创建不可修改
* <p>
* {@code NEVER}:本字段<b>永不进入 UPDATE 语句</b>,因此不存在「某条链路
* 忘了保护快照」的可能。改内容只能靠<b>新提交一条记录</b>。
*/
@TableField(updateStrategy = FieldStrategy.NEVER)
@Schema(description = "该次提交的全量认证内容JSON")
private String snapshotJson;
/**
* 驳回原因,仅当审核状态为已驳回时有值
*/
@Schema(description = "驳回原因")
private String rejectReason;
/**
* 提交时间
*/
@TableField(updateStrategy = FieldStrategy.NEVER)
@Schema(description = "提交时间")
private LocalDateTime submitTime;
/**
* 提交人
*/
@TableField(updateStrategy = FieldStrategy.NEVER)
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "提交人")
private Long submitUser;
/**
* 审核时间
*/
@Schema(description = "审核时间")
private LocalDateTime auditTime;
/**
* 审核人
*/
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "审核人")
private Long auditUser;
}
@@ -26,10 +26,15 @@
package org.springblade.transport.pojo.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springblade.core.tenant.mp.TenantEntity;
import org.springblade.transport.jackson.FlexibleLocalDateDeserializer;
import java.io.Serial;
import java.time.LocalDate;
@@ -63,6 +68,11 @@ public class TransportVehicle extends TenantEntity {
*/
@Schema(description = "车牌号")
private String plateNo;
/**
* 所有人:行驶证登记的单位名称,不是司机
*/
@Schema(description = "所有人(行驶证登记的单位名称,不是司机)")
private String ownerName;
/**
* 车牌颜色
*/
@@ -73,6 +83,11 @@ public class TransportVehicle extends TenantEntity {
*/
@Schema(description = "车辆类型")
private String vehicleType;
/**
* 车辆识别代号(车架号)
*/
@Schema(description = "车辆识别代号(车架号)")
private String vin;
/**
* 外廓长度,毫米
*/
@@ -112,6 +127,7 @@ public class TransportVehicle extends TenantEntity {
* 强制报废日期
*/
@Schema(description = "强制报废日期")
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
private LocalDate compulsoryScrapDate;
/**
* 强制报废长期有效
@@ -132,11 +148,13 @@ public class TransportVehicle extends TenantEntity {
* 行驶证有效期起
*/
@Schema(description = "行驶证有效期起")
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
private LocalDate drivingLicenseStartDate;
/**
* 行驶证有效期止
*/
@Schema(description = "行驶证有效期止")
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
private LocalDate drivingLicenseEndDate;
/**
* 行驶证长期有效
@@ -172,11 +190,13 @@ public class TransportVehicle extends TenantEntity {
* 道路运输证有效期起
*/
@Schema(description = "道路运输证有效期起")
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
private LocalDate roadTransportCertStartDate;
/**
* 道路运输证有效期止
*/
@Schema(description = "道路运输证有效期止")
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
private LocalDate roadTransportCertEndDate;
/**
* 道路运输证长期有效
@@ -192,6 +212,7 @@ public class TransportVehicle extends TenantEntity {
* 道路运输年审有效期
*/
@Schema(description = "道路运输年审有效期")
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
private LocalDate annualReviewEndDate;
/**
* 道路运输年审长期有效
@@ -207,6 +228,7 @@ public class TransportVehicle extends TenantEntity {
* 机动车登记日期
*/
@Schema(description = "机动车登记日期")
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
private LocalDate registrationDate;
/**
* 机动车登记本图片
@@ -224,5 +246,21 @@ public class TransportVehicle extends TenantEntity {
/** 认证驳回原因 */
@Schema(description = "认证驳回原因")
private String certificationRejectReason;
/**
* 生效快照指针:指向 blade_transport_cert_record.id
* <p>
* 「该车辆是否已认证」的<b>唯一</b>判据。为 null 表示从未认证通过;
* 一经设置<b>永不回退</b>——重新提交、待审、被驳回都不得修改它,
* 因此重新提交认证不会让一辆已认证的车掉出可用池。
* {@link #certificationStatus} 是「最新提交状态」的展示缓存,不代表是否已认证。
* <p>
* ⚠️ {@code READ_ONLY}:本字段<b>只能由审核通过时</b>写入。
* {@code POST /transport-vehicle/submit} 直接以本实体接收请求体,
* 若允许反序列化,调用方就能自行填一个记录 ID 把自己「认证」掉,绕过审核。
*/
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "生效快照指针")
private Long effectiveCertRecordId;
}
@@ -25,6 +25,8 @@ 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.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.NullSerializer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -82,6 +84,7 @@ public class Waybill extends TenantEntity {
@Schema(description = "型号")
private String model;
@JsonSerialize(nullsUsing = NullSerializer.class)
@Schema(description = "数量")
private BigDecimal quantity;
@@ -183,6 +186,7 @@ public class Waybill extends TenantEntity {
@Schema(description = "押运人手机号")
private String escortPhone;
@JsonSerialize(nullsUsing = NullSerializer.class)
@Schema(description = "里程")
private BigDecimal mileage;
@@ -195,12 +199,14 @@ public class Waybill extends TenantEntity {
@Schema(description = "预计完成日期")
private LocalDate estimatedEndTime;
@JsonSerialize(nullsUsing = NullSerializer.class)
@Schema(description = "单价")
private BigDecimal unitPrice;
@Schema(description = "计价单位")
private String priceUnit;
@JsonSerialize(nullsUsing = NullSerializer.class)
@Schema(description = "其他费用合计")
private BigDecimal otherFeeTotal;
@@ -44,4 +44,7 @@ public class AdminHomeBadgesVO implements Serializable {
@Schema(description = "风险记录待办数(disposalStatus=pending)")
private long risk;
@Schema(description = "重新派单待办数(司机已拒单且运单未取消未完成)")
private long pendingReassign;
}
@@ -49,7 +49,7 @@ public class AdminHomeVO implements Serializable {
@Schema(description = "快捷入口角标")
private AdminHomeBadgesVO badges = new AdminHomeBadgesVO();
@Schema(description = "待处理事项(异常处置状态≠已完成)")
@Schema(description = "待处理事项(异常处置状态≠已完成,或司机已拒单待重新派单)")
private List<AdminTodoItemVO> feed = new ArrayList<>();
}
@@ -56,4 +56,14 @@ public class BaiduOcrResultVO implements Serializable {
@Schema(description = "百度OCR原始返回结果")
private Map<String, Object> result;
/**
* 归一化后的业务字段
* <p>
* 键与提交请求里的字段同名({@code plateNo}、{@code ownerName}…),调用方直接回填即可,
* 不需要认识百度那套中文键名。只包含**识别到**的字段,没识别到的不出现(而不是给空串),
* 调用方据此就能区分「没识别到」与「识别到空值」,不会把用户已填的内容覆盖成空。
*/
@Schema(description = "归一化后的业务字段,键与提交请求同名")
private Map<String, Object> fields;
}
@@ -0,0 +1,97 @@
/**
* 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.vo;
import com.baomidou.mybatisplus.annotation.TableField;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 认证审核详情视图
* <p>
* 审核是「拿新提交的内容和当前生效的内容比」的过程,所以这里**同时**给出两侧:
* {@code pendingSnapshotJson} 是本次待审的全量内容,
* {@code effectiveSnapshotJson} 是该对象当前生效的全量内容。
* 前端对这两个 JSON 做字段级 diff,即可看出司机这次改了什么。
* <p>
* 两侧的 JSON 结构完全一致(同一套提交结构),因此可以逐键直接比较。
*
* @author Chill
*/
@Data
@Schema(description = "认证审核详情")
public class CertAuditDetailVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "认证记录主键")
private Long id;
@Schema(description = "认证对象类型:driver-司机,vehicle-车辆")
private String bizType;
@Schema(description = "认证对象主键")
private Long bizId;
@Schema(description = "展示键:司机姓名 / 车牌号")
private String bizKey;
@Schema(description = "审核状态:0审核中 1已通过 2已驳回 3已作废")
private Integer status;
@Schema(description = "状态中文文案")
private String statusText;
@Schema(description = "本次提交时间")
private LocalDateTime submitTime;
@Schema(description = "本次提交人")
private Long submitUser;
@Schema(description = "本次提交人姓名")
private String submitUserName;
@Schema(description = "本次审核人;未审核时为 null")
private Long auditUser;
@Schema(description = "本次审核人姓名;未审核时为空")
private String auditUserName;
@Schema(description = "审核时间(通过或驳回的时间);未审核时为 null")
private LocalDateTime auditTime;
@Schema(description = "待审快照:本次提交的全量内容")
private String pendingSnapshotJson;
@Schema(description = "生效快照:该对象当前生效的全量内容;从未认证通过时为 null")
private String effectiveSnapshotJson;
@Schema(description = "生效快照记录ID;null 表示该对象从未认证通过")
private Long effectiveRecordId;
/**
* 该对象是否已认证
* <p>
* 审核页据此提示「本次驳回不会影响该对象继续使用」——
* 已认证的对象即便本次被驳回,既得认证依然有效。
*/
@TableField(exist = false)
@Schema(description = "该对象当前是否已认证")
private Boolean certified;
}
@@ -49,4 +49,7 @@ public class DriverNodePunchVO implements Serializable {
@Schema(description = "数量")
private String quantity;
@Schema(description = "本次打卡是否使运单自动完成")
private Boolean waybillCompleted;
}
@@ -4,6 +4,8 @@
*/
package org.springblade.transport.pojo.vo;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -34,9 +36,37 @@ public class DriverPunchNodeVO implements Serializable {
@Schema(description = "是否已打卡(在途=今日已打)")
private Boolean done;
@Schema(description = "当前是否可打卡(顺序门禁 + 在途频次时段)")
@Schema(description = "当前是否可打卡(已打卡、被异常阻断、在途频次时段都会置否)")
private Boolean actionable;
@Schema(description = "是否被未了结的异常阻断:异常所在节点及其后的节点不可打卡;在途只要有未了结异常即不可打卡")
private Boolean blocked;
@Schema(description = "是否因前置节点未打卡而被顺序门禁拦住(在途豁免,恒为否)")
private Boolean blockedByOrder;
@Schema(description = "顺序门禁拦住的原因,如「请先完成装货打卡」")
private String orderLockReason;
@Schema(description = "是否允许展开:顺序门禁不允许,异常门禁允许(可查看回填内容)")
private Boolean expandable;
@Schema(description = "该节点自身是否被上报了未了结异常(仅异常所在节点为是,其后被牵连阻断的节点为否)")
private Boolean hasException;
@Schema(description = "该运单是否存在未了结的整单异常;整单异常不归属任何节点,故每个节点都带此标记")
private Boolean hasWaybillException;
@Schema(description = "与该节点相关的未了结异常ID,按上报时间升序;相关 = 归属该节点 ∪ 整单异常")
private List<String> relatedExceptionIds = new ArrayList<>();
@Schema(description = "阻断该节点的异常处置ID(司机端「异常详情」跳转用);未被阻断或异常已不存在时为空")
@JsonSerialize(using = ToStringSerializer.class)
private Long blockedExceptionId;
@Schema(description = "不可打卡的原因,供司机端展示")
private String lockReason;
@Schema(description = "是否展示该卡(在途可能因频次/时段隐藏)")
private Boolean visible;
@@ -44,29 +44,89 @@ public class DriverVehicleCardVO implements Serializable {
@Schema(description = "车牌号")
private String plateNo;
@Schema(description = "所有人(行驶证登记的单位名称,不是司机)")
private String ownerName;
@Schema(description = "车牌颜色")
private String plateColor;
@Schema(description = "车辆类型")
private String vehicleType;
@Schema(description = "行驶证主页照片")
private String licenseFrontUrl;
@Schema(description = "行驶证副页照片")
private String licenseBackUrl;
@Schema(description = "道路运输证号")
private String roadTransportNo;
@Schema(description = "道路运输证照片")
private String roadTransportUrl;
@Schema(description = "车架号")
@Schema(description = "车辆识别代号(车架号),选填")
private String vin;
@Schema(description = "发动机号")
private String engineNo;
@Schema(description = "外廓长度,毫米")
private Integer outerLength;
@Schema(description = "外廓宽度,毫米")
private Integer outerWidth;
@Schema(description = "外廓高度,毫米")
private Integer outerHeight;
@Schema(description = "核定载质量,KG")
private Integer approvedLoadKg;
@Schema(description = "准牵引总质量,KG")
private Integer tractionMassKg;
@Schema(description = "能源类型")
private String energyType;
@Schema(description = "业务关系:自有 / 管理挂靠 / 业务合作")
private String businessRelation;
@Schema(description = "强制报废日期")
private String compulsoryScrapDate;
@Schema(description = "强制报废长期有效:0否 1是")
private Integer compulsoryScrapLongTerm;
@Schema(description = "行驶证档案编号")
private String drivingLicenseNo;
@Schema(description = "行驶证主页正面照片")
private String drivingLicenseImage;
@Schema(description = "行驶证主页反面照片")
private String drivingLicenseMainBack;
@Schema(description = "行驶证副页正面照片")
private String drivingLicenseViceFront;
@Schema(description = "行驶证副页反面照片")
private String drivingLicenseViceBack;
@Schema(description = "行驶证有效期起")
private String drivingLicenseStartDate;
@Schema(description = "行驶证有效期止")
private String licenseValidEnd;
private String drivingLicenseEndDate;
@Schema(description = "行驶证长期有效:0否 1是")
private Integer drivingLicenseLongTerm;
@Schema(description = "道路运输证号")
private String roadTransportCertNo;
@Schema(description = "道路运输证照片")
private String roadTransportCertImage;
@Schema(description = "道路运输证有效期起")
private String roadTransportCertStartDate;
@Schema(description = "道路运输证有效期止")
private String roadTransportCertEndDate;
@Schema(description = "道路运输证长期有效:0否 1是")
private Integer roadTransportCertLongTerm;
@Schema(description = "机动车登记编号")
private String registrationNo;
@Schema(description = "机动车登记本照片")
private String registrationImage;
@Schema(description = "认证状态:0认证中 1认证通过 2认证驳回")
private Integer certificationStatus;
@@ -0,0 +1,100 @@
/**
* 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.vo;
import com.baomidou.mybatisplus.annotation.TableField;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springblade.transport.pojo.entity.TransportCertRecord;
import java.io.Serial;
/**
* 认证记录视图实体类
*
* @author Chill
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "认证记录")
public class TransportCertRecordVO extends TransportCertRecord {
@Serial
private static final long serialVersionUID = 1L;
/**
* 创建人姓名
*/
@TableField(exist = false)
@Schema(description = "创建人姓名")
private String createUserName;
/**
* 更新人姓名
*/
@TableField(exist = false)
@Schema(description = "更新人姓名")
private String updateUserName;
/**
* 提交人姓名
*/
@TableField(exist = false)
@Schema(description = "提交人姓名")
private String submitUserName;
/**
* 审核人姓名
*/
@TableField(exist = false)
@Schema(description = "审核人姓名")
private String auditUserName;
/**
* 状态中文文案:审核中 / 已通过 / 已驳回 / 已作废
* <p>
* 由服务层统一翻译后下发,避免小程序、Web、接口三处各造一套说法。
*/
@TableField(exist = false)
@Schema(description = "状态中文文案")
private String statusText;
/**
* 司机姓名(认证对象档案的**当前**姓名,不是提交时的历史值)
* <p>
* 司机侧取认证对象本身;车辆侧取提交该记录的司机。
* 解析不到档案时为空——存量订正记录里没有账号的那些司机补不出人,如实留空。
*/
@TableField(exist = false)
@Schema(description = "司机姓名")
private String driverName;
/**
* 司机手机号(认证对象档案的**当前**手机号,明文下发)
* <p>
* 审核员靠它联系司机核实材料,故不脱敏;认证快照里从来没有手机号,
* 它只有司机档案这一个来源。
*/
@TableField(exist = false)
@Schema(description = "司机手机号")
private String driverMobile;
}
@@ -0,0 +1,114 @@
/**
* 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>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.system.cache;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.cache.utils.CacheUtil;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.SpringUtil;
import org.springblade.core.tool.utils.StringPool;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.system.feign.IMpUserClient;
import org.springblade.system.pojo.entity.MpUser;
/**
* 小程序用户缓存
*
* @author Chill
*/
@Slf4j
public class MpUserCache {
public static final String MP_USER_CACHE = "blade:mp_user";
private static final String MP_USER_CACHE_ID = "mp-user:id:";
private static IMpUserClient mpUserClient;
private static IMpUserClient getMpUserClient() {
if (mpUserClient == null) {
mpUserClient = SpringUtil.getBean(IMpUserClient.class);
}
return mpUserClient;
}
/**
* 按主键获取小程序用户
*/
public static MpUser getUser(Long userId) {
if (userId == null) {
return null;
}
try {
return CacheUtil.get(MP_USER_CACHE, MP_USER_CACHE_ID, userId, () -> loadUserById(userId));
} catch (RuntimeException exception) {
log.warn("小程序用户缓存读取失败,改为直连查询, userId={}", userId, exception);
return loadUserById(userId);
}
}
/**
* 获取小程序用户真实姓名(优先 realName → name → account → id)
*/
public static String getUserRealName(Long userId) {
if (userId == null) {
return StringPool.EMPTY;
}
try {
MpUser user = getUser(userId);
if (user == null) {
return Func.toStr(userId);
}
if (StringUtil.isNotBlank(user.getRealName())) {
return user.getRealName();
}
if (StringUtil.isNotBlank(user.getName())) {
return user.getName();
}
if (StringUtil.isNotBlank(user.getAccount())) {
return user.getAccount();
}
return Func.toStr(userId);
} catch (RuntimeException exception) {
log.warn("翻译小程序用户姓名失败,回退为用户ID, userId={}", userId, exception);
return Func.toStr(userId);
}
}
/**
* 清理小程序用户缓存
*/
public static void clear() {
CacheUtil.clear(MP_USER_CACHE);
CacheUtil.clear(MP_USER_CACHE, Boolean.FALSE);
}
private static MpUser loadUserById(Long userId) {
R<MpUser> result = getMpUserClient().userById(userId);
if (result == null || !result.isSuccess()) {
return null;
}
return result.getData();
}
}
@@ -25,6 +25,7 @@
*/
package org.springblade.system.cache;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.cache.utils.CacheUtil;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.Func;
@@ -42,6 +43,7 @@ import static org.springblade.core.launch.constant.FlowConstant.TASK_USR_PREFIX;
*
* @author Chill
*/
@Slf4j
public class UserCache {
private static final String USER_CACHE_ID = "user:id:";
private static final String USER_CACHE_ACCOUNT = "user:account:";
@@ -73,10 +75,18 @@ public class UserCache {
* @return
*/
public static User getUser(Long userId) {
return CacheUtil.get(USER_CACHE, USER_CACHE_ID, userId, () -> {
R<User> result = getUserClient().userInfoById(userId);
return result.getData();
});
try {
return CacheUtil.get(USER_CACHE, USER_CACHE_ID, userId, () -> loadUserById(userId));
} catch (RuntimeException exception) {
log.warn("用户缓存读取失败,清理损坏缓存后重试, userId={}", userId, exception);
clearBrokenUserCache();
try {
return CacheUtil.get(USER_CACHE, USER_CACHE_ID, userId, () -> loadUserById(userId));
} catch (RuntimeException retryException) {
log.warn("用户缓存重试仍失败,改为直连查询, userId={}", userId, retryException);
return loadUserById(userId);
}
}
}
/**
@@ -89,20 +99,25 @@ public class UserCache {
if (userId == null) {
return StringPool.EMPTY;
}
User user = getUser(userId);
if (user == null) {
try {
User user = getUser(userId);
if (user == null) {
return Func.toStr(userId);
}
if (StringUtil.isNotBlank(user.getRealName())) {
return user.getRealName();
}
if (StringUtil.isNotBlank(user.getName())) {
return user.getName();
}
if (StringUtil.isNotBlank(user.getAccount())) {
return user.getAccount();
}
return Func.toStr(userId);
} catch (RuntimeException exception) {
log.warn("翻译用户姓名失败,回退为用户ID, userId={}", userId, exception);
return Func.toStr(userId);
}
if (StringUtil.isNotBlank(user.getRealName())) {
return user.getRealName();
}
if (StringUtil.isNotBlank(user.getName())) {
return user.getName();
}
if (StringUtil.isNotBlank(user.getAccount())) {
return user.getAccount();
}
return Func.toStr(userId);
}
/**
@@ -113,10 +128,34 @@ public class UserCache {
* @return
*/
public static User getUser(String tenantId, String account) {
return CacheUtil.get(USER_CACHE, USER_CACHE_ACCOUNT, tenantId + StringPool.DASH + account, () -> {
R<User> result = getUserClient().userByAccount(tenantId, account);
return result.getData();
});
String accountKey = tenantId + StringPool.DASH + account;
try {
return CacheUtil.get(USER_CACHE, USER_CACHE_ACCOUNT, accountKey, () -> loadUserByAccount(tenantId, account));
} catch (RuntimeException exception) {
log.warn("用户缓存读取失败,清理损坏缓存后重试, accountKey={}", accountKey, exception);
clearBrokenUserCache();
try {
return CacheUtil.get(USER_CACHE, USER_CACHE_ACCOUNT, accountKey, () -> loadUserByAccount(tenantId, account));
} catch (RuntimeException retryException) {
log.warn("用户缓存重试仍失败,改为直连查询, accountKey={}", accountKey, retryException);
return loadUserByAccount(tenantId, account);
}
}
}
private static User loadUserById(Long userId) {
R<User> result = getUserClient().userInfoById(userId);
return result.getData();
}
private static User loadUserByAccount(String tenantId, String account) {
R<User> result = getUserClient().userByAccount(tenantId, account);
return result.getData();
}
private static void clearBrokenUserCache() {
CacheUtil.clear(USER_CACHE);
CacheUtil.clear(USER_CACHE, Boolean.FALSE);
}
}
@@ -0,0 +1,74 @@
/**
* 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>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.system.cache;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.StringPool;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.system.pojo.entity.MpUser;
import org.springblade.system.pojo.entity.User;
/**
* 用户展示名解析(系统用户优先,未命中再查小程序用户)
* <p>
* 小程序链路写入的审计人/提交人 ID 落在 blade_mp_user,需用本工具翻译姓名。
*
* @author Chill
*/
public final class UserDisplayCache {
private UserDisplayCache() {
}
/**
* 翻译用户姓名:先 blade_user,再 blade_mp_user,都没有则回退 ID
*/
public static String getUserRealName(Long userId) {
if (userId == null) {
return StringPool.EMPTY;
}
User user = UserCache.getUser(userId);
if (user != null && Func.isNotEmpty(user.getId())) {
return resolveName(user.getRealName(), user.getName(), user.getAccount(), userId);
}
MpUser mpUser = MpUserCache.getUser(userId);
if (mpUser != null && Func.isNotEmpty(mpUser.getId())) {
return resolveName(mpUser.getRealName(), mpUser.getName(), mpUser.getAccount(), userId);
}
return Func.toStr(userId);
}
private static String resolveName(String realName, String name, String account, Long userId) {
if (StringUtil.isNotBlank(realName)) {
return realName;
}
if (StringUtil.isNotBlank(name)) {
return name;
}
if (StringUtil.isNotBlank(account)) {
return account;
}
return Func.toStr(userId);
}
}
@@ -0,0 +1,121 @@
/**
* 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>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.system.feign;
import org.springblade.core.launch.constant.AppConstant;
import org.springblade.core.tool.api.R;
import org.springblade.system.pojo.entity.MpUser;
import org.springblade.system.pojo.entity.MpUserInfo;
import org.springframework.cloud.openfeign.FeignClient;
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.RequestParam;
/**
* 小程序用户 Feign 接口
*
* @author Chill
*/
@FeignClient(
value = AppConstant.APPLICATION_SYSTEM_NAME
)
public interface IMpUserClient {
String API_PREFIX = "/feign/client/mp-user";
String USER_BY_ID = API_PREFIX + "/user-by-id";
String USER_BY_PHONE = API_PREFIX + "/user-by-phone";
String USER_BY_ACCOUNT = API_PREFIX + "/user-by-account";
String USER_INFO_BY_ID = API_PREFIX + "/info-by-id";
String USER_INFO_BY_PHONE = API_PREFIX + "/info-by-phone";
String USER_INFO_BY_ACCOUNT = API_PREFIX + "/info-by-account";
String SAVE_USER = API_PREFIX + "/save-user";
String UPDATE_USER = API_PREFIX + "/update-user";
String BIND_OPENID = API_PREFIX + "/bind-openid";
String SET_PASSWORD = API_PREFIX + "/set-password";
/**
* 按主键查询
*/
@GetMapping(USER_BY_ID)
R<MpUser> userById(@RequestParam("userId") Long userId);
/**
* 按租户 + 手机号查询
*/
@GetMapping(USER_BY_PHONE)
R<MpUser> userByPhone(@RequestParam("tenantId") String tenantId, @RequestParam("phone") String phone);
/**
* 按租户 + 账号查询
*/
@GetMapping(USER_BY_ACCOUNT)
R<MpUser> userByAccount(@RequestParam("tenantId") String tenantId, @RequestParam("account") String account);
/**
* 按主键查询登录信息
*/
@GetMapping(USER_INFO_BY_ID)
R<MpUserInfo> userInfoById(@RequestParam("userId") Long userId);
/**
* 按租户 + 手机号查询登录信息
*/
@GetMapping(USER_INFO_BY_PHONE)
R<MpUserInfo> userInfoByPhone(@RequestParam("tenantId") String tenantId, @RequestParam("phone") String phone);
/**
* 按租户 + 账号查询登录信息
*/
@GetMapping(USER_INFO_BY_ACCOUNT)
R<MpUserInfo> userInfoByAccount(@RequestParam("tenantId") String tenantId, @RequestParam("account") String account);
/**
* 新增小程序用户
*/
@PostMapping(SAVE_USER)
R<Boolean> saveUser(@RequestBody MpUser user);
/**
* 更新小程序用户
*/
@PostMapping(UPDATE_USER)
R<Boolean> updateUser(@RequestBody MpUser user);
/**
* 绑定/更新微信小程序 openid
*/
@PostMapping(BIND_OPENID)
R<Boolean> bindOpenId(@RequestParam("tenantId") String tenantId,
@RequestParam("userId") Long userId,
@RequestParam("openid") String openid);
/**
* 设置小程序用户密码
*/
@PostMapping(SET_PASSWORD)
R<Boolean> setPassword(@RequestParam("userId") Long userId,
@RequestParam("password") String password,
@RequestParam("password2") String password2);
}
@@ -0,0 +1,131 @@
/**
* 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.system.pojo.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springblade.core.tenant.mp.TenantEntity;
import org.springblade.core.tool.jackson.Sensitive;
import org.springblade.core.tool.sensitive.SensitiveType;
import java.io.Serial;
import java.util.Date;
/**
* 小程序用户实体(仅用于小程序登录,与 blade_user 分离)
*
* @author Chill
*/
@Data
@TableName("blade_mp_user")
@EqualsAndHashCode(callSuper = true)
@Schema(description = "小程序用户")
public class MpUser extends TenantEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 用户编号
*/
@Schema(description = "用户编号")
private String code;
/**
* 账号
*/
@Schema(description = "账号")
private String account;
/**
* 密码
*/
@Schema(description = "密码")
private String password;
/**
* 昵称
*/
@Schema(description = "昵称")
private String name;
/**
* 真名
*/
@Schema(description = "真名")
private String realName;
/**
* 头像
*/
@Schema(description = "头像")
private String avatar;
/**
* 邮箱
*/
@Sensitive(type = SensitiveType.EMAIL)
@Schema(description = "邮箱")
private String email;
/**
* 手机
*/
@Sensitive(type = SensitiveType.MOBILE)
@Schema(description = "手机")
private String phone;
/**
* 生日
*/
@Schema(description = "生日")
private Date birthday;
/**
* 性别
*/
@Schema(description = "性别")
private Integer sex;
/**
* 微信小程序 openid
*/
@Schema(description = "微信小程序openid")
private String openid;
/**
* 小程序身份,逗号分隔:driver,admin
*/
@Schema(description = "小程序身份")
private String identities;
/**
* 部门id
*/
@Schema(description = "部门id")
private String deptId;
/**
* 岗位id
*/
@Schema(description = "岗位id")
private String postId;
/**
* 备注
*/
@Schema(description = "备注")
private String remark;
}
@@ -0,0 +1,69 @@
/**
* 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>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.system.pojo.entity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springblade.core.tool.support.Kv;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
* 小程序用户登录信息
*
* @author Chill
*/
@Data
@Schema(description = "小程序用户信息")
public class MpUserInfo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 用户基础信息
*/
@Schema(description = "小程序用户")
private MpUser user;
/**
* 身份编码集合
*/
@Schema(description = "身份集合")
private List<String> identities;
/**
* 权限标识集合
*/
@Schema(description = "权限集合")
private List<String> permissions;
/**
* 拓展信息
*/
@Schema(description = "拓展信息")
private Kv detail;
}
@@ -25,6 +25,7 @@
*/
package org.springblade.system.pojo.entity;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@@ -131,6 +132,11 @@ public class User extends TenantEntity implements IPhone {
* 是否是oa的账号
*/
private Integer isOa;
/**
* OA上次更新时间(OA 的 modified 字段),仅同步比对使用
*/
@TableField(updateStrategy = FieldStrategy.NOT_NULL)
private String oaModified;
/**
* 是否推送mk
*/
@@ -0,0 +1,148 @@
/**
* 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>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.system.pojo.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.StringUtil;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 小程序用户身份枚举(可叠加,后续可扩展)
*
* @author Chill
*/
@Getter
@AllArgsConstructor
public enum MpUserIdentity {
/**
* 司机
*/
DRIVER("driver", "司机"),
/**
* 管理员(调度端)
*/
ADMIN("admin", "管理员"),
;
/**
* 身份编码
*/
private final String code;
/**
* 身份名称
*/
private final String label;
/**
* 管理员身份对应的小程序权限码(兼容原 mp_auth)
*/
public static final String PERMISSION_MP_AUTH = "mp_auth";
/**
* detail 中标记小程序用户的 type 值
*/
public static final String DETAIL_TYPE = "mp";
/**
* 解析身份编码
*/
public static MpUserIdentity of(String code) {
if (Func.isBlank(code)) {
return null;
}
String normalized = code.trim().toLowerCase(Locale.ROOT);
return Arrays.stream(values())
.filter(item -> item.code.equals(normalized))
.findFirst()
.orElse(null);
}
/**
* 拆分身份串为编码列表(去重、保序)
*/
public static List<String> split(String identities) {
if (Func.isBlank(identities)) {
return Collections.emptyList();
}
Set<String> identitySet = new LinkedHashSet<>();
for (String part : Func.toStrList(identities)) {
if (Func.isBlank(part)) {
continue;
}
String code = part.trim().toLowerCase(Locale.ROOT);
if (of(code) != null || StringUtil.isNotBlank(code)) {
identitySet.add(code);
}
}
return new ArrayList<>(identitySet);
}
/**
* 合并身份,确保包含指定身份
*/
public static String merge(String identities, String... appendCodes) {
Set<String> identitySet = new LinkedHashSet<>(split(identities));
if (appendCodes != null) {
for (String code : appendCodes) {
if (Func.isNotBlank(code)) {
identitySet.add(code.trim().toLowerCase(Locale.ROOT));
}
}
}
return identitySet.stream().collect(Collectors.joining(","));
}
/**
* 是否包含指定身份
*/
public static boolean has(String identities, MpUserIdentity identity) {
if (identity == null) {
return false;
}
return split(identities).contains(identity.getCode());
}
/**
* 根据身份生成小程序权限码(不再依赖系统角色菜单)
*/
public static List<String> toPermissions(String identities) {
List<String> permissions = new ArrayList<>();
if (has(identities, ADMIN)) {
permissions.add(PERMISSION_MP_AUTH);
}
return permissions;
}
}
@@ -0,0 +1,73 @@
/**
* 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>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.system.pojo.vo;
import com.baomidou.mybatisplus.annotation.TableField;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springblade.system.pojo.entity.MpUser;
import java.io.Serial;
/**
* 小程序用户视图实体类
*
* @author Chill
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "小程序用户")
public class MpUserVO extends MpUser {
@Serial
private static final long serialVersionUID = 1L;
/**
* 身份名称(逗号分隔中文)
*/
@TableField(exist = false)
@Schema(description = "身份名称")
private String identitiesName;
/**
* 性别名称
*/
@TableField(exist = false)
@Schema(description = "性别名称")
private String sexName;
/**
* 创建人姓名
*/
@TableField(exist = false)
@Schema(description = "创建人姓名")
private String createUserName;
/**
* 更新人姓名
*/
@TableField(exist = false)
@Schema(description = "更新人姓名")
private String updateUserName;
}
@@ -0,0 +1,75 @@
/**
* 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>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.system.utils;
import org.springblade.core.secure.BladeUser;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.tool.support.Kv;
import org.springblade.core.tool.utils.Func;
import org.springblade.system.pojo.enums.MpUserIdentity;
/**
* 小程序登录态识别工具
*
* @author Chill
*/
public final class MpUserAuth {
/**
* 严格令牌模式下的占位 ID。
* <p>
* {@code blade.secure.strict-token=true} 时,AuthUtil.userIncomplete 要求 roleId/deptId
* 非空且数值 &gt;= 0;小程序用户不绑定系统角色/部门,用 0 占位避免业务接口 401。
*/
public static final String PLACEHOLDER_ID = "0";
private MpUserAuth() {
}
/**
* 当前登录用户是否为小程序用户
*/
public static boolean isMpUser() {
return isMpUser(AuthUtil.getUser());
}
/**
* 判断指定登录用户是否为小程序用户
*/
public static boolean isMpUser(BladeUser bladeUser) {
if (bladeUser == null) {
return false;
}
Kv detail = bladeUser.getDetail();
if (detail != null && MpUserIdentity.DETAIL_TYPE.equalsIgnoreCase(Func.toStr(detail.get("type")))) {
return true;
}
// 兼容:无真实系统角色(空或占位 0)且 roleName 为小程序身份
String roleId = Func.toStr(bladeUser.getRoleId());
boolean noSystemRole = Func.isBlank(roleId) || PLACEHOLDER_ID.equals(roleId);
return noSystemRole
&& Func.isNotBlank(bladeUser.getRoleName())
&& !MpUserIdentity.split(bladeUser.getRoleName()).isEmpty();
}
}