✨ 小程序用户独立为 blade_mp_user,并兼容司机/车辆 OCR 宽松日期
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -51,6 +51,7 @@ import org.springblade.core.oauth2.service.OAuth2UserService;
|
||||
import org.springblade.system.feign.IAuthLockClient;
|
||||
import org.springblade.system.feign.IAuthLogClient;
|
||||
import org.springblade.core.tenant.BladeTenantProperties;
|
||||
import org.springblade.system.feign.IMpUserClient;
|
||||
import org.springblade.system.feign.IUserClient;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
@@ -117,8 +118,8 @@ public class BladeAuthConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public OAuth2UserService oAuth2UserService(IUserClient userClient) {
|
||||
return new BladeUserDetailService(userClient);
|
||||
public OAuth2UserService oAuth2UserService(IUserClient userClient, IMpUserClient mpUserClient) {
|
||||
return new BladeUserDetailService(userClient, mpUserClient);
|
||||
}
|
||||
|
||||
@Primary
|
||||
|
||||
+19
-1
@@ -30,9 +30,11 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.oauth2.endpoint.OAuth2TokenEndPoint;
|
||||
import org.springblade.core.secure.BladeUser;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.core.tool.support.Kv;
|
||||
import org.springblade.core.tool.utils.BeanUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.system.feign.ISysClient;
|
||||
import org.springblade.system.pojo.enums.MpUserIdentity;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
@@ -72,10 +74,26 @@ public class OAuth2UserInfoResponseAdvice implements ResponseBodyAdvice<Object>
|
||||
if (userInfo == null) {
|
||||
return body;
|
||||
}
|
||||
userInfo.setPermission(loadPermission(bladeUser.getRoleId()));
|
||||
if (isMpUser(bladeUser)) {
|
||||
// 小程序用户:按身份直接生成权限,不走系统角色菜单
|
||||
userInfo.setPermission(MpUserIdentity.toPermissions(bladeUser.getRoleName()));
|
||||
} else {
|
||||
userInfo.setPermission(loadPermission(bladeUser.getRoleId()));
|
||||
}
|
||||
return userInfo;
|
||||
}
|
||||
|
||||
private boolean isMpUser(BladeUser bladeUser) {
|
||||
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();
|
||||
}
|
||||
|
||||
private List<String> loadPermission(String roleId) {
|
||||
if (Func.isBlank(roleId)) {
|
||||
return Collections.emptyList();
|
||||
|
||||
@@ -2,10 +2,12 @@ package org.springblade.auth.granter;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.auth.utils.TokenUtil;
|
||||
import org.springblade.core.oauth2.exception.UserInvalidException;
|
||||
import org.springblade.core.oauth2.granter.AbstractTokenGranter;
|
||||
import org.springblade.core.oauth2.handler.PasswordHandler;
|
||||
import org.springblade.core.oauth2.provider.OAuth2Request;
|
||||
import org.springblade.core.oauth2.service.OAuth2Client;
|
||||
import org.springblade.core.oauth2.service.OAuth2ClientService;
|
||||
import org.springblade.core.oauth2.service.OAuth2User;
|
||||
import org.springblade.core.oauth2.service.OAuth2UserService;
|
||||
@@ -13,7 +15,8 @@ import org.springblade.core.tool.api.R;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.core.tool.utils.WebUtil;
|
||||
import org.springblade.system.feign.IUserClient;
|
||||
import org.springblade.system.feign.IMpUserClient;
|
||||
import org.springblade.system.pojo.entity.MpUserInfo;
|
||||
import org.springblade.thirdparty.wechat.constant.WechatMiniConstant;
|
||||
import org.springblade.thirdparty.wechat.exception.WechatMiniException;
|
||||
import org.springblade.thirdparty.wechat.pojo.vo.WechatPhoneVO;
|
||||
@@ -25,7 +28,8 @@ import org.springframework.stereotype.Component;
|
||||
* 微信小程序手机号一键登录。
|
||||
* <p>
|
||||
* grant_type=wechat_applet,参数:loginCode(wx.login)、phoneCode(getPhoneNumber)。
|
||||
* 流程:换 openid + 手机号 → 按手机号查用户(不存在则拒绝)→ 记录 openid → 发令牌(与密码登录一致)。
|
||||
* 流程:换 openid + 手机号 → 按手机号查小程序用户(blade_mp_user,不存在则拒绝)
|
||||
* → 记录 openid → 按身份发令牌(不依赖系统角色)。
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@@ -33,17 +37,17 @@ public class WechatMiniTokenGranter extends AbstractTokenGranter {
|
||||
|
||||
private final OAuth2UserService userService;
|
||||
private final IWechatMiniService wechatMiniService;
|
||||
private final IUserClient userClient;
|
||||
private final IMpUserClient mpUserClient;
|
||||
|
||||
public WechatMiniTokenGranter(OAuth2ClientService clientService,
|
||||
OAuth2UserService userService,
|
||||
PasswordHandler passwordHandler,
|
||||
IWechatMiniService wechatMiniService,
|
||||
IUserClient userClient) {
|
||||
IMpUserClient mpUserClient) {
|
||||
super(clientService, userService, passwordHandler);
|
||||
this.userService = userService;
|
||||
this.wechatMiniService = wechatMiniService;
|
||||
this.userClient = userClient;
|
||||
this.mpUserClient = mpUserClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -54,7 +58,7 @@ public class WechatMiniTokenGranter extends AbstractTokenGranter {
|
||||
@Override
|
||||
public OAuth2User user(OAuth2Request request) {
|
||||
// 先校验客户端是否允许该授权类型,避免先调微信再因客户端配置失败
|
||||
var oauthClient = client(request);
|
||||
OAuth2Client oauthClient = client(request);
|
||||
|
||||
HttpServletRequest httpRequest = WebUtil.getRequest();
|
||||
String loginCode = httpRequest.getParameter("loginCode");
|
||||
@@ -78,16 +82,21 @@ public class WechatMiniTokenGranter extends AbstractTokenGranter {
|
||||
throw new UserInvalidException("未获取到微信手机号");
|
||||
}
|
||||
|
||||
OAuth2User user = userService.loadByPhone(phone, request);
|
||||
if (!userService.validateUser(user)) {
|
||||
R<MpUserInfo> mpUserResult = mpUserClient.userInfoByPhone(request.getTenantId(), phone);
|
||||
if (mpUserResult == null || !mpUserResult.isSuccess() || mpUserResult.getData() == null
|
||||
|| mpUserResult.getData().getUser() == null) {
|
||||
throw new UserInvalidException("用户不存在,无法登录");
|
||||
}
|
||||
|
||||
R<Boolean> bindResult = userClient.bindWxMiniOpenId(
|
||||
OAuth2User user = TokenUtil.convertMpUser(mpUserResult.getData());
|
||||
if (!userService.validateUser(user)) {
|
||||
throw new UserInvalidException("用户身份无效,无法登录");
|
||||
}
|
||||
|
||||
R<Boolean> bindResult = mpUserClient.bindOpenId(
|
||||
request.getTenantId(),
|
||||
Func.toLong(user.getUserId()),
|
||||
session.getOpenid(),
|
||||
phone
|
||||
session.getOpenid()
|
||||
);
|
||||
if (bindResult == null || !bindResult.isSuccess()) {
|
||||
String msg = bindResult != null ? bindResult.getMsg() : "绑定 openid 失败";
|
||||
|
||||
@@ -18,9 +18,6 @@
|
||||
* 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.auth.service;
|
||||
@@ -35,8 +32,11 @@ import org.springblade.core.oauth2.utils.OAuth2ExceptionUtil;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.system.feign.IMpUserClient;
|
||||
import org.springblade.system.feign.IUserClient;
|
||||
import org.springblade.system.pojo.entity.MpUserInfo;
|
||||
import org.springblade.system.pojo.entity.UserInfo;
|
||||
import org.springblade.system.pojo.enums.MpUserIdentity;
|
||||
import org.springblade.system.pojo.enums.UserType;
|
||||
|
||||
import java.util.Optional;
|
||||
@@ -49,6 +49,7 @@ import java.util.Optional;
|
||||
@RequiredArgsConstructor
|
||||
public class BladeUserDetailService implements OAuth2UserService {
|
||||
private final IUserClient userClient;
|
||||
private final IMpUserClient mpUserClient;
|
||||
|
||||
@Override
|
||||
public OAuth2User loadByUserId(String userId, OAuth2Request request) {
|
||||
@@ -57,13 +58,29 @@ public class BladeUserDetailService implements OAuth2UserService {
|
||||
.filter(s -> !StringUtil.isBlank(s))
|
||||
.orElse(UserType.WEB.getName());
|
||||
|
||||
// 获取用户信息
|
||||
R<UserInfo> result = userClient.userInfo(Func.toLong(userId), userType);
|
||||
if (!result.isSuccess()) {
|
||||
OAuth2ExceptionUtil.throwFromCode(OAuth2ErrorCode.INVALID_USER);
|
||||
// 小程序刷新/鉴权:直接查 blade_mp_user
|
||||
if (MpUserIdentity.DETAIL_TYPE.equalsIgnoreCase(userType)) {
|
||||
OAuth2User mpUser = loadMpUserById(userId);
|
||||
if (mpUser == null) {
|
||||
OAuth2ExceptionUtil.throwFromCode(OAuth2ErrorCode.INVALID_USER);
|
||||
}
|
||||
return mpUser;
|
||||
}
|
||||
// 构建oauth2用户信息
|
||||
return TokenUtil.convertUser(result.getData(), request);
|
||||
|
||||
// 系统用户
|
||||
R<UserInfo> result = userClient.userInfo(Func.toLong(userId), userType);
|
||||
if (result != null && result.isSuccess() && result.getData() != null
|
||||
&& result.getData().getUser() != null) {
|
||||
return TokenUtil.convertUser(result.getData(), request);
|
||||
}
|
||||
|
||||
// 兼容:未带 userType=mp 时,回落查询小程序用户(令牌刷新)
|
||||
OAuth2User mpUser = loadMpUserById(userId);
|
||||
if (mpUser != null) {
|
||||
return mpUser;
|
||||
}
|
||||
OAuth2ExceptionUtil.throwFromCode(OAuth2ErrorCode.INVALID_USER);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -74,6 +91,15 @@ public class BladeUserDetailService implements OAuth2UserService {
|
||||
.orElse(UserType.WEB.getName());
|
||||
String tenantId = request.getTenantId();
|
||||
|
||||
// 小程序用户:按账号查 blade_mp_user
|
||||
if (MpUserIdentity.DETAIL_TYPE.equalsIgnoreCase(userType)) {
|
||||
R<MpUserInfo> mpResult = mpUserClient.userInfoByAccount(tenantId, username);
|
||||
if (mpResult == null || !mpResult.isSuccess() || mpResult.getData() == null) {
|
||||
return null;
|
||||
}
|
||||
return TokenUtil.convertMpUser(mpResult.getData());
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
R<UserInfo> result = userClient.userInfo(tenantId, username, userType);
|
||||
if (!result.isSuccess()) {
|
||||
@@ -91,6 +117,11 @@ public class BladeUserDetailService implements OAuth2UserService {
|
||||
.orElse(UserType.WEB.getName());
|
||||
String tenantId = request.getTenantId();
|
||||
|
||||
// 小程序用户类型:仅查 blade_mp_user
|
||||
if (MpUserIdentity.DETAIL_TYPE.equalsIgnoreCase(userType)) {
|
||||
return loadMpUserByPhone(tenantId, phone);
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
R<UserInfo> result = userClient.userInfoByPhone(tenantId, phone, userType);
|
||||
if (!result.isSuccess()) {
|
||||
@@ -108,4 +139,28 @@ public class BladeUserDetailService implements OAuth2UserService {
|
||||
.isPresent(); // 如果上述条件都满足,则返回true,否则返回false
|
||||
}
|
||||
|
||||
private OAuth2User loadMpUserById(String userId) {
|
||||
if (Func.isBlank(userId)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
R<MpUserInfo> result = mpUserClient.userInfoById(Func.toLong(userId));
|
||||
if (result != null && result.isSuccess() && result.getData() != null
|
||||
&& result.getData().getUser() != null) {
|
||||
return TokenUtil.convertMpUser(result.getData());
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
// 非小程序用户场景忽略,继续走系统用户
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private OAuth2User loadMpUserByPhone(String tenantId, String phone) {
|
||||
R<MpUserInfo> result = mpUserClient.userInfoByPhone(tenantId, phone);
|
||||
if (result == null || !result.isSuccess() || result.getData() == null) {
|
||||
return null;
|
||||
}
|
||||
return TokenUtil.convertMpUser(result.getData());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -28,12 +28,14 @@ package org.springblade.auth.utils;
|
||||
import org.springblade.core.oauth2.provider.OAuth2Request;
|
||||
import org.springblade.core.oauth2.service.OAuth2User;
|
||||
import org.springblade.core.oauth2.service.impl.OAuth2UserDetail;
|
||||
import org.springblade.core.tool.support.Kv;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.system.cache.SysCache;
|
||||
import org.springblade.system.pojo.entity.MpUser;
|
||||
import org.springblade.system.pojo.entity.MpUserInfo;
|
||||
import org.springblade.system.pojo.entity.User;
|
||||
import org.springblade.system.pojo.entity.UserInfo;
|
||||
|
||||
import java.util.Collections;
|
||||
import org.springblade.system.pojo.enums.MpUserIdentity;
|
||||
|
||||
/**
|
||||
* 认证工具类
|
||||
@@ -75,6 +77,7 @@ public class TokenUtil {
|
||||
userDetail.setRealName(user.getRealName());
|
||||
userDetail.setAccount(user.getAccount());
|
||||
userDetail.setPassword(user.getPassword());
|
||||
userDetail.setPhone(user.getPhone());
|
||||
userDetail.setDeptId(user.getDeptId());
|
||||
userDetail.setPostId(user.getPostId());
|
||||
userDetail.setRoleId(user.getRoleId());
|
||||
@@ -85,4 +88,37 @@ public class TokenUtil {
|
||||
return userDetail;
|
||||
}
|
||||
|
||||
/**
|
||||
* 小程序用户转换为OAuth2标准用户(身份直接写入 authorities,不走系统角色)
|
||||
*
|
||||
* @param userInfo 小程序用户信息
|
||||
* @return OAuth2User
|
||||
*/
|
||||
public static OAuth2User convertMpUser(MpUserInfo userInfo) {
|
||||
if (userInfo == null || userInfo.getUser() == null) {
|
||||
return null;
|
||||
}
|
||||
MpUser user = userInfo.getUser();
|
||||
OAuth2UserDetail userDetail = new OAuth2UserDetail();
|
||||
userDetail.setUserId(String.valueOf(user.getId()));
|
||||
userDetail.setTenantId(user.getTenantId());
|
||||
userDetail.setName(user.getName());
|
||||
userDetail.setRealName(user.getRealName());
|
||||
userDetail.setAccount(user.getAccount());
|
||||
userDetail.setPassword(user.getPassword());
|
||||
userDetail.setPhone(user.getPhone());
|
||||
userDetail.setDeptId(user.getDeptId());
|
||||
userDetail.setPostId(user.getPostId());
|
||||
// 小程序用户不绑定系统角色,roleId 留空;身份放入 roleName/authorities
|
||||
userDetail.setRoleId("");
|
||||
userDetail.setRoleName(Func.join(userInfo.getIdentities()));
|
||||
userDetail.setAvatar(user.getAvatar());
|
||||
userDetail.setAuthorities(userInfo.getIdentities());
|
||||
userDetail.setPermissions(userInfo.getPermissions());
|
||||
userDetail.setDetail(userInfo.getDetail() == null
|
||||
? Kv.create().set("type", MpUserIdentity.DETAIL_TYPE)
|
||||
: userInfo.getDetail());
|
||||
return userDetail;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+148
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
+11
-2
@@ -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;
|
||||
/**
|
||||
* 与联系人关系
|
||||
|
||||
+9
@@ -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;
|
||||
/**
|
||||
* 机动车登记本图片
|
||||
|
||||
Vendored
+114
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
Vendored
+74
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+121
@@ -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);
|
||||
|
||||
}
|
||||
+131
@@ -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;
|
||||
|
||||
}
|
||||
+69
@@ -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;
|
||||
|
||||
}
|
||||
+148
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+65
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
+42
@@ -53,11 +53,14 @@ import org.springblade.core.tool.utils.DateUtil;
|
||||
import org.springblade.core.tool.utils.StringPool;
|
||||
import org.springblade.system.excel.UserExcel;
|
||||
import org.springblade.system.excel.UserImporter;
|
||||
import org.springblade.system.pojo.entity.MpUser;
|
||||
import org.springblade.system.pojo.entity.User;
|
||||
import org.springblade.system.pojo.vo.OaPersonSyncPageVO;
|
||||
import org.springblade.system.pojo.vo.UserVO;
|
||||
import org.springblade.system.service.IOASyncService;
|
||||
import org.springblade.system.service.IMpUserService;
|
||||
import org.springblade.system.service.IUserService;
|
||||
import org.springblade.system.utils.MpUserAuth;
|
||||
import org.springblade.system.wrapper.UserWrapper;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
@@ -79,6 +82,7 @@ import java.util.Map;
|
||||
public class UserController {
|
||||
|
||||
private final IUserService userService;
|
||||
private final IMpUserService mpUserService;
|
||||
private final IOASyncService oaSyncService;
|
||||
|
||||
/**
|
||||
@@ -101,6 +105,10 @@ public class UserController {
|
||||
@Operation(summary = "查看详情", description = "传入id")
|
||||
@GetMapping("/info")
|
||||
public R<UserVO> info(BladeUser user) {
|
||||
if (MpUserAuth.isMpUser(user)) {
|
||||
MpUser mpUser = mpUserService.getById(user.getUserId());
|
||||
return R.data(toMpUserVO(mpUser));
|
||||
}
|
||||
User detail = userService.getById(user.getUserId());
|
||||
return R.data(UserWrapper.build().entityVO(detail));
|
||||
}
|
||||
@@ -252,6 +260,7 @@ public class UserController {
|
||||
* 当前用户设置/重置登录密码(小程序首次设密、短信验证后改密)
|
||||
* <p>
|
||||
* 对外路径:/blade-system/user/password ;网关别名 /blade-user/password 亦可到达。
|
||||
* 小程序用户写入 blade_mp_user,系统用户仍写入 blade_user。
|
||||
*/
|
||||
@PostMapping("/password")
|
||||
@ApiOperationSupport(order = 10)
|
||||
@@ -259,6 +268,9 @@ public class UserController {
|
||||
public R password(BladeUser user,
|
||||
@Parameter(description = "新密码", required = true) @RequestParam String password,
|
||||
@Parameter(description = "确认密码", required = true) @RequestParam String password2) {
|
||||
if (MpUserAuth.isMpUser(user)) {
|
||||
return R.status(mpUserService.setPassword(user.getUserId(), password, password2));
|
||||
}
|
||||
return R.status(userService.setPassword(user.getUserId(), password, password2));
|
||||
}
|
||||
|
||||
@@ -438,4 +450,34 @@ public class UserController {
|
||||
return R.data(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 小程序用户转 UserVO(兼容原 /user/info 响应结构)
|
||||
*/
|
||||
private UserVO toMpUserVO(MpUser mpUser) {
|
||||
if (mpUser == null) {
|
||||
return null;
|
||||
}
|
||||
UserVO userVO = new UserVO();
|
||||
userVO.setId(mpUser.getId());
|
||||
userVO.setTenantId(mpUser.getTenantId());
|
||||
userVO.setCode(mpUser.getCode());
|
||||
userVO.setAccount(mpUser.getAccount());
|
||||
userVO.setName(mpUser.getName());
|
||||
userVO.setRealName(mpUser.getRealName());
|
||||
userVO.setAvatar(mpUser.getAvatar());
|
||||
userVO.setEmail(mpUser.getEmail());
|
||||
userVO.setPhone(mpUser.getPhone());
|
||||
userVO.setBirthday(mpUser.getBirthday());
|
||||
userVO.setSex(mpUser.getSex());
|
||||
userVO.setDeptId(mpUser.getDeptId());
|
||||
userVO.setPostId(mpUser.getPostId());
|
||||
userVO.setRemark(mpUser.getRemark());
|
||||
userVO.setStatus(mpUser.getStatus());
|
||||
userVO.setCreateTime(mpUser.getCreateTime());
|
||||
userVO.setUpdateTime(mpUser.getUpdateTime());
|
||||
// 身份放入 roleName,便于前端兼容展示
|
||||
userVO.setRoleName(mpUser.getIdentities());
|
||||
return userVO;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 用户手机号变更(小程序「修改手机号」)
|
||||
* 小程序用户手机号变更(写入 blade_mp_user)
|
||||
* <p>
|
||||
* 对外路径:/blade-system/user/phone/** ;网关别名 /blade-user/phone/** 亦可到达。
|
||||
*
|
||||
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* 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 io.swagger.v3.oas.annotations.Hidden;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springblade.core.tenant.annotation.NonDS;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.system.pojo.entity.MpUser;
|
||||
import org.springblade.system.pojo.entity.MpUserInfo;
|
||||
import org.springblade.system.service.IMpUserService;
|
||||
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.RestController;
|
||||
|
||||
/**
|
||||
* 小程序用户 Feign 实现类
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@NonDS
|
||||
@Hidden
|
||||
@RestController
|
||||
@AllArgsConstructor
|
||||
public class MpUserClient implements IMpUserClient {
|
||||
|
||||
private final IMpUserService mpUserService;
|
||||
|
||||
@Override
|
||||
@GetMapping(USER_BY_ID)
|
||||
public R<MpUser> userById(Long userId) {
|
||||
return R.data(mpUserService.getById(userId));
|
||||
}
|
||||
|
||||
@Override
|
||||
@GetMapping(USER_BY_PHONE)
|
||||
public R<MpUser> userByPhone(String tenantId, String phone) {
|
||||
return R.data(mpUserService.getByPhone(tenantId, phone));
|
||||
}
|
||||
|
||||
@Override
|
||||
@GetMapping(USER_BY_ACCOUNT)
|
||||
public R<MpUser> userByAccount(String tenantId, String account) {
|
||||
return R.data(mpUserService.getByAccount(tenantId, account));
|
||||
}
|
||||
|
||||
@Override
|
||||
@GetMapping(USER_INFO_BY_ID)
|
||||
public R<MpUserInfo> userInfoById(Long userId) {
|
||||
return R.data(mpUserService.userInfo(userId));
|
||||
}
|
||||
|
||||
@Override
|
||||
@GetMapping(USER_INFO_BY_PHONE)
|
||||
public R<MpUserInfo> userInfoByPhone(String tenantId, String phone) {
|
||||
return R.data(mpUserService.userInfoByPhone(tenantId, phone));
|
||||
}
|
||||
|
||||
@Override
|
||||
@GetMapping(USER_INFO_BY_ACCOUNT)
|
||||
public R<MpUserInfo> userInfoByAccount(String tenantId, String account) {
|
||||
return R.data(mpUserService.userInfoByAccount(tenantId, account));
|
||||
}
|
||||
|
||||
@Override
|
||||
@PostMapping(SAVE_USER)
|
||||
public R<Boolean> saveUser(@RequestBody MpUser user) {
|
||||
return R.data(mpUserService.submit(user));
|
||||
}
|
||||
|
||||
@Override
|
||||
@PostMapping(UPDATE_USER)
|
||||
public R<Boolean> updateUser(@RequestBody MpUser user) {
|
||||
return R.data(mpUserService.updateUser(user));
|
||||
}
|
||||
|
||||
@Override
|
||||
@PostMapping(BIND_OPENID)
|
||||
public R<Boolean> bindOpenId(String tenantId, Long userId, String openid) {
|
||||
return R.data(mpUserService.bindOpenId(tenantId, userId, openid));
|
||||
}
|
||||
|
||||
@Override
|
||||
@PostMapping(SET_PASSWORD)
|
||||
public R<Boolean> setPassword(Long userId, String password, String password2) {
|
||||
return R.data(mpUserService.setPassword(userId, password, password2));
|
||||
}
|
||||
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* 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.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.springblade.system.pojo.entity.MpUser;
|
||||
|
||||
/**
|
||||
* 小程序用户 Mapper
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public interface MpUserMapper extends BaseMapper<MpUser> {
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* 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.service;
|
||||
|
||||
import org.springblade.core.mp.base.BaseService;
|
||||
import org.springblade.system.pojo.entity.MpUser;
|
||||
import org.springblade.system.pojo.entity.MpUserInfo;
|
||||
|
||||
/**
|
||||
* 小程序用户 服务类
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public interface IMpUserService extends BaseService<MpUser> {
|
||||
|
||||
/**
|
||||
* 按租户 + 手机号查询
|
||||
*/
|
||||
MpUser getByPhone(String tenantId, String phone);
|
||||
|
||||
/**
|
||||
* 按租户 + 账号查询
|
||||
*/
|
||||
MpUser getByAccount(String tenantId, String account);
|
||||
|
||||
/**
|
||||
* 按主键组装登录信息
|
||||
*/
|
||||
MpUserInfo userInfo(Long userId);
|
||||
|
||||
/**
|
||||
* 按租户 + 手机号组装登录信息
|
||||
*/
|
||||
MpUserInfo userInfoByPhone(String tenantId, String phone);
|
||||
|
||||
/**
|
||||
* 按租户 + 账号组装登录信息
|
||||
*/
|
||||
MpUserInfo userInfoByAccount(String tenantId, String account);
|
||||
|
||||
/**
|
||||
* 保存小程序用户
|
||||
*/
|
||||
boolean submit(MpUser user);
|
||||
|
||||
/**
|
||||
* 更新小程序用户
|
||||
*/
|
||||
boolean updateUser(MpUser user);
|
||||
|
||||
/**
|
||||
* 绑定/更新 openid
|
||||
*/
|
||||
boolean bindOpenId(String tenantId, Long userId, String openid);
|
||||
|
||||
/**
|
||||
* 当前小程序用户设置密码
|
||||
*/
|
||||
boolean setPassword(Long userId, String password, String password2);
|
||||
|
||||
}
|
||||
+272
@@ -0,0 +1,272 @@
|
||||
/**
|
||||
* 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.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springblade.common.utils.PasswordRuleUtil;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.mp.base.BaseServiceImpl;
|
||||
import org.springblade.core.tool.support.Kv;
|
||||
import org.springblade.core.tool.utils.DateUtil;
|
||||
import org.springblade.core.tool.utils.DigestUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.system.cache.MpUserCache;
|
||||
import org.springblade.system.mapper.MpUserMapper;
|
||||
import org.springblade.system.pojo.entity.MpUser;
|
||||
import org.springblade.system.pojo.entity.MpUserInfo;
|
||||
import org.springblade.system.pojo.enums.MpUserIdentity;
|
||||
import org.springblade.system.service.IMpUserService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 小程序用户 服务实现类
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class MpUserServiceImpl extends BaseServiceImpl<MpUserMapper, MpUser> implements IMpUserService {
|
||||
|
||||
@Override
|
||||
public MpUser getByPhone(String tenantId, String phone) {
|
||||
if (Func.isBlank(tenantId) || Func.isBlank(phone)) {
|
||||
return null;
|
||||
}
|
||||
return getOne(Wrappers.<MpUser>lambdaQuery()
|
||||
.eq(MpUser::getTenantId, tenantId)
|
||||
.eq(MpUser::getPhone, phone)
|
||||
.orderByDesc(MpUser::getCreateTime)
|
||||
.last("LIMIT 1"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public MpUser getByAccount(String tenantId, String account) {
|
||||
if (Func.isBlank(tenantId) || Func.isBlank(account)) {
|
||||
return null;
|
||||
}
|
||||
return getOne(Wrappers.<MpUser>lambdaQuery()
|
||||
.eq(MpUser::getTenantId, tenantId)
|
||||
.eq(MpUser::getAccount, account)
|
||||
.orderByDesc(MpUser::getCreateTime)
|
||||
.last("LIMIT 1"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public MpUserInfo userInfo(Long userId) {
|
||||
if (Func.isEmpty(userId)) {
|
||||
return null;
|
||||
}
|
||||
return buildUserInfo(getById(userId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public MpUserInfo userInfoByPhone(String tenantId, String phone) {
|
||||
return buildUserInfo(getByPhone(tenantId, phone));
|
||||
}
|
||||
|
||||
@Override
|
||||
public MpUserInfo userInfoByAccount(String tenantId, String account) {
|
||||
return buildUserInfo(getByAccount(tenantId, account));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean submit(MpUser user) {
|
||||
prepare(user);
|
||||
validate(user);
|
||||
MpUser existed = getByPhone(user.getTenantId(), user.getPhone());
|
||||
if (existed != null && Func.isNotEmpty(existed.getId())) {
|
||||
throw new ServiceException("该手机号已存在小程序用户");
|
||||
}
|
||||
boolean saved = save(user);
|
||||
if (saved) {
|
||||
MpUserCache.clear();
|
||||
}
|
||||
return saved;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean updateUser(MpUser user) {
|
||||
if (user == null || Func.isEmpty(user.getId())) {
|
||||
throw new ServiceException("小程序用户主键不能为空");
|
||||
}
|
||||
prepare(user);
|
||||
validate(user);
|
||||
MpUser existed = getByPhone(user.getTenantId(), user.getPhone());
|
||||
if (existed != null && Func.isNotEmpty(existed.getId()) && !existed.getId().equals(user.getId())) {
|
||||
throw new ServiceException("该手机号已存在小程序用户");
|
||||
}
|
||||
boolean updated = updateById(user);
|
||||
if (updated) {
|
||||
MpUserCache.clear();
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean bindOpenId(String tenantId, Long userId, String openid) {
|
||||
if (Func.isBlank(tenantId) || Func.isEmpty(userId) || Func.isBlank(openid)) {
|
||||
throw new ServiceException("绑定 openid 参数不完整");
|
||||
}
|
||||
MpUser user = getById(userId);
|
||||
if (user == null || Func.isEmpty(user.getId())) {
|
||||
throw new ServiceException("小程序用户不存在");
|
||||
}
|
||||
if (!tenantId.equals(user.getTenantId())) {
|
||||
throw new ServiceException("租户不匹配,无法绑定 openid");
|
||||
}
|
||||
MpUser byOpenId = getOne(Wrappers.<MpUser>lambdaQuery()
|
||||
.eq(MpUser::getTenantId, tenantId)
|
||||
.eq(MpUser::getOpenid, openid)
|
||||
.last("LIMIT 1"));
|
||||
if (byOpenId != null && !byOpenId.getId().equals(userId)) {
|
||||
throw new ServiceException("该微信已绑定其他小程序用户");
|
||||
}
|
||||
MpUser patch = new MpUser();
|
||||
patch.setId(userId);
|
||||
patch.setOpenid(openid);
|
||||
boolean updated = updateById(patch);
|
||||
if (updated) {
|
||||
MpUserCache.clear();
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean setPassword(Long userId, String password, String password2) {
|
||||
if (!Objects.equals(password, password2)) {
|
||||
throw new ServiceException("两次输入密码不一致!");
|
||||
}
|
||||
if (!PasswordRuleUtil.isValid(password)) {
|
||||
throw new ServiceException(PasswordRuleUtil.RULE_MESSAGE);
|
||||
}
|
||||
MpUser user = getById(userId);
|
||||
if (user == null || Func.isEmpty(user.getId())) {
|
||||
throw new ServiceException("小程序用户不存在");
|
||||
}
|
||||
MpUser patch = new MpUser();
|
||||
patch.setId(userId);
|
||||
patch.setPassword(DigestUtil.encrypt(password));
|
||||
patch.setUpdateTime(DateUtil.now());
|
||||
boolean updated = updateById(patch);
|
||||
if (updated) {
|
||||
MpUserCache.clear();
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
private MpUserInfo buildUserInfo(MpUser user) {
|
||||
if (user == null || Func.isEmpty(user.getId())) {
|
||||
return null;
|
||||
}
|
||||
List<String> identities = MpUserIdentity.split(user.getIdentities());
|
||||
if (identities.isEmpty()) {
|
||||
throw new ServiceException("小程序用户未配置身份,无法登录");
|
||||
}
|
||||
MpUserInfo userInfo = new MpUserInfo();
|
||||
userInfo.setUser(user);
|
||||
userInfo.setIdentities(identities);
|
||||
userInfo.setPermissions(MpUserIdentity.toPermissions(user.getIdentities()));
|
||||
userInfo.setDetail(Kv.create()
|
||||
.set("type", MpUserIdentity.DETAIL_TYPE)
|
||||
.set("identities", user.getIdentities())
|
||||
.set("openid", Func.toStr(user.getOpenid(), "")));
|
||||
return userInfo;
|
||||
}
|
||||
|
||||
private void prepare(MpUser user) {
|
||||
if (user == null) {
|
||||
return;
|
||||
}
|
||||
user.setAccount(trimToNull(user.getAccount()));
|
||||
user.setPhone(trimToNull(user.getPhone()));
|
||||
user.setName(trimToNull(user.getName()));
|
||||
user.setRealName(trimToNull(user.getRealName()));
|
||||
user.setAvatar(trimToNull(user.getAvatar()));
|
||||
user.setEmail(trimToNull(user.getEmail()));
|
||||
user.setOpenid(trimToNull(user.getOpenid()));
|
||||
user.setDeptId(trimToNull(user.getDeptId()));
|
||||
user.setPostId(trimToNull(user.getPostId()));
|
||||
user.setRemark(trimToNull(user.getRemark()));
|
||||
user.setIdentities(normalizeIdentities(user.getIdentities()));
|
||||
if (Func.isBlank(user.getAccount()) && Func.isNotBlank(user.getPhone())) {
|
||||
user.setAccount(user.getPhone());
|
||||
}
|
||||
if (Func.isBlank(user.getName()) && Func.isNotBlank(user.getRealName())) {
|
||||
user.setName(user.getRealName());
|
||||
}
|
||||
if (user.getStatus() == null) {
|
||||
user.setStatus(1);
|
||||
}
|
||||
}
|
||||
|
||||
private void validate(MpUser user) {
|
||||
if (user == null) {
|
||||
throw new ServiceException("小程序用户不能为空");
|
||||
}
|
||||
if (Func.isBlank(user.getTenantId())) {
|
||||
throw new ServiceException("租户信息不能为空");
|
||||
}
|
||||
if (Func.isBlank(user.getPhone())) {
|
||||
throw new ServiceException("手机号不能为空");
|
||||
}
|
||||
if (Func.isBlank(user.getIdentities())) {
|
||||
throw new ServiceException("小程序身份不能为空");
|
||||
}
|
||||
List<String> identities = MpUserIdentity.split(user.getIdentities());
|
||||
if (identities.isEmpty()) {
|
||||
throw new ServiceException("小程序身份不能为空");
|
||||
}
|
||||
for (String code : identities) {
|
||||
if (MpUserIdentity.of(code) == null) {
|
||||
throw new ServiceException("不支持的小程序身份:" + code);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String normalizeIdentities(String identities) {
|
||||
List<String> identityList = MpUserIdentity.split(identities);
|
||||
if (identityList.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return StringUtil.join(identityList);
|
||||
}
|
||||
|
||||
private String trimToNull(String value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
String trimValue = value.trim();
|
||||
return trimValue.isEmpty() ? null : trimValue;
|
||||
}
|
||||
|
||||
}
|
||||
+51
-39
@@ -18,9 +18,6 @@
|
||||
* 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.service.impl;
|
||||
@@ -28,7 +25,6 @@ package org.springblade.system.service.impl;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.cache.utils.CacheUtil;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.redis.cache.BladeRedis;
|
||||
import org.springblade.core.secure.utils.AuthUtil;
|
||||
@@ -37,21 +33,21 @@ import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.resource.feign.ISmsClient;
|
||||
import org.springblade.resource.utils.SmsUtil;
|
||||
import org.springblade.system.cache.MpUserCache;
|
||||
import org.springblade.system.pojo.dto.PhoneChangeDTO;
|
||||
import org.springblade.system.pojo.dto.PhoneVerifyDTO;
|
||||
import org.springblade.system.pojo.entity.User;
|
||||
import org.springblade.system.pojo.entity.MpUser;
|
||||
import org.springblade.system.service.IMpUserService;
|
||||
import org.springblade.system.service.IUserPhoneService;
|
||||
import org.springblade.system.service.IUserService;
|
||||
import org.springblade.system.utils.MpUserAuth;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.springblade.core.cache.constant.CacheConstant.USER_CACHE;
|
||||
|
||||
/**
|
||||
* 用户手机号变更服务实现
|
||||
* 小程序用户手机号变更服务实现(写入 blade_mp_user)
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@@ -68,24 +64,22 @@ public class UserPhoneServiceImpl implements IUserPhoneService {
|
||||
/**
|
||||
* 原手机号已校验凭证(Redis)
|
||||
*/
|
||||
private static final String PHONE_CHANGE_VERIFIED_KEY = "blade:user:phone:change:verified:";
|
||||
private static final String PHONE_CHANGE_VERIFIED_KEY = "blade:mp-user:phone:change:verified:";
|
||||
|
||||
private static final Duration PHONE_CHANGE_VERIFIED_TTL = Duration.ofMinutes(15);
|
||||
|
||||
private static final Pattern MOBILE_PATTERN = Pattern.compile("^1[3-9]\\d{9}$");
|
||||
|
||||
private final IUserService userService;
|
||||
private final IMpUserService mpUserService;
|
||||
private final ISmsClient smsClient;
|
||||
private final BladeRedis bladeRedis;
|
||||
|
||||
@Override
|
||||
public R sendCode(String phone) {
|
||||
assertMpLogin();
|
||||
String normalizedPhone = normalizePhone(phone);
|
||||
Long userId = AuthUtil.getUserId();
|
||||
if (Func.isEmpty(userId)) {
|
||||
throw new ServiceException("请先登录");
|
||||
}
|
||||
User currentUser = requireCurrentUser(userId);
|
||||
MpUser currentUser = requireCurrentUser(userId);
|
||||
String tenantId = Func.toStr(currentUser.getTenantId(), AuthUtil.getTenantId());
|
||||
boolean isCurrentPhone = StringUtil.equals(normalizedPhone, Func.toStr(currentUser.getPhone()));
|
||||
if (!isCurrentPhone) {
|
||||
@@ -100,16 +94,14 @@ public class UserPhoneServiceImpl implements IUserPhoneService {
|
||||
|
||||
@Override
|
||||
public boolean verifyOldPhone(PhoneVerifyDTO phoneVerify) {
|
||||
assertMpLogin();
|
||||
Long userId = AuthUtil.getUserId();
|
||||
if (Func.isEmpty(userId)) {
|
||||
throw new ServiceException("请先登录");
|
||||
}
|
||||
String id = Func.toStr(phoneVerify.getId()).trim();
|
||||
String code = Func.toStr(phoneVerify.getCode()).trim();
|
||||
if (StringUtil.isBlank(id) || StringUtil.isBlank(code)) {
|
||||
throw new ServiceException("请先获取并填写验证码");
|
||||
}
|
||||
User currentUser = requireCurrentUser(userId);
|
||||
MpUser currentUser = requireCurrentUser(userId);
|
||||
String oldPhone = Func.toStr(currentUser.getPhone()).trim();
|
||||
if (StringUtil.isBlank(oldPhone)) {
|
||||
throw new ServiceException("当前账号未绑定手机号");
|
||||
@@ -122,10 +114,8 @@ public class UserPhoneServiceImpl implements IUserPhoneService {
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean changePhone(PhoneChangeDTO phoneChange) {
|
||||
assertMpLogin();
|
||||
Long userId = AuthUtil.getUserId();
|
||||
if (Func.isEmpty(userId)) {
|
||||
throw new ServiceException("请先登录");
|
||||
}
|
||||
String verified = Func.toStr(bladeRedis.get(PHONE_CHANGE_VERIFIED_KEY + userId));
|
||||
if (!StringUtil.equals(verified, "1")) {
|
||||
throw new ServiceException("请先完成原手机号验证");
|
||||
@@ -136,7 +126,7 @@ public class UserPhoneServiceImpl implements IUserPhoneService {
|
||||
if (StringUtil.isBlank(id) || StringUtil.isBlank(code)) {
|
||||
throw new ServiceException("请先获取并填写验证码");
|
||||
}
|
||||
User currentUser = requireCurrentUser(userId);
|
||||
MpUser currentUser = requireCurrentUser(userId);
|
||||
String oldPhone = Func.toStr(currentUser.getPhone()).trim();
|
||||
if (StringUtil.equals(newPhone, oldPhone)) {
|
||||
throw new ServiceException("新手机号不可与当前手机号相同");
|
||||
@@ -145,27 +135,49 @@ public class UserPhoneServiceImpl implements IUserPhoneService {
|
||||
assertPhoneAvailable(tenantId, newPhone, userId);
|
||||
validateSms(tenantId, id, code, newPhone);
|
||||
|
||||
User updateUser = new User();
|
||||
MpUser updateUser = new MpUser();
|
||||
updateUser.setId(userId);
|
||||
updateUser.setTenantId(tenantId);
|
||||
updateUser.setPhone(newPhone);
|
||||
updateUser.setName(currentUser.getName());
|
||||
updateUser.setRealName(currentUser.getRealName());
|
||||
updateUser.setIdentities(currentUser.getIdentities());
|
||||
updateUser.setSex(currentUser.getSex());
|
||||
updateUser.setDeptId(currentUser.getDeptId());
|
||||
updateUser.setPostId(currentUser.getPostId());
|
||||
updateUser.setAvatar(currentUser.getAvatar());
|
||||
updateUser.setEmail(currentUser.getEmail());
|
||||
updateUser.setRemark(currentUser.getRemark());
|
||||
updateUser.setStatus(currentUser.getStatus());
|
||||
// 账号若等于原手机号,同步更新,保证短信登录可用
|
||||
if (StringUtil.isNotBlank(oldPhone) && StringUtil.equals(oldPhone, Func.toStr(currentUser.getAccount()))) {
|
||||
assertAccountAvailable(tenantId, newPhone, userId);
|
||||
updateUser.setAccount(newPhone);
|
||||
} else {
|
||||
updateUser.setAccount(currentUser.getAccount());
|
||||
}
|
||||
boolean updated = userService.updateById(updateUser);
|
||||
boolean updated = mpUserService.updateUser(updateUser);
|
||||
if (!updated) {
|
||||
throw new ServiceException("手机号修改失败");
|
||||
}
|
||||
bladeRedis.del(PHONE_CHANGE_VERIFIED_KEY + userId);
|
||||
CacheUtil.clear(USER_CACHE);
|
||||
MpUserCache.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
private User requireCurrentUser(Long userId) {
|
||||
User user = userService.getById(userId);
|
||||
private void assertMpLogin() {
|
||||
if (Func.isEmpty(AuthUtil.getUserId())) {
|
||||
throw new ServiceException("请先登录");
|
||||
}
|
||||
if (!MpUserAuth.isMpUser()) {
|
||||
throw new ServiceException("仅支持小程序用户修改手机号");
|
||||
}
|
||||
}
|
||||
|
||||
private MpUser requireCurrentUser(Long userId) {
|
||||
MpUser user = mpUserService.getById(userId);
|
||||
if (user == null) {
|
||||
throw new ServiceException("用户不存在");
|
||||
throw new ServiceException("小程序用户不存在");
|
||||
}
|
||||
return user;
|
||||
}
|
||||
@@ -178,11 +190,11 @@ public class UserPhoneServiceImpl implements IUserPhoneService {
|
||||
}
|
||||
|
||||
private void assertPhoneAvailable(String tenantId, String phone, Long excludeUserId) {
|
||||
Long phoneCount = userService.count(
|
||||
Wrappers.<User>lambdaQuery()
|
||||
.eq(User::getTenantId, tenantId)
|
||||
.eq(User::getPhone, phone)
|
||||
.ne(User::getId, excludeUserId)
|
||||
Long phoneCount = mpUserService.count(
|
||||
Wrappers.<MpUser>lambdaQuery()
|
||||
.eq(MpUser::getTenantId, tenantId)
|
||||
.eq(MpUser::getPhone, phone)
|
||||
.ne(MpUser::getId, excludeUserId)
|
||||
);
|
||||
if (phoneCount != null && phoneCount > 0L) {
|
||||
throw new ServiceException(StringUtil.format("当前手机 [{}] 已存在!", phone));
|
||||
@@ -190,11 +202,11 @@ public class UserPhoneServiceImpl implements IUserPhoneService {
|
||||
}
|
||||
|
||||
private void assertAccountAvailable(String tenantId, String account, Long excludeUserId) {
|
||||
Long accountCount = userService.count(
|
||||
Wrappers.<User>lambdaQuery()
|
||||
.eq(User::getTenantId, tenantId)
|
||||
.eq(User::getAccount, account)
|
||||
.ne(User::getId, excludeUserId)
|
||||
Long accountCount = mpUserService.count(
|
||||
Wrappers.<MpUser>lambdaQuery()
|
||||
.eq(MpUser::getTenantId, tenantId)
|
||||
.eq(MpUser::getAccount, account)
|
||||
.ne(MpUser::getId, excludeUserId)
|
||||
);
|
||||
if (accountCount != null && accountCount > 0L) {
|
||||
throw new ServiceException(StringUtil.format("当前用户 [{}] 已存在!", account));
|
||||
|
||||
+3
-4
@@ -33,6 +33,7 @@ import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.secure.utils.AuthUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.system.cache.UserCache;
|
||||
import org.springblade.system.cache.UserDisplayCache;
|
||||
import org.springblade.transport.mapper.TransportCertRecordMapper;
|
||||
import org.springblade.transport.pojo.dto.PersonCertSubmitRequest;
|
||||
import org.springblade.transport.pojo.dto.VehicleCertSubmitRequest;
|
||||
@@ -124,10 +125,8 @@ public class CertAuditServiceImpl implements ICertAuditService {
|
||||
detail.setStatusText(CertificationRuleSupport.statusText(record.getStatus()));
|
||||
detail.setSubmitTime(record.getSubmitTime());
|
||||
detail.setSubmitUser(record.getSubmitUser());
|
||||
// 面向前端的展示字段必须由服务端填好,否则审核页「提交人」一栏恒为空白
|
||||
detail.setSubmitUserName(UserCache.getUserRealName(record.getSubmitUser()));
|
||||
// 审核留痕同理:审核人姓名由服务端查好,前端不做用户ID → 姓名的二次请求。
|
||||
// 记录还在「审核中」时审核人与审核时间都为空,由前端显示占位符
|
||||
// 提交人来自小程序用户;审核人来自后台系统用户
|
||||
detail.setSubmitUserName(UserDisplayCache.getUserRealName(record.getSubmitUser()));
|
||||
detail.setAuditUser(record.getAuditUser());
|
||||
detail.setAuditTime(record.getAuditTime());
|
||||
detail.setAuditUserName(UserCache.getUserRealName(record.getAuditUser()));
|
||||
|
||||
+5
-6
@@ -29,8 +29,8 @@ import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.secure.utils.AuthUtil;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.system.feign.IUserClient;
|
||||
import org.springblade.system.pojo.entity.User;
|
||||
import org.springblade.system.feign.IMpUserClient;
|
||||
import org.springblade.system.pojo.entity.MpUser;
|
||||
import org.springblade.transport.pojo.entity.Driver;
|
||||
import org.springblade.transport.pojo.entity.TransportVehicle;
|
||||
import org.springblade.transport.pojo.vo.DriverVehicleCardVO;
|
||||
@@ -57,7 +57,7 @@ public class DriverAppServiceImpl implements IDriverAppService {
|
||||
|
||||
private final IDriverService driverService;
|
||||
private final ITransportVehicleService transportVehicleService;
|
||||
private final IUserClient userClient;
|
||||
private final IMpUserClient mpUserClient;
|
||||
|
||||
@Override
|
||||
public DriverVO currentByPhone(String mobile) {
|
||||
@@ -205,10 +205,9 @@ public class DriverAppServiceImpl implements IDriverAppService {
|
||||
if (isMobileLike(account)) {
|
||||
return account.trim();
|
||||
}
|
||||
// 用户中心的 phone 带 @Sensitive(MOBILE),经 Feign 回来是「175****12082」这类脱敏串,
|
||||
// 拿它去 eq(mobile) 永远查不到、拿它比对本人的真实号码又必然不等。
|
||||
// 小程序用户 phone 带 @Sensitive(MOBILE),经 Feign 回来是脱敏串,
|
||||
// 因此这里只取未脱敏的 account;脱敏的 phone 一律不用。
|
||||
R<User> result = userClient.userInfoById(userId);
|
||||
R<MpUser> result = mpUserClient.userById(userId);
|
||||
if (result != null && R.isSuccess(result) && result.getData() != null) {
|
||||
String userAccount = result.getData().getAccount();
|
||||
if (isMobileLike(userAccount)) {
|
||||
|
||||
+1
-1
@@ -327,7 +327,7 @@ public class DriverCertServiceImpl implements IDriverCertService {
|
||||
* 状态必须显式设置为待审:框架在插入时会把空的 {@code status} 静默填成 1,
|
||||
* 而 1 在本表是「已通过」,那会让一次提交凭空变成审核通过。
|
||||
*
|
||||
* @param submitUser 提交人**用户ID**(blade_user.id)——审核页要靠它翻译成姓名。
|
||||
* @param submitUser 提交人**用户ID**(blade_mp_user.id)——审核页要靠它翻译成姓名。
|
||||
* 不要传司机档案ID:两者都是雪花ID、类型也相同,传错不会报任何错,
|
||||
* 只会让「提交人」一栏显示成一串数字。
|
||||
*/
|
||||
|
||||
+48
-61
@@ -36,12 +36,11 @@ import org.springblade.core.tool.api.R;
|
||||
import org.springblade.core.tool.utils.BeanUtil;
|
||||
import org.springblade.system.cache.UserCache;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.system.feign.IMpUserClient;
|
||||
import org.springblade.system.feign.ISysClient;
|
||||
import org.springblade.system.feign.IUserClient;
|
||||
import org.springblade.system.pojo.entity.User;
|
||||
import org.springblade.system.pojo.entity.UserInfo;
|
||||
import org.springblade.system.pojo.entity.MpUser;
|
||||
import org.springblade.system.pojo.enums.MpUserIdentity;
|
||||
import org.springblade.system.pojo.enums.UserSex;
|
||||
import org.springblade.system.pojo.enums.UserType;
|
||||
import org.springblade.transport.excel.DriverExcel;
|
||||
import org.springblade.transport.mapper.DriverMapper;
|
||||
import org.springblade.transport.pojo.entity.Driver;
|
||||
@@ -76,7 +75,7 @@ public class DriverServiceImpl extends BaseServiceImpl<DriverMapper, Driver> imp
|
||||
private static final int DEFAULT_ENABLED_STATUS = 1;
|
||||
private static final int DEFAULT_FALSE = 0;
|
||||
|
||||
private final IUserClient userClient;
|
||||
private final IMpUserClient mpUserClient;
|
||||
private final ISysClient sysClient;
|
||||
|
||||
@Override
|
||||
@@ -310,13 +309,13 @@ public class DriverServiceImpl extends BaseServiceImpl<DriverMapper, Driver> imp
|
||||
tenantId = driver.getTenantId();
|
||||
}
|
||||
if (Func.isEmpty(tenantId)) {
|
||||
throw new ServiceException("租户信息为空,无法同步系统用户");
|
||||
throw new ServiceException("租户信息为空,无法同步小程序用户");
|
||||
}
|
||||
String mobile = driver.getMobile();
|
||||
if (Func.isEmpty(mobile) || mobile.length() < 6) {
|
||||
throw new ServiceException("手机号长度不足,无法同步系统用户");
|
||||
throw new ServiceException("手机号长度不足,无法同步小程序用户");
|
||||
}
|
||||
// 编辑时前端可能不传 userId,从库中补齐已关联用户
|
||||
// 编辑时前端可能不传 userId,从库中补齐已关联小程序用户
|
||||
if (Func.isEmpty(driver.getUserId()) && Func.isNotEmpty(driver.getId())) {
|
||||
Driver dbDriver = getById(driver.getId());
|
||||
if (dbDriver != null && Func.isNotEmpty(dbDriver.getUserId())) {
|
||||
@@ -324,95 +323,83 @@ public class DriverServiceImpl extends BaseServiceImpl<DriverMapper, Driver> imp
|
||||
}
|
||||
}
|
||||
|
||||
R<String> roleResult = sysClient.getRoleIdByAlias(tenantId, "driver");
|
||||
if (roleResult == null || !R.isSuccess(roleResult) || Func.isEmpty(roleResult.getData())) {
|
||||
throw new ServiceException("未配置角色别名为driver的角色,请先在系统角色中维护");
|
||||
}
|
||||
String roleId = roleResult.getData();
|
||||
|
||||
// 部门可选:匹配到则写入小程序用户,匹配不到不阻断同步
|
||||
String deptId = null;
|
||||
R<String> deptResult = sysClient.getDeptIds(tenantId, driver.getOrganizationName());
|
||||
if (deptResult == null || !R.isSuccess(deptResult) || Func.isEmpty(deptResult.getData())) {
|
||||
throw new ServiceException("所属组织未匹配到系统部门,无法同步用户");
|
||||
if (deptResult != null && R.isSuccess(deptResult) && Func.isNotEmpty(deptResult.getData())) {
|
||||
deptId = deptResult.getData();
|
||||
}
|
||||
String deptId = deptResult.getData();
|
||||
|
||||
User existing = findExistingUser(driver, tenantId, mobile);
|
||||
MpUser existing = findExistingMpUser(driver, tenantId, mobile);
|
||||
if (existing == null || Func.isEmpty(existing.getId())) {
|
||||
// 司机未关联有效用户时新建;初始密码由系统用户保存逻辑按默认参数/强度规则填充
|
||||
createAndLinkDriverUser(driver, tenantId, mobile, roleId, deptId);
|
||||
createAndLinkDriverMpUser(driver, tenantId, mobile, deptId);
|
||||
} else {
|
||||
updateAndLinkDriverUser(driver, existing, tenantId, mobile, roleId, deptId);
|
||||
updateAndLinkDriverMpUser(driver, existing, tenantId, mobile, deptId);
|
||||
}
|
||||
}
|
||||
|
||||
private User findExistingUser(Driver driver, String tenantId, String mobile) {
|
||||
private MpUser findExistingMpUser(Driver driver, String tenantId, String mobile) {
|
||||
if (Func.isNotEmpty(driver.getUserId())) {
|
||||
R<User> byId = userClient.userInfoById(driver.getUserId());
|
||||
R<MpUser> byId = mpUserClient.userById(driver.getUserId());
|
||||
if (byId != null && R.isSuccess(byId) && byId.getData() != null && Func.isNotEmpty(byId.getData().getId())) {
|
||||
return byId.getData();
|
||||
}
|
||||
// 关联的用户已不存在,清空后按手机号重建
|
||||
// 关联的小程序用户已不存在,清空后按手机号重建
|
||||
driver.setUserId(null);
|
||||
}
|
||||
return findExistingUserByAccountOrPhone(tenantId, mobile);
|
||||
return findExistingMpUserByPhone(tenantId, mobile);
|
||||
}
|
||||
|
||||
private User findExistingUserByAccountOrPhone(String tenantId, String mobile) {
|
||||
R<User> byAccount = userClient.userByAccount(tenantId, mobile);
|
||||
if (byAccount != null && R.isSuccess(byAccount) && byAccount.getData() != null
|
||||
&& Func.isNotEmpty(byAccount.getData().getId())) {
|
||||
return byAccount.getData();
|
||||
}
|
||||
R<UserInfo> byPhone = userClient.userInfoByPhone(tenantId, mobile, UserType.WEB.getName());
|
||||
private MpUser findExistingMpUserByPhone(String tenantId, String mobile) {
|
||||
R<MpUser> byPhone = mpUserClient.userByPhone(tenantId, mobile);
|
||||
if (byPhone != null && R.isSuccess(byPhone) && byPhone.getData() != null
|
||||
&& byPhone.getData().getUser() != null && Func.isNotEmpty(byPhone.getData().getUser().getId())) {
|
||||
return byPhone.getData().getUser();
|
||||
&& Func.isNotEmpty(byPhone.getData().getId())) {
|
||||
return byPhone.getData();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void createAndLinkDriverUser(Driver driver, String tenantId, String mobile, String roleId, String deptId) {
|
||||
User user = buildSyncUser(null, tenantId, mobile, driver.getDriverName(), driver.getGender(), roleId, deptId);
|
||||
// 不传密码:交由 UserServiceImpl.saveUser 使用系统默认初始密码或自动生成合规密码
|
||||
user.setUserType(UserType.WEB.getCategory());
|
||||
private void createAndLinkDriverMpUser(Driver driver, String tenantId, String mobile, String deptId) {
|
||||
MpUser user = buildSyncMpUser(null, tenantId, mobile, driver.getDriverName(), driver.getGender(),
|
||||
deptId, MpUserIdentity.DRIVER.getCode());
|
||||
|
||||
R<Boolean> saveResult = null;
|
||||
R<Boolean> saveResult;
|
||||
try {
|
||||
saveResult = userClient.saveUser(user);
|
||||
saveResult = mpUserClient.saveUser(user);
|
||||
} catch (Exception ex) {
|
||||
User existed = findExistingUserByAccountOrPhone(tenantId, mobile);
|
||||
MpUser existed = findExistingMpUserByPhone(tenantId, mobile);
|
||||
if (existed != null) {
|
||||
updateAndLinkDriverUser(driver, existed, tenantId, mobile, roleId, deptId);
|
||||
updateAndLinkDriverMpUser(driver, existed, tenantId, mobile, deptId);
|
||||
return;
|
||||
}
|
||||
throw new ServiceException(Func.isNotEmpty(ex.getMessage()) ? ex.getMessage() : "同步系统用户失败");
|
||||
throw new ServiceException(Func.isNotEmpty(ex.getMessage()) ? ex.getMessage() : "同步小程序用户失败");
|
||||
}
|
||||
if (!isFeignSuccess(saveResult)) {
|
||||
User existed = findExistingUserByAccountOrPhone(tenantId, mobile);
|
||||
MpUser existed = findExistingMpUserByPhone(tenantId, mobile);
|
||||
if (existed != null) {
|
||||
updateAndLinkDriverUser(driver, existed, tenantId, mobile, roleId, deptId);
|
||||
updateAndLinkDriverMpUser(driver, existed, tenantId, mobile, deptId);
|
||||
return;
|
||||
}
|
||||
throw new ServiceException(resolveFeignError(saveResult, "同步系统用户失败"));
|
||||
throw new ServiceException(resolveFeignError(saveResult, "同步小程序用户失败"));
|
||||
}
|
||||
|
||||
R<User> created = userClient.userByAccount(tenantId, mobile);
|
||||
User createdUser = (created != null && R.isSuccess(created)) ? created.getData() : null;
|
||||
MpUser createdUser = findExistingMpUserByPhone(tenantId, mobile);
|
||||
if (createdUser == null || Func.isEmpty(createdUser.getId())) {
|
||||
createdUser = findExistingUserByAccountOrPhone(tenantId, mobile);
|
||||
}
|
||||
if (createdUser == null || Func.isEmpty(createdUser.getId())) {
|
||||
throw new ServiceException("系统用户创建成功但无法回查用户ID");
|
||||
throw new ServiceException("小程序用户创建成功但无法回查用户ID");
|
||||
}
|
||||
linkDriverUserId(driver, createdUser.getId());
|
||||
}
|
||||
|
||||
private void updateAndLinkDriverUser(Driver driver, User existing, String tenantId, String mobile, String roleId, String deptId) {
|
||||
User user = buildSyncUser(existing.getId(), tenantId, mobile, driver.getDriverName(), driver.getGender(), roleId, deptId);
|
||||
R<Boolean> updateResult = userClient.updateUser(user);
|
||||
// updateById 无字段变化时可能返回 false,但业务上仍视为成功,以接口 code 为准
|
||||
private void updateAndLinkDriverMpUser(Driver driver, MpUser existing, String tenantId, String mobile, String deptId) {
|
||||
// 保留已有管理员等身份,确保包含司机身份
|
||||
String identities = MpUserIdentity.merge(existing.getIdentities(), MpUserIdentity.DRIVER.getCode());
|
||||
MpUser user = buildSyncMpUser(existing.getId(), tenantId, mobile, driver.getDriverName(), driver.getGender(),
|
||||
deptId, identities);
|
||||
// 更新时不覆盖 openid
|
||||
user.setOpenid(null);
|
||||
R<Boolean> updateResult = mpUserClient.updateUser(user);
|
||||
if (!isFeignSuccess(updateResult)) {
|
||||
throw new ServiceException(resolveFeignError(updateResult, "同步更新系统用户失败"));
|
||||
throw new ServiceException(resolveFeignError(updateResult, "同步更新小程序用户失败"));
|
||||
}
|
||||
linkDriverUserId(driver, existing.getId());
|
||||
}
|
||||
@@ -433,9 +420,9 @@ public class DriverServiceImpl extends BaseServiceImpl<DriverMapper, Driver> imp
|
||||
return msg;
|
||||
}
|
||||
|
||||
private User buildSyncUser(Long userId, String tenantId, String mobile, String driverName, String gender,
|
||||
String roleId, String deptId) {
|
||||
User user = new User();
|
||||
private MpUser buildSyncMpUser(Long userId, String tenantId, String mobile, String driverName, String gender,
|
||||
String deptId, String identities) {
|
||||
MpUser user = new MpUser();
|
||||
user.setId(userId);
|
||||
user.setTenantId(tenantId);
|
||||
user.setAccount(mobile);
|
||||
@@ -443,8 +430,8 @@ public class DriverServiceImpl extends BaseServiceImpl<DriverMapper, Driver> imp
|
||||
user.setName(driverName);
|
||||
user.setRealName(driverName);
|
||||
user.setSex(UserSex.getCodeByName(gender));
|
||||
user.setRoleId(roleId);
|
||||
user.setDeptId(deptId);
|
||||
user.setIdentities(identities);
|
||||
return user;
|
||||
}
|
||||
|
||||
|
||||
+75
-4
@@ -32,7 +32,10 @@ import org.springblade.core.secure.utils.AuthUtil;
|
||||
import org.springblade.core.tool.jackson.JsonUtil;
|
||||
import org.springblade.core.tool.utils.DateUtil;
|
||||
import org.springblade.core.tool.constant.BladeConstant;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.system.feign.IMpUserClient;
|
||||
import org.springblade.system.pojo.entity.MpUser;
|
||||
import org.springblade.transport.mapper.ExceptionDisposalMapper;
|
||||
import org.springblade.transport.mapper.WaybillEnroutePunchMapper;
|
||||
import org.springblade.transport.mapper.WaybillNodePunchMapper;
|
||||
@@ -56,6 +59,7 @@ import org.springblade.transport.pojo.vo.DriverPunchPhotoVO;
|
||||
import org.springblade.transport.pojo.vo.DriverWaybillCardVO;
|
||||
import org.springblade.transport.pojo.vo.DriverWaybillPreviewVO;
|
||||
import org.springblade.transport.pojo.vo.DriverWaybillTabCountsVO;
|
||||
import org.springblade.transport.pojo.vo.WaybillTrackVO;
|
||||
import org.springblade.transport.service.IDriverService;
|
||||
import org.springblade.transport.service.IDriverWaybillService;
|
||||
import org.springblade.transport.service.IProcessConfigService;
|
||||
@@ -120,6 +124,7 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService {
|
||||
|
||||
private final IWaybillService waybillService;
|
||||
private final IDriverService driverService;
|
||||
private final IMpUserClient mpUserClient;
|
||||
private final IProcessConfigService processConfigService;
|
||||
private final ITransportVehicleService transportVehicleService;
|
||||
private final CertificationGate certificationGate;
|
||||
@@ -239,6 +244,52 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService {
|
||||
return toCard(normalizeAcceptStatus(waybill), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WaybillTrackVO track(Long id) {
|
||||
Driver driver = requireCurrentDriver();
|
||||
Waybill waybill = loadDriverWaybill(id, currentBoundPlates(driver));
|
||||
LocalDate[] range = resolveTrackDateRange(waybill);
|
||||
return waybillService.trackVehicle(waybill.getId(), range[0].toString(), range[1].toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 按打卡记录 / 计划时间推导轨迹查询起止日期
|
||||
*/
|
||||
private LocalDate[] resolveTrackDateRange(Waybill waybill) {
|
||||
LocalDate today = LocalDate.now();
|
||||
List<LocalDate> punchDates = new ArrayList<>();
|
||||
nodePunchMapper.selectList(Wrappers.<WaybillNodePunch>lambdaQuery()
|
||||
.eq(WaybillNodePunch::getWaybillId, waybill.getId())
|
||||
.isNotNull(WaybillNodePunch::getPunchTime)
|
||||
.orderByAsc(WaybillNodePunch::getPunchTime)).forEach(punch -> {
|
||||
if (punch.getPunchTime() != null) {
|
||||
punchDates.add(punch.getPunchTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
|
||||
}
|
||||
});
|
||||
enroutePunchMapper.selectList(Wrappers.<WaybillEnroutePunch>lambdaQuery()
|
||||
.eq(WaybillEnroutePunch::getWaybillId, waybill.getId())
|
||||
.isNotNull(WaybillEnroutePunch::getPunchTime)
|
||||
.orderByAsc(WaybillEnroutePunch::getPunchTime)).forEach(punch -> {
|
||||
if (punch.getPunchTime() != null) {
|
||||
punchDates.add(punch.getPunchTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
|
||||
}
|
||||
});
|
||||
punchDates.sort(LocalDate::compareTo);
|
||||
if (punchDates.size() > 1) {
|
||||
return new LocalDate[]{punchDates.get(0), punchDates.get(punchDates.size() - 1)};
|
||||
}
|
||||
if (punchDates.size() == 1) {
|
||||
return new LocalDate[]{punchDates.get(0), today};
|
||||
}
|
||||
if (waybill.getEstimatedStartTime() != null) {
|
||||
return new LocalDate[]{waybill.getEstimatedStartTime(), today};
|
||||
}
|
||||
if (waybill.getStartDate() != null) {
|
||||
return new LocalDate[]{waybill.getStartDate(), today};
|
||||
}
|
||||
return new LocalDate[]{today.minusDays(3), today};
|
||||
}
|
||||
|
||||
@Override
|
||||
public DriverWaybillCardVO detailPunchSnapshot(Long id) {
|
||||
if (id == null) {
|
||||
@@ -590,7 +641,7 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前登录司机:优先 userId,其次 JWT 账号(手机号)匹配 driver.mobile
|
||||
* 当前登录司机:优先 userId,其次手机号(JWT 账号 / 小程序用户 account)匹配 driver.mobile
|
||||
*/
|
||||
private Driver currentDriver() {
|
||||
Long userId = AuthUtil.getUserId();
|
||||
@@ -603,16 +654,36 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService {
|
||||
if (driver != null) {
|
||||
return driver;
|
||||
}
|
||||
String account = AuthUtil.getUserAccount();
|
||||
if (Func.isNotEmpty(account) && account.matches("^1\\d{10}$")) {
|
||||
String phone = resolveSelfPhone(userId);
|
||||
if (Func.isNotEmpty(phone)) {
|
||||
return driverService.getOne(Wrappers.<Driver>lambdaQuery()
|
||||
.eq(Driver::getMobile, account.trim())
|
||||
.eq(Driver::getMobile, phone)
|
||||
.orderByDesc(Driver::getUpdateTime)
|
||||
.last("LIMIT 1"));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析当前小程序用户可用于匹配司机档案的手机号
|
||||
*/
|
||||
private String resolveSelfPhone(Long userId) {
|
||||
String account = AuthUtil.getUserAccount();
|
||||
if (isMobileLike(account)) {
|
||||
return account.trim();
|
||||
}
|
||||
R<MpUser> result = mpUserClient.userById(userId);
|
||||
if (result != null && R.isSuccess(result) && result.getData() != null
|
||||
&& isMobileLike(result.getData().getAccount())) {
|
||||
return result.getData().getAccount().trim();
|
||||
}
|
||||
return Func.isEmpty(account) ? null : account.trim();
|
||||
}
|
||||
|
||||
private boolean isMobileLike(String value) {
|
||||
return Func.isNotEmpty(value) && value.trim().matches("^1\\d{10,14}$");
|
||||
}
|
||||
|
||||
private List<String> splitPlates(String drivingVehicle) {
|
||||
return VehiclePlateSupport.splitPlates(drivingVehicle);
|
||||
}
|
||||
|
||||
+6
-6
@@ -31,7 +31,7 @@ import org.springblade.core.mp.base.BaseServiceImpl;
|
||||
import org.springblade.core.secure.utils.AuthUtil;
|
||||
import org.springblade.core.tool.utils.BeanUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.system.cache.UserCache;
|
||||
import org.springblade.system.cache.UserDisplayCache;
|
||||
import org.springblade.transport.mapper.ExceptionDisposalFollowRecordMapper;
|
||||
import org.springblade.transport.mapper.ExceptionDisposalMapper;
|
||||
import org.springblade.transport.pojo.dto.ExceptionDisposalFollowRequest;
|
||||
@@ -131,7 +131,7 @@ public class ExceptionDisposalServiceImpl
|
||||
disposal.setDisposalStatus(STATUS_PENDING);
|
||||
disposal.setReportTime(LocalDateTime.now());
|
||||
disposal.setReporterId(AuthUtil.getUserId());
|
||||
disposal.setReporterName(UserCache.getUserRealName(AuthUtil.getUserId()));
|
||||
disposal.setReporterName(UserDisplayCache.getUserRealName(AuthUtil.getUserId()));
|
||||
|
||||
fillFromWaybill(disposal, request.getWaybillId(), request.getWaybillNo());
|
||||
|
||||
@@ -267,7 +267,7 @@ public class ExceptionDisposalServiceImpl
|
||||
record.setDisposalId(disposal.getId());
|
||||
record.setFollowContent(changeLog);
|
||||
record.setFollowUser(AuthUtil.getUserId());
|
||||
record.setFollowUserName(UserCache.getUserRealName(AuthUtil.getUserId()));
|
||||
record.setFollowUserName(UserDisplayCache.getUserRealName(AuthUtil.getUserId()));
|
||||
record.setFollowTime(LocalDateTime.now());
|
||||
followRecordMapper.insert(record);
|
||||
}
|
||||
@@ -344,7 +344,7 @@ public class ExceptionDisposalServiceImpl
|
||||
record.setFollowContent(Func.isBlank(followContent) ? "已上传跟进附件" : followContent);
|
||||
record.setAttachmentsJson(hasAttachments ? request.getAttachmentsJson() : null);
|
||||
record.setFollowUser(AuthUtil.getUserId());
|
||||
record.setFollowUserName(UserCache.getUserRealName(AuthUtil.getUserId()));
|
||||
record.setFollowUserName(UserDisplayCache.getUserRealName(AuthUtil.getUserId()));
|
||||
record.setFollowTime(LocalDateTime.now());
|
||||
followRecordMapper.insert(record);
|
||||
}
|
||||
@@ -417,8 +417,8 @@ public class ExceptionDisposalServiceImpl
|
||||
if (vo == null) {
|
||||
return null;
|
||||
}
|
||||
vo.setCreateUserName(UserCache.getUserRealName(entity.getCreateUser()));
|
||||
vo.setUpdateUserName(UserCache.getUserRealName(entity.getUpdateUser()));
|
||||
vo.setCreateUserName(UserDisplayCache.getUserRealName(entity.getCreateUser()));
|
||||
vo.setUpdateUserName(UserDisplayCache.getUserRealName(entity.getUpdateUser()));
|
||||
vo.setDisposalStatusName(statusName(entity.getDisposalStatus()));
|
||||
vo.setScenePhotoList(splitPhotos(entity.getScenePhotos()));
|
||||
fillRouteAndCargo(vo, entity.getWaybillId());
|
||||
|
||||
+5
-4
@@ -30,7 +30,7 @@ import lombok.RequiredArgsConstructor;
|
||||
import org.springblade.core.secure.utils.AuthUtil;
|
||||
import org.springblade.core.tool.utils.DateUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.system.cache.UserCache;
|
||||
import org.springblade.system.cache.UserDisplayCache;
|
||||
import org.springblade.transport.pojo.entity.Driver;
|
||||
import org.springblade.transport.pojo.entity.ExceptionDisposal;
|
||||
import org.springblade.transport.pojo.entity.RiskDisposal;
|
||||
@@ -688,11 +688,12 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
|
||||
}
|
||||
|
||||
private String resolveUserName() {
|
||||
String realName = UserCache.getUserRealName(AuthUtil.getUserId());
|
||||
if (Func.isNotBlank(realName)) {
|
||||
String realName = UserDisplayCache.getUserRealName(AuthUtil.getUserId());
|
||||
if (Func.isNotBlank(realName) && !Func.toStr(AuthUtil.getUserId()).equals(realName)) {
|
||||
return realName;
|
||||
}
|
||||
return Func.toStr(AuthUtil.getUserName(), "");
|
||||
String jwtName = Func.toStr(AuthUtil.getNickName(), AuthUtil.getUserName());
|
||||
return Func.toStr(jwtName, realName);
|
||||
}
|
||||
|
||||
/** 相对时间文案;时间取不到时留空,避免把「现在」当成发生时刻显示成「刚刚」 */
|
||||
|
||||
+5
-3
@@ -28,6 +28,7 @@ package org.springblade.transport.wrapper;
|
||||
import org.springblade.core.mp.support.BaseEntityWrapper;
|
||||
import org.springblade.core.tool.utils.BeanUtil;
|
||||
import org.springblade.system.cache.UserCache;
|
||||
import org.springblade.system.cache.UserDisplayCache;
|
||||
import org.springblade.transport.pojo.entity.TransportCertRecord;
|
||||
import org.springblade.transport.pojo.vo.TransportCertRecordVO;
|
||||
import org.springblade.transport.support.CertificationRuleSupport;
|
||||
@@ -62,9 +63,10 @@ public class TransportCertRecordWrapper extends BaseEntityWrapper<TransportCertR
|
||||
* @return 同一个对象,便于链式调用
|
||||
*/
|
||||
public TransportCertRecordVO fillDisplayFields(TransportCertRecordVO transportCertRecordVO) {
|
||||
transportCertRecordVO.setCreateUserName(UserCache.getUserRealName(transportCertRecordVO.getCreateUser()));
|
||||
transportCertRecordVO.setUpdateUserName(UserCache.getUserRealName(transportCertRecordVO.getUpdateUser()));
|
||||
transportCertRecordVO.setSubmitUserName(UserCache.getUserRealName(transportCertRecordVO.getSubmitUser()));
|
||||
// create/update/submit 可能来自小程序用户;audit 一般为后台系统用户
|
||||
transportCertRecordVO.setCreateUserName(UserDisplayCache.getUserRealName(transportCertRecordVO.getCreateUser()));
|
||||
transportCertRecordVO.setUpdateUserName(UserDisplayCache.getUserRealName(transportCertRecordVO.getUpdateUser()));
|
||||
transportCertRecordVO.setSubmitUserName(UserDisplayCache.getUserRealName(transportCertRecordVO.getSubmitUser()));
|
||||
transportCertRecordVO.setAuditUserName(UserCache.getUserRealName(transportCertRecordVO.getAuditUser()));
|
||||
// 状态文案统一在此翻译,避免小程序、Web、接口各处各造一套说法
|
||||
transportCertRecordVO.setStatusText(CertificationRuleSupport.statusText(transportCertRecordVO.getStatus()));
|
||||
|
||||
+10
-11
@@ -8,13 +8,13 @@ import org.mockito.MockedStatic;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.secure.utils.AuthUtil;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.system.pojo.entity.User;
|
||||
import org.springblade.system.feign.IMpUserClient;
|
||||
import org.springblade.system.pojo.entity.MpUser;
|
||||
import org.springblade.transport.pojo.entity.Driver;
|
||||
import org.springblade.transport.pojo.entity.TransportVehicle;
|
||||
import org.springblade.transport.pojo.vo.DriverVehicleCardVO;
|
||||
import org.springblade.transport.service.IDriverService;
|
||||
import org.springblade.transport.service.ITransportVehicleService;
|
||||
import org.springblade.system.feign.IUserClient;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
@@ -46,7 +46,7 @@ class DriverAppServiceImplTest {
|
||||
void setUp() {
|
||||
driverService = mock(IDriverService.class);
|
||||
transportVehicleService = mock(ITransportVehicleService.class);
|
||||
service = new DriverAppServiceImpl(driverService, transportVehicleService, mock(IUserClient.class));
|
||||
service = new DriverAppServiceImpl(driverService, transportVehicleService, mock(IMpUserClient.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -94,9 +94,9 @@ class DriverAppServiceImplTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("用户中心 phone 是脱敏串时改用其 account——脱敏 phone 既查不到档案,也会把本人真实号码误判成越权")
|
||||
@DisplayName("小程序用户 phone 是脱敏串时改用其 account——脱敏 phone 既查不到档案,也会把本人真实号码误判成越权")
|
||||
void maskedUserPhoneFallsBackToAccount() {
|
||||
User user = new User();
|
||||
MpUser user = new MpUser();
|
||||
user.setPhone("175****12082");
|
||||
user.setAccount("175874212082");
|
||||
|
||||
@@ -156,14 +156,14 @@ class DriverAppServiceImplTest {
|
||||
*
|
||||
* @param mobileHint 前端传入的 mobile,可为 null
|
||||
* @param account JWT 里的账号,可为 null
|
||||
* @param user 用户中心返回的用户,可为 null(表示走不到用户中心)
|
||||
* @param user 小程序用户,可为 null(表示走不到小程序用户中心)
|
||||
*/
|
||||
private Driver currentByPhone(String mobileHint, String account, User user) {
|
||||
IUserClient userClient = mock(IUserClient.class);
|
||||
private Driver currentByPhone(String mobileHint, String account, MpUser user) {
|
||||
IMpUserClient mpUserClient = mock(IMpUserClient.class);
|
||||
if (user != null) {
|
||||
when(userClient.userInfoById(USER_ID)).thenReturn(R.data(user));
|
||||
when(mpUserClient.userById(USER_ID)).thenReturn(R.data(user));
|
||||
}
|
||||
DriverAppServiceImpl scoped = new DriverAppServiceImpl(driverService, transportVehicleService, userClient);
|
||||
DriverAppServiceImpl scoped = new DriverAppServiceImpl(driverService, transportVehicleService, mpUserClient);
|
||||
|
||||
Driver driver = new Driver();
|
||||
driver.setId(9002L);
|
||||
@@ -178,5 +178,4 @@ class DriverAppServiceImplTest {
|
||||
return scoped.currentByPhone(mobileHint);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
-3
@@ -12,7 +12,7 @@ import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.secure.utils.AuthUtil;
|
||||
import org.springblade.system.cache.UserCache;
|
||||
import org.springblade.system.cache.UserDisplayCache;
|
||||
import org.springblade.transport.mapper.ExceptionDisposalFollowRecordMapper;
|
||||
import org.springblade.transport.mapper.ExceptionDisposalMapper;
|
||||
import org.springblade.transport.pojo.entity.ExceptionDisposal;
|
||||
@@ -320,9 +320,9 @@ class ExceptionDisposalReportGateTest {
|
||||
|
||||
private void runSubmit(ExceptionDisposal request) {
|
||||
try (MockedStatic<AuthUtil> authUtil = mockStatic(AuthUtil.class);
|
||||
MockedStatic<UserCache> userCache = mockStatic(UserCache.class)) {
|
||||
MockedStatic<UserDisplayCache> userCache = mockStatic(UserDisplayCache.class)) {
|
||||
authUtil.when(AuthUtil::getUserId).thenReturn(USER_ID);
|
||||
userCache.when(() -> UserCache.getUserRealName(USER_ID)).thenReturn("韦司机");
|
||||
userCache.when(() -> UserDisplayCache.getUserRealName(USER_ID)).thenReturn("韦司机");
|
||||
service.submitReport(request);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
-- ----------------------------
|
||||
-- 小程序用户表(与 blade_user 分离,仅用于小程序登录)
|
||||
-- 身份 identities:逗号分隔,当前支持 driver(司机)、admin(管理员),可同时拥有,后续可扩展
|
||||
-- ----------------------------
|
||||
CREATE TABLE IF NOT EXISTS `blade_mp_user` (
|
||||
`id` bigint NOT NULL COMMENT '主键',
|
||||
`tenant_id` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000' COMMENT '租户ID',
|
||||
`code` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户编号',
|
||||
`account` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号(通常为手机号)',
|
||||
`password` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '密码(小程序登录可不使用)',
|
||||
`name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '昵称',
|
||||
`real_name` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '真名',
|
||||
`avatar` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '头像',
|
||||
`email` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '邮箱',
|
||||
`phone` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手机',
|
||||
`birthday` datetime NULL DEFAULT NULL COMMENT '生日',
|
||||
`sex` int NULL DEFAULT NULL COMMENT '性别',
|
||||
`openid` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信小程序 openid',
|
||||
`identities` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '小程序身份,逗号分隔:driver,admin',
|
||||
`dept_id` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '部门id',
|
||||
`post_id` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '岗位id',
|
||||
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
|
||||
`create_user` bigint NULL DEFAULT NULL COMMENT '创建人',
|
||||
`create_dept` bigint NULL DEFAULT NULL COMMENT '创建部门',
|
||||
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
|
||||
`update_user` bigint NULL DEFAULT NULL COMMENT '修改人',
|
||||
`update_time` datetime NULL DEFAULT NULL COMMENT '修改时间',
|
||||
`status` int NULL DEFAULT 1 COMMENT '状态',
|
||||
`is_deleted` int NULL DEFAULT 0 COMMENT '是否已删除',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE KEY `uk_mp_user_tenant_phone` (`tenant_id`, `phone`) USING BTREE,
|
||||
UNIQUE KEY `uk_mp_user_tenant_openid` (`tenant_id`, `openid`) USING BTREE,
|
||||
KEY `idx_mp_user_account` (`tenant_id`, `account`) USING BTREE,
|
||||
KEY `idx_mp_user_identities` (`identities`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '小程序用户表';
|
||||
|
||||
-- 司机表 user_id 语义变更为关联小程序用户
|
||||
ALTER TABLE `blade_transport_driver`
|
||||
MODIFY COLUMN `user_id` bigint(20) DEFAULT NULL COMMENT '关联小程序用户ID(blade_mp_user.id)';
|
||||
|
||||
-- 示例:创建小程序管理员(调度端),identities 可同时包含 driver,admin
|
||||
-- INSERT INTO `blade_mp_user`
|
||||
-- (`id`, `tenant_id`, `account`, `name`, `real_name`, `phone`, `identities`, `create_time`, `status`, `is_deleted`)
|
||||
-- VALUES
|
||||
-- (2091900000000000001, '000000', '13800000000', '调度管理员', '调度管理员', '13800000000', 'admin', NOW(), 1, 0);
|
||||
@@ -0,0 +1,61 @@
|
||||
-- ----------------------------
|
||||
-- 将已关联 blade_user 的司机迁移到 blade_mp_user(可选,按环境执行)
|
||||
-- 注意:执行前请先执行 blade_mp_user_20261012.sql 建表
|
||||
-- 策略:小程序用户主键使用司机表 id(避免与 blade_user.id 冲突导致令牌刷新串用户)
|
||||
-- 迁移后回写 driver.user_id;原 blade_user 账号不会自动删除,请人工确认后再清理
|
||||
-- 调度端管理员需另行在 blade_mp_user 中维护 identities 含 admin 的账号
|
||||
-- ----------------------------
|
||||
|
||||
-- 1) 按司机写入小程序用户
|
||||
INSERT INTO `blade_mp_user` (
|
||||
`id`, `tenant_id`, `account`, `name`, `real_name`, `phone`, `sex`,
|
||||
`openid`, `identities`, `dept_id`,
|
||||
`create_user`, `create_dept`, `create_time`, `update_user`, `update_time`,
|
||||
`status`, `is_deleted`
|
||||
)
|
||||
SELECT
|
||||
d.`id` AS `id`,
|
||||
IFNULL(d.`tenant_id`, '000000') AS `tenant_id`,
|
||||
d.`mobile` AS `account`,
|
||||
d.`driver_name` AS `name`,
|
||||
d.`driver_name` AS `real_name`,
|
||||
d.`mobile` AS `phone`,
|
||||
CASE
|
||||
WHEN d.`gender` IN ('男', '1') THEN 1
|
||||
WHEN d.`gender` IN ('女', '2') THEN 2
|
||||
ELSE NULL
|
||||
END AS `sex`,
|
||||
oauth.`uuid` AS `openid`,
|
||||
'driver' AS `identities`,
|
||||
u.`dept_id` AS `dept_id`,
|
||||
d.`create_user`,
|
||||
d.`create_dept`,
|
||||
IFNULL(d.`create_time`, NOW()) AS `create_time`,
|
||||
d.`update_user`,
|
||||
IFNULL(d.`update_time`, NOW()) AS `update_time`,
|
||||
IFNULL(d.`status`, 1) AS `status`,
|
||||
0 AS `is_deleted`
|
||||
FROM `blade_transport_driver` d
|
||||
LEFT JOIN `blade_user` u ON u.`id` = d.`user_id` AND u.`is_deleted` = 0
|
||||
LEFT JOIN `blade_user_oauth` oauth
|
||||
ON oauth.`user_id` = d.`user_id`
|
||||
AND oauth.`source` = 'WECHAT_MINI'
|
||||
AND oauth.`is_deleted` = 0
|
||||
WHERE d.`is_deleted` = 0
|
||||
AND d.`mobile` IS NOT NULL
|
||||
AND d.`mobile` <> ''
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM `blade_mp_user` m
|
||||
WHERE m.`id` = d.`id`
|
||||
OR (m.`tenant_id` = IFNULL(d.`tenant_id`, '000000') AND m.`phone` = d.`mobile` AND m.`is_deleted` = 0)
|
||||
);
|
||||
|
||||
-- 2) 回写司机关联的小程序用户ID
|
||||
UPDATE `blade_transport_driver` d
|
||||
INNER JOIN `blade_mp_user` m
|
||||
ON m.`id` = d.`id`
|
||||
OR (m.`tenant_id` = IFNULL(d.`tenant_id`, '000000') AND m.`phone` = d.`mobile` AND m.`is_deleted` = 0)
|
||||
SET d.`user_id` = m.`id`
|
||||
WHERE d.`is_deleted` = 0
|
||||
AND d.`mobile` IS NOT NULL
|
||||
AND d.`mobile` <> '';
|
||||
Reference in New Issue
Block a user