✨ 小程序用户独立为 blade_mp_user,并兼容司机/车辆 OCR 宽松日期

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-12 13:02:08 +08:00
parent 42ca5ee7bc
commit e19c3b100a
34 changed files with 1892 additions and 170 deletions
@@ -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");
}
}
@@ -29,12 +29,14 @@ 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;
@@ -67,6 +69,7 @@ public class Driver extends TenantEntity {
* 出生年月
*/
@Schema(description = "出生年月")
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
private LocalDate birthday;
/**
* 性别
@@ -118,11 +121,13 @@ public class Driver extends TenantEntity {
* 身份证有效期起
*/
@Schema(description = "身份证有效期起")
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
private LocalDate idCardValidStart;
/**
* 身份证有效期止
*/
@Schema(description = "身份证有效期止")
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
private LocalDate idCardValidEnd;
/**
* 身份证长期有效
@@ -148,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;
/**
* 驾驶证长期有效
@@ -183,6 +190,7 @@ public class Driver extends TenantEntity {
* 从业资格证有效期止
*/
@Schema(description = "从业资格证有效期止")
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
private LocalDate qualificationEndDate;
/**
* 从业资格证长期有效
@@ -210,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;
/**
* 与联系人关系
@@ -27,12 +27,14 @@ 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;
@@ -125,6 +127,7 @@ public class TransportVehicle extends TenantEntity {
* 强制报废日期
*/
@Schema(description = "强制报废日期")
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
private LocalDate compulsoryScrapDate;
/**
* 强制报废长期有效
@@ -145,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;
/**
* 行驶证长期有效
@@ -185,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;
/**
* 道路运输证长期有效
@@ -205,6 +212,7 @@ public class TransportVehicle extends TenantEntity {
* 道路运输年审有效期
*/
@Schema(description = "道路运输年审有效期")
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
private LocalDate annualReviewEndDate;
/**
* 道路运输年审长期有效
@@ -220,6 +228,7 @@ public class TransportVehicle extends TenantEntity {
* 机动车登记日期
*/
@Schema(description = "机动车登记日期")
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
private LocalDate registrationDate;
/**
* 机动车登记本图片
@@ -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();
}
}
@@ -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;
}
@@ -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,65 @@
/**
* 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 {
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;
}
// 兼容:无系统角色且 roleName 为小程序身份
return Func.isBlank(bladeUser.getRoleId())
&& Func.isNotBlank(bladeUser.getRoleName())
&& !MpUserIdentity.split(bladeUser.getRoleName()).isEmpty();
}
}