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

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-12 13:02:08 +08:00
parent 42ca5ee7bc
commit e19c3b100a
34 changed files with 1892 additions and 170 deletions
@@ -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;
}
}
@@ -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/** 亦可到达。
*
@@ -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));
}
}
@@ -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> {
}
@@ -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);
}
@@ -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;
}
}
@@ -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));
@@ -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()));
@@ -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)) {
@@ -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、类型也相同,传错不会报任何错,
* 只会让「提交人」一栏显示成一串数字。
*/
@@ -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;
}
@@ -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);
}
@@ -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());
@@ -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);
}
/** 相对时间文案;时间取不到时留空,避免把「现在」当成发生时刻显示成「刚刚」 */
@@ -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()));
@@ -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);
}
}
}
@@ -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);
}
}