Merge branch 'master' into feature/payment-apply-bfi-sync
# Conflicts: # blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerArchiveServiceImpl.java
This commit is contained in:
@@ -51,6 +51,7 @@ import org.springblade.core.oauth2.service.OAuth2UserService;
|
|||||||
import org.springblade.system.feign.IAuthLockClient;
|
import org.springblade.system.feign.IAuthLockClient;
|
||||||
import org.springblade.system.feign.IAuthLogClient;
|
import org.springblade.system.feign.IAuthLogClient;
|
||||||
import org.springblade.core.tenant.BladeTenantProperties;
|
import org.springblade.core.tenant.BladeTenantProperties;
|
||||||
|
import org.springblade.system.feign.IMpUserClient;
|
||||||
import org.springblade.system.feign.IUserClient;
|
import org.springblade.system.feign.IUserClient;
|
||||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
@@ -117,8 +118,8 @@ public class BladeAuthConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public OAuth2UserService oAuth2UserService(IUserClient userClient) {
|
public OAuth2UserService oAuth2UserService(IUserClient userClient, IMpUserClient mpUserClient) {
|
||||||
return new BladeUserDetailService(userClient);
|
return new BladeUserDetailService(userClient, mpUserClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Primary
|
@Primary
|
||||||
|
|||||||
+7
@@ -33,6 +33,8 @@ import org.springblade.core.tool.api.R;
|
|||||||
import org.springblade.core.tool.utils.BeanUtil;
|
import org.springblade.core.tool.utils.BeanUtil;
|
||||||
import org.springblade.core.tool.utils.Func;
|
import org.springblade.core.tool.utils.Func;
|
||||||
import org.springblade.system.feign.ISysClient;
|
import org.springblade.system.feign.ISysClient;
|
||||||
|
import org.springblade.system.pojo.enums.MpUserIdentity;
|
||||||
|
import org.springblade.system.utils.MpUserAuth;
|
||||||
import org.springframework.core.MethodParameter;
|
import org.springframework.core.MethodParameter;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.converter.HttpMessageConverter;
|
import org.springframework.http.converter.HttpMessageConverter;
|
||||||
@@ -72,7 +74,12 @@ public class OAuth2UserInfoResponseAdvice implements ResponseBodyAdvice<Object>
|
|||||||
if (userInfo == null) {
|
if (userInfo == null) {
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
|
if (MpUserAuth.isMpUser(bladeUser)) {
|
||||||
|
// 小程序用户:按身份直接生成权限,不走系统角色菜单
|
||||||
|
userInfo.setPermission(MpUserIdentity.toPermissions(bladeUser.getRoleName()));
|
||||||
|
} else {
|
||||||
userInfo.setPermission(loadPermission(bladeUser.getRoleId()));
|
userInfo.setPermission(loadPermission(bladeUser.getRoleId()));
|
||||||
|
}
|
||||||
return userInfo;
|
return userInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -88,7 +88,8 @@ public class Oauth2SmsEndpoint {
|
|||||||
return R.fail(USER_PHONE_NOT_FOUND);
|
return R.fail(USER_PHONE_NOT_FOUND);
|
||||||
}
|
}
|
||||||
// 校验手机是否已注册,防止恶意发送验证码
|
// 校验手机是否已注册,防止恶意发送验证码
|
||||||
OAuth2Request request = OAuth2Request.create();
|
// buildArgs:带上 User-Type 等请求头,小程序传 User-Type=mp 时查 blade_mp_user
|
||||||
|
OAuth2Request request = OAuth2Request.create().buildArgs();
|
||||||
request.setTenantId(tenantId);
|
request.setTenantId(tenantId);
|
||||||
OAuth2User oAuth2User = userService.loadByPhone(decryptedPhone, request);
|
OAuth2User oAuth2User = userService.loadByPhone(decryptedPhone, request);
|
||||||
if (oAuth2User == null) {
|
if (oAuth2User == null) {
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ package org.springblade.auth.granter;
|
|||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springblade.auth.utils.TokenUtil;
|
||||||
import org.springblade.core.oauth2.exception.UserInvalidException;
|
import org.springblade.core.oauth2.exception.UserInvalidException;
|
||||||
import org.springblade.core.oauth2.granter.AbstractTokenGranter;
|
import org.springblade.core.oauth2.granter.AbstractTokenGranter;
|
||||||
import org.springblade.core.oauth2.handler.PasswordHandler;
|
import org.springblade.core.oauth2.handler.PasswordHandler;
|
||||||
import org.springblade.core.oauth2.provider.OAuth2Request;
|
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.OAuth2ClientService;
|
||||||
import org.springblade.core.oauth2.service.OAuth2User;
|
import org.springblade.core.oauth2.service.OAuth2User;
|
||||||
import org.springblade.core.oauth2.service.OAuth2UserService;
|
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.Func;
|
||||||
import org.springblade.core.tool.utils.StringUtil;
|
import org.springblade.core.tool.utils.StringUtil;
|
||||||
import org.springblade.core.tool.utils.WebUtil;
|
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.constant.WechatMiniConstant;
|
||||||
import org.springblade.thirdparty.wechat.exception.WechatMiniException;
|
import org.springblade.thirdparty.wechat.exception.WechatMiniException;
|
||||||
import org.springblade.thirdparty.wechat.pojo.vo.WechatPhoneVO;
|
import org.springblade.thirdparty.wechat.pojo.vo.WechatPhoneVO;
|
||||||
@@ -25,7 +28,8 @@ import org.springframework.stereotype.Component;
|
|||||||
* 微信小程序手机号一键登录。
|
* 微信小程序手机号一键登录。
|
||||||
* <p>
|
* <p>
|
||||||
* grant_type=wechat_applet,参数:loginCode(wx.login)、phoneCode(getPhoneNumber)。
|
* grant_type=wechat_applet,参数:loginCode(wx.login)、phoneCode(getPhoneNumber)。
|
||||||
* 流程:换 openid + 手机号 → 按手机号查用户(不存在则拒绝)→ 记录 openid → 发令牌(与密码登录一致)。
|
* 流程:换 openid + 手机号 → 按手机号查小程序用户(blade_mp_user,不存在则拒绝)
|
||||||
|
* → 记录 openid → 按身份发令牌(不依赖系统角色)。
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
@@ -33,17 +37,17 @@ public class WechatMiniTokenGranter extends AbstractTokenGranter {
|
|||||||
|
|
||||||
private final OAuth2UserService userService;
|
private final OAuth2UserService userService;
|
||||||
private final IWechatMiniService wechatMiniService;
|
private final IWechatMiniService wechatMiniService;
|
||||||
private final IUserClient userClient;
|
private final IMpUserClient mpUserClient;
|
||||||
|
|
||||||
public WechatMiniTokenGranter(OAuth2ClientService clientService,
|
public WechatMiniTokenGranter(OAuth2ClientService clientService,
|
||||||
OAuth2UserService userService,
|
OAuth2UserService userService,
|
||||||
PasswordHandler passwordHandler,
|
PasswordHandler passwordHandler,
|
||||||
IWechatMiniService wechatMiniService,
|
IWechatMiniService wechatMiniService,
|
||||||
IUserClient userClient) {
|
IMpUserClient mpUserClient) {
|
||||||
super(clientService, userService, passwordHandler);
|
super(clientService, userService, passwordHandler);
|
||||||
this.userService = userService;
|
this.userService = userService;
|
||||||
this.wechatMiniService = wechatMiniService;
|
this.wechatMiniService = wechatMiniService;
|
||||||
this.userClient = userClient;
|
this.mpUserClient = mpUserClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -54,7 +58,7 @@ public class WechatMiniTokenGranter extends AbstractTokenGranter {
|
|||||||
@Override
|
@Override
|
||||||
public OAuth2User user(OAuth2Request request) {
|
public OAuth2User user(OAuth2Request request) {
|
||||||
// 先校验客户端是否允许该授权类型,避免先调微信再因客户端配置失败
|
// 先校验客户端是否允许该授权类型,避免先调微信再因客户端配置失败
|
||||||
var oauthClient = client(request);
|
OAuth2Client oauthClient = client(request);
|
||||||
|
|
||||||
HttpServletRequest httpRequest = WebUtil.getRequest();
|
HttpServletRequest httpRequest = WebUtil.getRequest();
|
||||||
String loginCode = httpRequest.getParameter("loginCode");
|
String loginCode = httpRequest.getParameter("loginCode");
|
||||||
@@ -78,16 +82,21 @@ public class WechatMiniTokenGranter extends AbstractTokenGranter {
|
|||||||
throw new UserInvalidException("未获取到微信手机号");
|
throw new UserInvalidException("未获取到微信手机号");
|
||||||
}
|
}
|
||||||
|
|
||||||
OAuth2User user = userService.loadByPhone(phone, request);
|
R<MpUserInfo> mpUserResult = mpUserClient.userInfoByPhone(request.getTenantId(), phone);
|
||||||
if (!userService.validateUser(user)) {
|
if (mpUserResult == null || !mpUserResult.isSuccess() || mpUserResult.getData() == null
|
||||||
|
|| mpUserResult.getData().getUser() == null) {
|
||||||
throw new UserInvalidException("用户不存在,无法登录");
|
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(),
|
request.getTenantId(),
|
||||||
Func.toLong(user.getUserId()),
|
Func.toLong(user.getUserId()),
|
||||||
session.getOpenid(),
|
session.getOpenid()
|
||||||
phone
|
|
||||||
);
|
);
|
||||||
if (bindResult == null || !bindResult.isSuccess()) {
|
if (bindResult == null || !bindResult.isSuccess()) {
|
||||||
String msg = bindResult != null ? bindResult.getMsg() : "绑定 openid 失败";
|
String msg = bindResult != null ? bindResult.getMsg() : "绑定 openid 失败";
|
||||||
|
|||||||
@@ -18,9 +18,6 @@
|
|||||||
* Using this software signifies agreement to this License, and the software
|
* Using this software signifies agreement to this License, and the software
|
||||||
* must not be used for illegal purposes.
|
* must not be used for illegal purposes.
|
||||||
* <p>
|
* <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)
|
* Author: Chill Zhuang (bladejava@qq.com)
|
||||||
*/
|
*/
|
||||||
package org.springblade.auth.service;
|
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.api.R;
|
||||||
import org.springblade.core.tool.utils.Func;
|
import org.springblade.core.tool.utils.Func;
|
||||||
import org.springblade.core.tool.utils.StringUtil;
|
import org.springblade.core.tool.utils.StringUtil;
|
||||||
|
import org.springblade.system.feign.IMpUserClient;
|
||||||
import org.springblade.system.feign.IUserClient;
|
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.entity.UserInfo;
|
||||||
|
import org.springblade.system.pojo.enums.MpUserIdentity;
|
||||||
import org.springblade.system.pojo.enums.UserType;
|
import org.springblade.system.pojo.enums.UserType;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@@ -49,63 +49,121 @@ import java.util.Optional;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class BladeUserDetailService implements OAuth2UserService {
|
public class BladeUserDetailService implements OAuth2UserService {
|
||||||
private final IUserClient userClient;
|
private final IUserClient userClient;
|
||||||
|
private final IMpUserClient mpUserClient;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OAuth2User loadByUserId(String userId, OAuth2Request request) {
|
public OAuth2User loadByUserId(String userId, OAuth2Request request) {
|
||||||
// 获取用户参数
|
// 小程序刷新/鉴权:User-Type=mp 时只查 blade_mp_user
|
||||||
String userType = Optional.ofNullable(request.getUserType())
|
if (isMpLoginRequest(request)) {
|
||||||
.filter(s -> !StringUtil.isBlank(s))
|
OAuth2User mpUser = loadMpUserById(userId);
|
||||||
.orElse(UserType.WEB.getName());
|
if (mpUser == null) {
|
||||||
|
|
||||||
// 获取用户信息
|
|
||||||
R<UserInfo> result = userClient.userInfo(Func.toLong(userId), userType);
|
|
||||||
if (!result.isSuccess()) {
|
|
||||||
OAuth2ExceptionUtil.throwFromCode(OAuth2ErrorCode.INVALID_USER);
|
OAuth2ExceptionUtil.throwFromCode(OAuth2ErrorCode.INVALID_USER);
|
||||||
}
|
}
|
||||||
// 构建oauth2用户信息
|
return mpUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
String userType = resolveUserType(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);
|
return TokenUtil.convertUser(result.getData(), request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 兼容:刷新令牌未带 User-Type=mp 时,回落查询小程序用户
|
||||||
|
OAuth2User mpUser = loadMpUserById(userId);
|
||||||
|
if (mpUser != null) {
|
||||||
|
return mpUser;
|
||||||
|
}
|
||||||
|
OAuth2ExceptionUtil.throwFromCode(OAuth2ErrorCode.INVALID_USER);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OAuth2User loadByUsername(String username, OAuth2Request request) {
|
public OAuth2User loadByUsername(String username, OAuth2Request request) {
|
||||||
// 获取用户参数
|
|
||||||
String userType = Optional.ofNullable(request.getUserType())
|
|
||||||
.filter(s -> !StringUtil.isBlank(s))
|
|
||||||
.orElse(UserType.WEB.getName());
|
|
||||||
String tenantId = request.getTenantId();
|
String tenantId = request.getTenantId();
|
||||||
|
|
||||||
// 获取用户信息
|
// 小程序账号密码 / 图形验证码登录:仅查 blade_mp_user
|
||||||
R<UserInfo> result = userClient.userInfo(tenantId, username, userType);
|
if (isMpLoginRequest(request)) {
|
||||||
if (!result.isSuccess()) {
|
R<MpUserInfo> mpResult = mpUserClient.userInfoByAccount(tenantId, username);
|
||||||
|
if (mpResult == null || !mpResult.isSuccess() || mpResult.getData() == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return TokenUtil.convertMpUser(mpResult.getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
String userType = resolveUserType(request);
|
||||||
|
R<UserInfo> result = userClient.userInfo(tenantId, username, userType);
|
||||||
|
if (result == null || !result.isSuccess()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// 构建oauth2用户信息
|
|
||||||
return TokenUtil.convertUser(result.getData(), request);
|
return TokenUtil.convertUser(result.getData(), request);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OAuth2User loadByPhone(String phone, OAuth2Request request) {
|
public OAuth2User loadByPhone(String phone, OAuth2Request request) {
|
||||||
// 获取用户参数
|
|
||||||
String userType = Optional.ofNullable(request.getUserType())
|
|
||||||
.filter(s -> !StringUtil.isBlank(s))
|
|
||||||
.orElse(UserType.WEB.getName());
|
|
||||||
String tenantId = request.getTenantId();
|
String tenantId = request.getTenantId();
|
||||||
|
|
||||||
// 获取用户信息
|
// 小程序短信登录:仅查 blade_mp_user
|
||||||
R<UserInfo> result = userClient.userInfoByPhone(tenantId, phone, userType);
|
if (isMpLoginRequest(request)) {
|
||||||
if (!result.isSuccess()) {
|
return loadMpUserByPhone(tenantId, phone);
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
// 构建oauth2用户信息
|
|
||||||
|
String userType = resolveUserType(request);
|
||||||
|
R<UserInfo> result = userClient.userInfoByPhone(tenantId, phone, userType);
|
||||||
|
if (result != null && result.isSuccess() && result.getData() != null) {
|
||||||
return TokenUtil.convertUser(result.getData(), request);
|
return TokenUtil.convertUser(result.getData(), request);
|
||||||
}
|
}
|
||||||
|
// 发短信校验等未带 User-Type 时,回落小程序用户,避免 mp 账号收不到验证码
|
||||||
|
return loadMpUserByPhone(tenantId, phone);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean validateUser(OAuth2User user) {
|
public boolean validateUser(OAuth2User user) {
|
||||||
return Optional.ofNullable(user)
|
return Optional.ofNullable(user)
|
||||||
.filter(u -> u.getUserId() != null && !u.getUserId().isEmpty()) // 检查userId不为空
|
.filter(u -> u.getUserId() != null && !u.getUserId().isEmpty())
|
||||||
.filter(u -> u.getAuthorities() != null && !u.getAuthorities().isEmpty()) // 检查authorities不为空
|
.filter(u -> u.getAuthorities() != null && !u.getAuthorities().isEmpty())
|
||||||
.isPresent(); // 如果上述条件都满足,则返回true,否则返回false
|
.isPresent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否小程序登录:请求头 User-Type=mp(Web 与小程序共用 saber3 客户端,不能按 clientId 分流)
|
||||||
|
*/
|
||||||
|
private boolean isMpLoginRequest(OAuth2Request request) {
|
||||||
|
return request != null && isMpUserType(request.getUserType());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isMpUserType(String userType) {
|
||||||
|
return MpUserIdentity.DETAIL_TYPE.equalsIgnoreCase(Func.toStr(userType));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveUserType(OAuth2Request request) {
|
||||||
|
return Optional.ofNullable(request.getUserType())
|
||||||
|
.filter(s -> !StringUtil.isBlank(s))
|
||||||
|
.orElse(UserType.WEB.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
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,15 @@ package org.springblade.auth.utils;
|
|||||||
import org.springblade.core.oauth2.provider.OAuth2Request;
|
import org.springblade.core.oauth2.provider.OAuth2Request;
|
||||||
import org.springblade.core.oauth2.service.OAuth2User;
|
import org.springblade.core.oauth2.service.OAuth2User;
|
||||||
import org.springblade.core.oauth2.service.impl.OAuth2UserDetail;
|
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.core.tool.utils.Func;
|
||||||
import org.springblade.system.cache.SysCache;
|
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.User;
|
||||||
import org.springblade.system.pojo.entity.UserInfo;
|
import org.springblade.system.pojo.entity.UserInfo;
|
||||||
|
import org.springblade.system.pojo.enums.MpUserIdentity;
|
||||||
import java.util.Collections;
|
import org.springblade.system.utils.MpUserAuth;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 认证工具类
|
* 认证工具类
|
||||||
@@ -75,6 +78,7 @@ public class TokenUtil {
|
|||||||
userDetail.setRealName(user.getRealName());
|
userDetail.setRealName(user.getRealName());
|
||||||
userDetail.setAccount(user.getAccount());
|
userDetail.setAccount(user.getAccount());
|
||||||
userDetail.setPassword(user.getPassword());
|
userDetail.setPassword(user.getPassword());
|
||||||
|
userDetail.setPhone(user.getPhone());
|
||||||
userDetail.setDeptId(user.getDeptId());
|
userDetail.setDeptId(user.getDeptId());
|
||||||
userDetail.setPostId(user.getPostId());
|
userDetail.setPostId(user.getPostId());
|
||||||
userDetail.setRoleId(user.getRoleId());
|
userDetail.setRoleId(user.getRoleId());
|
||||||
@@ -85,4 +89,40 @@ public class TokenUtil {
|
|||||||
return userDetail;
|
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());
|
||||||
|
// 严格令牌模式要求 deptId/roleId 非空且 >=0;小程序用户用占位值,身份写在 roleName/authorities
|
||||||
|
userDetail.setDeptId(Func.toStr(user.getDeptId(), MpUserAuth.PLACEHOLDER_ID));
|
||||||
|
if (Func.isBlank(userDetail.getDeptId()) || "-1".equals(userDetail.getDeptId())) {
|
||||||
|
userDetail.setDeptId(MpUserAuth.PLACEHOLDER_ID);
|
||||||
|
}
|
||||||
|
userDetail.setPostId(Func.toStr(user.getPostId(), MpUserAuth.PLACEHOLDER_ID));
|
||||||
|
userDetail.setRoleId(MpUserAuth.PLACEHOLDER_ID);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+14
@@ -144,6 +144,12 @@ public class Dept extends TenantEntity {
|
|||||||
@Schema(description = "金蝶部门代码")
|
@Schema(description = "金蝶部门代码")
|
||||||
private String kingdeeDeptCode;
|
private String kingdeeDeptCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 合同编码前缀(合同编号:前缀(年份)三位流水号)
|
||||||
|
*/
|
||||||
|
@Schema(description = "合同编码前缀")
|
||||||
|
private String contractCodePrefix;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 组织简称
|
* 组织简称
|
||||||
*/
|
*/
|
||||||
@@ -210,4 +216,12 @@ public class Dept extends TenantEntity {
|
|||||||
@TableField(updateStrategy = FieldStrategy.NOT_NULL)
|
@TableField(updateStrategy = FieldStrategy.NOT_NULL)
|
||||||
private String oaSupSubComId;
|
private String oaSupSubComId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OA上次更新时间(OA 的 modified 字段),仅同步比对使用
|
||||||
|
*/
|
||||||
|
@JsonIgnore
|
||||||
|
@Schema(description = "OA上次更新时间", hidden = true)
|
||||||
|
@TableField(updateStrategy = FieldStrategy.NOT_NULL)
|
||||||
|
private String oaModified;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+2
@@ -25,6 +25,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.springblade.system.pojo.entity;
|
package org.springblade.system.pojo.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.FieldStrategy;
|
||||||
import com.baomidou.mybatisplus.annotation.TableField;
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
@@ -63,6 +64,7 @@ public class RailwayStation extends BaseEntity {
|
|||||||
* 电报略码
|
* 电报略码
|
||||||
*/
|
*/
|
||||||
@Schema(description = "电报略码")
|
@Schema(description = "电报略码")
|
||||||
|
@TableField(updateStrategy = FieldStrategy.ALWAYS)
|
||||||
private String telegraphCode;
|
private String telegraphCode;
|
||||||
/**
|
/**
|
||||||
* 车站名称
|
* 车站名称
|
||||||
|
|||||||
+10
-5
@@ -4,29 +4,34 @@ import lombok.AllArgsConstructor;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 部门类型枚举
|
* 组织类型枚举,与业务字典 organization_type 对齐
|
||||||
|
*
|
||||||
* @author bfhuange
|
* @author bfhuange
|
||||||
* @date 2024/8/26
|
* @date 2024/8/26
|
||||||
*/
|
*/
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@Getter
|
@Getter
|
||||||
public enum DeptCategory {
|
public enum DeptCategory {
|
||||||
|
/**
|
||||||
|
* 集团
|
||||||
|
*/
|
||||||
|
GROUP(1, "集团"),
|
||||||
/**
|
/**
|
||||||
* 公司
|
* 公司
|
||||||
*/
|
*/
|
||||||
COMPANY(1, "公司"),
|
COMPANY(2, "公司"),
|
||||||
/**
|
/**
|
||||||
* 部门
|
* 部门
|
||||||
*/
|
*/
|
||||||
DEPT(2, "部门"),
|
DEPT(3, "部门"),
|
||||||
;
|
;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 部门类型编码
|
* 组织类型编码
|
||||||
*/
|
*/
|
||||||
private final Integer code;
|
private final Integer code;
|
||||||
/**
|
/**
|
||||||
* 部门类型名称
|
* 组织类型名称
|
||||||
*/
|
*/
|
||||||
private final String name;
|
private final String name;
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-1
@@ -35,8 +35,12 @@ public enum UserSex {
|
|||||||
if (name == null || name.isEmpty()) {
|
if (name == null || name.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
String sexName = name.trim();
|
||||||
|
if ("-1".equals(sexName) || "0".equals(sexName) || "未知".equals(sexName)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return Stream.of(values())
|
return Stream.of(values())
|
||||||
.filter(e -> e.getName().equals(name))
|
.filter(e -> e.getName().equals(sexName) || String.valueOf(e.getCode()).equals(sexName))
|
||||||
.map(UserSex::getCode)
|
.map(UserSex::getCode)
|
||||||
.findFirst().orElse(null);
|
.findFirst().orElse(null);
|
||||||
}
|
}
|
||||||
|
|||||||
+3
@@ -66,4 +66,7 @@ public class OaOrgSyncPageVO implements Serializable {
|
|||||||
|
|
||||||
@Schema(description = "是否已到最后一页")
|
@Schema(description = "是否已到最后一页")
|
||||||
private Boolean finished;
|
private Boolean finished;
|
||||||
|
|
||||||
|
@Schema(description = "增量同步使用的 OA modified 起始时间,后续分页需原样回传")
|
||||||
|
private String modifiedSince;
|
||||||
}
|
}
|
||||||
|
|||||||
+3
@@ -63,4 +63,7 @@ public class OaPersonSyncPageVO implements Serializable {
|
|||||||
|
|
||||||
@Schema(description = "是否已到最后一页")
|
@Schema(description = "是否已到最后一页")
|
||||||
private Boolean finished;
|
private Boolean finished;
|
||||||
|
|
||||||
|
@Schema(description = "增量同步使用的 OA modified 起始时间,后续分页需原样回传")
|
||||||
|
private String modifiedSince;
|
||||||
}
|
}
|
||||||
|
|||||||
+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");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+3
@@ -46,4 +46,7 @@ public class ExceptionDisposalFollowRequest implements Serializable {
|
|||||||
@Schema(description = "跟进说明")
|
@Schema(description = "跟进说明")
|
||||||
private String followContent;
|
private String followContent;
|
||||||
|
|
||||||
|
@Schema(description = "附件JSON,[{\"name\":\"...\",\"url\":\"...\"}]")
|
||||||
|
private String attachmentsJson;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+63
@@ -0,0 +1,63 @@
|
|||||||
|
/**
|
||||||
|
* BladeX Commercial License Agreement
|
||||||
|
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||||
|
* <p>
|
||||||
|
* Use of this software is governed by the Commercial License Agreement
|
||||||
|
* obtained after purchasing a license from BladeX.
|
||||||
|
* <p>
|
||||||
|
* 1. This software is for development use only under a valid license
|
||||||
|
* from BladeX.
|
||||||
|
* <p>
|
||||||
|
* 2. Redistribution of this software's source code to any third party
|
||||||
|
* without a commercial license is strictly prohibited.
|
||||||
|
* <p>
|
||||||
|
* 3. Licensees may copyright their own code but cannot use segments
|
||||||
|
* from this software for such purposes. Copyright of this software
|
||||||
|
* remains with BladeX.
|
||||||
|
* <p>
|
||||||
|
* Using this software signifies agreement to this License, and the software
|
||||||
|
* must not be used for illegal purposes.
|
||||||
|
* <p>
|
||||||
|
* Author: Chill Zhuang (bladejava@qq.com)
|
||||||
|
*/
|
||||||
|
package org.springblade.transport.pojo.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异常处置编辑请求
|
||||||
|
* <p>
|
||||||
|
* 只承载上报四件套。车牌、项目、承运商、配载单号等字段由上报时从运单回填,
|
||||||
|
* 不在此列,避免编辑后与运单脱钩。
|
||||||
|
* <p>
|
||||||
|
* 不含处置状态:状态只能经跟进与完成流转,编辑不得改变它。
|
||||||
|
*
|
||||||
|
* @author Chill
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Schema(description = "异常处置编辑请求")
|
||||||
|
public class ExceptionDisposalUpdateRequest implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "异常处置ID")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Schema(description = "异常类型")
|
||||||
|
private String exceptionType;
|
||||||
|
|
||||||
|
@Schema(description = "异常原因")
|
||||||
|
private String exceptionReason;
|
||||||
|
|
||||||
|
@Schema(description = "上报说明")
|
||||||
|
private String reportDescription;
|
||||||
|
|
||||||
|
@Schema(description = "现场照片,逗号分隔")
|
||||||
|
private String scenePhotos;
|
||||||
|
|
||||||
|
}
|
||||||
+13
-1
@@ -30,7 +30,19 @@ public class NodeSubmitDTO implements Serializable {
|
|||||||
@Schema(description = "定位信息")
|
@Schema(description = "定位信息")
|
||||||
private Location location;
|
private Location location;
|
||||||
|
|
||||||
@Schema(description = "凭证照片 URL 列表")
|
/**
|
||||||
|
* 带类型的打卡照片,类型由提交方显式给出;非空时优先于 photos。
|
||||||
|
*/
|
||||||
|
@Schema(description = "带类型的照片列表,非空时优先于 photos")
|
||||||
|
private List<PunchPhotoDTO> photoItems;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 旧版小程序提交的裸 URL 列表,类型只能按下标回推过程配置的凭证类型。
|
||||||
|
* <p>
|
||||||
|
* 与 photoItems 并存是为了不强制升级小程序:新版提交 photoItems,旧版仍走这里;
|
||||||
|
* 等旧版本覆盖率达标后由后续改造删除。
|
||||||
|
*/
|
||||||
|
@Schema(description = "凭证照片 URL 列表,类型按下标回推")
|
||||||
private List<String> photos;
|
private List<String> photos;
|
||||||
|
|
||||||
@Schema(description = "重量(吨)")
|
@Schema(description = "重量(吨)")
|
||||||
|
|||||||
+13
-32
@@ -23,50 +23,31 @@
|
|||||||
* <p>
|
* <p>
|
||||||
* Author: Chill Zhuang (bladejava@qq.com)
|
* Author: Chill Zhuang (bladejava@qq.com)
|
||||||
*/
|
*/
|
||||||
package org.springblade.core.tool.node;
|
package org.springblade.transport.pojo.dto;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import org.springblade.core.tool.utils.Func;
|
|
||||||
|
|
||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
import java.util.Objects;
|
import java.io.Serializable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 树型节点类
|
* 证件 OCR 识别请求(小程序)
|
||||||
*
|
*
|
||||||
* @author smallchill
|
* @author Chill
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public class TreeNode extends BaseNode<TreeNode> {
|
@Schema(description = "证件OCR识别请求")
|
||||||
|
public class OcrRecognizeRequest implements Serializable {
|
||||||
|
|
||||||
@Serial
|
@Serial
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
private String title;
|
@Schema(description = "已上传图片的地址,必须是百度可访问的HTTP或HTTPS地址")
|
||||||
|
private String imageUrl;
|
||||||
@JsonSerialize(using = ToStringSerializer.class)
|
@Schema(description = "证件类型:idcard-身份证,driver-驾驶证,vehicle-行驶证,road_transport-道路运输证")
|
||||||
private Long key;
|
private String type;
|
||||||
|
@Schema(description = "正副面:front或back,默认front")
|
||||||
@JsonSerialize(using = ToStringSerializer.class)
|
private String side;
|
||||||
private Long value;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (this == obj) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (obj == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
TreeNode other = (TreeNode) obj;
|
|
||||||
return Func.equals(this.getId(), other.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(id, parentId);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
+98
@@ -0,0 +1,98 @@
|
|||||||
|
/**
|
||||||
|
* BladeX Commercial License Agreement
|
||||||
|
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||||
|
* <p>
|
||||||
|
* Use of this software is governed by the Commercial License Agreement
|
||||||
|
* obtained after purchasing a license from BladeX.
|
||||||
|
* <p>
|
||||||
|
* 1. This software is for development use only under a valid license
|
||||||
|
* from BladeX.
|
||||||
|
* <p>
|
||||||
|
* 2. Redistribution of this software's source code to any third party
|
||||||
|
* without a commercial license is strictly prohibited.
|
||||||
|
* <p>
|
||||||
|
* 3. Licensees may copyright their own code but cannot use segments
|
||||||
|
* from this software for such purposes. Copyright of this software
|
||||||
|
* remains with BladeX.
|
||||||
|
* <p>
|
||||||
|
* Using this software signifies agreement to this License, and the software
|
||||||
|
* must not be used for illegal purposes.
|
||||||
|
* <p>
|
||||||
|
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||||
|
* not liable for any claims arising from secondary or illegal development.
|
||||||
|
* <p>
|
||||||
|
* Author: Chill Zhuang (bladejava@qq.com)
|
||||||
|
*/
|
||||||
|
package org.springblade.transport.pojo.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 司机端个人认证提交请求
|
||||||
|
* <p>
|
||||||
|
* 一次提交承载个人认证的**全部**内容(身份证 + 驾驶证 + 从业资格证),
|
||||||
|
* 三证一起提交、一起审核,不拆开。
|
||||||
|
*
|
||||||
|
* @author Chill
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Schema(description = "司机端个人认证提交请求")
|
||||||
|
public class PersonCertSubmitRequest implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "真实姓名")
|
||||||
|
private String realName;
|
||||||
|
@Schema(description = "身份证号")
|
||||||
|
private String idCardNo;
|
||||||
|
@Schema(description = "身份证正面照")
|
||||||
|
private String idCardFrontUrl;
|
||||||
|
@Schema(description = "身份证反面照")
|
||||||
|
private String idCardBackUrl;
|
||||||
|
@Schema(description = "身份证有效期起")
|
||||||
|
private LocalDate idCardValidStart;
|
||||||
|
@Schema(description = "身份证有效期止")
|
||||||
|
private LocalDate idCardValidEnd;
|
||||||
|
@Schema(description = "身份证长期有效")
|
||||||
|
private Integer idCardLongTerm;
|
||||||
|
|
||||||
|
@Schema(description = "驾驶证档案编号")
|
||||||
|
private String driverFileNo;
|
||||||
|
@Schema(description = "准驾车型")
|
||||||
|
private String driverClass;
|
||||||
|
@Schema(description = "驾驶证主页照片")
|
||||||
|
private String driverLicenseFrontUrl;
|
||||||
|
@Schema(description = "驾驶证副页照片")
|
||||||
|
private String driverLicenseBackUrl;
|
||||||
|
@Schema(description = "驾驶证有效期起")
|
||||||
|
private LocalDate driverValidStart;
|
||||||
|
@Schema(description = "驾驶证有效期止")
|
||||||
|
private LocalDate driverValidEnd;
|
||||||
|
@Schema(description = "驾驶证长期有效")
|
||||||
|
private Integer driverLongTerm;
|
||||||
|
|
||||||
|
@Schema(description = "从业资格证类型")
|
||||||
|
private String qualificationType;
|
||||||
|
@Schema(description = "从业资格证号")
|
||||||
|
private String qualificationNo;
|
||||||
|
@Schema(description = "从业资格证封面页")
|
||||||
|
private String qualificationFront;
|
||||||
|
@Schema(description = "从业资格证内容页")
|
||||||
|
private String qualificationBack;
|
||||||
|
@Schema(description = "从业资格证有效期起")
|
||||||
|
private LocalDate qualificationValidStart;
|
||||||
|
@Schema(description = "从业资格证有效期止")
|
||||||
|
private LocalDate qualificationValidEnd;
|
||||||
|
@Schema(description = "从业资格证长期有效")
|
||||||
|
private Integer qualificationLongTerm;
|
||||||
|
|
||||||
|
@Schema(description = "大头照")
|
||||||
|
private String headPhoto;
|
||||||
|
|
||||||
|
}
|
||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* BladeX Commercial License Agreement
|
||||||
|
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||||
|
*/
|
||||||
|
package org.springblade.transport.pojo.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 带类型的打卡照片
|
||||||
|
* <p>
|
||||||
|
* 类型取值域是固定的「打卡照片」加该节点过程配置勾选的凭证类型,域外类型一律拒绝:
|
||||||
|
* 类型是照片分组的唯一依据,放进来路不明的类型会让回显分组错乱。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Schema(description = "带类型的打卡照片")
|
||||||
|
public class PunchPhotoDTO implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "照片类型")
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
@Schema(description = "图片URL")
|
||||||
|
private String url;
|
||||||
|
|
||||||
|
}
|
||||||
+115
@@ -0,0 +1,115 @@
|
|||||||
|
/**
|
||||||
|
* BladeX Commercial License Agreement
|
||||||
|
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||||
|
* <p>
|
||||||
|
* Use of this software is governed by the Commercial License Agreement
|
||||||
|
* obtained after purchasing a license from BladeX.
|
||||||
|
* <p>
|
||||||
|
* 1. This software is for development use only under a valid license
|
||||||
|
* from BladeX.
|
||||||
|
* <p>
|
||||||
|
* 2. Redistribution of this software's source code to any third party
|
||||||
|
* without a commercial license is strictly prohibited.
|
||||||
|
* <p>
|
||||||
|
* 3. Licensees may copyright their own code but cannot use segments
|
||||||
|
* from this software for such purposes. Copyright of this software
|
||||||
|
* remains with BladeX.
|
||||||
|
* <p>
|
||||||
|
* Using this software signifies agreement to this License, and the software
|
||||||
|
* must not be used for illegal purposes.
|
||||||
|
* <p>
|
||||||
|
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||||
|
* not liable for any claims arising from secondary or illegal development.
|
||||||
|
* <p>
|
||||||
|
* Author: Chill Zhuang (bladejava@qq.com)
|
||||||
|
*/
|
||||||
|
package org.springblade.transport.pojo.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 司机端车辆认证提交请求
|
||||||
|
* <p>
|
||||||
|
* 一次提交承载该车辆认证的**全部**内容(行驶证 + 道路运输证),两证一起提交、一起审核。
|
||||||
|
* 提交只产生新的待审记录,不覆盖车辆当前已生效的认证内容。
|
||||||
|
*
|
||||||
|
* @author Chill
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Schema(description = "司机端车辆认证提交请求")
|
||||||
|
public class VehicleCertSubmitRequest implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "车辆ID:新增时为空,重新认证时必填")
|
||||||
|
private Long id;
|
||||||
|
@Schema(description = "车牌号")
|
||||||
|
private String plateNo;
|
||||||
|
@Schema(description = "所有人:行驶证登记的单位名称,不是司机")
|
||||||
|
private String ownerName;
|
||||||
|
@Schema(description = "车牌颜色")
|
||||||
|
private String plateColor;
|
||||||
|
@Schema(description = "车辆类型")
|
||||||
|
private String vehicleType;
|
||||||
|
@Schema(description = "车辆识别代号(车架号),选填")
|
||||||
|
private String vin;
|
||||||
|
|
||||||
|
@Schema(description = "外廓长度,毫米")
|
||||||
|
private Integer outerLength;
|
||||||
|
@Schema(description = "外廓宽度,毫米")
|
||||||
|
private Integer outerWidth;
|
||||||
|
@Schema(description = "外廓高度,毫米")
|
||||||
|
private Integer outerHeight;
|
||||||
|
@Schema(description = "核定载质量,KG")
|
||||||
|
private Integer approvedLoadKg;
|
||||||
|
@Schema(description = "准牵引总质量,KG")
|
||||||
|
private Integer tractionMassKg;
|
||||||
|
@Schema(description = "能源类型")
|
||||||
|
private String energyType;
|
||||||
|
@Schema(description = "业务关系:自有 / 管理挂靠 / 业务合作")
|
||||||
|
private String businessRelation;
|
||||||
|
@Schema(description = "强制报废日期")
|
||||||
|
private LocalDate compulsoryScrapDate;
|
||||||
|
@Schema(description = "强制报废长期有效:0否 1是")
|
||||||
|
private Integer compulsoryScrapLongTerm;
|
||||||
|
|
||||||
|
@Schema(description = "行驶证档案编号")
|
||||||
|
private String drivingLicenseNo;
|
||||||
|
@Schema(description = "行驶证主页正面照片")
|
||||||
|
private String drivingLicenseImage;
|
||||||
|
@Schema(description = "行驶证主页反面照片")
|
||||||
|
private String drivingLicenseMainBack;
|
||||||
|
@Schema(description = "行驶证副页正面照片")
|
||||||
|
private String drivingLicenseViceFront;
|
||||||
|
@Schema(description = "行驶证副页反面照片")
|
||||||
|
private String drivingLicenseViceBack;
|
||||||
|
@Schema(description = "行驶证有效期起")
|
||||||
|
private LocalDate drivingLicenseStartDate;
|
||||||
|
@Schema(description = "行驶证有效期止")
|
||||||
|
private LocalDate drivingLicenseEndDate;
|
||||||
|
@Schema(description = "行驶证长期有效:0否 1是")
|
||||||
|
private Integer drivingLicenseLongTerm;
|
||||||
|
|
||||||
|
@Schema(description = "道路运输证号")
|
||||||
|
private String roadTransportCertNo;
|
||||||
|
@Schema(description = "道路运输证照片")
|
||||||
|
private String roadTransportCertImage;
|
||||||
|
@Schema(description = "道路运输证有效期起")
|
||||||
|
private LocalDate roadTransportCertStartDate;
|
||||||
|
@Schema(description = "道路运输证有效期止")
|
||||||
|
private LocalDate roadTransportCertEndDate;
|
||||||
|
@Schema(description = "道路运输证长期有效:0否 1是")
|
||||||
|
private Integer roadTransportCertLongTerm;
|
||||||
|
|
||||||
|
@Schema(description = "机动车登记编号")
|
||||||
|
private String registrationNo;
|
||||||
|
@Schema(description = "机动车登记本照片")
|
||||||
|
private String registrationImage;
|
||||||
|
|
||||||
|
}
|
||||||
+6
@@ -32,6 +32,12 @@ public class WaybillImportBatchRequest {
|
|||||||
private Long planId;
|
private Long planId;
|
||||||
private String planName;
|
private String planName;
|
||||||
private String remark;
|
private String remark;
|
||||||
|
/** 列表筛选:创建人 */
|
||||||
|
private Long createUser;
|
||||||
|
/** 列表筛选:创建时间起(yyyy-MM-dd HH:mm:ss) */
|
||||||
|
private String createTimeStart;
|
||||||
|
/** 列表筛选:创建时间止(yyyy-MM-dd HH:mm:ss) */
|
||||||
|
private String createTimeEnd;
|
||||||
private List<Map<String, Object>> rows;
|
private List<Map<String, Object>> rows;
|
||||||
/** 分批确认导入时,是否追加到已有批次(首批为 false,后续批为 true)。 */
|
/** 分批确认导入时,是否追加到已有批次(首批为 false,后续批为 true)。 */
|
||||||
private Boolean append;
|
private Boolean append;
|
||||||
|
|||||||
+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.transport.pojo.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理端打卡记录编辑请求
|
||||||
|
* <p>
|
||||||
|
* 承载时间轴上单条打卡记录的全部可编辑项,字段按过程配置取舍:
|
||||||
|
* 打卡定位(地址/经纬度)、上传货量(重量/体积/数量)、上传凭证(照片)。
|
||||||
|
* <p>
|
||||||
|
* {@code punched=false} 表示取消打卡,服务端会清除该节点的打卡流水
|
||||||
|
* ——时间轴上「未打卡」的判据就是没有流水,故不能只改状态位。
|
||||||
|
*
|
||||||
|
* @author Chill
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Schema(description = "管理端打卡记录编辑请求")
|
||||||
|
public class WaybillPunchRecordSaveRequest implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "运单ID")
|
||||||
|
private Long waybillId;
|
||||||
|
|
||||||
|
@Schema(description = "打卡类型:node=过程节点打卡,enroute=在途打卡")
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
@Schema(description = "过程节点 key")
|
||||||
|
private String nodeCode;
|
||||||
|
|
||||||
|
@Schema(description = "过程节点名称")
|
||||||
|
private String nodeName;
|
||||||
|
|
||||||
|
@Schema(description = "打卡状态:true 已打卡,false 未打卡")
|
||||||
|
private Boolean punched;
|
||||||
|
|
||||||
|
@Schema(description = "打卡时间 yyyy-MM-dd HH:mm:ss,为空时取当前时间")
|
||||||
|
private String punchTime;
|
||||||
|
|
||||||
|
@Schema(description = "打卡地址")
|
||||||
|
private String address;
|
||||||
|
|
||||||
|
@Schema(description = "经度")
|
||||||
|
private String longitude;
|
||||||
|
|
||||||
|
@Schema(description = "纬度")
|
||||||
|
private String latitude;
|
||||||
|
|
||||||
|
@Schema(description = "重量(吨)")
|
||||||
|
private String weight;
|
||||||
|
|
||||||
|
@Schema(description = "体积(方)")
|
||||||
|
private String volume;
|
||||||
|
|
||||||
|
@Schema(description = "数量(件)")
|
||||||
|
private String quantity;
|
||||||
|
|
||||||
|
@Schema(description = "备注")
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
@Schema(description = "打卡凭证图,按上传顺序回写凭证类型")
|
||||||
|
private List<Photo> photos = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打卡凭证图
|
||||||
|
* <p>
|
||||||
|
* 类型随图一起回写:展示层对「逗号分隔的裸 URL」按凭证类型下标回推标签,
|
||||||
|
* 编辑换了顺序就会串标,所以这里保留原类型。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Schema(description = "打卡凭证图")
|
||||||
|
public static class Photo implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "凭证类型")
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
@Schema(description = "图片URL")
|
||||||
|
private String url;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+10
-3
@@ -61,10 +61,10 @@ public class ContractManage extends TenantEntity {
|
|||||||
@Schema(description = "所属项目")
|
@Schema(description = "所属项目")
|
||||||
private String projectName;
|
private String projectName;
|
||||||
|
|
||||||
@Schema(description = "所属组织ID")
|
@Schema(description = "合同负责部门ID")
|
||||||
private Long organizationId;
|
private Long organizationId;
|
||||||
|
|
||||||
@Schema(description = "所属组织")
|
@Schema(description = "合同负责部门")
|
||||||
private String organizationName;
|
private String organizationName;
|
||||||
|
|
||||||
@Schema(description = "合同类别")
|
@Schema(description = "合同类别")
|
||||||
@@ -100,6 +100,9 @@ public class ContractManage extends TenantEntity {
|
|||||||
@Schema(description = "签订日期")
|
@Schema(description = "签订日期")
|
||||||
private LocalDate signDate;
|
private LocalDate signDate;
|
||||||
|
|
||||||
|
@Schema(description = "申请日期(提交正式合同时写入)")
|
||||||
|
private LocalDate applyDate;
|
||||||
|
|
||||||
@Schema(description = "结算方式")
|
@Schema(description = "结算方式")
|
||||||
private String settlementMode;
|
private String settlementMode;
|
||||||
|
|
||||||
@@ -107,6 +110,7 @@ public class ContractManage extends TenantEntity {
|
|||||||
private String contractFormat;
|
private String contractFormat;
|
||||||
|
|
||||||
@Schema(description = "是否需要加盖法人章")
|
@Schema(description = "是否需要加盖法人章")
|
||||||
|
@TableField(insertStrategy = FieldStrategy.ALWAYS, updateStrategy = FieldStrategy.ALWAYS)
|
||||||
private Integer legalSealFlag;
|
private Integer legalSealFlag;
|
||||||
|
|
||||||
@Schema(description = "一式份数")
|
@Schema(description = "一式份数")
|
||||||
@@ -206,7 +210,10 @@ public class ContractManage extends TenantEntity {
|
|||||||
@Schema(description = "终止原因")
|
@Schema(description = "终止原因")
|
||||||
private String terminateReason;
|
private String terminateReason;
|
||||||
|
|
||||||
@Schema(description = "备注")
|
@Schema(description = "合同主要内容")
|
||||||
|
private String mainContent;
|
||||||
|
|
||||||
|
@Schema(description = "其他事项说明")
|
||||||
private String remark;
|
private String remark;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+56
-2
@@ -28,10 +28,15 @@ package org.springblade.transport.pojo.entity;
|
|||||||
import com.baomidou.mybatisplus.annotation.FieldStrategy;
|
import com.baomidou.mybatisplus.annotation.FieldStrategy;
|
||||||
import com.baomidou.mybatisplus.annotation.TableField;
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
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 io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import org.springblade.core.tenant.mp.TenantEntity;
|
import org.springblade.core.tenant.mp.TenantEntity;
|
||||||
|
import org.springblade.transport.jackson.FlexibleLocalDateDeserializer;
|
||||||
|
|
||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
@@ -64,6 +69,7 @@ public class Driver extends TenantEntity {
|
|||||||
* 出生年月
|
* 出生年月
|
||||||
*/
|
*/
|
||||||
@Schema(description = "出生年月")
|
@Schema(description = "出生年月")
|
||||||
|
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
|
||||||
private LocalDate birthday;
|
private LocalDate birthday;
|
||||||
/**
|
/**
|
||||||
* 性别
|
* 性别
|
||||||
@@ -111,6 +117,23 @@ public class Driver extends TenantEntity {
|
|||||||
*/
|
*/
|
||||||
@Schema(description = "身份证反面照")
|
@Schema(description = "身份证反面照")
|
||||||
private String idCardBack;
|
private String idCardBack;
|
||||||
|
/**
|
||||||
|
* 身份证有效期起
|
||||||
|
*/
|
||||||
|
@Schema(description = "身份证有效期起")
|
||||||
|
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
|
||||||
|
private LocalDate idCardValidStart;
|
||||||
|
/**
|
||||||
|
* 身份证有效期止
|
||||||
|
*/
|
||||||
|
@Schema(description = "身份证有效期止")
|
||||||
|
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
|
||||||
|
private LocalDate idCardValidEnd;
|
||||||
|
/**
|
||||||
|
* 身份证长期有效
|
||||||
|
*/
|
||||||
|
@Schema(description = "身份证长期有效")
|
||||||
|
private Integer idCardLongTerm;
|
||||||
/**
|
/**
|
||||||
* 大头照
|
* 大头照
|
||||||
*/
|
*/
|
||||||
@@ -130,11 +153,13 @@ public class Driver extends TenantEntity {
|
|||||||
* 驾驶证有效期起
|
* 驾驶证有效期起
|
||||||
*/
|
*/
|
||||||
@Schema(description = "驾驶证有效期起")
|
@Schema(description = "驾驶证有效期起")
|
||||||
|
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
|
||||||
private LocalDate drivingLicenseStartDate;
|
private LocalDate drivingLicenseStartDate;
|
||||||
/**
|
/**
|
||||||
* 驾驶证有效期止
|
* 驾驶证有效期止
|
||||||
*/
|
*/
|
||||||
@Schema(description = "驾驶证有效期止")
|
@Schema(description = "驾驶证有效期止")
|
||||||
|
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
|
||||||
private LocalDate drivingLicenseEndDate;
|
private LocalDate drivingLicenseEndDate;
|
||||||
/**
|
/**
|
||||||
* 驾驶证长期有效
|
* 驾驶证长期有效
|
||||||
@@ -165,6 +190,7 @@ public class Driver extends TenantEntity {
|
|||||||
* 从业资格证有效期止
|
* 从业资格证有效期止
|
||||||
*/
|
*/
|
||||||
@Schema(description = "从业资格证有效期止")
|
@Schema(description = "从业资格证有效期止")
|
||||||
|
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
|
||||||
private LocalDate qualificationEndDate;
|
private LocalDate qualificationEndDate;
|
||||||
/**
|
/**
|
||||||
* 从业资格证长期有效
|
* 从业资格证长期有效
|
||||||
@@ -192,9 +218,10 @@ public class Driver extends TenantEntity {
|
|||||||
@Schema(description = "手机号")
|
@Schema(description = "手机号")
|
||||||
private String mobile;
|
private String mobile;
|
||||||
/**
|
/**
|
||||||
* 关联系统用户ID
|
* 关联小程序用户ID(blade_mp_user.id)
|
||||||
*/
|
*/
|
||||||
@Schema(description = "关联系统用户ID")
|
@Schema(description = "关联小程序用户ID")
|
||||||
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
private Long userId;
|
private Long userId;
|
||||||
/**
|
/**
|
||||||
* 与联系人关系
|
* 与联系人关系
|
||||||
@@ -221,5 +248,32 @@ public class Driver extends TenantEntity {
|
|||||||
*/
|
*/
|
||||||
@Schema(description = "备注")
|
@Schema(description = "备注")
|
||||||
private String remark;
|
private String remark;
|
||||||
|
/**
|
||||||
|
* 最新提交状态展示缓存:0审核中 1已通过 2已驳回 3已作废,null 表示从未提交
|
||||||
|
* <p>
|
||||||
|
* 仅为列表展示而冗余,<b>不是</b>可用性判据;「是否已认证」一律读生效快照指针。
|
||||||
|
*/
|
||||||
|
@Schema(description = "最新提交状态展示缓存:0审核中 1已通过 2已驳回 3已作废,null为从未提交")
|
||||||
|
private Integer certificationStatus;
|
||||||
|
/**
|
||||||
|
* 最新一次驳回原因展示缓存
|
||||||
|
*/
|
||||||
|
@Schema(description = "最新一次驳回原因展示缓存")
|
||||||
|
private String certificationRejectReason;
|
||||||
|
/**
|
||||||
|
* 生效快照指针:指向 blade_transport_cert_record.id
|
||||||
|
* <p>
|
||||||
|
* 「该司机是否已认证」的<b>唯一</b>判据。为 null 表示从未认证通过;
|
||||||
|
* 一经设置<b>永不回退</b>——提交、驳回、作废都不得修改它,
|
||||||
|
* 因此重新提交认证不会让一个已认证的司机失去接单资格。
|
||||||
|
* <p>
|
||||||
|
* ⚠️ {@code READ_ONLY}:本字段<b>只能由审核通过时</b>写入。
|
||||||
|
* 司机档案的 {@code /submit} 直接以本实体接收请求体,若允许反序列化,
|
||||||
|
* 调用方就能自行填一个记录 ID 把自己「认证」掉,绕过审核。
|
||||||
|
*/
|
||||||
|
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
|
||||||
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
|
@Schema(description = "生效快照指针")
|
||||||
|
private Long effectiveCertRecordId;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+6
@@ -51,6 +51,12 @@ public class ExceptionDisposal extends TenantEntity {
|
|||||||
@Schema(description = "运单号")
|
@Schema(description = "运单号")
|
||||||
private String waybillNo;
|
private String waybillNo;
|
||||||
|
|
||||||
|
@Schema(description = "过程节点 key(为空表示未指明节点)")
|
||||||
|
private String nodeCode;
|
||||||
|
|
||||||
|
@Schema(description = "过程节点名称(上报时复制,此后不随过程配置变化)")
|
||||||
|
private String nodeName;
|
||||||
|
|
||||||
@Schema(description = "配载单号/总单号")
|
@Schema(description = "配载单号/总单号")
|
||||||
private String loadingOrMasterNo;
|
private String loadingOrMasterNo;
|
||||||
|
|
||||||
|
|||||||
+3
@@ -51,6 +51,9 @@ public class ExceptionDisposalFollowRecord extends TenantEntity {
|
|||||||
@Schema(description = "跟进说明")
|
@Schema(description = "跟进说明")
|
||||||
private String followContent;
|
private String followContent;
|
||||||
|
|
||||||
|
@Schema(description = "附件JSON")
|
||||||
|
private String attachmentsJson;
|
||||||
|
|
||||||
@Schema(description = "跟进人")
|
@Schema(description = "跟进人")
|
||||||
private Long followUser;
|
private Long followUser;
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -70,10 +70,10 @@ public class ProjectApply extends TenantEntity {
|
|||||||
@Schema(description = "业务部门")
|
@Schema(description = "业务部门")
|
||||||
private String businessDeptName;
|
private String businessDeptName;
|
||||||
|
|
||||||
@Schema(description = "承办部门ID")
|
@Schema(description = "平台公司ID,多个逗号分隔")
|
||||||
private Long undertakeDeptId;
|
private String undertakeDeptId;
|
||||||
|
|
||||||
@Schema(description = "承办部门")
|
@Schema(description = "平台公司,多个顿号分隔")
|
||||||
private String undertakeDeptName;
|
private String undertakeDeptName;
|
||||||
|
|
||||||
@Schema(description = "项目由来")
|
@Schema(description = "项目由来")
|
||||||
|
|||||||
+158
@@ -0,0 +1,158 @@
|
|||||||
|
/**
|
||||||
|
* BladeX Commercial License Agreement
|
||||||
|
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||||
|
* <p>
|
||||||
|
* Use of this software is governed by the Commercial License Agreement
|
||||||
|
* obtained after purchasing a license from BladeX.
|
||||||
|
* <p>
|
||||||
|
* 1. This software is for development use only under a valid license
|
||||||
|
* from BladeX.
|
||||||
|
* <p>
|
||||||
|
* 2. Redistribution of this software's source code to any third party
|
||||||
|
* without a commercial license is strictly prohibited.
|
||||||
|
* <p>
|
||||||
|
* 3. Licensees may copyright their own code but cannot use segments
|
||||||
|
* from this software for such purposes. Copyright of this software
|
||||||
|
* remains with BladeX.
|
||||||
|
* <p>
|
||||||
|
* Using this software signifies agreement to this License, and the software
|
||||||
|
* must not be used for illegal purposes.
|
||||||
|
* <p>
|
||||||
|
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||||
|
* not liable for any claims arising from secondary or illegal development.
|
||||||
|
* <p>
|
||||||
|
* Author: Chill Zhuang (bladejava@qq.com)
|
||||||
|
*/
|
||||||
|
package org.springblade.transport.pojo.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.FieldStrategy;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||||
|
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import org.springblade.core.tenant.mp.TenantEntity;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 认证记录实体类
|
||||||
|
* <p>
|
||||||
|
* 认证对象(司机 / 车辆)<b>每一次提交</b>所产生的、<b>不可变</b>的快照。
|
||||||
|
* 记录一经创建,其 {@code snapshotJson} 与提交元信息永不修改;被同对象的后续提交取代时
|
||||||
|
* 标记为「已作废」,而<b>不是</b>删除。判定某个对象「是否已认证」只看它持有的生效快照指针,
|
||||||
|
* 与记录表里的任何一条记录的状态都无关。
|
||||||
|
* <p>
|
||||||
|
* ⚠️ <b>{@code status} 承载的是「审核状态」,不是 BladeX 的「启用 / 停用」。</b>
|
||||||
|
* 该字段继承自 {@link TenantEntity},带来两条必须堵住的框架通道:
|
||||||
|
* 一是 {@code BaseServiceImpl#resolveEntity} 在插入时会把 {@code null} 的 {@code status}
|
||||||
|
* <b>静默填成 {@code 1}</b>(恰好是 {@link #STATUS_APPROVED});
|
||||||
|
* 二是继承来的 {@code changeStatus(ids, 1)} 会直接写 {@code status},等于批量通过审核。
|
||||||
|
* 二者都已在 {@code TransportCertRecordServiceImpl} 中封堵(前者校验抛错、后者覆写抛错)。
|
||||||
|
* 审核必须逐条、带审核人与审核时间落库,不得借道 {@code changeStatus} 批量改状态。
|
||||||
|
*
|
||||||
|
* @author Chill
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@TableName("blade_transport_cert_record")
|
||||||
|
@Schema(description = "认证记录")
|
||||||
|
public class TransportCertRecord extends TenantEntity {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 认证对象类型:司机
|
||||||
|
*/
|
||||||
|
public static final String BIZ_TYPE_DRIVER = "driver";
|
||||||
|
/**
|
||||||
|
* 认证对象类型:车辆
|
||||||
|
*/
|
||||||
|
public static final String BIZ_TYPE_VEHICLE = "vehicle";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审核状态:审核中(即该对象的「待审快照」)
|
||||||
|
*/
|
||||||
|
public static final Integer STATUS_PENDING = 0;
|
||||||
|
/**
|
||||||
|
* 审核状态:已通过(该对象最新一条已通过者即其「生效快照」)
|
||||||
|
*/
|
||||||
|
public static final Integer STATUS_APPROVED = 1;
|
||||||
|
/**
|
||||||
|
* 审核状态:已驳回(审核的否定结论,需带驳回原因)
|
||||||
|
*/
|
||||||
|
public static final Integer STATUS_REJECTED = 2;
|
||||||
|
/**
|
||||||
|
* 审核状态:已作废(被同对象的再次提交取代,非驳回)
|
||||||
|
*/
|
||||||
|
public static final Integer STATUS_SUPERSEDED = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 驳回原因最大长度,与 {@code reject_reason} 列宽保持一致
|
||||||
|
*/
|
||||||
|
public static final int REJECT_REASON_MAX_LENGTH = 200;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 认证对象类型:driver-司机,vehicle-车辆
|
||||||
|
* <p>
|
||||||
|
* {@code NEVER}:记录一经创建,认证对象的身份不可改。
|
||||||
|
*/
|
||||||
|
@TableField(updateStrategy = FieldStrategy.NEVER)
|
||||||
|
@Schema(description = "认证对象类型:driver-司机,vehicle-车辆")
|
||||||
|
private String bizType;
|
||||||
|
/**
|
||||||
|
* 认证对象主键:司机ID 或 车辆ID(车辆按具体车辆,不是司机)
|
||||||
|
*/
|
||||||
|
@TableField(updateStrategy = FieldStrategy.NEVER)
|
||||||
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
|
@Schema(description = "认证对象主键")
|
||||||
|
private Long bizId;
|
||||||
|
/**
|
||||||
|
* 展示键:司机姓名 / 车牌号,供审核列表直接展示
|
||||||
|
*/
|
||||||
|
@Schema(description = "展示键:司机姓名 / 车牌号")
|
||||||
|
private String bizKey;
|
||||||
|
/**
|
||||||
|
* 该次提交的全量认证内容(JSON),一经创建不可修改
|
||||||
|
* <p>
|
||||||
|
* {@code NEVER}:本字段<b>永不进入 UPDATE 语句</b>,因此不存在「某条链路
|
||||||
|
* 忘了保护快照」的可能。改内容只能靠<b>新提交一条记录</b>。
|
||||||
|
*/
|
||||||
|
@TableField(updateStrategy = FieldStrategy.NEVER)
|
||||||
|
@Schema(description = "该次提交的全量认证内容JSON")
|
||||||
|
private String snapshotJson;
|
||||||
|
/**
|
||||||
|
* 驳回原因,仅当审核状态为已驳回时有值
|
||||||
|
*/
|
||||||
|
@Schema(description = "驳回原因")
|
||||||
|
private String rejectReason;
|
||||||
|
/**
|
||||||
|
* 提交时间
|
||||||
|
*/
|
||||||
|
@TableField(updateStrategy = FieldStrategy.NEVER)
|
||||||
|
@Schema(description = "提交时间")
|
||||||
|
private LocalDateTime submitTime;
|
||||||
|
/**
|
||||||
|
* 提交人
|
||||||
|
*/
|
||||||
|
@TableField(updateStrategy = FieldStrategy.NEVER)
|
||||||
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
|
@Schema(description = "提交人")
|
||||||
|
private Long submitUser;
|
||||||
|
/**
|
||||||
|
* 审核时间
|
||||||
|
*/
|
||||||
|
@Schema(description = "审核时间")
|
||||||
|
private LocalDateTime auditTime;
|
||||||
|
/**
|
||||||
|
* 审核人
|
||||||
|
*/
|
||||||
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
|
@Schema(description = "审核人")
|
||||||
|
private Long auditUser;
|
||||||
|
|
||||||
|
}
|
||||||
+38
@@ -26,10 +26,15 @@
|
|||||||
package org.springblade.transport.pojo.entity;
|
package org.springblade.transport.pojo.entity;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
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 io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import org.springblade.core.tenant.mp.TenantEntity;
|
import org.springblade.core.tenant.mp.TenantEntity;
|
||||||
|
import org.springblade.transport.jackson.FlexibleLocalDateDeserializer;
|
||||||
|
|
||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
@@ -63,6 +68,11 @@ public class TransportVehicle extends TenantEntity {
|
|||||||
*/
|
*/
|
||||||
@Schema(description = "车牌号")
|
@Schema(description = "车牌号")
|
||||||
private String plateNo;
|
private String plateNo;
|
||||||
|
/**
|
||||||
|
* 所有人:行驶证登记的单位名称,不是司机
|
||||||
|
*/
|
||||||
|
@Schema(description = "所有人(行驶证登记的单位名称,不是司机)")
|
||||||
|
private String ownerName;
|
||||||
/**
|
/**
|
||||||
* 车牌颜色
|
* 车牌颜色
|
||||||
*/
|
*/
|
||||||
@@ -73,6 +83,11 @@ public class TransportVehicle extends TenantEntity {
|
|||||||
*/
|
*/
|
||||||
@Schema(description = "车辆类型")
|
@Schema(description = "车辆类型")
|
||||||
private String vehicleType;
|
private String vehicleType;
|
||||||
|
/**
|
||||||
|
* 车辆识别代号(车架号)
|
||||||
|
*/
|
||||||
|
@Schema(description = "车辆识别代号(车架号)")
|
||||||
|
private String vin;
|
||||||
/**
|
/**
|
||||||
* 外廓长度,毫米
|
* 外廓长度,毫米
|
||||||
*/
|
*/
|
||||||
@@ -112,6 +127,7 @@ public class TransportVehicle extends TenantEntity {
|
|||||||
* 强制报废日期
|
* 强制报废日期
|
||||||
*/
|
*/
|
||||||
@Schema(description = "强制报废日期")
|
@Schema(description = "强制报废日期")
|
||||||
|
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
|
||||||
private LocalDate compulsoryScrapDate;
|
private LocalDate compulsoryScrapDate;
|
||||||
/**
|
/**
|
||||||
* 强制报废长期有效
|
* 强制报废长期有效
|
||||||
@@ -132,11 +148,13 @@ public class TransportVehicle extends TenantEntity {
|
|||||||
* 行驶证有效期起
|
* 行驶证有效期起
|
||||||
*/
|
*/
|
||||||
@Schema(description = "行驶证有效期起")
|
@Schema(description = "行驶证有效期起")
|
||||||
|
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
|
||||||
private LocalDate drivingLicenseStartDate;
|
private LocalDate drivingLicenseStartDate;
|
||||||
/**
|
/**
|
||||||
* 行驶证有效期止
|
* 行驶证有效期止
|
||||||
*/
|
*/
|
||||||
@Schema(description = "行驶证有效期止")
|
@Schema(description = "行驶证有效期止")
|
||||||
|
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
|
||||||
private LocalDate drivingLicenseEndDate;
|
private LocalDate drivingLicenseEndDate;
|
||||||
/**
|
/**
|
||||||
* 行驶证长期有效
|
* 行驶证长期有效
|
||||||
@@ -172,11 +190,13 @@ public class TransportVehicle extends TenantEntity {
|
|||||||
* 道路运输证有效期起
|
* 道路运输证有效期起
|
||||||
*/
|
*/
|
||||||
@Schema(description = "道路运输证有效期起")
|
@Schema(description = "道路运输证有效期起")
|
||||||
|
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
|
||||||
private LocalDate roadTransportCertStartDate;
|
private LocalDate roadTransportCertStartDate;
|
||||||
/**
|
/**
|
||||||
* 道路运输证有效期止
|
* 道路运输证有效期止
|
||||||
*/
|
*/
|
||||||
@Schema(description = "道路运输证有效期止")
|
@Schema(description = "道路运输证有效期止")
|
||||||
|
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
|
||||||
private LocalDate roadTransportCertEndDate;
|
private LocalDate roadTransportCertEndDate;
|
||||||
/**
|
/**
|
||||||
* 道路运输证长期有效
|
* 道路运输证长期有效
|
||||||
@@ -192,6 +212,7 @@ public class TransportVehicle extends TenantEntity {
|
|||||||
* 道路运输年审有效期
|
* 道路运输年审有效期
|
||||||
*/
|
*/
|
||||||
@Schema(description = "道路运输年审有效期")
|
@Schema(description = "道路运输年审有效期")
|
||||||
|
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
|
||||||
private LocalDate annualReviewEndDate;
|
private LocalDate annualReviewEndDate;
|
||||||
/**
|
/**
|
||||||
* 道路运输年审长期有效
|
* 道路运输年审长期有效
|
||||||
@@ -207,6 +228,7 @@ public class TransportVehicle extends TenantEntity {
|
|||||||
* 机动车登记日期
|
* 机动车登记日期
|
||||||
*/
|
*/
|
||||||
@Schema(description = "机动车登记日期")
|
@Schema(description = "机动车登记日期")
|
||||||
|
@JsonDeserialize(using = FlexibleLocalDateDeserializer.class)
|
||||||
private LocalDate registrationDate;
|
private LocalDate registrationDate;
|
||||||
/**
|
/**
|
||||||
* 机动车登记本图片
|
* 机动车登记本图片
|
||||||
@@ -224,5 +246,21 @@ public class TransportVehicle extends TenantEntity {
|
|||||||
/** 认证驳回原因 */
|
/** 认证驳回原因 */
|
||||||
@Schema(description = "认证驳回原因")
|
@Schema(description = "认证驳回原因")
|
||||||
private String certificationRejectReason;
|
private String certificationRejectReason;
|
||||||
|
/**
|
||||||
|
* 生效快照指针:指向 blade_transport_cert_record.id
|
||||||
|
* <p>
|
||||||
|
* 「该车辆是否已认证」的<b>唯一</b>判据。为 null 表示从未认证通过;
|
||||||
|
* 一经设置<b>永不回退</b>——重新提交、待审、被驳回都不得修改它,
|
||||||
|
* 因此重新提交认证不会让一辆已认证的车掉出可用池。
|
||||||
|
* {@link #certificationStatus} 是「最新提交状态」的展示缓存,不代表是否已认证。
|
||||||
|
* <p>
|
||||||
|
* ⚠️ {@code READ_ONLY}:本字段<b>只能由审核通过时</b>写入。
|
||||||
|
* {@code POST /transport-vehicle/submit} 直接以本实体接收请求体,
|
||||||
|
* 若允许反序列化,调用方就能自行填一个记录 ID 把自己「认证」掉,绕过审核。
|
||||||
|
*/
|
||||||
|
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
|
||||||
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
|
@Schema(description = "生效快照指针")
|
||||||
|
private Long effectiveCertRecordId;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+6
@@ -25,6 +25,8 @@ package org.springblade.transport.pojo.entity;
|
|||||||
import com.baomidou.mybatisplus.annotation.FieldStrategy;
|
import com.baomidou.mybatisplus.annotation.FieldStrategy;
|
||||||
import com.baomidou.mybatisplus.annotation.TableField;
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||||
|
import com.fasterxml.jackson.databind.ser.std.NullSerializer;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
@@ -82,6 +84,7 @@ public class Waybill extends TenantEntity {
|
|||||||
@Schema(description = "型号")
|
@Schema(description = "型号")
|
||||||
private String model;
|
private String model;
|
||||||
|
|
||||||
|
@JsonSerialize(nullsUsing = NullSerializer.class)
|
||||||
@Schema(description = "数量")
|
@Schema(description = "数量")
|
||||||
private BigDecimal quantity;
|
private BigDecimal quantity;
|
||||||
|
|
||||||
@@ -183,6 +186,7 @@ public class Waybill extends TenantEntity {
|
|||||||
@Schema(description = "押运人手机号")
|
@Schema(description = "押运人手机号")
|
||||||
private String escortPhone;
|
private String escortPhone;
|
||||||
|
|
||||||
|
@JsonSerialize(nullsUsing = NullSerializer.class)
|
||||||
@Schema(description = "里程")
|
@Schema(description = "里程")
|
||||||
private BigDecimal mileage;
|
private BigDecimal mileage;
|
||||||
|
|
||||||
@@ -195,12 +199,14 @@ public class Waybill extends TenantEntity {
|
|||||||
@Schema(description = "预计完成日期")
|
@Schema(description = "预计完成日期")
|
||||||
private LocalDate estimatedEndTime;
|
private LocalDate estimatedEndTime;
|
||||||
|
|
||||||
|
@JsonSerialize(nullsUsing = NullSerializer.class)
|
||||||
@Schema(description = "单价")
|
@Schema(description = "单价")
|
||||||
private BigDecimal unitPrice;
|
private BigDecimal unitPrice;
|
||||||
|
|
||||||
@Schema(description = "计价单位")
|
@Schema(description = "计价单位")
|
||||||
private String priceUnit;
|
private String priceUnit;
|
||||||
|
|
||||||
|
@JsonSerialize(nullsUsing = NullSerializer.class)
|
||||||
@Schema(description = "其他费用合计")
|
@Schema(description = "其他费用合计")
|
||||||
private BigDecimal otherFeeTotal;
|
private BigDecimal otherFeeTotal;
|
||||||
|
|
||||||
|
|||||||
+3
@@ -44,4 +44,7 @@ public class AdminHomeBadgesVO implements Serializable {
|
|||||||
@Schema(description = "风险记录待办数(disposalStatus=pending)")
|
@Schema(description = "风险记录待办数(disposalStatus=pending)")
|
||||||
private long risk;
|
private long risk;
|
||||||
|
|
||||||
|
@Schema(description = "重新派单待办数(司机已拒单且运单未取消未完成)")
|
||||||
|
private long pendingReassign;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -49,7 +49,7 @@ public class AdminHomeVO implements Serializable {
|
|||||||
@Schema(description = "快捷入口角标")
|
@Schema(description = "快捷入口角标")
|
||||||
private AdminHomeBadgesVO badges = new AdminHomeBadgesVO();
|
private AdminHomeBadgesVO badges = new AdminHomeBadgesVO();
|
||||||
|
|
||||||
@Schema(description = "待处理事项(异常处置状态≠已完成)")
|
@Schema(description = "待处理事项(异常处置状态≠已完成,或司机已拒单待重新派单)")
|
||||||
private List<AdminTodoItemVO> feed = new ArrayList<>();
|
private List<AdminTodoItemVO> feed = new ArrayList<>();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+10
@@ -56,4 +56,14 @@ public class BaiduOcrResultVO implements Serializable {
|
|||||||
@Schema(description = "百度OCR原始返回结果")
|
@Schema(description = "百度OCR原始返回结果")
|
||||||
private Map<String, Object> result;
|
private Map<String, Object> result;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 归一化后的业务字段
|
||||||
|
* <p>
|
||||||
|
* 键与提交请求里的字段同名({@code plateNo}、{@code ownerName}…),调用方直接回填即可,
|
||||||
|
* 不需要认识百度那套中文键名。只包含**识别到**的字段,没识别到的不出现(而不是给空串),
|
||||||
|
* 调用方据此就能区分「没识别到」与「识别到空值」,不会把用户已填的内容覆盖成空。
|
||||||
|
*/
|
||||||
|
@Schema(description = "归一化后的业务字段,键与提交请求同名")
|
||||||
|
private Map<String, Object> fields;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+97
@@ -0,0 +1,97 @@
|
|||||||
|
/**
|
||||||
|
* BladeX Commercial License Agreement
|
||||||
|
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||||
|
* <p>
|
||||||
|
* Use of this software is governed by the Commercial License Agreement
|
||||||
|
* obtained after purchasing a license from BladeX.
|
||||||
|
* <p>
|
||||||
|
* 1. This software is for development use only under a valid license
|
||||||
|
* from BladeX.
|
||||||
|
* <p>
|
||||||
|
* 2. Redistribution of this software's source code to any third party
|
||||||
|
* without a commercial license is strictly prohibited.
|
||||||
|
* <p>
|
||||||
|
* 3. Licensees may copyright their own code but cannot use segments
|
||||||
|
* from this software for such purposes. Copyright of this software
|
||||||
|
* remains with BladeX.
|
||||||
|
* <p>
|
||||||
|
* Using this software signifies agreement to this License, and the software
|
||||||
|
* must not be used for illegal purposes.
|
||||||
|
* <p>
|
||||||
|
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||||
|
* not liable for any claims arising from secondary or illegal development.
|
||||||
|
* <p>
|
||||||
|
* Author: Chill Zhuang (bladejava@qq.com)
|
||||||
|
*/
|
||||||
|
package org.springblade.transport.pojo.vo;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 认证审核详情视图
|
||||||
|
* <p>
|
||||||
|
* 审核是「拿新提交的内容和当前生效的内容比」的过程,所以这里**同时**给出两侧:
|
||||||
|
* {@code pendingSnapshotJson} 是本次待审的全量内容,
|
||||||
|
* {@code effectiveSnapshotJson} 是该对象当前生效的全量内容。
|
||||||
|
* 前端对这两个 JSON 做字段级 diff,即可看出司机这次改了什么。
|
||||||
|
* <p>
|
||||||
|
* 两侧的 JSON 结构完全一致(同一套提交结构),因此可以逐键直接比较。
|
||||||
|
*
|
||||||
|
* @author Chill
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Schema(description = "认证审核详情")
|
||||||
|
public class CertAuditDetailVO implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "认证记录主键")
|
||||||
|
private Long id;
|
||||||
|
@Schema(description = "认证对象类型:driver-司机,vehicle-车辆")
|
||||||
|
private String bizType;
|
||||||
|
@Schema(description = "认证对象主键")
|
||||||
|
private Long bizId;
|
||||||
|
@Schema(description = "展示键:司机姓名 / 车牌号")
|
||||||
|
private String bizKey;
|
||||||
|
@Schema(description = "审核状态:0审核中 1已通过 2已驳回 3已作废")
|
||||||
|
private Integer status;
|
||||||
|
@Schema(description = "状态中文文案")
|
||||||
|
private String statusText;
|
||||||
|
@Schema(description = "本次提交时间")
|
||||||
|
private LocalDateTime submitTime;
|
||||||
|
@Schema(description = "本次提交人")
|
||||||
|
private Long submitUser;
|
||||||
|
@Schema(description = "本次提交人姓名")
|
||||||
|
private String submitUserName;
|
||||||
|
@Schema(description = "本次审核人;未审核时为 null")
|
||||||
|
private Long auditUser;
|
||||||
|
@Schema(description = "本次审核人姓名;未审核时为空")
|
||||||
|
private String auditUserName;
|
||||||
|
@Schema(description = "审核时间(通过或驳回的时间);未审核时为 null")
|
||||||
|
private LocalDateTime auditTime;
|
||||||
|
|
||||||
|
@Schema(description = "待审快照:本次提交的全量内容")
|
||||||
|
private String pendingSnapshotJson;
|
||||||
|
@Schema(description = "生效快照:该对象当前生效的全量内容;从未认证通过时为 null")
|
||||||
|
private String effectiveSnapshotJson;
|
||||||
|
@Schema(description = "生效快照记录ID;null 表示该对象从未认证通过")
|
||||||
|
private Long effectiveRecordId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 该对象是否已认证
|
||||||
|
* <p>
|
||||||
|
* 审核页据此提示「本次驳回不会影响该对象继续使用」——
|
||||||
|
* 已认证的对象即便本次被驳回,既得认证依然有效。
|
||||||
|
*/
|
||||||
|
@TableField(exist = false)
|
||||||
|
@Schema(description = "该对象当前是否已认证")
|
||||||
|
private Boolean certified;
|
||||||
|
|
||||||
|
}
|
||||||
+3
@@ -49,4 +49,7 @@ public class DriverNodePunchVO implements Serializable {
|
|||||||
@Schema(description = "数量")
|
@Schema(description = "数量")
|
||||||
private String quantity;
|
private String quantity;
|
||||||
|
|
||||||
|
@Schema(description = "本次打卡是否使运单自动完成")
|
||||||
|
private Boolean waybillCompleted;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+31
-1
@@ -4,6 +4,8 @@
|
|||||||
*/
|
*/
|
||||||
package org.springblade.transport.pojo.vo;
|
package org.springblade.transport.pojo.vo;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||||
|
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@@ -34,9 +36,37 @@ public class DriverPunchNodeVO implements Serializable {
|
|||||||
@Schema(description = "是否已打卡(在途=今日已打)")
|
@Schema(description = "是否已打卡(在途=今日已打)")
|
||||||
private Boolean done;
|
private Boolean done;
|
||||||
|
|
||||||
@Schema(description = "当前是否可打卡(顺序门禁 + 在途频次时段)")
|
@Schema(description = "当前是否可打卡(已打卡、被异常阻断、在途频次时段都会置否)")
|
||||||
private Boolean actionable;
|
private Boolean actionable;
|
||||||
|
|
||||||
|
@Schema(description = "是否被未了结的异常阻断:异常所在节点及其后的节点不可打卡;在途只要有未了结异常即不可打卡")
|
||||||
|
private Boolean blocked;
|
||||||
|
|
||||||
|
@Schema(description = "是否因前置节点未打卡而被顺序门禁拦住(在途豁免,恒为否)")
|
||||||
|
private Boolean blockedByOrder;
|
||||||
|
|
||||||
|
@Schema(description = "顺序门禁拦住的原因,如「请先完成装货打卡」")
|
||||||
|
private String orderLockReason;
|
||||||
|
|
||||||
|
@Schema(description = "是否允许展开:顺序门禁不允许,异常门禁允许(可查看回填内容)")
|
||||||
|
private Boolean expandable;
|
||||||
|
|
||||||
|
@Schema(description = "该节点自身是否被上报了未了结异常(仅异常所在节点为是,其后被牵连阻断的节点为否)")
|
||||||
|
private Boolean hasException;
|
||||||
|
|
||||||
|
@Schema(description = "该运单是否存在未了结的整单异常;整单异常不归属任何节点,故每个节点都带此标记")
|
||||||
|
private Boolean hasWaybillException;
|
||||||
|
|
||||||
|
@Schema(description = "与该节点相关的未了结异常ID,按上报时间升序;相关 = 归属该节点 ∪ 整单异常")
|
||||||
|
private List<String> relatedExceptionIds = new ArrayList<>();
|
||||||
|
|
||||||
|
@Schema(description = "阻断该节点的异常处置ID(司机端「异常详情」跳转用);未被阻断或异常已不存在时为空")
|
||||||
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
|
private Long blockedExceptionId;
|
||||||
|
|
||||||
|
@Schema(description = "不可打卡的原因,供司机端展示")
|
||||||
|
private String lockReason;
|
||||||
|
|
||||||
@Schema(description = "是否展示该卡(在途可能因频次/时段隐藏)")
|
@Schema(description = "是否展示该卡(在途可能因频次/时段隐藏)")
|
||||||
private Boolean visible;
|
private Boolean visible;
|
||||||
|
|
||||||
|
|||||||
+76
-16
@@ -44,29 +44,89 @@ public class DriverVehicleCardVO implements Serializable {
|
|||||||
@Schema(description = "车牌号")
|
@Schema(description = "车牌号")
|
||||||
private String plateNo;
|
private String plateNo;
|
||||||
|
|
||||||
|
@Schema(description = "所有人(行驶证登记的单位名称,不是司机)")
|
||||||
|
private String ownerName;
|
||||||
|
|
||||||
|
@Schema(description = "车牌颜色")
|
||||||
|
private String plateColor;
|
||||||
|
|
||||||
@Schema(description = "车辆类型")
|
@Schema(description = "车辆类型")
|
||||||
private String vehicleType;
|
private String vehicleType;
|
||||||
|
|
||||||
@Schema(description = "行驶证主页照片")
|
@Schema(description = "车辆识别代号(车架号),选填")
|
||||||
private String licenseFrontUrl;
|
|
||||||
|
|
||||||
@Schema(description = "行驶证副页照片")
|
|
||||||
private String licenseBackUrl;
|
|
||||||
|
|
||||||
@Schema(description = "道路运输证号")
|
|
||||||
private String roadTransportNo;
|
|
||||||
|
|
||||||
@Schema(description = "道路运输证照片")
|
|
||||||
private String roadTransportUrl;
|
|
||||||
|
|
||||||
@Schema(description = "车架号")
|
|
||||||
private String vin;
|
private String vin;
|
||||||
|
|
||||||
@Schema(description = "发动机号")
|
@Schema(description = "外廓长度,毫米")
|
||||||
private String engineNo;
|
private Integer outerLength;
|
||||||
|
|
||||||
|
@Schema(description = "外廓宽度,毫米")
|
||||||
|
private Integer outerWidth;
|
||||||
|
|
||||||
|
@Schema(description = "外廓高度,毫米")
|
||||||
|
private Integer outerHeight;
|
||||||
|
|
||||||
|
@Schema(description = "核定载质量,KG")
|
||||||
|
private Integer approvedLoadKg;
|
||||||
|
|
||||||
|
@Schema(description = "准牵引总质量,KG")
|
||||||
|
private Integer tractionMassKg;
|
||||||
|
|
||||||
|
@Schema(description = "能源类型")
|
||||||
|
private String energyType;
|
||||||
|
|
||||||
|
@Schema(description = "业务关系:自有 / 管理挂靠 / 业务合作")
|
||||||
|
private String businessRelation;
|
||||||
|
|
||||||
|
@Schema(description = "强制报废日期")
|
||||||
|
private String compulsoryScrapDate;
|
||||||
|
|
||||||
|
@Schema(description = "强制报废长期有效:0否 1是")
|
||||||
|
private Integer compulsoryScrapLongTerm;
|
||||||
|
|
||||||
|
@Schema(description = "行驶证档案编号")
|
||||||
|
private String drivingLicenseNo;
|
||||||
|
|
||||||
|
@Schema(description = "行驶证主页正面照片")
|
||||||
|
private String drivingLicenseImage;
|
||||||
|
|
||||||
|
@Schema(description = "行驶证主页反面照片")
|
||||||
|
private String drivingLicenseMainBack;
|
||||||
|
|
||||||
|
@Schema(description = "行驶证副页正面照片")
|
||||||
|
private String drivingLicenseViceFront;
|
||||||
|
|
||||||
|
@Schema(description = "行驶证副页反面照片")
|
||||||
|
private String drivingLicenseViceBack;
|
||||||
|
|
||||||
|
@Schema(description = "行驶证有效期起")
|
||||||
|
private String drivingLicenseStartDate;
|
||||||
|
|
||||||
@Schema(description = "行驶证有效期止")
|
@Schema(description = "行驶证有效期止")
|
||||||
private String licenseValidEnd;
|
private String drivingLicenseEndDate;
|
||||||
|
|
||||||
|
@Schema(description = "行驶证长期有效:0否 1是")
|
||||||
|
private Integer drivingLicenseLongTerm;
|
||||||
|
|
||||||
|
@Schema(description = "道路运输证号")
|
||||||
|
private String roadTransportCertNo;
|
||||||
|
|
||||||
|
@Schema(description = "道路运输证照片")
|
||||||
|
private String roadTransportCertImage;
|
||||||
|
|
||||||
|
@Schema(description = "道路运输证有效期起")
|
||||||
|
private String roadTransportCertStartDate;
|
||||||
|
|
||||||
|
@Schema(description = "道路运输证有效期止")
|
||||||
|
private String roadTransportCertEndDate;
|
||||||
|
|
||||||
|
@Schema(description = "道路运输证长期有效:0否 1是")
|
||||||
|
private Integer roadTransportCertLongTerm;
|
||||||
|
|
||||||
|
@Schema(description = "机动车登记编号")
|
||||||
|
private String registrationNo;
|
||||||
|
|
||||||
|
@Schema(description = "机动车登记本照片")
|
||||||
|
private String registrationImage;
|
||||||
|
|
||||||
@Schema(description = "认证状态:0认证中 1认证通过 2认证驳回")
|
@Schema(description = "认证状态:0认证中 1认证通过 2认证驳回")
|
||||||
private Integer certificationStatus;
|
private Integer certificationStatus;
|
||||||
|
|||||||
+100
@@ -0,0 +1,100 @@
|
|||||||
|
/**
|
||||||
|
* BladeX Commercial License Agreement
|
||||||
|
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||||
|
* <p>
|
||||||
|
* Use of this software is governed by the Commercial License Agreement
|
||||||
|
* obtained after purchasing a license from BladeX.
|
||||||
|
* <p>
|
||||||
|
* 1. This software is for development use only under a valid license
|
||||||
|
* from BladeX.
|
||||||
|
* <p>
|
||||||
|
* 2. Redistribution of this software's source code to any third party
|
||||||
|
* without a commercial license is strictly prohibited.
|
||||||
|
* <p>
|
||||||
|
* 3. Licensees may copyright their own code but cannot use segments
|
||||||
|
* from this software for such purposes. Copyright of this software
|
||||||
|
* remains with BladeX.
|
||||||
|
* <p>
|
||||||
|
* Using this software signifies agreement to this License, and the software
|
||||||
|
* must not be used for illegal purposes.
|
||||||
|
* <p>
|
||||||
|
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||||
|
* not liable for any claims arising from secondary or illegal development.
|
||||||
|
* <p>
|
||||||
|
* Author: Chill Zhuang (bladejava@qq.com)
|
||||||
|
*/
|
||||||
|
package org.springblade.transport.pojo.vo;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import org.springblade.transport.pojo.entity.TransportCertRecord;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 认证记录视图实体类
|
||||||
|
*
|
||||||
|
* @author Chill
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Schema(description = "认证记录")
|
||||||
|
public class TransportCertRecordVO extends TransportCertRecord {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建人姓名
|
||||||
|
*/
|
||||||
|
@TableField(exist = false)
|
||||||
|
@Schema(description = "创建人姓名")
|
||||||
|
private String createUserName;
|
||||||
|
/**
|
||||||
|
* 更新人姓名
|
||||||
|
*/
|
||||||
|
@TableField(exist = false)
|
||||||
|
@Schema(description = "更新人姓名")
|
||||||
|
private String updateUserName;
|
||||||
|
/**
|
||||||
|
* 提交人姓名
|
||||||
|
*/
|
||||||
|
@TableField(exist = false)
|
||||||
|
@Schema(description = "提交人姓名")
|
||||||
|
private String submitUserName;
|
||||||
|
/**
|
||||||
|
* 审核人姓名
|
||||||
|
*/
|
||||||
|
@TableField(exist = false)
|
||||||
|
@Schema(description = "审核人姓名")
|
||||||
|
private String auditUserName;
|
||||||
|
/**
|
||||||
|
* 状态中文文案:审核中 / 已通过 / 已驳回 / 已作废
|
||||||
|
* <p>
|
||||||
|
* 由服务层统一翻译后下发,避免小程序、Web、接口三处各造一套说法。
|
||||||
|
*/
|
||||||
|
@TableField(exist = false)
|
||||||
|
@Schema(description = "状态中文文案")
|
||||||
|
private String statusText;
|
||||||
|
/**
|
||||||
|
* 司机姓名(认证对象档案的**当前**姓名,不是提交时的历史值)
|
||||||
|
* <p>
|
||||||
|
* 司机侧取认证对象本身;车辆侧取提交该记录的司机。
|
||||||
|
* 解析不到档案时为空——存量订正记录里没有账号的那些司机补不出人,如实留空。
|
||||||
|
*/
|
||||||
|
@TableField(exist = false)
|
||||||
|
@Schema(description = "司机姓名")
|
||||||
|
private String driverName;
|
||||||
|
/**
|
||||||
|
* 司机手机号(认证对象档案的**当前**手机号,明文下发)
|
||||||
|
* <p>
|
||||||
|
* 审核员靠它联系司机核实材料,故不脱敏;认证快照里从来没有手机号,
|
||||||
|
* 它只有司机档案这一个来源。
|
||||||
|
*/
|
||||||
|
@TableField(exist = false)
|
||||||
|
@Schema(description = "司机手机号")
|
||||||
|
private String driverMobile;
|
||||||
|
|
||||||
|
}
|
||||||
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+45
-6
@@ -25,6 +25,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.springblade.system.cache;
|
package org.springblade.system.cache;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springblade.core.cache.utils.CacheUtil;
|
import org.springblade.core.cache.utils.CacheUtil;
|
||||||
import org.springblade.core.tool.api.R;
|
import org.springblade.core.tool.api.R;
|
||||||
import org.springblade.core.tool.utils.Func;
|
import org.springblade.core.tool.utils.Func;
|
||||||
@@ -42,6 +43,7 @@ import static org.springblade.core.launch.constant.FlowConstant.TASK_USR_PREFIX;
|
|||||||
*
|
*
|
||||||
* @author Chill
|
* @author Chill
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
public class UserCache {
|
public class UserCache {
|
||||||
private static final String USER_CACHE_ID = "user:id:";
|
private static final String USER_CACHE_ID = "user:id:";
|
||||||
private static final String USER_CACHE_ACCOUNT = "user:account:";
|
private static final String USER_CACHE_ACCOUNT = "user:account:";
|
||||||
@@ -73,10 +75,18 @@ public class UserCache {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static User getUser(Long userId) {
|
public static User getUser(Long userId) {
|
||||||
return CacheUtil.get(USER_CACHE, USER_CACHE_ID, userId, () -> {
|
try {
|
||||||
R<User> result = getUserClient().userInfoById(userId);
|
return CacheUtil.get(USER_CACHE, USER_CACHE_ID, userId, () -> loadUserById(userId));
|
||||||
return result.getData();
|
} catch (RuntimeException exception) {
|
||||||
});
|
log.warn("用户缓存读取失败,清理损坏缓存后重试, userId={}", userId, exception);
|
||||||
|
clearBrokenUserCache();
|
||||||
|
try {
|
||||||
|
return CacheUtil.get(USER_CACHE, USER_CACHE_ID, userId, () -> loadUserById(userId));
|
||||||
|
} catch (RuntimeException retryException) {
|
||||||
|
log.warn("用户缓存重试仍失败,改为直连查询, userId={}", userId, retryException);
|
||||||
|
return loadUserById(userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -89,6 +99,7 @@ public class UserCache {
|
|||||||
if (userId == null) {
|
if (userId == null) {
|
||||||
return StringPool.EMPTY;
|
return StringPool.EMPTY;
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
User user = getUser(userId);
|
User user = getUser(userId);
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
return Func.toStr(userId);
|
return Func.toStr(userId);
|
||||||
@@ -103,6 +114,10 @@ public class UserCache {
|
|||||||
return user.getAccount();
|
return user.getAccount();
|
||||||
}
|
}
|
||||||
return Func.toStr(userId);
|
return Func.toStr(userId);
|
||||||
|
} catch (RuntimeException exception) {
|
||||||
|
log.warn("翻译用户姓名失败,回退为用户ID, userId={}", userId, exception);
|
||||||
|
return Func.toStr(userId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -113,10 +128,34 @@ public class UserCache {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static User getUser(String tenantId, String account) {
|
public static User getUser(String tenantId, String account) {
|
||||||
return CacheUtil.get(USER_CACHE, USER_CACHE_ACCOUNT, tenantId + StringPool.DASH + account, () -> {
|
String accountKey = tenantId + StringPool.DASH + account;
|
||||||
|
try {
|
||||||
|
return CacheUtil.get(USER_CACHE, USER_CACHE_ACCOUNT, accountKey, () -> loadUserByAccount(tenantId, account));
|
||||||
|
} catch (RuntimeException exception) {
|
||||||
|
log.warn("用户缓存读取失败,清理损坏缓存后重试, accountKey={}", accountKey, exception);
|
||||||
|
clearBrokenUserCache();
|
||||||
|
try {
|
||||||
|
return CacheUtil.get(USER_CACHE, USER_CACHE_ACCOUNT, accountKey, () -> loadUserByAccount(tenantId, account));
|
||||||
|
} catch (RuntimeException retryException) {
|
||||||
|
log.warn("用户缓存重试仍失败,改为直连查询, accountKey={}", accountKey, retryException);
|
||||||
|
return loadUserByAccount(tenantId, account);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static User loadUserById(Long userId) {
|
||||||
|
R<User> result = getUserClient().userInfoById(userId);
|
||||||
|
return result.getData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static User loadUserByAccount(String tenantId, String account) {
|
||||||
R<User> result = getUserClient().userByAccount(tenantId, account);
|
R<User> result = getUserClient().userByAccount(tenantId, account);
|
||||||
return result.getData();
|
return result.getData();
|
||||||
});
|
}
|
||||||
|
|
||||||
|
private static void clearBrokenUserCache() {
|
||||||
|
CacheUtil.clear(USER_CACHE);
|
||||||
|
CacheUtil.clear(USER_CACHE, Boolean.FALSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
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;
|
||||||
|
|
||||||
|
}
|
||||||
+6
@@ -25,6 +25,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.springblade.system.pojo.entity;
|
package org.springblade.system.pojo.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.FieldStrategy;
|
||||||
import com.baomidou.mybatisplus.annotation.TableField;
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@@ -131,6 +132,11 @@ public class User extends TenantEntity implements IPhone {
|
|||||||
* 是否是oa的账号
|
* 是否是oa的账号
|
||||||
*/
|
*/
|
||||||
private Integer isOa;
|
private Integer isOa;
|
||||||
|
/**
|
||||||
|
* OA上次更新时间(OA 的 modified 字段),仅同步比对使用
|
||||||
|
*/
|
||||||
|
@TableField(updateStrategy = FieldStrategy.NOT_NULL)
|
||||||
|
private String oaModified;
|
||||||
/**
|
/**
|
||||||
* 是否推送mk
|
* 是否推送mk
|
||||||
*/
|
*/
|
||||||
|
|||||||
+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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+73
@@ -0,0 +1,73 @@
|
|||||||
|
/**
|
||||||
|
* BladeX Commercial License Agreement
|
||||||
|
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||||
|
* <p>
|
||||||
|
* Use of this software is governed by the Commercial License Agreement
|
||||||
|
* obtained after purchasing a license from BladeX.
|
||||||
|
* <p>
|
||||||
|
* 1. This software is for development use only under a valid license
|
||||||
|
* from BladeX.
|
||||||
|
* <p>
|
||||||
|
* 2. Redistribution of this software's source code to any third party
|
||||||
|
* without a commercial license is strictly prohibited.
|
||||||
|
* <p>
|
||||||
|
* 3. Licensees may copyright their own code but cannot use segments
|
||||||
|
* from this software for such purposes. Copyright of this software
|
||||||
|
* remains with BladeX.
|
||||||
|
* <p>
|
||||||
|
* Using this software signifies agreement to this License, and the software
|
||||||
|
* must not be used for illegal purposes.
|
||||||
|
* <p>
|
||||||
|
* Author: Chill Zhuang (bladejava@qq.com)
|
||||||
|
*/
|
||||||
|
package org.springblade.system.pojo.vo;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import org.springblade.system.pojo.entity.MpUser;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小程序用户视图实体类
|
||||||
|
*
|
||||||
|
* @author Chill
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Schema(description = "小程序用户")
|
||||||
|
public class MpUserVO extends MpUser {
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 身份名称(逗号分隔中文)
|
||||||
|
*/
|
||||||
|
@TableField(exist = false)
|
||||||
|
@Schema(description = "身份名称")
|
||||||
|
private String identitiesName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 性别名称
|
||||||
|
*/
|
||||||
|
@TableField(exist = false)
|
||||||
|
@Schema(description = "性别名称")
|
||||||
|
private String sexName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建人姓名
|
||||||
|
*/
|
||||||
|
@TableField(exist = false)
|
||||||
|
@Schema(description = "创建人姓名")
|
||||||
|
private String createUserName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新人姓名
|
||||||
|
*/
|
||||||
|
@TableField(exist = false)
|
||||||
|
@Schema(description = "更新人姓名")
|
||||||
|
private String updateUserName;
|
||||||
|
|
||||||
|
}
|
||||||
+75
@@ -0,0 +1,75 @@
|
|||||||
|
/**
|
||||||
|
* BladeX Commercial License Agreement
|
||||||
|
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||||
|
* <p>
|
||||||
|
* Use of this software is governed by the Commercial License Agreement
|
||||||
|
* obtained after purchasing a license from BladeX.
|
||||||
|
* <p>
|
||||||
|
* 1. This software is for development use only under a valid license
|
||||||
|
* from BladeX.
|
||||||
|
* <p>
|
||||||
|
* 2. Redistribution of this software's source code to any third party
|
||||||
|
* without a commercial license is strictly prohibited.
|
||||||
|
* <p>
|
||||||
|
* 3. Licensees may copyright their own code but cannot use segments
|
||||||
|
* from this software for such purposes. Copyright of this software
|
||||||
|
* remains with BladeX.
|
||||||
|
* <p>
|
||||||
|
* Using this software signifies agreement to this License, and the software
|
||||||
|
* must not be used for illegal purposes.
|
||||||
|
* <p>
|
||||||
|
* Author: Chill Zhuang (bladejava@qq.com)
|
||||||
|
*/
|
||||||
|
package org.springblade.system.utils;
|
||||||
|
|
||||||
|
import org.springblade.core.secure.BladeUser;
|
||||||
|
import org.springblade.core.secure.utils.AuthUtil;
|
||||||
|
import org.springblade.core.tool.support.Kv;
|
||||||
|
import org.springblade.core.tool.utils.Func;
|
||||||
|
import org.springblade.system.pojo.enums.MpUserIdentity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小程序登录态识别工具
|
||||||
|
*
|
||||||
|
* @author Chill
|
||||||
|
*/
|
||||||
|
public final class MpUserAuth {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 严格令牌模式下的占位 ID。
|
||||||
|
* <p>
|
||||||
|
* {@code blade.secure.strict-token=true} 时,AuthUtil.userIncomplete 要求 roleId/deptId
|
||||||
|
* 非空且数值 >= 0;小程序用户不绑定系统角色/部门,用 0 占位避免业务接口 401。
|
||||||
|
*/
|
||||||
|
public static final String PLACEHOLDER_ID = "0";
|
||||||
|
|
||||||
|
private MpUserAuth() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前登录用户是否为小程序用户
|
||||||
|
*/
|
||||||
|
public static boolean isMpUser() {
|
||||||
|
return isMpUser(AuthUtil.getUser());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断指定登录用户是否为小程序用户
|
||||||
|
*/
|
||||||
|
public static boolean isMpUser(BladeUser bladeUser) {
|
||||||
|
if (bladeUser == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Kv detail = bladeUser.getDetail();
|
||||||
|
if (detail != null && MpUserIdentity.DETAIL_TYPE.equalsIgnoreCase(Func.toStr(detail.get("type")))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 兼容:无真实系统角色(空或占位 0)且 roleName 为小程序身份
|
||||||
|
String roleId = Func.toStr(bladeUser.getRoleId());
|
||||||
|
boolean noSystemRole = Func.isBlank(roleId) || PLACEHOLDER_ID.equals(roleId);
|
||||||
|
return noSystemRole
|
||||||
|
&& Func.isNotBlank(bladeUser.getRoleName())
|
||||||
|
&& !MpUserIdentity.split(bladeUser.getRoleName()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package org.springblade.file.config;
|
|||||||
import com.aliyun.oss.OSSClientBuilder;
|
import com.aliyun.oss.OSSClientBuilder;
|
||||||
import com.obs.services.ObsClient;
|
import com.obs.services.ObsClient;
|
||||||
import io.minio.MinioClient;
|
import io.minio.MinioClient;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springblade.core.log.exception.ServiceException;
|
import org.springblade.core.log.exception.ServiceException;
|
||||||
import org.springblade.core.tool.utils.StringUtil;
|
import org.springblade.core.tool.utils.StringUtil;
|
||||||
import org.springblade.file.service.IFileService;
|
import org.springblade.file.service.IFileService;
|
||||||
@@ -10,6 +11,7 @@ import org.springblade.file.service.impl.LocalFileServiceImpl;
|
|||||||
import org.springblade.file.service.impl.MinioFileServiceImpl;
|
import org.springblade.file.service.impl.MinioFileServiceImpl;
|
||||||
import org.springblade.file.service.impl.OssFileServiceImpl;
|
import org.springblade.file.service.impl.OssFileServiceImpl;
|
||||||
import org.springblade.file.service.impl.ObsFileServiceImpl;
|
import org.springblade.file.service.impl.ObsFileServiceImpl;
|
||||||
|
import org.springblade.file.util.MinioClientFactory;
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
@@ -20,6 +22,7 @@ import org.springframework.core.env.Environment;
|
|||||||
* @author bfhuange
|
* @author bfhuange
|
||||||
* @date 2024/10/21
|
* @date 2024/10/21
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
@Configuration
|
@Configuration
|
||||||
public class FileConfig {
|
public class FileConfig {
|
||||||
|
|
||||||
@@ -63,10 +66,22 @@ public class FileConfig {
|
|||||||
|
|
||||||
private IFileService buildMinioFileService(FileStorageProperties.StoreProperties properties) {
|
private IFileService buildMinioFileService(FileStorageProperties.StoreProperties properties) {
|
||||||
validateStoreProperties(STORAGE_MINIO, properties);
|
validateStoreProperties(STORAGE_MINIO, properties);
|
||||||
MinioClient minioClient = MinioClient.builder()
|
String endpointOrigin = MinioClientFactory.resolveOrigin(properties.getEndpoint());
|
||||||
.endpoint(properties.getEndpoint())
|
String endpointPathPrefix = MinioClientFactory.resolvePathPrefix(properties.getEndpoint());
|
||||||
.credentials(properties.getAccessKeyId(), properties.getAccessKeySecret())
|
if (StringUtil.isNotBlank(endpointPathPrefix)) {
|
||||||
.build();
|
log.warn("MinIO endpoint 含 path,将按 origin={} pathPrefix={} 访问;建议 endpoint 使用内网地址,transform-endpoint 使用外网 HTTPS",
|
||||||
|
endpointOrigin, endpointPathPrefix);
|
||||||
|
}
|
||||||
|
if (StringUtil.isBlank(properties.getTransformEndpoint())
|
||||||
|
&& properties.getEndpoint().trim().toLowerCase().startsWith("http://")) {
|
||||||
|
log.warn("MinIO transform-endpoint 未配置,endpoint={}。HTTPS 页面分片直传会被浏览器 Mixed Content 拦截,将回退为服务端代传",
|
||||||
|
properties.getEndpoint());
|
||||||
|
} else {
|
||||||
|
log.info("MinIO 文件存储已启用 endpoint={} pathPrefix={} transformEndpoint={}",
|
||||||
|
endpointOrigin, endpointPathPrefix, properties.getTransformEndpoint());
|
||||||
|
}
|
||||||
|
MinioClient minioClient = MinioClientFactory.create(
|
||||||
|
properties.getEndpoint(), properties.getAccessKeyId(), properties.getAccessKeySecret());
|
||||||
return new MinioFileServiceImpl(properties, minioClient);
|
return new MinioFileServiceImpl(properties, minioClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,7 +110,16 @@ public class FileConfig {
|
|||||||
FileStorageProperties.StoreProperties mergedProperties = new FileStorageProperties.StoreProperties();
|
FileStorageProperties.StoreProperties mergedProperties = new FileStorageProperties.StoreProperties();
|
||||||
String fileStoragePrefix = "file.storage." + prefix;
|
String fileStoragePrefix = "file.storage." + prefix;
|
||||||
mergedProperties.setEndpoint(firstNotBlank(properties.getEndpoint(), environment.getProperty(fileStoragePrefix + ".endpoint"), environment.getProperty(prefix + ".endpoint")));
|
mergedProperties.setEndpoint(firstNotBlank(properties.getEndpoint(), environment.getProperty(fileStoragePrefix + ".endpoint"), environment.getProperty(prefix + ".endpoint")));
|
||||||
mergedProperties.setTransformEndpoint(firstNotBlank(properties.getTransformEndpoint(), environment.getProperty(fileStoragePrefix + ".transform-endpoint"), environment.getProperty(prefix + ".transform-endpoint")));
|
// 资源管理页 /resource/oss 的外网地址写在库表,不进 Spring Environment;凭证分片必须单独配 file.storage.minio.transform-endpoint
|
||||||
|
mergedProperties.setTransformEndpoint(firstNotBlank(
|
||||||
|
properties.getTransformEndpoint(),
|
||||||
|
environment.getProperty(fileStoragePrefix + ".transform-endpoint"),
|
||||||
|
environment.getProperty(fileStoragePrefix + ".transformEndpoint"),
|
||||||
|
environment.getProperty(prefix + ".transform-endpoint"),
|
||||||
|
environment.getProperty(prefix + ".transformEndpoint"),
|
||||||
|
environment.getProperty("oss.transform-endpoint"),
|
||||||
|
environment.getProperty("oss.transformEndpoint")
|
||||||
|
));
|
||||||
mergedProperties.setAccessKeyId(firstNotBlank(properties.getAccessKeyId(), environment.getProperty(fileStoragePrefix + ".access-key-id"), environment.getProperty(fileStoragePrefix + ".access-key"), environment.getProperty(prefix + ".access-key-id"), environment.getProperty(prefix + ".access-key")));
|
mergedProperties.setAccessKeyId(firstNotBlank(properties.getAccessKeyId(), environment.getProperty(fileStoragePrefix + ".access-key-id"), environment.getProperty(fileStoragePrefix + ".access-key"), environment.getProperty(prefix + ".access-key-id"), environment.getProperty(prefix + ".access-key")));
|
||||||
mergedProperties.setAccessKeySecret(firstNotBlank(properties.getAccessKeySecret(), environment.getProperty(fileStoragePrefix + ".access-key-secret"), environment.getProperty(fileStoragePrefix + ".secret-key"), environment.getProperty(prefix + ".access-key-secret"), environment.getProperty(prefix + ".secret-key")));
|
mergedProperties.setAccessKeySecret(firstNotBlank(properties.getAccessKeySecret(), environment.getProperty(fileStoragePrefix + ".access-key-secret"), environment.getProperty(fileStoragePrefix + ".secret-key"), environment.getProperty(prefix + ".access-key-secret"), environment.getProperty(prefix + ".secret-key")));
|
||||||
mergedProperties.setBucketName(firstNotBlank(properties.getBucketName(), environment.getProperty(fileStoragePrefix + ".bucket-name"), environment.getProperty(prefix + ".bucket-name")));
|
mergedProperties.setBucketName(firstNotBlank(properties.getBucketName(), environment.getProperty(fileStoragePrefix + ".bucket-name"), environment.getProperty(prefix + ".bucket-name")));
|
||||||
@@ -107,7 +131,7 @@ public class FileConfig {
|
|||||||
private String firstNotBlank(String... values) {
|
private String firstNotBlank(String... values) {
|
||||||
for (String value : values) {
|
for (String value : values) {
|
||||||
if (StringUtil.isNotBlank(value)) {
|
if (StringUtil.isNotBlank(value)) {
|
||||||
return value;
|
return value.trim();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
+22
@@ -111,4 +111,26 @@ public class FileTaskController extends BladeController {
|
|||||||
String uploadUrl = fileTaskService.getUploadUrl(id, partNumber);
|
String uploadUrl = fileTaskService.getUploadUrl(id, partNumber);
|
||||||
return R.data(uploadUrl);
|
return R.data(uploadUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiLog("上传任务-服务端代传分片")
|
||||||
|
@PostMapping(value = "/uploadPart", consumes = org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||||
|
@Parameters({
|
||||||
|
@Parameter(name = "id", description = "文件任务id", in = ParameterIn.QUERY, schema = @Schema(type = "string")),
|
||||||
|
@Parameter(name = "partNumber", description = "分段编号", in = ParameterIn.QUERY, schema = @Schema(type = "int"))
|
||||||
|
})
|
||||||
|
@Operation(summary = "服务端代传分片", description = "HTTPS 页面无法直连 HTTP MinIO 时,由服务端转发分片到对象存储")
|
||||||
|
public R<String> uploadPart(Long id, Integer partNumber, org.springframework.web.multipart.MultipartFile file) throws java.io.IOException {
|
||||||
|
if (file == null || file.isEmpty()) {
|
||||||
|
return R.fail("分片内容不能为空");
|
||||||
|
}
|
||||||
|
java.io.File tempFile = java.io.File.createTempFile("file-part-", ".bin");
|
||||||
|
try {
|
||||||
|
file.transferTo(tempFile);
|
||||||
|
return R.data(fileTaskService.uploadPart(id, partNumber, tempFile));
|
||||||
|
} finally {
|
||||||
|
if (tempFile.exists() && !tempFile.delete()) {
|
||||||
|
tempFile.deleteOnExit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
@@ -74,6 +74,16 @@ public interface IFileTaskService extends IService<FileTask> {
|
|||||||
*/
|
*/
|
||||||
String getUploadUrl(Long id, Integer partNumber);
|
String getUploadUrl(Long id, Integer partNumber);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务端代传分片(HTTPS 页面无法直连 HTTP MinIO 时使用)
|
||||||
|
*
|
||||||
|
* @param id 文件任务id
|
||||||
|
* @param partNumber 分段编号
|
||||||
|
* @param partFile 分片临时文件
|
||||||
|
* @return ETag
|
||||||
|
*/
|
||||||
|
String uploadPart(Long id, Integer partNumber, java.io.File partFile);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 取消文件任务
|
* 取消文件任务
|
||||||
* @param taskIds
|
* @param taskIds
|
||||||
|
|||||||
+27
@@ -259,6 +259,33 @@ public class FileTaskServiceImpl extends ServiceImpl<FileTaskMapper, FileTask> i
|
|||||||
return fileService.getUploadPartUrl(fileTask.getObjectKey(), fileTask.getUploadId(), partNumber, null);
|
return fileService.getUploadPartUrl(fileTask.getObjectKey(), fileTask.getUploadId(), partNumber, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String uploadPart(Long id, Integer partNumber, java.io.File partFile) {
|
||||||
|
FileTask fileTask = this.getById(id);
|
||||||
|
Func.requireNotNull(fileTask, "文件任务不存在");
|
||||||
|
if (FileTaskStatus.COMPLETED.getCode().equals(fileTask.getStatus())) {
|
||||||
|
throw new ServiceException("文件任务已完成");
|
||||||
|
}
|
||||||
|
if (FileTaskStatus.PAUSED.getCode().equals(fileTask.getStatus())) {
|
||||||
|
throw new ServiceException("文件任务已暂停");
|
||||||
|
}
|
||||||
|
if (partFile == null || !partFile.exists()) {
|
||||||
|
throw new ServiceException("分片文件不存在");
|
||||||
|
}
|
||||||
|
org.springblade.file.pojo.vo.PartEtagVO partEtag = fileService.uploadPart(
|
||||||
|
fileTask.getObjectKey(),
|
||||||
|
fileTask.getUploadId(),
|
||||||
|
partFile,
|
||||||
|
partNumber,
|
||||||
|
0L,
|
||||||
|
partFile.length()
|
||||||
|
);
|
||||||
|
if (partEtag == null || StringUtil.isBlank(partEtag.getEtag())) {
|
||||||
|
throw new ServiceException("分片上传未返回 ETag");
|
||||||
|
}
|
||||||
|
return partEtag.getEtag().replace("\"", "");
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
@Override
|
@Override
|
||||||
public void cancelBatchByIds(List<Long> taskIds) {
|
public void cancelBatchByIds(List<Long> taskIds) {
|
||||||
|
|||||||
+106
-6
@@ -25,11 +25,12 @@
|
|||||||
*/
|
*/
|
||||||
package org.springblade.file.service.impl;
|
package org.springblade.file.service.impl;
|
||||||
|
|
||||||
|
import io.minio.ComposeObjectArgs;
|
||||||
|
import io.minio.ComposeSource;
|
||||||
import io.minio.GetObjectArgs;
|
import io.minio.GetObjectArgs;
|
||||||
import io.minio.GetPresignedObjectUrlArgs;
|
import io.minio.GetPresignedObjectUrlArgs;
|
||||||
import io.minio.MinioClient;
|
import io.minio.MinioClient;
|
||||||
import io.minio.ComposeObjectArgs;
|
import io.minio.ObjectWriteResponse;
|
||||||
import io.minio.ComposeSource;
|
|
||||||
import io.minio.PutObjectArgs;
|
import io.minio.PutObjectArgs;
|
||||||
import io.minio.RemoveObjectArgs;
|
import io.minio.RemoveObjectArgs;
|
||||||
import io.minio.UploadObjectArgs;
|
import io.minio.UploadObjectArgs;
|
||||||
@@ -39,9 +40,13 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import org.springblade.core.log.exception.ServiceException;
|
import org.springblade.core.log.exception.ServiceException;
|
||||||
import org.springblade.core.tool.utils.StringUtil;
|
import org.springblade.core.tool.utils.StringUtil;
|
||||||
import org.springblade.file.config.FileStorageProperties;
|
import org.springblade.file.config.FileStorageProperties;
|
||||||
|
import org.springblade.file.util.MinioClientFactory;
|
||||||
|
import org.springblade.file.pojo.vo.PartEtagVO;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.net.URI;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.StandardCopyOption;
|
import java.nio.file.StandardCopyOption;
|
||||||
@@ -61,9 +66,11 @@ import java.util.UUID;
|
|||||||
public class MinioFileServiceImpl extends AbstractFileService {
|
public class MinioFileServiceImpl extends AbstractFileService {
|
||||||
|
|
||||||
private static final long PART_SIZE = 10 * 1024 * 1024L;
|
private static final long PART_SIZE = 10 * 1024 * 1024L;
|
||||||
|
private static final String DEFAULT_REGION = MinioClientFactory.DEFAULT_REGION;
|
||||||
|
|
||||||
private final FileStorageProperties.StoreProperties properties;
|
private final FileStorageProperties.StoreProperties properties;
|
||||||
private final MinioClient minioClient;
|
private final MinioClient minioClient;
|
||||||
|
private volatile MinioClient transformPresignClient;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String initiateMultipartUpload(String objectKey) {
|
public String initiateMultipartUpload(String objectKey) {
|
||||||
@@ -73,15 +80,50 @@ public class MinioFileServiceImpl extends AbstractFileService {
|
|||||||
@Override
|
@Override
|
||||||
public String getUploadPartUrl(String objectKey, String uploadId, Integer partNumber, Long expires) {
|
public String getUploadPartUrl(String objectKey, String uploadId, Integer partNumber, Long expires) {
|
||||||
try {
|
try {
|
||||||
return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
|
String presignedUrl = resolvePresignClient().getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
|
||||||
.method(Method.PUT).bucket(properties.getBucketName()).object(partObjectKey(objectKey, uploadId, partNumber))
|
.method(Method.PUT)
|
||||||
.expiry(Math.toIntExact(resolveExpires(expires))).build());
|
.bucket(properties.getBucketName())
|
||||||
|
.object(partObjectKey(objectKey, uploadId, partNumber))
|
||||||
|
.region(DEFAULT_REGION)
|
||||||
|
.expiry(Math.toIntExact(resolveExpires(expires)))
|
||||||
|
.build());
|
||||||
|
return applyTransformPathPrefix(presignedUrl);
|
||||||
} catch (Exception exception) {
|
} catch (Exception exception) {
|
||||||
log.error("MinIO获取分片上传链接异常 objectKey:{}", objectKey, exception);
|
log.error("MinIO获取分片上传链接异常 objectKey:{}", objectKey, exception);
|
||||||
throw new ServiceException("获取分片上传链接异常");
|
throw new ServiceException("获取分片上传链接异常");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PartEtagVO uploadPart(String objectKey, String uploadId, File file, int partNumber, Long offset, Long partSize) {
|
||||||
|
long start = offset == null ? 0L : offset;
|
||||||
|
long size = partSize == null ? Math.max(0L, file.length() - start) : partSize;
|
||||||
|
try (FileInputStream inputStream = new FileInputStream(file)) {
|
||||||
|
long skipped = inputStream.skip(start);
|
||||||
|
if (skipped < start) {
|
||||||
|
throw new ServiceException("MinIO分段上传读取文件偏移失败");
|
||||||
|
}
|
||||||
|
ObjectWriteResponse response = minioClient.putObject(PutObjectArgs.builder()
|
||||||
|
.bucket(properties.getBucketName())
|
||||||
|
.object(partObjectKey(objectKey, uploadId, partNumber))
|
||||||
|
.stream(inputStream, size, PART_SIZE)
|
||||||
|
.build());
|
||||||
|
String etag = response.etag() == null ? "" : response.etag().replace("\"", "");
|
||||||
|
return new PartEtagVO(etag, partNumber);
|
||||||
|
} catch (ServiceException serviceException) {
|
||||||
|
throw serviceException;
|
||||||
|
} catch (Exception exception) {
|
||||||
|
log.error("MinIO分段上传异常 objectKey:{} partNumber:{}", objectKey, partNumber, exception);
|
||||||
|
throw new ServiceException("MinIO分段上传异常");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancelMultipartUpload(String objectKey, String uploadId) {
|
||||||
|
// MinIO 分片以独立对象模拟,取消时由任务层清理业务状态即可
|
||||||
|
log.debug("MinIO取消分段上传 objectKey:{} uploadId:{}", objectKey, uploadId);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void completeMultipartUpload(String objectKey, String uploadId, List<org.springblade.file.pojo.vo.PartEtagVO> partEtags) {
|
public void completeMultipartUpload(String objectKey, String uploadId, List<org.springblade.file.pojo.vo.PartEtagVO> partEtags) {
|
||||||
try {
|
try {
|
||||||
@@ -143,12 +185,14 @@ public class MinioFileServiceImpl extends AbstractFileService {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
|
String presignedUrl = resolvePresignClient().getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
|
||||||
.method(Method.GET)
|
.method(Method.GET)
|
||||||
.bucket(properties.getBucketName())
|
.bucket(properties.getBucketName())
|
||||||
.object(resolveObjectKey(objectKey))
|
.object(resolveObjectKey(objectKey))
|
||||||
|
.region(DEFAULT_REGION)
|
||||||
.expiry(Math.toIntExact(resolveExpires(expires)))
|
.expiry(Math.toIntExact(resolveExpires(expires)))
|
||||||
.build());
|
.build());
|
||||||
|
return applyTransformPathPrefix(presignedUrl);
|
||||||
} catch (Exception exception) {
|
} catch (Exception exception) {
|
||||||
log.error("MinIO获取文件链接异常 objectKey:{}", objectKey, exception);
|
log.error("MinIO获取文件链接异常 objectKey:{}", objectKey, exception);
|
||||||
throw new ServiceException("MinIO获取文件链接异常");
|
throw new ServiceException("MinIO获取文件链接异常");
|
||||||
@@ -170,6 +214,62 @@ public class MinioFileServiceImpl extends AbstractFileService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 浏览器侧预签名优先使用外网地址,避免 HTTPS 页面访问 HTTP MinIO 被 Mixed Content 拦截。
|
||||||
|
* MinIO SDK 不允许 endpoint 带 path,因此外网地址中的 /oss 等前缀在签名后单独拼回。
|
||||||
|
*/
|
||||||
|
private MinioClient resolvePresignClient() {
|
||||||
|
String transformEndpoint = properties.getTransformEndpoint();
|
||||||
|
if (StringUtil.isBlank(transformEndpoint)) {
|
||||||
|
return minioClient;
|
||||||
|
}
|
||||||
|
MinioClient cachedClient = transformPresignClient;
|
||||||
|
if (cachedClient != null) {
|
||||||
|
return cachedClient;
|
||||||
|
}
|
||||||
|
synchronized (this) {
|
||||||
|
if (transformPresignClient == null) {
|
||||||
|
transformPresignClient = MinioClientFactory.create(
|
||||||
|
transformEndpoint, properties.getAccessKeyId(), properties.getAccessKeySecret());
|
||||||
|
}
|
||||||
|
return transformPresignClient;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String applyTransformPathPrefix(String presignedUrl) {
|
||||||
|
String transformEndpoint = properties.getTransformEndpoint();
|
||||||
|
if (StringUtil.isBlank(transformEndpoint) || StringUtil.isBlank(presignedUrl)) {
|
||||||
|
return presignedUrl;
|
||||||
|
}
|
||||||
|
String pathPrefix = normalizePathPrefix(URI.create(transformEndpoint.trim()).getPath());
|
||||||
|
if (StringUtil.isBlank(pathPrefix)) {
|
||||||
|
return presignedUrl;
|
||||||
|
}
|
||||||
|
URI url = URI.create(presignedUrl);
|
||||||
|
String path = url.getRawPath() == null ? "" : url.getRawPath();
|
||||||
|
if (path.equals(pathPrefix) || path.startsWith(pathPrefix + "/")) {
|
||||||
|
return presignedUrl;
|
||||||
|
}
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
result.append(url.getScheme()).append("://").append(url.getHost());
|
||||||
|
if (url.getPort() > 0) {
|
||||||
|
result.append(':').append(url.getPort());
|
||||||
|
}
|
||||||
|
result.append(pathPrefix).append(path);
|
||||||
|
if (StringUtil.isNotBlank(url.getRawQuery())) {
|
||||||
|
result.append('?').append(url.getRawQuery());
|
||||||
|
}
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizePathPrefix(String path) {
|
||||||
|
if (StringUtil.isBlank(path) || "/".equals(path)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
String normalized = path.endsWith("/") ? path.substring(0, path.length() - 1) : path;
|
||||||
|
return normalized.startsWith("/") ? normalized : "/" + normalized;
|
||||||
|
}
|
||||||
|
|
||||||
private String resolveObjectKey(String objectKey) {
|
private String resolveObjectKey(String objectKey) {
|
||||||
if (StringUtil.isBlank(properties.getRootDirectory())) {
|
if (StringUtil.isBlank(properties.getRootDirectory())) {
|
||||||
return objectKey;
|
return objectKey;
|
||||||
|
|||||||
+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>
|
||||||
|
* 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.file.util;
|
||||||
|
|
||||||
|
import io.minio.MinioClient;
|
||||||
|
import okhttp3.HttpUrl;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import org.springblade.core.log.exception.ServiceException;
|
||||||
|
import org.springblade.core.tool.utils.StringUtil;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MinIO 客户端构建。
|
||||||
|
* <p>
|
||||||
|
* SDK 不允许 endpoint 带 path;若配置为 {@code https://host/oss},则:
|
||||||
|
* <ul>
|
||||||
|
* <li>endpoint 使用 origin</li>
|
||||||
|
* <li>通过 OkHttp 拦截器把 /oss 拼回每个 API 请求</li>
|
||||||
|
* <li>固定 region,避免 GetBucketLocation 打到业务网关拿到 HTML</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public final class MinioClientFactory {
|
||||||
|
|
||||||
|
public static final String DEFAULT_REGION = "us-east-1";
|
||||||
|
|
||||||
|
private MinioClientFactory() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MinioClient create(String endpoint, String accessKey, String secretKey) {
|
||||||
|
EndpointParts parts = parseEndpoint(endpoint);
|
||||||
|
MinioClient.Builder builder = MinioClient.builder()
|
||||||
|
.endpoint(parts.origin())
|
||||||
|
.credentials(accessKey, secretKey)
|
||||||
|
.region(DEFAULT_REGION);
|
||||||
|
if (StringUtil.isNotBlank(parts.pathPrefix())) {
|
||||||
|
builder.httpClient(buildHttpClient(parts.pathPrefix()), true);
|
||||||
|
}
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String resolveOrigin(String endpoint) {
|
||||||
|
return parseEndpoint(endpoint).origin();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String resolvePathPrefix(String endpoint) {
|
||||||
|
return parseEndpoint(endpoint).pathPrefix();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static EndpointParts parseEndpoint(String endpoint) {
|
||||||
|
if (StringUtil.isBlank(endpoint)) {
|
||||||
|
throw new ServiceException("MinIO endpoint 不能为空");
|
||||||
|
}
|
||||||
|
URI uri = URI.create(endpoint.trim());
|
||||||
|
if (StringUtil.isBlank(uri.getScheme()) || StringUtil.isBlank(uri.getHost())) {
|
||||||
|
throw new ServiceException("MinIO endpoint 配置不正确: " + endpoint);
|
||||||
|
}
|
||||||
|
StringBuilder origin = new StringBuilder();
|
||||||
|
origin.append(uri.getScheme()).append("://").append(uri.getHost());
|
||||||
|
if (uri.getPort() > 0) {
|
||||||
|
origin.append(':').append(uri.getPort());
|
||||||
|
}
|
||||||
|
return new EndpointParts(origin.toString(), normalizePathPrefix(uri.getPath()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String normalizePathPrefix(String path) {
|
||||||
|
if (StringUtil.isBlank(path) || "/".equals(path)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
String normalized = path.endsWith("/") ? path.substring(0, path.length() - 1) : path;
|
||||||
|
return normalized.startsWith("/") ? normalized : "/" + normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static OkHttpClient buildHttpClient(String pathPrefix) {
|
||||||
|
return new OkHttpClient.Builder()
|
||||||
|
.connectTimeout(30, TimeUnit.SECONDS)
|
||||||
|
.writeTimeout(30, TimeUnit.SECONDS)
|
||||||
|
.readTimeout(30, TimeUnit.SECONDS)
|
||||||
|
.addInterceptor(chain -> {
|
||||||
|
Request original = chain.request();
|
||||||
|
HttpUrl originalUrl = original.url();
|
||||||
|
String encodedPath = originalUrl.encodedPath();
|
||||||
|
if (encodedPath.startsWith(pathPrefix + "/") || encodedPath.equals(pathPrefix)) {
|
||||||
|
return chain.proceed(original);
|
||||||
|
}
|
||||||
|
HttpUrl newUrl = originalUrl.newBuilder()
|
||||||
|
.encodedPath(pathPrefix + encodedPath)
|
||||||
|
.build();
|
||||||
|
return chain.proceed(original.newBuilder().url(newUrl).build());
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private record EndpointParts(String origin, String pathPrefix) {
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
-6
@@ -93,7 +93,8 @@ public class DeptController extends BladeController {
|
|||||||
@PreAuth(menu = "dept")
|
@PreAuth(menu = "dept")
|
||||||
@GetMapping("/list")
|
@GetMapping("/list")
|
||||||
@Parameters({
|
@Parameters({
|
||||||
@Parameter(name = "deptName", description = "部门名称", in = ParameterIn.QUERY, schema = @Schema(type = "string")),
|
@Parameter(name = "deptName", description = "组织名称", in = ParameterIn.QUERY, schema = @Schema(type = "string")),
|
||||||
|
@Parameter(name = "shortName", description = "组织简称", in = ParameterIn.QUERY, schema = @Schema(type = "string")),
|
||||||
@Parameter(name = "fullName", description = "部门全称", in = ParameterIn.QUERY, schema = @Schema(type = "string"))
|
@Parameter(name = "fullName", description = "部门全称", in = ParameterIn.QUERY, schema = @Schema(type = "string"))
|
||||||
})
|
})
|
||||||
@ApiOperationSupport(order = 2)
|
@ApiOperationSupport(order = 2)
|
||||||
@@ -108,7 +109,8 @@ public class DeptController extends BladeController {
|
|||||||
@PreAuth(menu = "dept")
|
@PreAuth(menu = "dept")
|
||||||
@GetMapping("/lazy-list")
|
@GetMapping("/lazy-list")
|
||||||
@Parameters({
|
@Parameters({
|
||||||
@Parameter(name = "deptName", description = "部门名称", in = ParameterIn.QUERY, schema = @Schema(type = "string")),
|
@Parameter(name = "deptName", description = "组织名称", in = ParameterIn.QUERY, schema = @Schema(type = "string")),
|
||||||
|
@Parameter(name = "shortName", description = "组织简称", in = ParameterIn.QUERY, schema = @Schema(type = "string")),
|
||||||
@Parameter(name = "fullName", description = "部门全称", in = ParameterIn.QUERY, schema = @Schema(type = "string"))
|
@Parameter(name = "fullName", description = "部门全称", in = ParameterIn.QUERY, schema = @Schema(type = "string"))
|
||||||
})
|
})
|
||||||
@ApiOperationSupport(order = 3)
|
@ApiOperationSupport(order = 3)
|
||||||
@@ -195,8 +197,10 @@ public class DeptController extends BladeController {
|
|||||||
@Operation(summary = "同步OA公司")
|
@Operation(summary = "同步OA公司")
|
||||||
public R<OaOrgSyncPageVO> syncOaCompany(
|
public R<OaOrgSyncPageVO> syncOaCompany(
|
||||||
@RequestParam(defaultValue = "1") Integer current,
|
@RequestParam(defaultValue = "1") Integer current,
|
||||||
@RequestParam(defaultValue = "20") Integer size) {
|
@RequestParam(defaultValue = "20") Integer size,
|
||||||
return R.data(oaSyncService.syncCompanyPage(current, size));
|
@RequestParam(defaultValue = "false") Boolean incremental,
|
||||||
|
@RequestParam(required = false) String modifiedSince) {
|
||||||
|
return R.data(oaSyncService.syncCompanyPage(current, size, Boolean.TRUE.equals(incremental), modifiedSince));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -208,8 +212,10 @@ public class DeptController extends BladeController {
|
|||||||
@Operation(summary = "同步OA部门")
|
@Operation(summary = "同步OA部门")
|
||||||
public R<OaOrgSyncPageVO> syncOaDepartment(
|
public R<OaOrgSyncPageVO> syncOaDepartment(
|
||||||
@RequestParam(defaultValue = "1") Integer current,
|
@RequestParam(defaultValue = "1") Integer current,
|
||||||
@RequestParam(defaultValue = "20") Integer size) {
|
@RequestParam(defaultValue = "20") Integer size,
|
||||||
return R.data(oaSyncService.syncDepartmentPage(current, size));
|
@RequestParam(defaultValue = "false") Boolean incremental,
|
||||||
|
@RequestParam(required = false) String modifiedSince) {
|
||||||
|
return R.data(oaSyncService.syncDepartmentPage(current, size, Boolean.TRUE.equals(incremental), modifiedSince));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+134
@@ -0,0 +1,134 @@
|
|||||||
|
/**
|
||||||
|
* 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.controller;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.springblade.core.boot.ctrl.BladeController;
|
||||||
|
import org.springblade.core.mp.support.Condition;
|
||||||
|
import org.springblade.core.mp.support.Query;
|
||||||
|
import org.springblade.core.secure.annotation.PreAuth;
|
||||||
|
import org.springblade.core.tenant.annotation.NonDS;
|
||||||
|
import org.springblade.core.tool.api.R;
|
||||||
|
import org.springblade.core.tool.utils.Func;
|
||||||
|
import org.springblade.system.pojo.entity.MpUser;
|
||||||
|
import org.springblade.system.pojo.vo.MpUserVO;
|
||||||
|
import org.springblade.system.service.IMpUserService;
|
||||||
|
import org.springblade.system.wrapper.MpUserWrapper;
|
||||||
|
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.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小程序用户管理 控制器
|
||||||
|
*
|
||||||
|
* @author Chill
|
||||||
|
*/
|
||||||
|
@NonDS
|
||||||
|
@RestController
|
||||||
|
@AllArgsConstructor
|
||||||
|
@PreAuth(menu = "mp_user")
|
||||||
|
@RequestMapping("mp-user")
|
||||||
|
@Tag(name = "小程序用户", description = "小程序用户管理")
|
||||||
|
public class MpUserController extends BladeController {
|
||||||
|
|
||||||
|
private final IMpUserService mpUserService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 详情
|
||||||
|
*/
|
||||||
|
@GetMapping("/detail")
|
||||||
|
@ApiOperationSupport(order = 1)
|
||||||
|
@Operation(summary = "详情", description = "传入id")
|
||||||
|
public R<MpUserVO> detail(MpUser mpUser) {
|
||||||
|
MpUser detail = mpUserService.getOne(Condition.getQueryWrapper(mpUser));
|
||||||
|
if (detail == null) {
|
||||||
|
return R.fail("小程序用户不存在");
|
||||||
|
}
|
||||||
|
return R.data(MpUserWrapper.build().entityVO(detail));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页
|
||||||
|
*/
|
||||||
|
@GetMapping("/list")
|
||||||
|
@ApiOperationSupport(order = 2)
|
||||||
|
@Operation(summary = "分页", description = "传入查询条件")
|
||||||
|
public R<IPage<MpUserVO>> list(MpUser mpUser, Query query) {
|
||||||
|
IPage<MpUser> pages = mpUserService.selectMpUserPage(Condition.getPage(query), mpUser);
|
||||||
|
return R.data(MpUserWrapper.build().pageVO(pages));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增或修改
|
||||||
|
*/
|
||||||
|
@PostMapping("/submit")
|
||||||
|
@ApiOperationSupport(order = 3)
|
||||||
|
@Operation(summary = "新增或修改", description = "传入MpUser")
|
||||||
|
public R submit(@Valid @RequestBody MpUser mpUser) {
|
||||||
|
return R.status(mpUserService.submitOrUpdate(mpUser));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 逻辑删除
|
||||||
|
*/
|
||||||
|
@PostMapping("/remove")
|
||||||
|
@ApiOperationSupport(order = 4)
|
||||||
|
@Operation(summary = "逻辑删除", description = "传入ids")
|
||||||
|
public R remove(@Parameter(description = "主键集合", required = true) @RequestParam String ids) {
|
||||||
|
return R.status(mpUserService.removeMpUser(Func.toLongList(ids)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启用 / 停用
|
||||||
|
*/
|
||||||
|
@PostMapping("/change-status")
|
||||||
|
@ApiOperationSupport(order = 5)
|
||||||
|
@Operation(summary = "修改状态", description = "status:1启用,0停用")
|
||||||
|
public R changeStatus(@Parameter(description = "用户主键", required = true) @RequestParam Long id,
|
||||||
|
@Parameter(description = "状态:1启用,0停用", required = true) @RequestParam Integer status) {
|
||||||
|
return R.status(mpUserService.changeStatus(id, status));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置密码
|
||||||
|
*/
|
||||||
|
@PostMapping("/reset-password")
|
||||||
|
@ApiOperationSupport(order = 6)
|
||||||
|
@Operation(summary = "重置密码", description = "传入userId集合,返回账号与明文密码")
|
||||||
|
public R<Map<String, String>> resetPassword(
|
||||||
|
@Parameter(description = "userId集合", required = true) @RequestParam String userIds) {
|
||||||
|
return R.data(mpUserService.resetPassword(Func.toLongList(userIds)));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+66
-8
@@ -53,11 +53,14 @@ import org.springblade.core.tool.utils.DateUtil;
|
|||||||
import org.springblade.core.tool.utils.StringPool;
|
import org.springblade.core.tool.utils.StringPool;
|
||||||
import org.springblade.system.excel.UserExcel;
|
import org.springblade.system.excel.UserExcel;
|
||||||
import org.springblade.system.excel.UserImporter;
|
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.entity.User;
|
||||||
import org.springblade.system.pojo.vo.OaPersonSyncPageVO;
|
import org.springblade.system.pojo.vo.OaPersonSyncPageVO;
|
||||||
import org.springblade.system.pojo.vo.UserVO;
|
import org.springblade.system.pojo.vo.UserVO;
|
||||||
import org.springblade.system.service.IOASyncService;
|
import org.springblade.system.service.IOASyncService;
|
||||||
|
import org.springblade.system.service.IMpUserService;
|
||||||
import org.springblade.system.service.IUserService;
|
import org.springblade.system.service.IUserService;
|
||||||
|
import org.springblade.system.utils.MpUserAuth;
|
||||||
import org.springblade.system.wrapper.UserWrapper;
|
import org.springblade.system.wrapper.UserWrapper;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
@@ -79,6 +82,7 @@ import java.util.Map;
|
|||||||
public class UserController {
|
public class UserController {
|
||||||
|
|
||||||
private final IUserService userService;
|
private final IUserService userService;
|
||||||
|
private final IMpUserService mpUserService;
|
||||||
private final IOASyncService oaSyncService;
|
private final IOASyncService oaSyncService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -101,6 +105,10 @@ public class UserController {
|
|||||||
@Operation(summary = "查看详情", description = "传入id")
|
@Operation(summary = "查看详情", description = "传入id")
|
||||||
@GetMapping("/info")
|
@GetMapping("/info")
|
||||||
public R<UserVO> info(BladeUser user) {
|
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());
|
User detail = userService.getById(user.getUserId());
|
||||||
return R.data(UserWrapper.build().entityVO(detail));
|
return R.data(UserWrapper.build().entityVO(detail));
|
||||||
}
|
}
|
||||||
@@ -112,11 +120,12 @@ public class UserController {
|
|||||||
@GetMapping("/list")
|
@GetMapping("/list")
|
||||||
@BladeView(Views.Admin.class)
|
@BladeView(Views.Admin.class)
|
||||||
@Parameters({
|
@Parameters({
|
||||||
@Parameter(name = "account", description = "账号名", in = ParameterIn.QUERY, schema = @Schema(type = "string")),
|
@Parameter(name = "account", description = "账号名(模糊)", in = ParameterIn.QUERY, schema = @Schema(type = "string")),
|
||||||
@Parameter(name = "realName", description = "姓名", in = ParameterIn.QUERY, schema = @Schema(type = "string"))
|
@Parameter(name = "realName", description = "姓名(模糊)", in = ParameterIn.QUERY, schema = @Schema(type = "string")),
|
||||||
|
@Parameter(name = "phone", description = "手机号(模糊)", in = ParameterIn.QUERY, schema = @Schema(type = "string"))
|
||||||
})
|
})
|
||||||
@ApiOperationSupport(order = 3)
|
@ApiOperationSupport(order = 3)
|
||||||
@Operation(summary = "列表", description = "传入account和realName")
|
@Operation(summary = "列表", description = "传入account、realName、phone,支持模糊搜索")
|
||||||
public R<IPage<UserVO>> list(@Parameter(hidden = true) @RequestParam Map<String, Object> user, Query query) {
|
public R<IPage<UserVO>> list(@Parameter(hidden = true) @RequestParam Map<String, Object> user, Query query) {
|
||||||
return R.data(userService.selectPage(user, query));
|
return R.data(userService.selectPage(user, query));
|
||||||
}
|
}
|
||||||
@@ -128,11 +137,12 @@ public class UserController {
|
|||||||
@GetMapping("/page")
|
@GetMapping("/page")
|
||||||
@BladeView(Views.Admin.class)
|
@BladeView(Views.Admin.class)
|
||||||
@Parameters({
|
@Parameters({
|
||||||
@Parameter(name = "account", description = "账号名", in = ParameterIn.QUERY, schema = @Schema(type = "string")),
|
@Parameter(name = "account", description = "账号名(模糊)", in = ParameterIn.QUERY, schema = @Schema(type = "string")),
|
||||||
@Parameter(name = "realName", description = "姓名", in = ParameterIn.QUERY, schema = @Schema(type = "string"))
|
@Parameter(name = "realName", description = "姓名(模糊)", in = ParameterIn.QUERY, schema = @Schema(type = "string")),
|
||||||
|
@Parameter(name = "phone", description = "手机号(模糊)", in = ParameterIn.QUERY, schema = @Schema(type = "string"))
|
||||||
})
|
})
|
||||||
@ApiOperationSupport(order = 4)
|
@ApiOperationSupport(order = 4)
|
||||||
@Operation(summary = "列表", description = "传入account和realName")
|
@Operation(summary = "列表", description = "传入account、realName、phone,支持模糊搜索")
|
||||||
public R<IPage<UserVO>> page(@Parameter(hidden = true) User user, Query query, Long deptId, BladeUser bladeUser) {
|
public R<IPage<UserVO>> page(@Parameter(hidden = true) User user, Query query, Long deptId, BladeUser bladeUser) {
|
||||||
IPage<User> pages = userService.selectUserPage(Condition.getPage(query), user, deptId, (AuthUtil.isAdministrator() ? StringPool.EMPTY : bladeUser.getTenantId()));
|
IPage<User> pages = userService.selectUserPage(Condition.getPage(query), user, deptId, (AuthUtil.isAdministrator() ? StringPool.EMPTY : bladeUser.getTenantId()));
|
||||||
return R.data(UserWrapper.build().pageVO(pages));
|
return R.data(UserWrapper.build().pageVO(pages));
|
||||||
@@ -169,8 +179,10 @@ public class UserController {
|
|||||||
@Operation(summary = "同步OA人员")
|
@Operation(summary = "同步OA人员")
|
||||||
public R<OaPersonSyncPageVO> syncIamAccounts(
|
public R<OaPersonSyncPageVO> syncIamAccounts(
|
||||||
@RequestParam(defaultValue = "1") Integer current,
|
@RequestParam(defaultValue = "1") Integer current,
|
||||||
@RequestParam(defaultValue = "20") Integer size) {
|
@RequestParam(defaultValue = "20") Integer size,
|
||||||
return R.data(oaSyncService.syncPersonFromUserList(current, size));
|
@RequestParam(defaultValue = "false") Boolean incremental,
|
||||||
|
@RequestParam(required = false) String modifiedSince) {
|
||||||
|
return R.data(oaSyncService.syncPersonFromUserList(current, size, Boolean.TRUE.equals(incremental), modifiedSince));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -184,6 +196,18 @@ public class UserController {
|
|||||||
return R.status(userService.updateUser(user));
|
return R.status(userService.updateUser(user));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启用或停用
|
||||||
|
*/
|
||||||
|
@IsAdmin
|
||||||
|
@PostMapping("/change-status")
|
||||||
|
@ApiOperationSupport(order = 6)
|
||||||
|
@Operation(summary = "启用停用", description = "传入id和status")
|
||||||
|
public R changeStatus(@Parameter(description = "用户主键", required = true) @RequestParam Long id,
|
||||||
|
@Parameter(description = "状态:1启用,0停用", required = true) @RequestParam Integer status) {
|
||||||
|
return R.status(userService.changeUserStatus(id, status));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除
|
* 删除
|
||||||
*/
|
*/
|
||||||
@@ -236,6 +260,7 @@ public class UserController {
|
|||||||
* 当前用户设置/重置登录密码(小程序首次设密、短信验证后改密)
|
* 当前用户设置/重置登录密码(小程序首次设密、短信验证后改密)
|
||||||
* <p>
|
* <p>
|
||||||
* 对外路径:/blade-system/user/password ;网关别名 /blade-user/password 亦可到达。
|
* 对外路径:/blade-system/user/password ;网关别名 /blade-user/password 亦可到达。
|
||||||
|
* 小程序用户写入 blade_mp_user,系统用户仍写入 blade_user。
|
||||||
*/
|
*/
|
||||||
@PostMapping("/password")
|
@PostMapping("/password")
|
||||||
@ApiOperationSupport(order = 10)
|
@ApiOperationSupport(order = 10)
|
||||||
@@ -243,6 +268,9 @@ public class UserController {
|
|||||||
public R password(BladeUser user,
|
public R password(BladeUser user,
|
||||||
@Parameter(description = "新密码", required = true) @RequestParam String password,
|
@Parameter(description = "新密码", required = true) @RequestParam String password,
|
||||||
@Parameter(description = "确认密码", required = true) @RequestParam String password2) {
|
@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));
|
return R.status(userService.setPassword(user.getUserId(), password, password2));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -422,4 +450,34 @@ public class UserController {
|
|||||||
return R.data(list);
|
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;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户手机号变更(小程序「修改手机号」)
|
* 小程序用户手机号变更(写入 blade_mp_user)
|
||||||
* <p>
|
* <p>
|
||||||
* 对外路径:/blade-system/user/phone/** ;网关别名 /blade-user/phone/** 亦可到达。
|
* 对外路径:/blade-system/user/phone/** ;网关别名 /blade-user/phone/** 亦可到达。
|
||||||
*
|
*
|
||||||
|
|||||||
+31
-44
@@ -44,92 +44,79 @@ public interface DeptConvert {
|
|||||||
|
|
||||||
@Mapping(source = "subcompanyname", target = "deptName")
|
@Mapping(source = "subcompanyname", target = "deptName")
|
||||||
@Mapping(source = "subcompanydesc", target = "fullName")
|
@Mapping(source = "subcompanydesc", target = "fullName")
|
||||||
// 父公司id转为父部门编码,要拼接前缀
|
|
||||||
@Mapping(source = "supsubcomid", target = "parentCode")
|
@Mapping(source = "supsubcomid", target = "parentCode")
|
||||||
@Mapping(source = "created", target = "createTime", dateFormat = "yyyy-MM-dd HH:mm:ss")
|
@Mapping(source = "created", target = "createTime", dateFormat = "yyyy-MM-dd HH:mm:ss")
|
||||||
@Mapping(source = "modified", target = "updateTime", dateFormat = "yyyy-MM-dd HH:mm:ss")
|
@Mapping(source = "modified", target = "oaModified")
|
||||||
Dept baseConvert(OACompanyResponse company);
|
Dept baseConvert(OACompanyResponse company);
|
||||||
|
|
||||||
@Mapping(source = "departmentname", target = "deptName")
|
@Mapping(source = "departmentname", target = "deptName")
|
||||||
@Mapping(source = "departmentmark", target = "fullName")
|
@Mapping(source = "departmentmark", target = "fullName")
|
||||||
// 父部门id转为父部门编码,要拼接前缀
|
|
||||||
@Mapping(source = "supdepid", target = "parentCode")
|
@Mapping(source = "supdepid", target = "parentCode")
|
||||||
// 所属子公司id转为所属公司编码,要拼接前缀
|
|
||||||
@Mapping(source = "subcompanyid1", target = "belongCompanyCode")
|
@Mapping(source = "subcompanyid1", target = "belongCompanyCode")
|
||||||
@Mapping(source = "created", target = "createTime", dateFormat = "yyyy-MM-dd HH:mm:ss")
|
@Mapping(source = "created", target = "createTime", dateFormat = "yyyy-MM-dd HH:mm:ss")
|
||||||
@Mapping(source = "modified", target = "updateTime", dateFormat = "yyyy-MM-dd HH:mm:ss")
|
@Mapping(source = "modified", target = "oaModified")
|
||||||
Dept baseConvert(OADepartmentResponse department);
|
Dept baseConvert(OADepartmentResponse department);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* oa公司转本系统部门
|
* oa公司转本系统部门。组织编码由同步服务按 GWWL+三位数字 统一分配。
|
||||||
* @param company
|
|
||||||
* @return
|
|
||||||
*/
|
*/
|
||||||
default Dept company2dept(OACompanyResponse company) {
|
default Dept company2dept(OACompanyResponse company) {
|
||||||
Dept dept = this.baseConvert(company);
|
Dept dept = this.baseConvert(company);
|
||||||
// 部门编码添加前缀
|
// 编码稍后按规则生成;此处仅落 OA 主键,供按 oaId 匹配与重建层级
|
||||||
dept.setDeptCode(OAConvertConstant.COMPANY_OA_PREFIX + company.getId());
|
dept.setDeptCode(null);
|
||||||
String parentCode = dept.getParentCode();
|
String parentCode = dept.getParentCode();
|
||||||
if (!OAConstant.ROOT_COMPANY_ID.equals(parentCode)) {
|
if (!OAConstant.ROOT_COMPANY_ID.equals(parentCode)) {
|
||||||
// 父部门编码不是0,拼接前缀
|
// 非顶级:parentCode 暂存上级 OA 主键,分配编码后再替换为正式组织编码
|
||||||
dept.setParentCode(OAConvertConstant.COMPANY_OA_PREFIX + parentCode);
|
dept.setParentCode(parentCode);
|
||||||
// 父部门id后续处理
|
dept.setParentId(null);
|
||||||
} else {
|
} else {
|
||||||
// 父部门编码是0,父id也设置为0
|
dept.setParentCode(OAConvertConstant.OA_SYNC_ROOT_DEPT_CODE);
|
||||||
dept.setParentId(OAConvertConstant.ROOT_PARENT_ID);
|
dept.setParentId(OAConvertConstant.OA_SYNC_ROOT_DEPT_ID);
|
||||||
}
|
}
|
||||||
// 公司所属公司编码就是他自己的部门编码
|
dept.setBelongCompanyCode(null);
|
||||||
dept.setBelongCompanyCode(dept.getDeptCode());
|
|
||||||
// OA主键与上级OA主键,分批同步结束后用于重建层级
|
|
||||||
dept.setOaId(company.getId());
|
dept.setOaId(company.getId());
|
||||||
dept.setOaSupSubComId(company.getSupsubcomid());
|
dept.setOaSupSubComId(company.getSupsubcomid());
|
||||||
// 显示顺序处理
|
|
||||||
dept.setSort(OAUtils.parseInt(company.getShoworder()));
|
dept.setSort(OAUtils.parseInt(company.getShoworder()));
|
||||||
// 部门类型,公司
|
|
||||||
dept.setDeptCategory(DeptCategory.COMPANY.getCode());
|
dept.setDeptCategory(DeptCategory.COMPANY.getCode());
|
||||||
dept.setSyncTime(new Date());
|
dept.setSyncTime(new Date());
|
||||||
// 是oa的
|
|
||||||
dept.setIsOa(CommonConstant.YES);
|
dept.setIsOa(CommonConstant.YES);
|
||||||
return dept;
|
return dept;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* oa部门转本系统部门
|
* oa部门转本系统部门。组织编码由同步服务按「所属公司编码+三位数字」统一分配。
|
||||||
* @param oaDept
|
*
|
||||||
* @param companyDeptMap 所有公司的部门编码与id map
|
* @param oaDept OA部门
|
||||||
* @return
|
* @param companyByOaId 已同步公司(oaId → 本地公司)
|
||||||
*/
|
*/
|
||||||
default Dept dept2dept(OADepartmentResponse oaDept, Map<String, Long> companyDeptMap) {
|
default Dept dept2dept(OADepartmentResponse oaDept, Map<String, Dept> companyByOaId) {
|
||||||
Dept dept = this.baseConvert(oaDept);
|
Dept dept = this.baseConvert(oaDept);
|
||||||
// 部门编码添加前缀
|
dept.setDeptCode(null);
|
||||||
dept.setDeptCode(OAConvertConstant.DEPARTMENT_OA_PREFIX + oaDept.getId());
|
|
||||||
// OA主键;上级为根部门时记录所属公司OA主键,否则记录上级部门OA主键
|
|
||||||
dept.setOaId(oaDept.getId());
|
dept.setOaId(oaDept.getId());
|
||||||
if (OAConstant.ROOT_COMPANY_ID.equals(oaDept.getSupdepid())) {
|
if (OAConstant.ROOT_COMPANY_ID.equals(oaDept.getSupdepid())) {
|
||||||
dept.setOaSupSubComId(oaDept.getSubcompanyid1());
|
dept.setOaSupSubComId(oaDept.getSubcompanyid1());
|
||||||
} else {
|
} else {
|
||||||
dept.setOaSupSubComId(oaDept.getSupdepid());
|
dept.setOaSupSubComId(oaDept.getSupdepid());
|
||||||
}
|
}
|
||||||
// 部门所属公司编码就是他所属公司编码加前缀
|
Dept belongCompany = companyByOaId == null ? null : companyByOaId.get(oaDept.getSubcompanyid1());
|
||||||
dept.setBelongCompanyCode(OAConvertConstant.COMPANY_OA_PREFIX + dept.getBelongCompanyCode());
|
if (belongCompany != null) {
|
||||||
String parentCode = dept.getParentCode();
|
dept.setBelongCompanyCode(belongCompany.getDeptCode());
|
||||||
if (!OAConstant.ROOT_COMPANY_ID.equals(parentCode)) {
|
dept.setParentId(null);
|
||||||
// 父部门编码不是0,拼接前缀
|
if (OAConstant.ROOT_COMPANY_ID.equals(oaDept.getSupdepid())) {
|
||||||
dept.setParentCode(OAConvertConstant.DEPARTMENT_OA_PREFIX + parentCode);
|
dept.setParentCode(belongCompany.getDeptCode());
|
||||||
// 父部门id后续处理
|
dept.setParentId(belongCompany.getId());
|
||||||
} else {
|
} else {
|
||||||
// 父部门编码是0,设置为公司编码
|
// 上级为部门:parentCode 暂存上级部门 OA 主键,重建层级时再解析
|
||||||
dept.setParentCode(dept.getBelongCompanyCode());
|
dept.setParentCode(oaDept.getSupdepid());
|
||||||
// 根据父部门编码获取对应的id
|
}
|
||||||
dept.setParentId(companyDeptMap.get(dept.getParentCode()));
|
} else {
|
||||||
|
// 所属公司尚未解析时,暂存 OA 公司主键,后续补齐正式编码
|
||||||
|
dept.setBelongCompanyCode(oaDept.getSubcompanyid1());
|
||||||
|
dept.setParentCode(oaDept.getSupdepid());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 显示顺序处理
|
|
||||||
dept.setSort(OAUtils.parseInt(oaDept.getShoworder()));
|
dept.setSort(OAUtils.parseInt(oaDept.getShoworder()));
|
||||||
// 部门类型,部门
|
|
||||||
dept.setDeptCategory(DeptCategory.DEPT.getCode());
|
dept.setDeptCategory(DeptCategory.DEPT.getCode());
|
||||||
dept.setSyncTime(new Date());
|
dept.setSyncTime(new Date());
|
||||||
// 是oa的
|
|
||||||
dept.setIsOa(CommonConstant.YES);
|
dept.setIsOa(CommonConstant.YES);
|
||||||
return dept;
|
return dept;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -70,6 +70,7 @@ public interface UserConvert {
|
|||||||
@Mapping(source = "lastname", target = "realName")
|
@Mapping(source = "lastname", target = "realName")
|
||||||
@Mapping(source = "mobile", target = "phone")
|
@Mapping(source = "mobile", target = "phone")
|
||||||
@Mapping(source = "email", target = "email")
|
@Mapping(source = "email", target = "email")
|
||||||
|
@Mapping(source = "modified", target = "oaModified")
|
||||||
User baseConvert(OAPersonResponse person);
|
User baseConvert(OAPersonResponse person);
|
||||||
|
|
||||||
@Mapping(source = "subcompanyname", target = "companyName")
|
@Mapping(source = "subcompanyname", target = "companyName")
|
||||||
|
|||||||
+1
-1
@@ -54,7 +54,7 @@ public class RailwayStationExcel implements Serializable {
|
|||||||
@ExcelProperty("TMIS国标编码*")
|
@ExcelProperty("TMIS国标编码*")
|
||||||
private String tmisCode;
|
private String tmisCode;
|
||||||
|
|
||||||
@ExcelProperty("电报略码*")
|
@ExcelProperty("电报略码")
|
||||||
private String telegraphCode;
|
private String telegraphCode;
|
||||||
|
|
||||||
@ExcelProperty("车站名称*")
|
@ExcelProperty("车站名称*")
|
||||||
|
|||||||
+1
-1
@@ -56,7 +56,7 @@ public class RailwayStationExportExcel implements Serializable {
|
|||||||
@ExcelProperty("TMIS国标编码*")
|
@ExcelProperty("TMIS国标编码*")
|
||||||
private String tmisCode;
|
private String tmisCode;
|
||||||
|
|
||||||
@ExcelProperty("电报略码*")
|
@ExcelProperty("电报略码")
|
||||||
private String telegraphCode;
|
private String telegraphCode;
|
||||||
|
|
||||||
@ExcelProperty("车站名称*")
|
@ExcelProperty("车站名称*")
|
||||||
|
|||||||
+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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
<result column="dept_category" property="deptCategory"/>
|
<result column="dept_category" property="deptCategory"/>
|
||||||
<result column="dept_code" property="deptCode"/>
|
<result column="dept_code" property="deptCode"/>
|
||||||
<result column="kingdee_dept_code" property="kingdeeDeptCode"/>
|
<result column="kingdee_dept_code" property="kingdeeDeptCode"/>
|
||||||
|
<result column="contract_code_prefix" property="contractCodePrefix"/>
|
||||||
<result column="short_name" property="shortName"/>
|
<result column="short_name" property="shortName"/>
|
||||||
<result column="pinyin_mnemonic" property="pinyinMnemonic"/>
|
<result column="pinyin_mnemonic" property="pinyinMnemonic"/>
|
||||||
<result column="mnemonic_code" property="mnemonicCode"/>
|
<result column="mnemonic_code" property="mnemonicCode"/>
|
||||||
@@ -34,6 +35,7 @@
|
|||||||
<result column="dept_category" property="deptCategory"/>
|
<result column="dept_category" property="deptCategory"/>
|
||||||
<result column="dept_code" property="deptCode"/>
|
<result column="dept_code" property="deptCode"/>
|
||||||
<result column="kingdee_dept_code" property="kingdeeDeptCode"/>
|
<result column="kingdee_dept_code" property="kingdeeDeptCode"/>
|
||||||
|
<result column="contract_code_prefix" property="contractCodePrefix"/>
|
||||||
<result column="short_name" property="shortName"/>
|
<result column="short_name" property="shortName"/>
|
||||||
<result column="pinyin_mnemonic" property="pinyinMnemonic"/>
|
<result column="pinyin_mnemonic" property="pinyinMnemonic"/>
|
||||||
<result column="mnemonic_code" property="mnemonicCode"/>
|
<result column="mnemonic_code" property="mnemonicCode"/>
|
||||||
@@ -78,6 +80,9 @@
|
|||||||
<if test="param3.deptName!=null and param3.deptName!=''">
|
<if test="param3.deptName!=null and param3.deptName!=''">
|
||||||
and dept.dept_name like concat(concat('%', #{param3.deptName}),'%')
|
and dept.dept_name like concat(concat('%', #{param3.deptName}),'%')
|
||||||
</if>
|
</if>
|
||||||
|
<if test="param3.shortName!=null and param3.shortName!=''">
|
||||||
|
and dept.short_name like concat(concat('%', #{param3.shortName}),'%')
|
||||||
|
</if>
|
||||||
<if test="param3.fullName!=null and param3.fullName!=''">
|
<if test="param3.fullName!=null and param3.fullName!=''">
|
||||||
and dept.full_name like concat(concat('%', #{param3.fullName}),'%')
|
and dept.full_name like concat(concat('%', #{param3.fullName}),'%')
|
||||||
</if>
|
</if>
|
||||||
|
|||||||
+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> {
|
||||||
|
}
|
||||||
+5
@@ -45,6 +45,11 @@ public interface RailwayStationMapper extends BaseMapper<RailwayStation> {
|
|||||||
*/
|
*/
|
||||||
RailwayStation selectByCodeIncludingDeleted(@Param("code") String code);
|
RailwayStation selectByCodeIncludingDeleted(@Param("code") String code);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按 TMIS 国标编码查询铁路车站(包含逻辑删除记录,用于导入恢复)。
|
||||||
|
*/
|
||||||
|
RailwayStation selectByTmisCodeIncludingDeleted(@Param("tmisCode") String tmisCode);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 恢复逻辑删除铁路车站。
|
* 恢复逻辑删除铁路车站。
|
||||||
*/
|
*/
|
||||||
|
|||||||
+8
@@ -36,6 +36,14 @@
|
|||||||
SELECT *
|
SELECT *
|
||||||
FROM blade_railway_station
|
FROM blade_railway_station
|
||||||
WHERE code = #{code}
|
WHERE code = #{code}
|
||||||
|
LIMIT 1
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectByTmisCodeIncludingDeleted" resultType="org.springblade.system.pojo.entity.RailwayStation">
|
||||||
|
SELECT *
|
||||||
|
FROM blade_railway_station
|
||||||
|
WHERE tmis_code = #{tmisCode}
|
||||||
|
LIMIT 1
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<update id="restoreById">
|
<update id="restoreById">
|
||||||
|
|||||||
+6
-3
@@ -44,13 +44,16 @@
|
|||||||
and bu.tenant_id = #{user.tenantId}
|
and bu.tenant_id = #{user.tenantId}
|
||||||
</if>
|
</if>
|
||||||
<if test="user.account!=null and user.account != ''">
|
<if test="user.account!=null and user.account != ''">
|
||||||
and bu.account = #{user.account}
|
and bu.account like concat('%', #{user.account}, '%')
|
||||||
</if>
|
</if>
|
||||||
<if test="user.realName!=null and user.realName != ''">
|
<if test="user.realName!=null and user.realName != ''">
|
||||||
and bu.real_name = #{user.realName}
|
and (
|
||||||
|
bu.real_name like concat('%', #{user.realName}, '%')
|
||||||
|
or bu.name like concat('%', #{user.realName}, '%')
|
||||||
|
)
|
||||||
</if>
|
</if>
|
||||||
<if test="user.phone!=null and user.phone != ''">
|
<if test="user.phone!=null and user.phone != ''">
|
||||||
and bu.phone = #{user.phone}
|
and bu.phone like concat('%', #{user.phone}, '%')
|
||||||
</if>
|
</if>
|
||||||
<if test="user.userType!=null and user.userType != ''">
|
<if test="user.userType!=null and user.userType != ''">
|
||||||
and bu.user_type = #{user.userType}
|
and bu.user_type = #{user.userType}
|
||||||
|
|||||||
+2
-1
@@ -30,6 +30,7 @@ import org.springblade.core.literule.core.RuleComponent;
|
|||||||
import org.springblade.core.tool.constant.BladeConstant;
|
import org.springblade.core.tool.constant.BladeConstant;
|
||||||
import org.springblade.system.pojo.entity.Dept;
|
import org.springblade.system.pojo.entity.Dept;
|
||||||
import org.springblade.system.pojo.entity.Tenant;
|
import org.springblade.system.pojo.entity.Tenant;
|
||||||
|
import org.springblade.system.pojo.enums.DeptCategory;
|
||||||
import org.springblade.system.rule.context.TenantContext;
|
import org.springblade.system.rule.context.TenantContext;
|
||||||
|
|
||||||
import static org.springblade.system.rule.constant.TenantRuleConstant.TENANT_DEPT_RULE;
|
import static org.springblade.system.rule.constant.TenantRuleConstant.TENANT_DEPT_RULE;
|
||||||
@@ -54,7 +55,7 @@ public class TenantDeptRule extends RuleComponent {
|
|||||||
dept.setAncestors(String.valueOf(BladeConstant.TOP_PARENT_ID));
|
dept.setAncestors(String.valueOf(BladeConstant.TOP_PARENT_ID));
|
||||||
dept.setDeptName(tenant.getTenantName());
|
dept.setDeptName(tenant.getTenantName());
|
||||||
dept.setFullName(tenant.getTenantName());
|
dept.setFullName(tenant.getTenantName());
|
||||||
dept.setDeptCategory(1);
|
dept.setDeptCategory(DeptCategory.GROUP.getCode());
|
||||||
dept.setSort(2);
|
dept.setSort(2);
|
||||||
dept.setIsDeleted(BladeConstant.DB_NOT_DELETED);
|
dept.setIsDeleted(BladeConstant.DB_NOT_DELETED);
|
||||||
|
|
||||||
|
|||||||
+8
-1
@@ -149,12 +149,19 @@ public interface IDeptService extends IService<Dept> {
|
|||||||
boolean removeDept(String ids);
|
boolean removeDept(String ids);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除非顶级组织,顶级组织(父级为 0)保留
|
* 清除非保留顶级外的组织;保留 id=135 集团锚点,以及名称为「外部组织」的顶级
|
||||||
*
|
*
|
||||||
* @return 删除数量
|
* @return 删除数量
|
||||||
*/
|
*/
|
||||||
int clearNonTopDepts();
|
int clearNonTopDepts();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确保双顶级:id=135 集团(编码 GWWL)+ 独立「外部组织」顶级
|
||||||
|
*
|
||||||
|
* @return 集团顶级组织(id=135)
|
||||||
|
*/
|
||||||
|
Dept ensureOaSyncRootDept();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 新增或修改部门
|
* 新增或修改部门
|
||||||
* <p>
|
* <p>
|
||||||
|
|||||||
+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.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import org.springblade.core.mp.base.BaseService;
|
||||||
|
import org.springblade.system.pojo.entity.MpUser;
|
||||||
|
import org.springblade.system.pojo.entity.MpUserInfo;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小程序用户 服务类
|
||||||
|
*
|
||||||
|
* @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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 后台分页查询
|
||||||
|
*/
|
||||||
|
IPage<MpUser> selectMpUserPage(IPage<MpUser> page, MpUser query);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存小程序用户
|
||||||
|
*/
|
||||||
|
boolean submit(MpUser user);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新小程序用户
|
||||||
|
*/
|
||||||
|
boolean updateUser(MpUser user);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增或修改(后台管理)
|
||||||
|
*/
|
||||||
|
boolean submitOrUpdate(MpUser user);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 逻辑删除
|
||||||
|
*/
|
||||||
|
boolean removeMpUser(List<Long> ids);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启用 / 停用
|
||||||
|
*/
|
||||||
|
boolean changeStatus(Long id, Integer status);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置密码为随机密码,返回 account -> 明文密码
|
||||||
|
*/
|
||||||
|
Map<String, String> resetPassword(List<Long> userIds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绑定/更新 openid
|
||||||
|
*/
|
||||||
|
boolean bindOpenId(String tenantId, Long userId, String openid);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前小程序用户设置密码
|
||||||
|
*/
|
||||||
|
boolean setPassword(Long userId, String password, String password2);
|
||||||
|
|
||||||
|
}
|
||||||
+9
-3
@@ -34,25 +34,31 @@ public interface IOASyncService {
|
|||||||
*
|
*
|
||||||
* @param current 当前页,从 1 开始
|
* @param current 当前页,从 1 开始
|
||||||
* @param size 每页条数
|
* @param size 每页条数
|
||||||
|
* @param incremental 是否增量同步(按本地最大 oaModified 过滤 OA)
|
||||||
|
* @param modifiedSince 增量起始时间;首屏可空,后续分页回传首屏返回值
|
||||||
* @return 本页同步结果
|
* @return 本页同步结果
|
||||||
*/
|
*/
|
||||||
OaPersonSyncPageVO syncPersonFromUserList(int current, int size);
|
OaPersonSyncPageVO syncPersonFromUserList(int current, int size, boolean incremental, String modifiedSince);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按页从 OA 公司接口同步公司
|
* 按页从 OA 公司接口同步公司
|
||||||
*
|
*
|
||||||
* @param current 当前页,从 1 开始
|
* @param current 当前页,从 1 开始
|
||||||
* @param size 每页条数
|
* @param size 每页条数
|
||||||
|
* @param incremental 是否增量同步(按本地最大 oaModified 过滤 OA)
|
||||||
|
* @param modifiedSince 增量起始时间;首屏可空,后续分页回传首屏返回值
|
||||||
* @return 本页同步结果
|
* @return 本页同步结果
|
||||||
*/
|
*/
|
||||||
OaOrgSyncPageVO syncCompanyPage(int current, int size);
|
OaOrgSyncPageVO syncCompanyPage(int current, int size, boolean incremental, String modifiedSince);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按页从 OA 部门接口同步部门;最后一页完成后更新祖级列表
|
* 按页从 OA 部门接口同步部门;最后一页完成后更新祖级列表
|
||||||
*
|
*
|
||||||
* @param current 当前页,从 1 开始
|
* @param current 当前页,从 1 开始
|
||||||
* @param size 每页条数
|
* @param size 每页条数
|
||||||
|
* @param incremental 是否增量同步(按本地最大 oaModified 过滤 OA)
|
||||||
|
* @param modifiedSince 增量起始时间;首屏可空,后续分页回传首屏返回值
|
||||||
* @return 本页同步结果
|
* @return 本页同步结果
|
||||||
*/
|
*/
|
||||||
OaOrgSyncPageVO syncDepartmentPage(int current, int size);
|
OaOrgSyncPageVO syncDepartmentPage(int current, int size, boolean incremental, String modifiedSince);
|
||||||
}
|
}
|
||||||
|
|||||||
+9
@@ -93,6 +93,15 @@ public interface IUserService extends BaseService<User> {
|
|||||||
*/
|
*/
|
||||||
boolean updateUser(User user);
|
boolean updateUser(User user);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启用或停用用户(租户守卫校验用户归属,仅更新状态)
|
||||||
|
*
|
||||||
|
* @param userId 用户主键
|
||||||
|
* @param status 状态:1 启用,0 停用
|
||||||
|
* @return 是否成功
|
||||||
|
*/
|
||||||
|
boolean changeUserStatus(Long userId, Integer status);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改用户基本信息(仅限当前登录用户自身,忽略角色 / 部门 / 岗位 / 账号等敏感字段)
|
* 修改用户基本信息(仅限当前登录用户自身,忽略角色 / 部门 / 岗位 / 账号等敏感字段)
|
||||||
*
|
*
|
||||||
|
|||||||
+286
-48
@@ -27,6 +27,7 @@ package org.springblade.system.service.impl;
|
|||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
@@ -389,16 +390,193 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
|
|||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public int clearNonTopDepts() {
|
public int clearNonTopDepts() {
|
||||||
Long nonTopCount = this.count(Wrappers.<Dept>lambdaQuery()
|
// 同步前清理:保留集团锚点 id=135,以及「外部组织」顶级;其余删除
|
||||||
.isNotNull(Dept::getParentId)
|
Dept groupRoot = this.ensureOaSyncRootDept();
|
||||||
.ne(Dept::getParentId, BladeConstant.TOP_PARENT_ID));
|
Dept externalRoot = this.ensureExternalOrgTopDept();
|
||||||
if (nonTopCount == null || nonTopCount == 0L) {
|
Set<Long> keepIds = new HashSet<>();
|
||||||
|
if (groupRoot != null && groupRoot.getId() != null) {
|
||||||
|
keepIds.add(groupRoot.getId());
|
||||||
|
}
|
||||||
|
if (externalRoot != null && externalRoot.getId() != null) {
|
||||||
|
keepIds.add(externalRoot.getId());
|
||||||
|
}
|
||||||
|
if (keepIds.isEmpty()) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
this.remove(Wrappers.<Dept>lambdaQuery()
|
Long clearCount = this.count(Wrappers.<Dept>lambdaQuery().notIn(Dept::getId, keepIds));
|
||||||
.isNotNull(Dept::getParentId)
|
if (clearCount == null || clearCount == 0L) {
|
||||||
.ne(Dept::getParentId, BladeConstant.TOP_PARENT_ID));
|
return 0;
|
||||||
return nonTopCount.intValue();
|
}
|
||||||
|
this.remove(Wrappers.<Dept>lambdaQuery().notIn(Dept::getId, keepIds));
|
||||||
|
return clearCount.intValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Dept ensureOaSyncRootDept() {
|
||||||
|
this.migrateLegacyOaDeptCategory();
|
||||||
|
this.removeLegacyOaSyncRoots();
|
||||||
|
Dept rootDept = this.getById(OAConvertConstant.OA_SYNC_ROOT_DEPT_ID);
|
||||||
|
String tenantId = AuthUtil.getTenantId();
|
||||||
|
if (StringUtil.isBlank(tenantId)) {
|
||||||
|
tenantId = BladeConstant.ADMIN_TENANT_ID;
|
||||||
|
}
|
||||||
|
if (rootDept == null) {
|
||||||
|
rootDept = new Dept();
|
||||||
|
rootDept.setId(OAConvertConstant.OA_SYNC_ROOT_DEPT_ID);
|
||||||
|
rootDept.setTenantId(tenantId);
|
||||||
|
rootDept.setParentId(BladeConstant.TOP_PARENT_ID);
|
||||||
|
rootDept.setAncestors(String.valueOf(BladeConstant.TOP_PARENT_ID));
|
||||||
|
rootDept.setDeptName(OAConvertConstant.OA_SYNC_ROOT_DEPT_NAME);
|
||||||
|
rootDept.setFullName(OAConvertConstant.OA_SYNC_ROOT_DEPT_NAME);
|
||||||
|
rootDept.setShortName(OAConvertConstant.OA_SYNC_ROOT_DEPT_NAME);
|
||||||
|
rootDept.setDeptCode(OAConvertConstant.OA_SYNC_ROOT_DEPT_CODE);
|
||||||
|
rootDept.setParentCode(String.valueOf(BladeConstant.TOP_PARENT_ID));
|
||||||
|
rootDept.setBelongCompanyCode(OAConvertConstant.OA_SYNC_ROOT_DEPT_CODE);
|
||||||
|
rootDept.setDeptCategory(DeptCategory.GROUP.getCode());
|
||||||
|
rootDept.setSort(0);
|
||||||
|
rootDept.setStatus(DataStatusEnum.ENABLE.getCode());
|
||||||
|
rootDept.setIsDeleted(BladeConstant.DB_NOT_DELETED);
|
||||||
|
rootDept.setIsOa(0);
|
||||||
|
rootDept.setIsPlatformCompany(0);
|
||||||
|
rootDept.setSyncTime(new Date());
|
||||||
|
this.save(rootDept);
|
||||||
|
log.info("已创建 OA 同步集团顶级 id={} name={} code={}",
|
||||||
|
OAConvertConstant.OA_SYNC_ROOT_DEPT_ID,
|
||||||
|
OAConvertConstant.OA_SYNC_ROOT_DEPT_NAME,
|
||||||
|
OAConvertConstant.OA_SYNC_ROOT_DEPT_CODE);
|
||||||
|
} else {
|
||||||
|
boolean wronglyNamedAsExternal = OAConvertConstant.EXTERNAL_ORG_NAME.equals(rootDept.getDeptName());
|
||||||
|
boolean needUpdate = wronglyNamedAsExternal
|
||||||
|
|| !OAConvertConstant.OA_SYNC_ROOT_DEPT_CODE.equals(rootDept.getDeptCode())
|
||||||
|
|| !DeptCategory.GROUP.getCode().equals(rootDept.getDeptCategory())
|
||||||
|
|| !Objects.equals(BladeConstant.TOP_PARENT_ID, rootDept.getParentId());
|
||||||
|
if (needUpdate) {
|
||||||
|
Dept updateParam = new Dept();
|
||||||
|
updateParam.setId(rootDept.getId());
|
||||||
|
if (wronglyNamedAsExternal || StringUtil.isBlank(rootDept.getDeptName())) {
|
||||||
|
updateParam.setDeptName(OAConvertConstant.OA_SYNC_ROOT_DEPT_NAME);
|
||||||
|
updateParam.setFullName(OAConvertConstant.OA_SYNC_ROOT_DEPT_NAME);
|
||||||
|
updateParam.setShortName(OAConvertConstant.OA_SYNC_ROOT_DEPT_NAME);
|
||||||
|
rootDept.setDeptName(OAConvertConstant.OA_SYNC_ROOT_DEPT_NAME);
|
||||||
|
rootDept.setFullName(OAConvertConstant.OA_SYNC_ROOT_DEPT_NAME);
|
||||||
|
rootDept.setShortName(OAConvertConstant.OA_SYNC_ROOT_DEPT_NAME);
|
||||||
|
}
|
||||||
|
updateParam.setDeptCode(OAConvertConstant.OA_SYNC_ROOT_DEPT_CODE);
|
||||||
|
updateParam.setBelongCompanyCode(OAConvertConstant.OA_SYNC_ROOT_DEPT_CODE);
|
||||||
|
updateParam.setDeptCategory(DeptCategory.GROUP.getCode());
|
||||||
|
updateParam.setParentId(BladeConstant.TOP_PARENT_ID);
|
||||||
|
updateParam.setAncestors(String.valueOf(BladeConstant.TOP_PARENT_ID));
|
||||||
|
updateParam.setParentCode(String.valueOf(BladeConstant.TOP_PARENT_ID));
|
||||||
|
this.updateById(updateParam);
|
||||||
|
rootDept.setDeptCode(OAConvertConstant.OA_SYNC_ROOT_DEPT_CODE);
|
||||||
|
rootDept.setBelongCompanyCode(OAConvertConstant.OA_SYNC_ROOT_DEPT_CODE);
|
||||||
|
rootDept.setDeptCategory(DeptCategory.GROUP.getCode());
|
||||||
|
rootDept.setParentId(BladeConstant.TOP_PARENT_ID);
|
||||||
|
rootDept.setAncestors(String.valueOf(BladeConstant.TOP_PARENT_ID));
|
||||||
|
rootDept.setParentCode(String.valueOf(BladeConstant.TOP_PARENT_ID));
|
||||||
|
log.info("已校正 OA 同步集团顶级 id={} name={} code={} category={}",
|
||||||
|
rootDept.getId(), rootDept.getDeptName(), rootDept.getDeptCode(), rootDept.getDeptCategory());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.ensureExternalOrgTopDept();
|
||||||
|
return rootDept;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确保存在独立的「外部组织」顶级(与 id=135 集团并存,仅保留一个同名顶级)
|
||||||
|
*/
|
||||||
|
private Dept ensureExternalOrgTopDept() {
|
||||||
|
List<Dept> externalTops = this.list(Wrappers.<Dept>lambdaQuery()
|
||||||
|
.eq(Dept::getDeptName, OAConvertConstant.EXTERNAL_ORG_NAME)
|
||||||
|
.and(wrapper -> wrapper
|
||||||
|
.isNull(Dept::getParentId)
|
||||||
|
.or()
|
||||||
|
.eq(Dept::getParentId, BladeConstant.TOP_PARENT_ID))
|
||||||
|
.ne(Dept::getId, OAConvertConstant.OA_SYNC_ROOT_DEPT_ID)
|
||||||
|
.orderByAsc(Dept::getId));
|
||||||
|
if (CollectionUtil.isNotEmpty(externalTops)) {
|
||||||
|
Dept keep = externalTops.get(0);
|
||||||
|
if (externalTops.size() > 1) {
|
||||||
|
List<Long> duplicateIds = externalTops.stream()
|
||||||
|
.skip(1)
|
||||||
|
.map(Dept::getId)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.toList();
|
||||||
|
if (CollectionUtil.isNotEmpty(duplicateIds)) {
|
||||||
|
this.update(Wrappers.<Dept>lambdaUpdate()
|
||||||
|
.set(Dept::getParentId, keep.getId())
|
||||||
|
.in(Dept::getParentId, duplicateIds));
|
||||||
|
this.removeByIds(duplicateIds);
|
||||||
|
log.info("已合并重复的外部组织顶级,保留 id={},删除{}", keep.getId(), duplicateIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return keep;
|
||||||
|
}
|
||||||
|
String tenantId = AuthUtil.getTenantId();
|
||||||
|
if (StringUtil.isBlank(tenantId)) {
|
||||||
|
tenantId = BladeConstant.ADMIN_TENANT_ID;
|
||||||
|
}
|
||||||
|
Dept externalDept = new Dept();
|
||||||
|
externalDept.setId(DefaultIdentifierGenerator.getInstance().nextId(null));
|
||||||
|
externalDept.setTenantId(tenantId);
|
||||||
|
externalDept.setParentId(BladeConstant.TOP_PARENT_ID);
|
||||||
|
externalDept.setAncestors(String.valueOf(BladeConstant.TOP_PARENT_ID));
|
||||||
|
externalDept.setDeptName(OAConvertConstant.EXTERNAL_ORG_NAME);
|
||||||
|
externalDept.setFullName(OAConvertConstant.EXTERNAL_ORG_NAME);
|
||||||
|
externalDept.setShortName(OAConvertConstant.EXTERNAL_ORG_NAME);
|
||||||
|
externalDept.setDeptCode(null);
|
||||||
|
externalDept.setParentCode(String.valueOf(BladeConstant.TOP_PARENT_ID));
|
||||||
|
externalDept.setDeptCategory(DeptCategory.GROUP.getCode());
|
||||||
|
externalDept.setSort(0);
|
||||||
|
externalDept.setStatus(DataStatusEnum.ENABLE.getCode());
|
||||||
|
externalDept.setIsDeleted(BladeConstant.DB_NOT_DELETED);
|
||||||
|
externalDept.setIsOa(0);
|
||||||
|
externalDept.setIsPlatformCompany(0);
|
||||||
|
externalDept.setSyncTime(new Date());
|
||||||
|
this.save(externalDept);
|
||||||
|
log.info("已创建外部组织顶级 id={}", externalDept.getId());
|
||||||
|
return externalDept;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理历史伪顶级(OACROOT);同名「桂物物流集团」仅清理非 id=135 的顶级
|
||||||
|
*/
|
||||||
|
private void removeLegacyOaSyncRoots() {
|
||||||
|
List<Dept> legacyRoots = this.list(Wrappers.<Dept>lambdaQuery()
|
||||||
|
.ne(Dept::getId, OAConvertConstant.OA_SYNC_ROOT_DEPT_ID)
|
||||||
|
.and(wrapper -> wrapper
|
||||||
|
.eq(Dept::getDeptCode, OAConvertConstant.LEGACY_ROOT_DEPT_CODE)
|
||||||
|
.or(nameWrapper -> nameWrapper
|
||||||
|
.eq(Dept::getDeptName, OAConvertConstant.LEGACY_ROOT_DEPT_NAME)
|
||||||
|
.eq(Dept::getDeptCode, OAConvertConstant.LEGACY_ROOT_DEPT_CODE)
|
||||||
|
.and(parentWrapper -> parentWrapper
|
||||||
|
.isNull(Dept::getParentId)
|
||||||
|
.or()
|
||||||
|
.eq(Dept::getParentId, BladeConstant.TOP_PARENT_ID)))));
|
||||||
|
if (CollectionUtil.isEmpty(legacyRoots)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Set<Long> legacyIds = legacyRoots.stream().map(Dept::getId).filter(Objects::nonNull).collect(Collectors.toSet());
|
||||||
|
this.update(Wrappers.<Dept>lambdaUpdate()
|
||||||
|
.set(Dept::getParentId, OAConvertConstant.OA_SYNC_ROOT_DEPT_ID)
|
||||||
|
.set(Dept::getParentCode, OAConvertConstant.OA_SYNC_ROOT_DEPT_CODE)
|
||||||
|
.in(Dept::getParentId, legacyIds));
|
||||||
|
this.removeByIds(legacyIds);
|
||||||
|
log.info("已清理历史伪顶级组织{}条:{}", legacyIds.size(), legacyIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按编码前缀对齐组织类型:OAC→公司、OAD→部门(覆盖历史 1/2 映射)
|
||||||
|
*/
|
||||||
|
private void migrateLegacyOaDeptCategory() {
|
||||||
|
this.update(Wrappers.<Dept>lambdaUpdate()
|
||||||
|
.set(Dept::getDeptCategory, DeptCategory.DEPT.getCode())
|
||||||
|
.likeRight(Dept::getDeptCode, OAConvertConstant.DEPARTMENT_OA_PREFIX));
|
||||||
|
this.update(Wrappers.<Dept>lambdaUpdate()
|
||||||
|
.set(Dept::getDeptCategory, DeptCategory.COMPANY.getCode())
|
||||||
|
.likeRight(Dept::getDeptCode, OAConvertConstant.COMPANY_OA_PREFIX)
|
||||||
|
.ne(Dept::getDeptCode, OAConvertConstant.LEGACY_ROOT_DEPT_CODE));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -434,10 +612,14 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
|
|||||||
if (dept.getIsPlatformCompany() == null) {
|
if (dept.getIsPlatformCompany() == null) {
|
||||||
dept.setIsPlatformCompany(0);
|
dept.setIsPlatformCompany(0);
|
||||||
}
|
}
|
||||||
|
// 排序未填时默认 1
|
||||||
|
if (dept.getSort() == null) {
|
||||||
|
dept.setSort(1);
|
||||||
|
}
|
||||||
if (Func.isEmpty(dept.getTenantId())) {
|
if (Func.isEmpty(dept.getTenantId())) {
|
||||||
throw new ServiceException("租户ID不能为空");
|
throw new ServiceException("租户ID不能为空");
|
||||||
}
|
}
|
||||||
validateDeptCategory(dept, parent);
|
validateDeptCategory(dept);
|
||||||
validateDeptCode(dept, parent);
|
validateDeptCode(dept, parent);
|
||||||
return saveOrUpdate(dept);
|
return saveOrUpdate(dept);
|
||||||
}
|
}
|
||||||
@@ -619,27 +801,8 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
|
|||||||
return "Basic " + value;
|
return "Basic " + value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validateDeptCategory(Dept dept, Dept parent) {
|
private void validateDeptCategory(Dept dept) {
|
||||||
// 顶级组织不要求选择上级组织
|
// 组织树层级仅由 parentId / ancestors 决定,组织类型不再做上下级约束
|
||||||
if (parent == null) {
|
|
||||||
if (Integer.valueOf(6).equals(dept.getDeptCategory()) && Func.isEmpty(dept.getCarrierCustomerId())) {
|
|
||||||
throw new ServiceException("请选择承运商");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
List<Integer> allowedCategories;
|
|
||||||
if (BladeConstant.TOP_PARENT_ID.equals(parent.getParentId())) {
|
|
||||||
allowedCategories = "外部组织".equals(parent.getDeptName()) ? List.of(6) : List.of(1);
|
|
||||||
} else if (Integer.valueOf(1).equals(parent.getDeptCategory())) {
|
|
||||||
allowedCategories = List.of(2, 3);
|
|
||||||
} else if (List.of(2, 3).contains(parent.getDeptCategory())) {
|
|
||||||
allowedCategories = List.of(4, 5, 6);
|
|
||||||
} else {
|
|
||||||
throw new ServiceException("上级组织层级不支持新增下级组织");
|
|
||||||
}
|
|
||||||
if (!allowedCategories.contains(dept.getDeptCategory())) {
|
|
||||||
throw new ServiceException("组织类型不符合上级组织层级规则");
|
|
||||||
}
|
|
||||||
if (Integer.valueOf(6).equals(dept.getDeptCategory()) && Func.isEmpty(dept.getCarrierCustomerId())) {
|
if (Integer.valueOf(6).equals(dept.getDeptCategory()) && Func.isEmpty(dept.getCarrierCustomerId())) {
|
||||||
throw new ServiceException("请选择承运商");
|
throw new ServiceException("请选择承运商");
|
||||||
}
|
}
|
||||||
@@ -665,9 +828,16 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
|
|||||||
if (StringUtil.isBlank(parent.getDeptCode())) {
|
if (StringUtil.isBlank(parent.getDeptCode())) {
|
||||||
throw new ServiceException("请选择已配置组织编码的上级组织");
|
throw new ServiceException("请选择已配置组织编码的上级组织");
|
||||||
}
|
}
|
||||||
String pattern = Pattern.quote(parent.getDeptCode()) + "-\\d+";
|
// 公司:GWWL+三位数字;部门:所属公司编码+三位数字;其他:上级编码+三位数字
|
||||||
if (!deptCode.matches(pattern)) {
|
String expectedParentCode = parent.getDeptCode();
|
||||||
throw new ServiceException("组织编码格式应为:上级编码-分段数字");
|
if (DeptCategory.COMPANY.getCode().equals(dept.getDeptCategory())) {
|
||||||
|
expectedParentCode = OAConvertConstant.OA_SYNC_ROOT_DEPT_CODE;
|
||||||
|
} else if (DeptCategory.DEPT.getCode().equals(dept.getDeptCategory())
|
||||||
|
&& StringUtil.isNotBlank(dept.getBelongCompanyCode())) {
|
||||||
|
expectedParentCode = dept.getBelongCompanyCode();
|
||||||
|
}
|
||||||
|
if (!org.springblade.system.util.DeptCodeUtil.matchesChildCode(expectedParentCode, deptCode)) {
|
||||||
|
throw new ServiceException("组织编码格式应为:" + expectedParentCode + "三位数字(不足补零,中间不加-)");
|
||||||
}
|
}
|
||||||
this.ensureDeptCodeUnique(dept, deptCode);
|
this.ensureDeptCodeUnique(dept, deptCode);
|
||||||
dept.setDeptCode(deptCode);
|
dept.setDeptCode(deptCode);
|
||||||
@@ -739,8 +909,8 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
|
|||||||
log.info("没有记录OA主键的组织,跳过层级重建");
|
log.info("没有记录OA主键的组织,跳过层级重建");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Map<String, Dept> companyByOaId = this.indexByOaId(oaDeptList, DeptCategory.COMPANY.getCode());
|
Map<String, Dept> companyByOaId = this.indexOaCompanies(oaDeptList);
|
||||||
Map<String, Dept> departmentByOaId = this.indexByOaId(oaDeptList, DeptCategory.DEPT.getCode());
|
Map<String, Dept> departmentByOaId = this.indexOaDepartments(oaDeptList);
|
||||||
Map<Long, Long> desiredParentIdMap = new LinkedHashMap<>();
|
Map<Long, Long> desiredParentIdMap = new LinkedHashMap<>();
|
||||||
Map<Long, String> desiredParentCodeMap = new HashMap<>();
|
Map<Long, String> desiredParentCodeMap = new HashMap<>();
|
||||||
for (Dept dept : oaDeptList) {
|
for (Dept dept : oaDeptList) {
|
||||||
@@ -793,21 +963,20 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
|
|||||||
if (CollectionUtil.isEmpty(oaDeptList)) {
|
if (CollectionUtil.isEmpty(oaDeptList)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
Map<String, Dept> companyByOaId = this.indexByOaId(oaDeptList, DeptCategory.COMPANY.getCode());
|
Map<String, Dept> companyByOaId = this.indexOaCompanies(oaDeptList);
|
||||||
Map<String, Dept> departmentByOaId = this.indexByOaId(oaDeptList, DeptCategory.DEPT.getCode());
|
Map<String, Dept> departmentByOaId = this.indexOaDepartments(oaDeptList);
|
||||||
Set<Long> deleteIds = new HashSet<>();
|
Set<Long> deleteIds = new HashSet<>();
|
||||||
for (Dept dept : oaDeptList) {
|
for (Dept dept : oaDeptList) {
|
||||||
String parentOaId = dept.getOaSupSubComId();
|
String parentOaId = dept.getOaSupSubComId();
|
||||||
// 上级为空或为 0:视为本地锚点/根级,不作为孤儿删除
|
// 上级为空或为 0:OA 顶级公司挂本地锚点,不作为孤儿
|
||||||
if (StringUtil.isBlank(parentOaId) || OAConstant.ROOT_COMPANY_ID.equals(parentOaId)) {
|
if (StringUtil.isBlank(parentOaId) || OAConstant.ROOT_COMPANY_ID.equals(parentOaId)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
boolean parentExists;
|
boolean isCompany = this.isOaCompany(dept);
|
||||||
if (DeptCategory.COMPANY.getCode().equals(dept.getDeptCategory())) {
|
boolean parentExists = isCompany
|
||||||
parentExists = companyByOaId.containsKey(parentOaId);
|
? companyByOaId.containsKey(parentOaId)
|
||||||
} else {
|
: departmentByOaId.containsKey(parentOaId) || companyByOaId.containsKey(parentOaId);
|
||||||
parentExists = departmentByOaId.containsKey(parentOaId) || companyByOaId.containsKey(parentOaId);
|
// 全量数据整理后仍找不到上级:公司、部门一律删除,不改挂保留
|
||||||
}
|
|
||||||
if (!parentExists) {
|
if (!parentExists) {
|
||||||
deleteIds.add(dept.getId());
|
deleteIds.add(dept.getId());
|
||||||
}
|
}
|
||||||
@@ -828,7 +997,7 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
|
|||||||
if (StringUtil.isBlank(parentOaId) || OAConstant.ROOT_COMPANY_ID.equals(parentOaId)) {
|
if (StringUtil.isBlank(parentOaId) || OAConstant.ROOT_COMPANY_ID.equals(parentOaId)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Dept parent = DeptCategory.COMPANY.getCode().equals(dept.getDeptCategory())
|
Dept parent = this.isOaCompany(dept)
|
||||||
? companyByOaId.get(parentOaId)
|
? companyByOaId.get(parentOaId)
|
||||||
: Optional.ofNullable(departmentByOaId.get(parentOaId)).orElse(companyByOaId.get(parentOaId));
|
: Optional.ofNullable(departmentByOaId.get(parentOaId)).orElse(companyByOaId.get(parentOaId));
|
||||||
if (parent != null && deleteIds.contains(parent.getId())) {
|
if (parent != null && deleteIds.contains(parent.getId())) {
|
||||||
@@ -857,6 +1026,72 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
|
|||||||
return deleteIds.size();
|
return deleteIds.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 索引 OA 公司:同时兼容类型迁移前后的公司记录
|
||||||
|
*/
|
||||||
|
private Map<String, Dept> indexOaCompanies(List<Dept> oaDeptList) {
|
||||||
|
Map<String, Dept> oaIdMap = new HashMap<>();
|
||||||
|
for (Dept dept : oaDeptList) {
|
||||||
|
if (!this.isOaCompany(dept) || StringUtil.isBlank(dept.getOaId())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Dept previous = oaIdMap.put(dept.getOaId(), dept);
|
||||||
|
if (previous != null) {
|
||||||
|
log.warn("OA公司主键重复,层级重建使用后一条 oaId={} id={} previousId={}",
|
||||||
|
dept.getOaId(), dept.getId(), previous.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return oaIdMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isOaCompany(Dept dept) {
|
||||||
|
if (dept == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (DeptCategory.COMPANY.getCode().equals(dept.getDeptCategory())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (org.springblade.system.util.DeptCodeUtil.isCompanyCode(dept.getDeptCode())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return StringUtil.isNotBlank(dept.getDeptCode())
|
||||||
|
&& dept.getDeptCode().startsWith(OAConvertConstant.COMPANY_OA_PREFIX)
|
||||||
|
&& !OAConvertConstant.LEGACY_ROOT_DEPT_CODE.equals(dept.getDeptCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isOaDepartment(Dept dept) {
|
||||||
|
if (dept == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (DeptCategory.DEPT.getCode().equals(dept.getDeptCategory())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (StringUtil.isNotBlank(dept.getBelongCompanyCode())
|
||||||
|
&& org.springblade.system.util.DeptCodeUtil.matchesChildCode(dept.getBelongCompanyCode(), dept.getDeptCode())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return StringUtil.isNotBlank(dept.getDeptCode())
|
||||||
|
&& dept.getDeptCode().startsWith(OAConvertConstant.DEPARTMENT_OA_PREFIX);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 索引 OA 部门:兼容类型迁移前后
|
||||||
|
*/
|
||||||
|
private Map<String, Dept> indexOaDepartments(List<Dept> oaDeptList) {
|
||||||
|
Map<String, Dept> oaIdMap = new HashMap<>();
|
||||||
|
for (Dept dept : oaDeptList) {
|
||||||
|
if (!this.isOaDepartment(dept) || StringUtil.isBlank(dept.getOaId())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Dept previous = oaIdMap.put(dept.getOaId(), dept);
|
||||||
|
if (previous != null) {
|
||||||
|
log.warn("OA部门主键重复,层级重建使用后一条 oaId={} id={} previousId={}",
|
||||||
|
dept.getOaId(), dept.getId(), previous.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return oaIdMap;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按机构类型建立 OA 主键索引
|
* 按机构类型建立 OA 主键索引
|
||||||
*/
|
*/
|
||||||
@@ -881,19 +1116,22 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
|
|||||||
private OaParentLink resolveOaParentLink(Dept dept, Map<String, Dept> companyByOaId, Map<String, Dept> departmentByOaId) {
|
private OaParentLink resolveOaParentLink(Dept dept, Map<String, Dept> companyByOaId, Map<String, Dept> departmentByOaId) {
|
||||||
String parentOaId = dept.getOaSupSubComId();
|
String parentOaId = dept.getOaSupSubComId();
|
||||||
boolean rootParent = StringUtil.isBlank(parentOaId) || OAConstant.ROOT_COMPANY_ID.equals(parentOaId);
|
boolean rootParent = StringUtil.isBlank(parentOaId) || OAConstant.ROOT_COMPANY_ID.equals(parentOaId);
|
||||||
if (DeptCategory.COMPANY.getCode().equals(dept.getDeptCategory())) {
|
if (this.isOaCompany(dept)) {
|
||||||
if (rootParent) {
|
if (rootParent) {
|
||||||
return new OaParentLink(OAConvertConstant.ROOT_PARENT_ID, OAConstant.ROOT_COMPANY_ID);
|
// OA 顶级公司统一挂到本地顶级锚点下
|
||||||
|
return new OaParentLink(OAConvertConstant.OA_SYNC_ROOT_DEPT_ID, OAConvertConstant.OA_SYNC_ROOT_DEPT_CODE);
|
||||||
}
|
}
|
||||||
Dept parent = companyByOaId.get(parentOaId);
|
Dept parent = companyByOaId.get(parentOaId);
|
||||||
if (parent == null || parent.getId() == null || Objects.equals(parent.getId(), dept.getId())) {
|
if (parent == null || parent.getId() == null || Objects.equals(parent.getId(), dept.getId())) {
|
||||||
|
// 全量整理后仍无上级:跳过重建,交由孤儿清理直接删除
|
||||||
log.warn("未找到上级公司,跳过层级重建 oaId={} oaSupSubComId={} deptId={}",
|
log.warn("未找到上级公司,跳过层级重建 oaId={} oaSupSubComId={} deptId={}",
|
||||||
dept.getOaId(), parentOaId, dept.getId());
|
dept.getOaId(), parentOaId, dept.getId());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return new OaParentLink(parent.getId(), this.resolveParentCode(parent, OAConvertConstant.COMPANY_OA_PREFIX, parentOaId));
|
return new OaParentLink(parent.getId(), this.resolveParentCode(parent, OAConvertConstant.COMPANY_OA_PREFIX, parentOaId));
|
||||||
}
|
}
|
||||||
if (!DeptCategory.DEPT.getCode().equals(dept.getDeptCategory())) {
|
if (!DeptCategory.DEPT.getCode().equals(dept.getDeptCategory())
|
||||||
|
&& !(StringUtil.isNotBlank(dept.getDeptCode()) && dept.getDeptCode().startsWith(OAConvertConstant.DEPARTMENT_OA_PREFIX))) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (rootParent) {
|
if (rootParent) {
|
||||||
|
|||||||
+1
-1
@@ -50,7 +50,7 @@ public class MeasurementUnitServiceImpl extends BaseServiceImpl<MeasurementUnitM
|
|||||||
private static final int UNIT_NAME_MAX_LENGTH = 50;
|
private static final int UNIT_NAME_MAX_LENGTH = 50;
|
||||||
private static final int DIMENSION_MAX_LENGTH = 20;
|
private static final int DIMENSION_MAX_LENGTH = 20;
|
||||||
private static final int REMARK_MAX_LENGTH = 200;
|
private static final int REMARK_MAX_LENGTH = 200;
|
||||||
private static final Set<String> DIMENSIONS = Set.of("重量", "体积", "数量");
|
private static final Set<String> DIMENSIONS = Set.of("重量", "体积", "数量", "整车", "里程", "吨·公里");
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IPage<MeasurementUnitVO> selectMeasurementUnitPage(IPage<MeasurementUnitVO> page,
|
public IPage<MeasurementUnitVO> selectMeasurementUnitPage(IPage<MeasurementUnitVO> page,
|
||||||
|
|||||||
+380
@@ -0,0 +1,380 @@
|
|||||||
|
/**
|
||||||
|
* 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.metadata.IPage;
|
||||||
|
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.secure.utils.AuthUtil;
|
||||||
|
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.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
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
|
||||||
|
public IPage<MpUser> selectMpUserPage(IPage<MpUser> page, MpUser query) {
|
||||||
|
MpUser condition = query == null ? new MpUser() : query;
|
||||||
|
return page(page, Wrappers.<MpUser>lambdaQuery()
|
||||||
|
.like(Func.isNotBlank(condition.getPhone()), MpUser::getPhone, condition.getPhone())
|
||||||
|
.like(Func.isNotBlank(condition.getAccount()), MpUser::getAccount, condition.getAccount())
|
||||||
|
.like(Func.isNotBlank(condition.getRealName()), MpUser::getRealName, condition.getRealName())
|
||||||
|
.like(Func.isNotBlank(condition.getName()), MpUser::getName, condition.getName())
|
||||||
|
.like(Func.isNotBlank(condition.getIdentities()), MpUser::getIdentities, condition.getIdentities())
|
||||||
|
.eq(condition.getStatus() != null, MpUser::getStatus, condition.getStatus())
|
||||||
|
.orderByDesc(MpUser::getCreateTime));
|
||||||
|
}
|
||||||
|
|
||||||
|
@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("该手机号已存在小程序用户");
|
||||||
|
}
|
||||||
|
encryptPasswordIfPresent(user);
|
||||||
|
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("该手机号已存在小程序用户");
|
||||||
|
}
|
||||||
|
// 后台编辑不改密码,避免空串覆盖
|
||||||
|
user.setPassword(null);
|
||||||
|
boolean updated = updateById(user);
|
||||||
|
if (updated) {
|
||||||
|
MpUserCache.clear();
|
||||||
|
}
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public boolean submitOrUpdate(MpUser user) {
|
||||||
|
if (user != null && Func.isNotEmpty(user.getId())) {
|
||||||
|
return updateUser(user);
|
||||||
|
}
|
||||||
|
return submit(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public boolean removeMpUser(List<Long> ids) {
|
||||||
|
if (Func.isEmpty(ids)) {
|
||||||
|
throw new ServiceException("请选择要删除的小程序用户");
|
||||||
|
}
|
||||||
|
boolean removed = deleteLogic(ids);
|
||||||
|
if (removed) {
|
||||||
|
MpUserCache.clear();
|
||||||
|
}
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public boolean changeStatus(Long id, Integer status) {
|
||||||
|
if (Func.isEmpty(id)) {
|
||||||
|
throw new ServiceException("小程序用户主键不能为空");
|
||||||
|
}
|
||||||
|
if (status == null || (status != 0 && status != 1)) {
|
||||||
|
throw new ServiceException("状态仅支持启用(1)或停用(0)");
|
||||||
|
}
|
||||||
|
MpUser user = getById(id);
|
||||||
|
if (user == null || Func.isEmpty(user.getId())) {
|
||||||
|
throw new ServiceException("小程序用户不存在");
|
||||||
|
}
|
||||||
|
MpUser patch = new MpUser();
|
||||||
|
patch.setId(id);
|
||||||
|
patch.setStatus(status);
|
||||||
|
boolean updated = updateById(patch);
|
||||||
|
if (updated) {
|
||||||
|
MpUserCache.clear();
|
||||||
|
}
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Map<String, String> resetPassword(List<Long> userIds) {
|
||||||
|
if (Func.isEmpty(userIds)) {
|
||||||
|
throw new ServiceException("请选择要重置密码的小程序用户");
|
||||||
|
}
|
||||||
|
Map<String, String> passwordMap = new LinkedHashMap<>();
|
||||||
|
for (Long userId : userIds) {
|
||||||
|
MpUser user = getById(userId);
|
||||||
|
if (user == null || Func.isEmpty(user.getId())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String password = PasswordRuleUtil.generate();
|
||||||
|
MpUser patch = new MpUser();
|
||||||
|
patch.setId(userId);
|
||||||
|
patch.setPassword(DigestUtil.encrypt(password));
|
||||||
|
patch.setUpdateTime(DateUtil.now());
|
||||||
|
updateById(patch);
|
||||||
|
String account = Func.toStr(user.getAccount(), user.getPhone());
|
||||||
|
passwordMap.put(account, password);
|
||||||
|
}
|
||||||
|
if (passwordMap.isEmpty()) {
|
||||||
|
throw new ServiceException("未找到可重置密码的小程序用户");
|
||||||
|
}
|
||||||
|
MpUserCache.clear();
|
||||||
|
return passwordMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
if (Func.isBlank(user.getTenantId())) {
|
||||||
|
user.setTenantId(AuthUtil.getTenantId());
|
||||||
|
}
|
||||||
|
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 encryptPasswordIfPresent(MpUser user) {
|
||||||
|
if (user == null || Func.isBlank(user.getPassword())) {
|
||||||
|
user.setPassword(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String password = user.getPassword().trim();
|
||||||
|
if (!PasswordRuleUtil.isValid(password)) {
|
||||||
|
throw new ServiceException(PasswordRuleUtil.RULE_MESSAGE);
|
||||||
|
}
|
||||||
|
user.setPassword(DigestUtil.encrypt(password));
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+434
-84
@@ -22,6 +22,7 @@ import org.springblade.system.pojo.vo.OaOrgSyncPageVO;
|
|||||||
import org.springblade.system.pojo.vo.OaPersonSyncPageVO;
|
import org.springblade.system.pojo.vo.OaPersonSyncPageVO;
|
||||||
import org.springblade.system.service.*;
|
import org.springblade.system.service.*;
|
||||||
import org.springblade.system.util.DataSyncRecordUtils;
|
import org.springblade.system.util.DataSyncRecordUtils;
|
||||||
|
import org.springblade.system.util.DeptCodeUtil;
|
||||||
import org.springblade.thirdparty.oa.constant.OAConstant;
|
import org.springblade.thirdparty.oa.constant.OAConstant;
|
||||||
import org.springblade.thirdparty.oa.constant.OAConvertConstant;
|
import org.springblade.thirdparty.oa.constant.OAConvertConstant;
|
||||||
import org.springblade.thirdparty.oa.feign.IOAClient;
|
import org.springblade.thirdparty.oa.feign.IOAClient;
|
||||||
@@ -60,6 +61,7 @@ public class OASyncServiceImpl implements IOASyncService {
|
|||||||
private final IOAClient oaClient;
|
private final IOAClient oaClient;
|
||||||
private final DeptConvert deptConvert;
|
private final DeptConvert deptConvert;
|
||||||
private final IDeptService deptService;
|
private final IDeptService deptService;
|
||||||
|
private final IUserService userService;
|
||||||
private final IRoleService roleService;
|
private final IRoleService roleService;
|
||||||
private final IMKPushService mkPushService;
|
private final IMKPushService mkPushService;
|
||||||
private final IDataSyncRecordService dataSyncRecordService;
|
private final IDataSyncRecordService dataSyncRecordService;
|
||||||
@@ -72,6 +74,8 @@ public class OASyncServiceImpl implements IOASyncService {
|
|||||||
ComposeLogUtil.addLog(log);
|
ComposeLogUtil.addLog(log);
|
||||||
// 1. 同步公司和部门并记录
|
// 1. 同步公司和部门并记录
|
||||||
Date startTime = this.syncAndRecord(DataSyncRecordUtils::createOAOrgFetch, queryStartTime -> {
|
Date startTime = this.syncAndRecord(DataSyncRecordUtils::createOAOrgFetch, queryStartTime -> {
|
||||||
|
// 0. 确保双顶级:id=135 集团(GWWL) + 独立「外部组织」
|
||||||
|
deptService.ensureOaSyncRootDept();
|
||||||
// 1. 同步公司
|
// 1. 同步公司
|
||||||
this.syncCompany(queryStartTime);
|
this.syncCompany(queryStartTime);
|
||||||
// 公司分批入库后,重建层级并清理无法挂载的公司
|
// 公司分批入库后,重建层级并清理无法挂载的公司
|
||||||
@@ -124,10 +128,10 @@ public class OASyncServiceImpl implements IOASyncService {
|
|||||||
|
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
@Override
|
@Override
|
||||||
public OaPersonSyncPageVO syncPersonFromUserList(int current, int size) {
|
public OaPersonSyncPageVO syncPersonFromUserList(int current, int size, boolean incremental, String modifiedSince) {
|
||||||
try {
|
try {
|
||||||
ComposeLogUtil.addLog(log);
|
ComposeLogUtil.addLog(log);
|
||||||
return this.syncPersonFromOaPage(current, size);
|
return this.syncPersonFromOaPage(current, size, incremental, modifiedSince);
|
||||||
} finally {
|
} finally {
|
||||||
ComposeLogUtil.removeLastLog();
|
ComposeLogUtil.removeLastLog();
|
||||||
}
|
}
|
||||||
@@ -135,10 +139,10 @@ public class OASyncServiceImpl implements IOASyncService {
|
|||||||
|
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
@Override
|
@Override
|
||||||
public OaOrgSyncPageVO syncCompanyPage(int current, int size) {
|
public OaOrgSyncPageVO syncCompanyPage(int current, int size, boolean incremental, String modifiedSince) {
|
||||||
try {
|
try {
|
||||||
ComposeLogUtil.addLog(log);
|
ComposeLogUtil.addLog(log);
|
||||||
return this.syncCompanyFromOaPage(current, size);
|
return this.syncCompanyFromOaPage(current, size, incremental, modifiedSince);
|
||||||
} finally {
|
} finally {
|
||||||
ComposeLogUtil.removeLastLog();
|
ComposeLogUtil.removeLastLog();
|
||||||
}
|
}
|
||||||
@@ -146,10 +150,10 @@ public class OASyncServiceImpl implements IOASyncService {
|
|||||||
|
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
@Override
|
@Override
|
||||||
public OaOrgSyncPageVO syncDepartmentPage(int current, int size) {
|
public OaOrgSyncPageVO syncDepartmentPage(int current, int size, boolean incremental, String modifiedSince) {
|
||||||
try {
|
try {
|
||||||
ComposeLogUtil.addLog(log);
|
ComposeLogUtil.addLog(log);
|
||||||
return this.syncDepartmentFromOaPage(current, size);
|
return this.syncDepartmentFromOaPage(current, size, incremental, modifiedSince);
|
||||||
} finally {
|
} finally {
|
||||||
ComposeLogUtil.removeLastLog();
|
ComposeLogUtil.removeLastLog();
|
||||||
}
|
}
|
||||||
@@ -282,12 +286,15 @@ public class OASyncServiceImpl implements IOASyncService {
|
|||||||
*
|
*
|
||||||
* @param current 当前页
|
* @param current 当前页
|
||||||
* @param size 每页条数
|
* @param size 每页条数
|
||||||
|
* @param incremental 是否增量同步
|
||||||
|
* @param modifiedSince 增量起始时间(后续分页回传)
|
||||||
* @return 本页同步结果
|
* @return 本页同步结果
|
||||||
*/
|
*/
|
||||||
private OaPersonSyncPageVO syncPersonFromOaPage(int current, int size) {
|
private OaPersonSyncPageVO syncPersonFromOaPage(int current, int size, boolean incremental, String modifiedSince) {
|
||||||
int pageNo = current < 1 ? 1 : current;
|
int pageNo = current < 1 ? 1 : current;
|
||||||
int pageSize = size < 1 ? 20 : Math.min(size, 200);
|
int pageSize = size < 1 ? 20 : Math.min(size, 200);
|
||||||
OAPersonSearch personSearch = buildPersonSearch(null);
|
IncrementalStart incrementalStart = resolvePersonIncrementalStart(incremental, modifiedSince);
|
||||||
|
OAPersonSearch personSearch = buildPersonSearch(incrementalStart.getStartTime());
|
||||||
personSearch.setCurPage(pageNo);
|
personSearch.setCurPage(pageNo);
|
||||||
personSearch.setPageSize(pageSize);
|
personSearch.setPageSize(pageSize);
|
||||||
OAResponse<OAPersonResponse> oaResponse = oaClient.queryPersonPage(new OASearch<>(personSearch));
|
OAResponse<OAPersonResponse> oaResponse = oaClient.queryPersonPage(new OASearch<>(personSearch));
|
||||||
@@ -311,12 +318,14 @@ public class OASyncServiceImpl implements IOASyncService {
|
|||||||
pageVO.setFetchedCount(oaPersons.size());
|
pageVO.setFetchedCount(oaPersons.size());
|
||||||
pageVO.setSyncedCount(personSyncCount.getSyncedCount());
|
pageVO.setSyncedCount(personSyncCount.getSyncedCount());
|
||||||
pageVO.setSkippedCount(personSyncCount.getSkippedCount());
|
pageVO.setSkippedCount(personSyncCount.getSkippedCount());
|
||||||
|
pageVO.setModifiedSince(incrementalStart.getModifiedSince());
|
||||||
boolean finished = oaPersons.isEmpty()
|
boolean finished = oaPersons.isEmpty()
|
||||||
|| oaPersons.size() < pageSize
|
|| oaPersons.size() < pageSize
|
||||||
|| (long) pageNo * pageSize >= totalSize;
|
|| (long) pageNo * pageSize >= totalSize;
|
||||||
pageVO.setFinished(finished);
|
pageVO.setFinished(finished);
|
||||||
ComposeLogUtil.getLastLog().info("OA人员分页同步完成 {}/{},成功{},跳过{}",
|
ComposeLogUtil.getLastLog().info("OA人员分页同步完成 mode={} {}/{},成功{},跳过{}",
|
||||||
pageNo, totalSize, personSyncCount.getSyncedCount(), personSyncCount.getSkippedCount());
|
incremental ? "incremental" : "full", pageNo, totalSize,
|
||||||
|
personSyncCount.getSyncedCount(), personSyncCount.getSkippedCount());
|
||||||
return pageVO;
|
return pageVO;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,12 +334,19 @@ public class OASyncServiceImpl implements IOASyncService {
|
|||||||
*
|
*
|
||||||
* @param current 当前页
|
* @param current 当前页
|
||||||
* @param size 每页条数
|
* @param size 每页条数
|
||||||
|
* @param incremental 是否增量同步
|
||||||
|
* @param modifiedSince 增量起始时间(后续分页回传)
|
||||||
* @return 本页同步结果
|
* @return 本页同步结果
|
||||||
*/
|
*/
|
||||||
private OaOrgSyncPageVO syncCompanyFromOaPage(int current, int size) {
|
private OaOrgSyncPageVO syncCompanyFromOaPage(int current, int size, boolean incremental, String modifiedSince) {
|
||||||
int pageNo = current < 1 ? 1 : current;
|
int pageNo = current < 1 ? 1 : current;
|
||||||
int pageSize = size < 1 ? 20 : Math.min(size, 200);
|
int pageSize = size < 1 ? 20 : Math.min(size, 200);
|
||||||
OACompanySearch companySearch = buildCompanySearch(null);
|
// 首屏确保双顶级:id=135 集团(GWWL) + 独立「外部组织」
|
||||||
|
if (pageNo == 1) {
|
||||||
|
deptService.ensureOaSyncRootDept();
|
||||||
|
}
|
||||||
|
IncrementalStart incrementalStart = resolveIncrementalStart(incremental, modifiedSince, DeptCategory.COMPANY);
|
||||||
|
OACompanySearch companySearch = buildCompanySearch(incrementalStart.getStartTime());
|
||||||
companySearch.setCurPage(pageNo);
|
companySearch.setCurPage(pageNo);
|
||||||
companySearch.setPageSize(pageSize);
|
companySearch.setPageSize(pageSize);
|
||||||
OAResponse<OACompanyResponse> oaResponse = oaClient.queryCompanyPage(new OASearch<>(companySearch));
|
OAResponse<OACompanyResponse> oaResponse = oaClient.queryCompanyPage(new OASearch<>(companySearch));
|
||||||
@@ -347,6 +363,7 @@ public class OASyncServiceImpl implements IOASyncService {
|
|||||||
ComposeLogUtil.getLastLog().warn("同步公司,本页未处理数据:{}", JSON.toJSONString(syncCount.getNotHandledList()));
|
ComposeLogUtil.getLastLog().warn("同步公司,本页未处理数据:{}", JSON.toJSONString(syncCount.getNotHandledList()));
|
||||||
}
|
}
|
||||||
OaOrgSyncPageVO pageVO = buildOrgSyncPageVO("company", pageNo, pageSize, totalSize, oaCompanies.size(), syncCount);
|
OaOrgSyncPageVO pageVO = buildOrgSyncPageVO("company", pageNo, pageSize, totalSize, oaCompanies.size(), syncCount);
|
||||||
|
pageVO.setModifiedSince(incrementalStart.getModifiedSince());
|
||||||
if (Boolean.TRUE.equals(pageVO.getFinished())) {
|
if (Boolean.TRUE.equals(pageVO.getFinished())) {
|
||||||
// 公司全部分批入库后:重建层级,并删除上级不存在的数据
|
// 公司全部分批入库后:重建层级,并删除上级不存在的数据
|
||||||
deptService.rebuildOaHierarchy();
|
deptService.rebuildOaHierarchy();
|
||||||
@@ -354,8 +371,9 @@ public class OASyncServiceImpl implements IOASyncService {
|
|||||||
}
|
}
|
||||||
CacheUtil.clear(SYS_CACHE);
|
CacheUtil.clear(SYS_CACHE);
|
||||||
CacheUtil.clear(SYS_CACHE, Boolean.FALSE);
|
CacheUtil.clear(SYS_CACHE, Boolean.FALSE);
|
||||||
ComposeLogUtil.getLastLog().info("OA公司分页同步完成 {}/{},成功{},跳过{}",
|
ComposeLogUtil.getLastLog().info("OA公司分页同步完成 mode={} {}/{},成功{},跳过{}",
|
||||||
pageNo, totalSize, syncCount.getSyncedCount(), syncCount.getSkippedCount());
|
incremental ? "incremental" : "full", pageNo, totalSize,
|
||||||
|
syncCount.getSyncedCount(), syncCount.getSkippedCount());
|
||||||
return pageVO;
|
return pageVO;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -364,9 +382,11 @@ public class OASyncServiceImpl implements IOASyncService {
|
|||||||
*
|
*
|
||||||
* @param current 当前页
|
* @param current 当前页
|
||||||
* @param size 每页条数
|
* @param size 每页条数
|
||||||
|
* @param incremental 是否增量同步
|
||||||
|
* @param modifiedSince 增量起始时间(后续分页回传)
|
||||||
* @return 本页同步结果
|
* @return 本页同步结果
|
||||||
*/
|
*/
|
||||||
private OaOrgSyncPageVO syncDepartmentFromOaPage(int current, int size) {
|
private OaOrgSyncPageVO syncDepartmentFromOaPage(int current, int size, boolean incremental, String modifiedSince) {
|
||||||
int pageNo = current < 1 ? 1 : current;
|
int pageNo = current < 1 ? 1 : current;
|
||||||
int pageSize = size < 1 ? 20 : Math.min(size, 200);
|
int pageSize = size < 1 ? 20 : Math.min(size, 200);
|
||||||
String subCompanyIds = getOaSubCompanyIds(true);
|
String subCompanyIds = getOaSubCompanyIds(true);
|
||||||
@@ -376,7 +396,8 @@ public class OASyncServiceImpl implements IOASyncService {
|
|||||||
emptyPageVO.setFinished(true);
|
emptyPageVO.setFinished(true);
|
||||||
return emptyPageVO;
|
return emptyPageVO;
|
||||||
}
|
}
|
||||||
OADepartmentSearch departmentSearch = buildDepartmentSearch(null, subCompanyIds);
|
IncrementalStart incrementalStart = resolveIncrementalStart(incremental, modifiedSince, DeptCategory.DEPT);
|
||||||
|
OADepartmentSearch departmentSearch = buildDepartmentSearch(incrementalStart.getStartTime(), subCompanyIds);
|
||||||
departmentSearch.setCurPage(pageNo);
|
departmentSearch.setCurPage(pageNo);
|
||||||
departmentSearch.setPageSize(pageSize);
|
departmentSearch.setPageSize(pageSize);
|
||||||
OAResponse<OADepartmentResponse> oaResponse = oaClient.queryDepartmentPage(new OASearch<>(departmentSearch));
|
OAResponse<OADepartmentResponse> oaResponse = oaClient.queryDepartmentPage(new OASearch<>(departmentSearch));
|
||||||
@@ -393,6 +414,7 @@ public class OASyncServiceImpl implements IOASyncService {
|
|||||||
ComposeLogUtil.getLastLog().warn("同步部门,本页未处理数据:{}", JSON.toJSONString(syncCount.getNotHandledList()));
|
ComposeLogUtil.getLastLog().warn("同步部门,本页未处理数据:{}", JSON.toJSONString(syncCount.getNotHandledList()));
|
||||||
}
|
}
|
||||||
OaOrgSyncPageVO pageVO = buildOrgSyncPageVO("department", pageNo, pageSize, totalSize, oaDepartments.size(), syncCount);
|
OaOrgSyncPageVO pageVO = buildOrgSyncPageVO("department", pageNo, pageSize, totalSize, oaDepartments.size(), syncCount);
|
||||||
|
pageVO.setModifiedSince(incrementalStart.getModifiedSince());
|
||||||
if (Boolean.TRUE.equals(pageVO.getFinished())) {
|
if (Boolean.TRUE.equals(pageVO.getFinished())) {
|
||||||
// 部门分批同步完成后,重建层级、清理无法挂载数据并刷新祖级列表
|
// 部门分批同步完成后,重建层级、清理无法挂载数据并刷新祖级列表
|
||||||
deptService.rebuildOaHierarchy();
|
deptService.rebuildOaHierarchy();
|
||||||
@@ -401,11 +423,118 @@ public class OASyncServiceImpl implements IOASyncService {
|
|||||||
}
|
}
|
||||||
CacheUtil.clear(SYS_CACHE);
|
CacheUtil.clear(SYS_CACHE);
|
||||||
CacheUtil.clear(SYS_CACHE, Boolean.FALSE);
|
CacheUtil.clear(SYS_CACHE, Boolean.FALSE);
|
||||||
ComposeLogUtil.getLastLog().info("OA部门分页同步完成 {}/{},成功{},跳过{}",
|
ComposeLogUtil.getLastLog().info("OA部门分页同步完成 mode={} {}/{},成功{},跳过{}",
|
||||||
pageNo, totalSize, syncCount.getSyncedCount(), syncCount.getSkippedCount());
|
incremental ? "incremental" : "full", pageNo, totalSize,
|
||||||
|
syncCount.getSyncedCount(), syncCount.getSkippedCount());
|
||||||
return pageVO;
|
return pageVO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析增量同步起始时间:优先使用前端回传值,保证同阶段分页起点不变
|
||||||
|
*/
|
||||||
|
private IncrementalStart resolveIncrementalStart(boolean incremental, String modifiedSince, DeptCategory deptCategory) {
|
||||||
|
if (!incremental) {
|
||||||
|
return IncrementalStart.empty();
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotBlank(modifiedSince)) {
|
||||||
|
try {
|
||||||
|
Date startTime = DateUtil.parse(modifiedSince.trim(), DateUtil.PATTERN_DATETIME);
|
||||||
|
return new IncrementalStart(startTime, modifiedSince.trim());
|
||||||
|
} catch (Exception exception) {
|
||||||
|
ComposeLogUtil.getLastLog().warn("解析回传 modifiedSince 失败:{},重新计算", modifiedSince);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String codePrefix = DeptCategory.COMPANY.equals(deptCategory)
|
||||||
|
? OAConvertConstant.COMPANY_OA_PREFIX
|
||||||
|
: OAConvertConstant.DEPARTMENT_OA_PREFIX;
|
||||||
|
Dept latest = deptService.getOne(Wrappers.<Dept>lambdaQuery()
|
||||||
|
.select(Dept::getOaModified)
|
||||||
|
.eq(Dept::getIsOa, YES)
|
||||||
|
.and(wrapper -> wrapper
|
||||||
|
.eq(Dept::getDeptCategory, deptCategory.getCode())
|
||||||
|
.or()
|
||||||
|
.likeRight(Dept::getDeptCode, codePrefix))
|
||||||
|
.isNotNull(Dept::getOaModified)
|
||||||
|
.ne(Dept::getOaModified, "")
|
||||||
|
.orderByDesc(Dept::getOaModified)
|
||||||
|
.last("limit 1"), false);
|
||||||
|
if (latest == null || StringUtils.isBlank(latest.getOaModified())) {
|
||||||
|
ComposeLogUtil.getLastLog().info("本地无 {} oaModified 记录,增量同步按全量拉取后按字段比对跳过",
|
||||||
|
deptCategory.getName());
|
||||||
|
return IncrementalStart.empty();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Date startTime = DateUtil.parse(latest.getOaModified(), DateUtil.PATTERN_DATETIME);
|
||||||
|
ComposeLogUtil.getLastLog().info("增量同步{} OA modified 起始时间:{}",
|
||||||
|
deptCategory.getName(), latest.getOaModified());
|
||||||
|
return new IncrementalStart(startTime, latest.getOaModified());
|
||||||
|
} catch (Exception exception) {
|
||||||
|
ComposeLogUtil.getLastLog().warn("解析本地最大 oaModified 失败:{},回退为全量拉取", latest.getOaModified());
|
||||||
|
return IncrementalStart.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 人员增量同步起始时间:优先回传值,否则取本地 OA 用户最大 oaModified
|
||||||
|
*/
|
||||||
|
private IncrementalStart resolvePersonIncrementalStart(boolean incremental, String modifiedSince) {
|
||||||
|
if (!incremental) {
|
||||||
|
return IncrementalStart.empty();
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotBlank(modifiedSince)) {
|
||||||
|
try {
|
||||||
|
Date startTime = DateUtil.parse(modifiedSince.trim(), DateUtil.PATTERN_DATETIME);
|
||||||
|
return new IncrementalStart(startTime, modifiedSince.trim());
|
||||||
|
} catch (Exception exception) {
|
||||||
|
ComposeLogUtil.getLastLog().warn("解析人员回传 modifiedSince 失败:{},重新计算", modifiedSince);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
User latest = userService.getOne(Wrappers.<User>lambdaQuery()
|
||||||
|
.select(User::getOaModified)
|
||||||
|
.eq(User::getIsOa, YES)
|
||||||
|
.isNotNull(User::getOaModified)
|
||||||
|
.ne(User::getOaModified, "")
|
||||||
|
.orderByDesc(User::getOaModified)
|
||||||
|
.last("limit 1"), false);
|
||||||
|
if (latest == null || StringUtils.isBlank(latest.getOaModified())) {
|
||||||
|
ComposeLogUtil.getLastLog().info("本地无人员 oaModified 记录,增量同步按全量拉取后按字段比对跳过");
|
||||||
|
return IncrementalStart.empty();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Date startTime = DateUtil.parse(latest.getOaModified(), DateUtil.PATTERN_DATETIME);
|
||||||
|
ComposeLogUtil.getLastLog().info("增量同步人员 OA modified 起始时间:{}", latest.getOaModified());
|
||||||
|
return new IncrementalStart(startTime, latest.getOaModified());
|
||||||
|
} catch (Exception exception) {
|
||||||
|
ComposeLogUtil.getLastLog().warn("解析本地人员最大 oaModified 失败:{},回退为全量拉取", latest.getOaModified());
|
||||||
|
return IncrementalStart.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 增量同步起点
|
||||||
|
*/
|
||||||
|
private static class IncrementalStart {
|
||||||
|
private final Date startTime;
|
||||||
|
private final String modifiedSince;
|
||||||
|
|
||||||
|
private IncrementalStart(Date startTime, String modifiedSince) {
|
||||||
|
this.startTime = startTime;
|
||||||
|
this.modifiedSince = modifiedSince;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IncrementalStart empty() {
|
||||||
|
return new IncrementalStart(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Date getStartTime() {
|
||||||
|
return startTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getModifiedSince() {
|
||||||
|
return modifiedSince;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 组装组织分页同步结果
|
* 组装组织分页同步结果
|
||||||
*/
|
*/
|
||||||
@@ -492,13 +621,8 @@ public class OASyncServiceImpl implements IOASyncService {
|
|||||||
if (CollectionUtil.isEmpty(oaCompanies)) {
|
if (CollectionUtil.isEmpty(oaCompanies)) {
|
||||||
return OrgSyncCount.empty();
|
return OrgSyncCount.empty();
|
||||||
}
|
}
|
||||||
// 获取需要的公司名称
|
|
||||||
Set<String> companyNames = syncAll ? Collections.emptySet() : getCompanyNames();
|
Set<String> companyNames = syncAll ? Collections.emptySet() : getCompanyNames();
|
||||||
// 转换数据
|
|
||||||
List<Dept> allParam = oaCompanies.stream()
|
List<Dept> allParam = oaCompanies.stream()
|
||||||
// 强制跳过上级公司为 0 的数据
|
|
||||||
.filter(company -> !OAConstant.ROOT_COMPANY_ID.equals(company.getSupsubcomid()))
|
|
||||||
// 非全量同步时,筛选只要配置的公司
|
|
||||||
.filter(company -> syncAll || companyNames.contains(company.getSubcompanyname()))
|
.filter(company -> syncAll || companyNames.contains(company.getSubcompanyname()))
|
||||||
.map(deptConvert::company2dept)
|
.map(deptConvert::company2dept)
|
||||||
.toList();
|
.toList();
|
||||||
@@ -507,14 +631,41 @@ public class OASyncServiceImpl implements IOASyncService {
|
|||||||
return new OrgSyncCount(0, filteredSkipCount, Collections.emptyList());
|
return new OrgSyncCount(0, filteredSkipCount, Collections.emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询数据库的部门,转换成map
|
Map<String, Dept> existingByOaId = loadExistingByOaId(DeptCategory.COMPANY);
|
||||||
Map<String, Long> deptMap = getAllCompanyDeptMap();
|
Map<String, Dept> codeOwnerMap = loadCompanyCodeOwnerMap();
|
||||||
|
Set<String> usedCodes = new HashSet<>(codeOwnerMap.keySet());
|
||||||
// 处理部门
|
Map<String, Dept> batchByOaId = new LinkedHashMap<>();
|
||||||
List<Dept> notHandledList = handleDept(allParam, deptMap, DeptCategory.COMPANY);
|
for (Dept dept : allParam) {
|
||||||
int syncedCount = allParam.size() - notHandledList.size();
|
this.assignCompanyCode(dept, existingByOaId, usedCodes);
|
||||||
int skippedCount = filteredSkipCount + notHandledList.size();
|
batchByOaId.put(dept.getOaId(), dept);
|
||||||
return new OrgSyncCount(syncedCount, skippedCount, notHandledList);
|
}
|
||||||
|
// 上级公司编码:OA 上级为 0 → GWWL;否则取上级公司已分配编码
|
||||||
|
for (Dept dept : allParam) {
|
||||||
|
if (OAConstant.ROOT_COMPANY_ID.equals(dept.getOaSupSubComId()) || StringUtils.isBlank(dept.getOaSupSubComId())) {
|
||||||
|
dept.setParentCode(OAConvertConstant.OA_SYNC_ROOT_DEPT_CODE);
|
||||||
|
dept.setParentId(OAConvertConstant.OA_SYNC_ROOT_DEPT_ID);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Dept parent = batchByOaId.get(dept.getOaSupSubComId());
|
||||||
|
if (parent == null) {
|
||||||
|
parent = existingByOaId.get(dept.getOaSupSubComId());
|
||||||
|
}
|
||||||
|
if (parent != null && StringUtils.isNotBlank(parent.getDeptCode())) {
|
||||||
|
dept.setParentCode(parent.getDeptCode());
|
||||||
|
dept.setParentId(parent.getId());
|
||||||
|
} else {
|
||||||
|
dept.setParentCode(null);
|
||||||
|
dept.setParentId(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Map<String, Long> deptIdByCode = new HashMap<>();
|
||||||
|
existingByOaId.values().forEach(dept -> {
|
||||||
|
if (StringUtils.isNotBlank(dept.getDeptCode()) && dept.getId() != null) {
|
||||||
|
deptIdByCode.put(dept.getDeptCode(), dept.getId());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
deptIdByCode.put(OAConvertConstant.OA_SYNC_ROOT_DEPT_CODE, OAConvertConstant.OA_SYNC_ROOT_DEPT_ID);
|
||||||
|
return persistSyncedDepts(allParam, existingByOaId, deptIdByCode, DeptCategory.COMPANY, filteredSkipCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -527,80 +678,239 @@ public class OASyncServiceImpl implements IOASyncService {
|
|||||||
if (CollectionUtil.isEmpty(oaDepts)) {
|
if (CollectionUtil.isEmpty(oaDepts)) {
|
||||||
return OrgSyncCount.empty();
|
return OrgSyncCount.empty();
|
||||||
}
|
}
|
||||||
// 查询所有公司的编码和id的map
|
Map<String, Dept> companyByOaId = loadCompanyByOaId();
|
||||||
Map<String, Long> companyDeptMap = getAllCompanyDeptMap();
|
|
||||||
// 根公司id
|
|
||||||
String rootCompanyId = getRootCompanyId();
|
String rootCompanyId = getRootCompanyId();
|
||||||
if (!syncAll && rootCompanyId == null) {
|
if (!syncAll && rootCompanyId == null) {
|
||||||
return new OrgSyncCount(0, oaDepts.size(), Collections.emptyList());
|
return new OrgSyncCount(0, oaDepts.size(), Collections.emptyList());
|
||||||
}
|
}
|
||||||
// 根公司下要同步的部门名称
|
|
||||||
Set<String> rootCompanyDeptNames = syncAll ? Collections.emptySet() : getRootCompanyDeptNames();
|
Set<String> rootCompanyDeptNames = syncAll ? Collections.emptySet() : getRootCompanyDeptNames();
|
||||||
// 转换数据
|
|
||||||
List<Dept> allParam = oaDepts.stream()
|
List<Dept> allParam = oaDepts.stream()
|
||||||
// 全量同步时不过滤;否则:部门的公司不是根公司,或者部门名称在根公司下需要同步的部门列表中,且是根部门
|
|
||||||
.filter(oaDept -> syncAll
|
.filter(oaDept -> syncAll
|
||||||
|| rootCompanyId == null
|
|| rootCompanyId == null
|
||||||
|| !rootCompanyId.equals(oaDept.getSubcompanyid1())
|
|| !rootCompanyId.equals(oaDept.getSubcompanyid1())
|
||||||
|| (OAConstant.ROOT_COMPANY_ID.equals(oaDept.getSupdepid()) && rootCompanyDeptNames.contains(oaDept.getDepartmentname())))
|
|| (OAConstant.ROOT_COMPANY_ID.equals(oaDept.getSupdepid()) && rootCompanyDeptNames.contains(oaDept.getDepartmentname())))
|
||||||
.map(dept -> deptConvert.dept2dept(dept, companyDeptMap))
|
.map(dept -> deptConvert.dept2dept(dept, companyByOaId))
|
||||||
.toList();
|
.toList();
|
||||||
int filteredSkipCount = oaDepts.size() - allParam.size();
|
int filteredSkipCount = oaDepts.size() - allParam.size();
|
||||||
if (CollectionUtil.isEmpty(allParam)) {
|
if (CollectionUtil.isEmpty(allParam)) {
|
||||||
return new OrgSyncCount(0, filteredSkipCount, Collections.emptyList());
|
return new OrgSyncCount(0, filteredSkipCount, Collections.emptyList());
|
||||||
}
|
}
|
||||||
Set<String> deptCodes = new HashSet<>();
|
|
||||||
allParam.forEach(dept -> {
|
Map<String, Dept> existingByOaId = loadExistingByOaId(DeptCategory.DEPT);
|
||||||
if (StringUtils.isNotEmpty(dept.getDeptCode())) {
|
Map<String, Integer> nextSerialByCompany = new HashMap<>();
|
||||||
deptCodes.add(dept.getDeptCode());
|
Map<String, Set<String>> usedCodesByCompany = new HashMap<>();
|
||||||
|
// 预载各公司下已有部门编码,保证分段连续
|
||||||
|
for (Dept existing : existingByOaId.values()) {
|
||||||
|
if (StringUtils.isBlank(existing.getBelongCompanyCode()) || StringUtils.isBlank(existing.getDeptCode())) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
if (StringUtils.isNotEmpty(dept.getParentCode())) {
|
usedCodesByCompany
|
||||||
deptCodes.add(dept.getParentCode());
|
.computeIfAbsent(existing.getBelongCompanyCode(), key -> new HashSet<>())
|
||||||
|
.add(existing.getDeptCode());
|
||||||
|
}
|
||||||
|
Map<String, Dept> batchByOaId = new LinkedHashMap<>();
|
||||||
|
for (Dept dept : allParam) {
|
||||||
|
this.assignDepartmentCode(dept, existingByOaId, companyByOaId, usedCodesByCompany, nextSerialByCompany);
|
||||||
|
batchByOaId.put(dept.getOaId(), dept);
|
||||||
|
}
|
||||||
|
// 解析上级:根部门挂公司;子部门挂上级部门(本批或已存在)
|
||||||
|
Map<String, Long> deptIdByCode = new HashMap<>();
|
||||||
|
companyByOaId.values().forEach(company -> {
|
||||||
|
if (StringUtils.isNotBlank(company.getDeptCode()) && company.getId() != null) {
|
||||||
|
deptIdByCode.put(company.getDeptCode(), company.getId());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// 查询数据库的部门,转换成map。包含本页及上级编码,便于命中已入库的上级
|
existingByOaId.values().forEach(dept -> {
|
||||||
Map<String, Long> deptMap = deptCodes.isEmpty() ? new HashMap<>() : deptService.list(Wrappers.<Dept>lambdaQuery()
|
if (StringUtils.isNotBlank(dept.getDeptCode()) && dept.getId() != null) {
|
||||||
.eq(Dept::getDeptCategory, DeptCategory.DEPT.getCode())
|
deptIdByCode.put(dept.getDeptCode(), dept.getId());
|
||||||
.in(Dept::getDeptCode, deptCodes)
|
}
|
||||||
).stream()
|
});
|
||||||
.filter(dept -> StringUtils.isNotEmpty(dept.getDeptCode()))
|
for (Dept dept : allParam) {
|
||||||
.collect(Collectors.toMap(Dept::getDeptCode, Dept::getId, (a, b) -> b));
|
if (dept.getParentId() != null && StringUtils.isNotBlank(dept.getParentCode())
|
||||||
// 公司编码也放入 map,根部门挂到公司时才能解析父级
|
&& DeptCodeUtil.matchesChildCode(dept.getBelongCompanyCode(), dept.getDeptCode())
|
||||||
companyDeptMap.forEach(deptMap::putIfAbsent);
|
&& dept.getBelongCompanyCode().equals(dept.getParentCode())) {
|
||||||
// 处理部门
|
continue;
|
||||||
List<Dept> notHandledList = this.handleDept(allParam, deptMap, DeptCategory.DEPT);
|
}
|
||||||
int syncedCount = allParam.size() - notHandledList.size();
|
String parentOaId = dept.getOaSupSubComId();
|
||||||
int skippedCount = filteredSkipCount + notHandledList.size();
|
Dept belongCompany = companyByOaId.values().stream()
|
||||||
return new OrgSyncCount(syncedCount, skippedCount, notHandledList);
|
.filter(company -> StringUtils.equals(company.getDeptCode(), dept.getBelongCompanyCode()))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
if (belongCompany != null && (OAConstant.ROOT_COMPANY_ID.equals(parentOaId)
|
||||||
|
|| StringUtils.equals(parentOaId, belongCompany.getOaId()))) {
|
||||||
|
dept.setParentCode(belongCompany.getDeptCode());
|
||||||
|
dept.setParentId(belongCompany.getId());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Dept parentDept = batchByOaId.get(parentOaId);
|
||||||
|
if (parentDept == null) {
|
||||||
|
parentDept = existingByOaId.get(parentOaId);
|
||||||
|
}
|
||||||
|
if (parentDept != null && StringUtils.isNotBlank(parentDept.getDeptCode())) {
|
||||||
|
dept.setParentCode(parentDept.getDeptCode());
|
||||||
|
dept.setParentId(parentDept.getId());
|
||||||
|
} else if (belongCompany != null) {
|
||||||
|
// 上级部门尚未就绪时先挂所属公司,分批结束后按 OA 主键重建
|
||||||
|
dept.setParentCode(belongCompany.getDeptCode());
|
||||||
|
dept.setParentId(belongCompany.getId());
|
||||||
|
} else {
|
||||||
|
dept.setParentCode(null);
|
||||||
|
dept.setParentId(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return persistSyncedDepts(allParam, existingByOaId, deptIdByCode, DeptCategory.DEPT, filteredSkipCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理部门
|
* 公司编码:GWWL+三位数字;已有合规编码则复用
|
||||||
* @param allParam 待处理的部门
|
|
||||||
* @param deptMap 数据库所有部门编码和id map
|
|
||||||
* @return 未处理的数据
|
|
||||||
*/
|
*/
|
||||||
private List<Dept> handleDept(List<Dept> allParam, Map<String, Long> deptMap, DeptCategory deptCategory) {
|
private void assignCompanyCode(Dept dept, Map<String, Dept> existingByOaId, Set<String> usedCodes) {
|
||||||
// 数据库存在的所有部门id
|
Dept existing = existingByOaId.get(dept.getOaId());
|
||||||
Set<Long> existsDeptIds = new HashSet<>(deptMap.values());
|
if (existing != null && DeptCodeUtil.isCompanyCode(existing.getDeptCode())) {
|
||||||
|
dept.setDeptCode(existing.getDeptCode());
|
||||||
|
dept.setBelongCompanyCode(existing.getDeptCode());
|
||||||
|
usedCodes.add(existing.getDeptCode());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int serial = DeptCodeUtil.nextSerial(OAConvertConstant.OA_SYNC_ROOT_DEPT_CODE, usedCodes);
|
||||||
|
String deptCode = DeptCodeUtil.formatChildCode(OAConvertConstant.OA_SYNC_ROOT_DEPT_CODE, serial);
|
||||||
|
while (usedCodes.contains(deptCode)) {
|
||||||
|
serial++;
|
||||||
|
deptCode = DeptCodeUtil.formatChildCode(OAConvertConstant.OA_SYNC_ROOT_DEPT_CODE, serial);
|
||||||
|
}
|
||||||
|
usedCodes.add(deptCode);
|
||||||
|
dept.setDeptCode(deptCode);
|
||||||
|
dept.setBelongCompanyCode(deptCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 部门编码:所属公司编码+三位数字;已有合规编码则复用
|
||||||
|
*/
|
||||||
|
private void assignDepartmentCode(Dept dept, Map<String, Dept> existingByOaId, Map<String, Dept> companyByOaId,
|
||||||
|
Map<String, Set<String>> usedCodesByCompany, Map<String, Integer> nextSerialByCompany) {
|
||||||
|
Dept existing = existingByOaId.get(dept.getOaId());
|
||||||
|
Dept belongCompany = null;
|
||||||
|
if (StringUtils.isNotBlank(dept.getBelongCompanyCode())) {
|
||||||
|
belongCompany = companyByOaId.values().stream()
|
||||||
|
.filter(company -> StringUtils.equals(company.getDeptCode(), dept.getBelongCompanyCode())
|
||||||
|
|| StringUtils.equals(company.getOaId(), dept.getBelongCompanyCode()))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
if (belongCompany == null && existing != null && StringUtils.isNotBlank(existing.getBelongCompanyCode())) {
|
||||||
|
String belongCode = existing.getBelongCompanyCode();
|
||||||
|
belongCompany = companyByOaId.values().stream()
|
||||||
|
.filter(company -> StringUtils.equals(company.getDeptCode(), belongCode))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
if (belongCompany == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final Dept resolvedCompany = belongCompany;
|
||||||
|
final String companyCode = resolvedCompany.getDeptCode();
|
||||||
|
dept.setBelongCompanyCode(companyCode);
|
||||||
|
if (existing != null && DeptCodeUtil.matchesChildCode(companyCode, existing.getDeptCode())) {
|
||||||
|
dept.setDeptCode(existing.getDeptCode());
|
||||||
|
usedCodesByCompany.computeIfAbsent(companyCode, key -> new HashSet<>()).add(existing.getDeptCode());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Set<String> usedCodes = usedCodesByCompany.computeIfAbsent(companyCode, key -> new HashSet<>());
|
||||||
|
int serial = nextSerialByCompany.computeIfAbsent(companyCode,
|
||||||
|
key -> DeptCodeUtil.nextSerial(companyCode, usedCodes));
|
||||||
|
String deptCode = DeptCodeUtil.formatChildCode(companyCode, serial);
|
||||||
|
while (usedCodes.contains(deptCode)) {
|
||||||
|
serial++;
|
||||||
|
deptCode = DeptCodeUtil.formatChildCode(companyCode, serial);
|
||||||
|
}
|
||||||
|
nextSerialByCompany.put(companyCode, serial + 1);
|
||||||
|
usedCodes.add(deptCode);
|
||||||
|
dept.setDeptCode(deptCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按 oaId 加载已存在组织
|
||||||
|
*/
|
||||||
|
private Map<String, Dept> loadExistingByOaId(DeptCategory deptCategory) {
|
||||||
|
List<Dept> deptList = deptService.list(Wrappers.<Dept>lambdaQuery()
|
||||||
|
.eq(Dept::getDeptCategory, deptCategory.getCode())
|
||||||
|
.isNotNull(Dept::getOaId)
|
||||||
|
.ne(Dept::getOaId, "")
|
||||||
|
.select(Dept::getId, Dept::getDeptCode, Dept::getOaModified, Dept::getParentId,
|
||||||
|
Dept::getOaId, Dept::getBelongCompanyCode, Dept::getParentCode, Dept::getOaSupSubComId));
|
||||||
|
Map<String, Dept> byOaId = new HashMap<>();
|
||||||
|
for (Dept dept : deptList) {
|
||||||
|
if (StringUtils.isNotBlank(dept.getOaId())) {
|
||||||
|
byOaId.put(dept.getOaId(), dept);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return byOaId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 已占用的公司编码(GWWLxxx)
|
||||||
|
*/
|
||||||
|
private Map<String, Dept> loadCompanyCodeOwnerMap() {
|
||||||
|
List<Dept> companyList = deptService.list(Wrappers.<Dept>lambdaQuery()
|
||||||
|
.eq(Dept::getDeptCategory, DeptCategory.COMPANY.getCode())
|
||||||
|
.likeRight(Dept::getDeptCode, OAConvertConstant.OA_SYNC_ROOT_DEPT_CODE)
|
||||||
|
.select(Dept::getId, Dept::getDeptCode, Dept::getOaId));
|
||||||
|
Map<String, Dept> codeMap = new HashMap<>();
|
||||||
|
for (Dept dept : companyList) {
|
||||||
|
if (DeptCodeUtil.isCompanyCode(dept.getDeptCode())) {
|
||||||
|
codeMap.put(dept.getDeptCode(), dept);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return codeMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 已同步公司:oaId → 公司
|
||||||
|
*/
|
||||||
|
private Map<String, Dept> loadCompanyByOaId() {
|
||||||
|
List<Dept> companyList = deptService.list(Wrappers.<Dept>lambdaQuery()
|
||||||
|
.eq(Dept::getDeptCategory, DeptCategory.COMPANY.getCode())
|
||||||
|
.isNotNull(Dept::getOaId)
|
||||||
|
.ne(Dept::getOaId, ""));
|
||||||
|
Map<String, Dept> byOaId = new HashMap<>();
|
||||||
|
for (Dept dept : companyList) {
|
||||||
|
if (StringUtils.isNotBlank(dept.getOaId())) {
|
||||||
|
byOaId.put(dept.getOaId(), dept);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return byOaId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 持久化同步数据:按 oaId 匹配;不存在则新增;存在且 oaModified 不一致才修改
|
||||||
|
*/
|
||||||
|
private OrgSyncCount persistSyncedDepts(List<Dept> allParam, Map<String, Dept> existingByOaId,
|
||||||
|
Map<String, Long> deptIdByCode, DeptCategory deptCategory,
|
||||||
|
int filteredSkipCount) {
|
||||||
|
Set<Long> existsDeptIds = existingByOaId.values().stream()
|
||||||
|
.map(Dept::getId)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
allParam.forEach(dept -> {
|
allParam.forEach(dept -> {
|
||||||
if (deptMap.containsKey(dept.getDeptCode())) {
|
Dept existing = existingByOaId.get(dept.getOaId());
|
||||||
// 根据部门编码获取对应的部门id
|
if (existing != null && existing.getId() != null) {
|
||||||
dept.setId(deptMap.get(dept.getDeptCode()));
|
dept.setId(existing.getId());
|
||||||
|
} else if (StringUtils.isNotBlank(dept.getDeptCode()) && deptIdByCode.containsKey(dept.getDeptCode())) {
|
||||||
|
dept.setId(deptIdByCode.get(dept.getDeptCode()));
|
||||||
} else {
|
} else {
|
||||||
// 没有就生成一个id
|
|
||||||
dept.setId(DefaultIdentifierGenerator.getInstance().nextId(null));
|
dept.setId(DefaultIdentifierGenerator.getInstance().nextId(null));
|
||||||
deptMap.put(dept.getDeptCode(), dept.getId());
|
}
|
||||||
|
if (StringUtils.isNotBlank(dept.getDeptCode())) {
|
||||||
|
deptIdByCode.put(dept.getDeptCode(), dept.getId());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 重新把没有父id的数据设置一下父id;上级尚未入库时,新增记录先挂到根节点,全部分批完成后重建层级并清理无法挂载的数据
|
|
||||||
int unresolvedParentCount = 0;
|
int unresolvedParentCount = 0;
|
||||||
for (Dept dept : allParam) {
|
for (Dept dept : allParam) {
|
||||||
if (dept.getParentId() != null) {
|
if (dept.getParentId() != null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Long parentId = deptMap.get(dept.getParentCode());
|
Long parentId = StringUtils.isNotBlank(dept.getParentCode()) ? deptIdByCode.get(dept.getParentCode()) : null;
|
||||||
if (parentId != null) {
|
if (parentId != null) {
|
||||||
dept.setParentId(parentId);
|
dept.setParentId(parentId);
|
||||||
continue;
|
continue;
|
||||||
@@ -615,11 +925,10 @@ public class OASyncServiceImpl implements IOASyncService {
|
|||||||
deptCategory.getName(), unresolvedParentCount);
|
deptCategory.getName(), unresolvedParentCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 父id不为空才新增;已存在的记录即使父id未解析也更新(空父id不会覆盖原值)
|
|
||||||
List<Dept> list = allParam.stream()
|
List<Dept> list = allParam.stream()
|
||||||
|
.filter(dept -> StringUtils.isNotBlank(dept.getDeptCode()))
|
||||||
.filter(dept -> dept.getParentId() != null || existsDeptIds.contains(dept.getId()))
|
.filter(dept -> dept.getParentId() != null || existsDeptIds.contains(dept.getId()))
|
||||||
.toList();
|
.toList();
|
||||||
// 不存在的新增
|
|
||||||
List<Dept> addList = list.stream()
|
List<Dept> addList = list.stream()
|
||||||
.filter(dept -> !existsDeptIds.contains(dept.getId()))
|
.filter(dept -> !existsDeptIds.contains(dept.getId()))
|
||||||
.toList();
|
.toList();
|
||||||
@@ -627,18 +936,48 @@ public class OASyncServiceImpl implements IOASyncService {
|
|||||||
ComposeLogUtil.getLastLog().info("批量新增{}:{}", deptCategory.getName(), addList.size());
|
ComposeLogUtil.getLastLog().info("批量新增{}:{}", deptCategory.getName(), addList.size());
|
||||||
deptService.saveBatch(addList);
|
deptService.saveBatch(addList);
|
||||||
}
|
}
|
||||||
// 存在的修改
|
|
||||||
List<Dept> updateList = list.stream()
|
List<Dept> updateList = list.stream()
|
||||||
.filter(dept -> existsDeptIds.contains(dept.getId()))
|
.filter(dept -> existsDeptIds.contains(dept.getId()))
|
||||||
|
.filter(dept -> !shouldSkipOaUpdate(existingByOaId.get(dept.getOaId()), dept))
|
||||||
.toList();
|
.toList();
|
||||||
if (CollectionUtil.isNotEmpty(updateList)) {
|
if (CollectionUtil.isNotEmpty(updateList)) {
|
||||||
ComposeLogUtil.getLastLog().info("批量修改{}:{}", deptCategory.getName(), updateList.size());
|
ComposeLogUtil.getLastLog().info("批量修改{}:{}", deptCategory.getName(), updateList.size());
|
||||||
deptService.updateBatchById(updateList);
|
deptService.updateBatchById(updateList);
|
||||||
}
|
}
|
||||||
// 返回仍未保存的数据
|
int unchangedCount = (int) list.stream()
|
||||||
return allParam.stream()
|
.filter(dept -> existsDeptIds.contains(dept.getId()))
|
||||||
.filter(dept -> dept.getParentId() == null && !existsDeptIds.contains(dept.getId()))
|
.filter(dept -> shouldSkipOaUpdate(existingByOaId.get(dept.getOaId()), dept))
|
||||||
|
.count();
|
||||||
|
List<Dept> notHandledList = allParam.stream()
|
||||||
|
.filter(dept -> StringUtils.isBlank(dept.getDeptCode())
|
||||||
|
|| (dept.getParentId() == null && !existsDeptIds.contains(dept.getId())))
|
||||||
.toList();
|
.toList();
|
||||||
|
int syncedCount = addList.size() + updateList.size();
|
||||||
|
int skippedCount = filteredSkipCount + unchangedCount + notHandledList.size();
|
||||||
|
if (unchangedCount > 0) {
|
||||||
|
ComposeLogUtil.getLastLog().info("跳过未变更{}:{}", deptCategory.getName(), unchangedCount);
|
||||||
|
}
|
||||||
|
return new OrgSyncCount(syncedCount, skippedCount, notHandledList);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* oaModified 一致则跳过更新;任一侧为空无法确认一致,执行更新以补齐字段。
|
||||||
|
* 组织编码已按新规则重算且与库中不一致时,即使 modified 相同也要更新。
|
||||||
|
*/
|
||||||
|
private boolean shouldSkipOaUpdate(Dept existing, Dept incoming) {
|
||||||
|
if (existing == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (incoming != null && StringUtils.isNotBlank(incoming.getDeptCode())
|
||||||
|
&& !StringUtils.equals(existing.getDeptCode(), incoming.getDeptCode())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String localModified = StringUtils.trimToEmpty(existing.getOaModified());
|
||||||
|
String remoteModified = StringUtils.trimToEmpty(incoming == null ? null : incoming.getOaModified());
|
||||||
|
if (localModified.isEmpty() || remoteModified.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return localModified.equals(remoteModified);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -683,6 +1022,8 @@ public class OASyncServiceImpl implements IOASyncService {
|
|||||||
if (syncAll) {
|
if (syncAll) {
|
||||||
companyList = deptService.list(Wrappers.<Dept>lambdaQuery()
|
companyList = deptService.list(Wrappers.<Dept>lambdaQuery()
|
||||||
.eq(Dept::getDeptCategory, DeptCategory.COMPANY.getCode())
|
.eq(Dept::getDeptCategory, DeptCategory.COMPANY.getCode())
|
||||||
|
.isNotNull(Dept::getOaId)
|
||||||
|
.ne(Dept::getOaId, "")
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
Set<String> companyNames = this.getCompanyNames();
|
Set<String> companyNames = this.getCompanyNames();
|
||||||
@@ -692,6 +1033,8 @@ public class OASyncServiceImpl implements IOASyncService {
|
|||||||
companyList = deptService.list(Wrappers.<Dept>lambdaQuery()
|
companyList = deptService.list(Wrappers.<Dept>lambdaQuery()
|
||||||
.in(Dept::getDeptName, companyNames)
|
.in(Dept::getDeptName, companyNames)
|
||||||
.eq(Dept::getDeptCategory, DeptCategory.COMPANY.getCode())
|
.eq(Dept::getDeptCategory, DeptCategory.COMPANY.getCode())
|
||||||
|
.isNotNull(Dept::getOaId)
|
||||||
|
.ne(Dept::getOaId, "")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return companyList.stream()
|
return companyList.stream()
|
||||||
@@ -792,13 +1135,20 @@ public class OASyncServiceImpl implements IOASyncService {
|
|||||||
return Collections.emptySet();
|
return Collections.emptySet();
|
||||||
}
|
}
|
||||||
// 查询父部门编码是根公司的,部门类型的,根公司下部门名称的部门
|
// 查询父部门编码是根公司的,部门类型的,根公司下部门名称的部门
|
||||||
|
Dept rootCompany = deptService.getOne(Wrappers.<Dept>lambdaQuery()
|
||||||
|
.eq(Dept::getOaId, rootCompanyId)
|
||||||
|
.eq(Dept::getDeptCategory, DeptCategory.COMPANY.getCode())
|
||||||
|
.last("limit 1"), false);
|
||||||
|
if (rootCompany == null || StringUtils.isBlank(rootCompany.getDeptCode())) {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
return deptService.list(Wrappers.<Dept>lambdaQuery()
|
return deptService.list(Wrappers.<Dept>lambdaQuery()
|
||||||
.in(Dept::getDeptName, rootCompanyDeptNames)
|
.in(Dept::getDeptName, rootCompanyDeptNames)
|
||||||
.eq(Dept::getDeptCategory, DeptCategory.DEPT.getCode())
|
.eq(Dept::getDeptCategory, DeptCategory.DEPT.getCode())
|
||||||
.eq(Dept::getParentCode, OAConvertConstant.COMPANY_OA_PREFIX + rootCompanyId)
|
.eq(Dept::getParentCode, rootCompany.getDeptCode())
|
||||||
).stream()
|
).stream()
|
||||||
.filter(dept -> StringUtils.isNotBlank(dept.getDeptCode()))
|
.map(Dept::getOaId)
|
||||||
.map(dept -> dept.getDeptCode().replace(OAConvertConstant.DEPARTMENT_OA_PREFIX, ""))
|
.filter(StringUtils::isNotBlank)
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -821,11 +1171,11 @@ public class OASyncServiceImpl implements IOASyncService {
|
|||||||
ComposeLogUtil.getLastLog().warn("未查询到根公司 名称:{}", companyName);
|
ComposeLogUtil.getLastLog().warn("未查询到根公司 名称:{}", companyName);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (StringUtils.isBlank(dept.getDeptCode())) {
|
if (StringUtils.isBlank(dept.getOaId()) && StringUtils.isBlank(dept.getDeptCode())) {
|
||||||
ComposeLogUtil.getLastLog().warn("根公司编码为空:{}", companyName);
|
ComposeLogUtil.getLastLog().warn("根公司编码为空:{}", companyName);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// 部门编码去掉前缀,就是oa的id
|
return StringUtils.isNotBlank(dept.getOaId()) ? dept.getOaId()
|
||||||
return dept.getDeptCode().replace(OAConvertConstant.COMPANY_OA_PREFIX, "");
|
: dept.getDeptCode().replace(OAConvertConstant.COMPANY_OA_PREFIX, "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+36
-30
@@ -86,7 +86,7 @@ public class OaUserListSyncHelper {
|
|||||||
private final IRoleService roleService;
|
private final IRoleService roleService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从人员数据提取二级公司、三级部门,并挂到「桂物物流集团」下
|
* 从人员数据提取二级公司、三级部门,并挂到集团顶级(id=135/GWWL) 下
|
||||||
*
|
*
|
||||||
* @param oaPersons OA人员
|
* @param oaPersons OA人员
|
||||||
* @return 组织索引
|
* @return 组织索引
|
||||||
@@ -249,6 +249,7 @@ public class OaUserListSyncHelper {
|
|||||||
});
|
});
|
||||||
Map<String, Long> userMap = new HashMap<>();
|
Map<String, Long> userMap = new HashMap<>();
|
||||||
Set<Long> existsUserIds = new HashSet<>();
|
Set<Long> existsUserIds = new HashSet<>();
|
||||||
|
Set<Long> unchangedUserIds = new HashSet<>();
|
||||||
users.forEach(user -> {
|
users.forEach(user -> {
|
||||||
User existingUser = existingByAccount.get(user.getAccount());
|
User existingUser = existingByAccount.get(user.getAccount());
|
||||||
if (existingUser == null && StringUtils.isNotBlank(user.getPhone())) {
|
if (existingUser == null && StringUtils.isNotBlank(user.getPhone())) {
|
||||||
@@ -258,6 +259,9 @@ public class OaUserListSyncHelper {
|
|||||||
user.setId(existingUser.getId());
|
user.setId(existingUser.getId());
|
||||||
user.setPassword(null);
|
user.setPassword(null);
|
||||||
existsUserIds.add(existingUser.getId());
|
existsUserIds.add(existingUser.getId());
|
||||||
|
if (shouldSkipOaUpdate(existingUser, user)) {
|
||||||
|
unchangedUserIds.add(existingUser.getId());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
user.setId(DefaultIdentifierGenerator.getInstance().nextId(null));
|
user.setId(DefaultIdentifierGenerator.getInstance().nextId(null));
|
||||||
user.setPostId("-1");
|
user.setPostId("-1");
|
||||||
@@ -279,12 +283,18 @@ public class OaUserListSyncHelper {
|
|||||||
}
|
}
|
||||||
List<User> updateUsers = users.stream()
|
List<User> updateUsers = users.stream()
|
||||||
.filter(user -> existsUserIds.contains(user.getId()))
|
.filter(user -> existsUserIds.contains(user.getId()))
|
||||||
|
.filter(user -> !unchangedUserIds.contains(user.getId()))
|
||||||
.toList();
|
.toList();
|
||||||
if (CollectionUtil.isNotEmpty(updateUsers)) {
|
if (CollectionUtil.isNotEmpty(updateUsers)) {
|
||||||
ComposeLogUtil.getLastLog().info("批量修改用户:{}", updateUsers.size());
|
ComposeLogUtil.getLastLog().info("批量修改用户:{}", updateUsers.size());
|
||||||
userService.updateBatchById(updateUsers);
|
userService.updateBatchById(updateUsers);
|
||||||
}
|
}
|
||||||
|
if (!unchangedUserIds.isEmpty()) {
|
||||||
|
ComposeLogUtil.getLastLog().info("跳过未变更用户:{}", unchangedUserIds.size());
|
||||||
|
}
|
||||||
ComposeLogUtil.getLastLog().info("缺少账号已跳过的人员:{}", skippedCount);
|
ComposeLogUtil.getLastLog().info("缺少账号已跳过的人员:{}", skippedCount);
|
||||||
|
int syncedCount = addUsers.size() + updateUsers.size();
|
||||||
|
skippedCount += unchangedUserIds.size();
|
||||||
if (userMap.isEmpty()) {
|
if (userMap.isEmpty()) {
|
||||||
return new PersonSyncCount(0, skippedCount);
|
return new PersonSyncCount(0, skippedCount);
|
||||||
}
|
}
|
||||||
@@ -295,7 +305,13 @@ public class OaUserListSyncHelper {
|
|||||||
.collect(Collectors.toMap(this::getUserDeptKey, UserDept::getId, (first, second) -> second));
|
.collect(Collectors.toMap(this::getUserDeptKey, UserDept::getId, (first, second) -> second));
|
||||||
Set<Long> existsUserDeptIds = new HashSet<>(userDeptMap.values());
|
Set<Long> existsUserDeptIds = new HashSet<>(userDeptMap.values());
|
||||||
List<UserDept> userDeptList = oaPersons.stream()
|
List<UserDept> userDeptList = oaPersons.stream()
|
||||||
.filter(oaPerson -> userMap.containsKey(userConvert.resolveAccount(oaPerson)))
|
.filter(oaPerson -> {
|
||||||
|
String account = userConvert.resolveAccount(oaPerson);
|
||||||
|
if (!userMap.containsKey(account)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !unchangedUserIds.contains(userMap.get(account));
|
||||||
|
})
|
||||||
.map(oaPerson -> this.buildUserDept(oaPerson, userMap, orgIndex))
|
.map(oaPerson -> this.buildUserDept(oaPerson, userMap, orgIndex))
|
||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
.toList();
|
.toList();
|
||||||
@@ -333,6 +349,7 @@ public class OaUserListSyncHelper {
|
|||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
String defaultRoleId = getDefaultRoleId();
|
String defaultRoleId = getDefaultRoleId();
|
||||||
List<User> updateUserParams = list.stream()
|
List<User> updateUserParams = list.stream()
|
||||||
|
.filter(userDeptIds -> !unchangedUserIds.contains(userDeptIds.getUserId()))
|
||||||
.filter(userDeptIds -> StringUtils.isNotBlank(userDeptIds.getDeptIds()) && userDeptIds.getDeptIds().length() <= MAX_DEPT_ID_LENGTH)
|
.filter(userDeptIds -> StringUtils.isNotBlank(userDeptIds.getDeptIds()) && userDeptIds.getDeptIds().length() <= MAX_DEPT_ID_LENGTH)
|
||||||
.map(userDeptIds -> {
|
.map(userDeptIds -> {
|
||||||
User user = new User();
|
User user = new User();
|
||||||
@@ -347,37 +364,26 @@ public class OaUserListSyncHelper {
|
|||||||
if (CollectionUtil.isNotEmpty(updateUserParams)) {
|
if (CollectionUtil.isNotEmpty(updateUserParams)) {
|
||||||
userService.updateBatchById(updateUserParams);
|
userService.updateBatchById(updateUserParams);
|
||||||
}
|
}
|
||||||
return new PersonSyncCount(users.size(), skippedCount);
|
return new PersonSyncCount(syncedCount, skippedCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* oaModified 一致则跳过更新;任一侧为空无法确认一致,执行更新以补齐字段
|
||||||
|
*/
|
||||||
|
private boolean shouldSkipOaUpdate(User existing, User incoming) {
|
||||||
|
if (existing == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String localModified = StringUtils.trimToEmpty(existing.getOaModified());
|
||||||
|
String remoteModified = StringUtils.trimToEmpty(incoming == null ? null : incoming.getOaModified());
|
||||||
|
if (localModified.isEmpty() || remoteModified.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return localModified.equals(remoteModified);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Dept getOrCreateRootCompany() {
|
private Dept getOrCreateRootCompany() {
|
||||||
Dept rootCompany = deptService.getOne(Wrappers.<Dept>lambdaQuery()
|
return deptService.ensureOaSyncRootDept();
|
||||||
.eq(Dept::getDeptName, OAConvertConstant.ROOT_COMPANY_NAME)
|
|
||||||
.last("limit 1"), false);
|
|
||||||
if (rootCompany != null) {
|
|
||||||
return rootCompany;
|
|
||||||
}
|
|
||||||
rootCompany = new Dept();
|
|
||||||
rootCompany.setId(DefaultIdentifierGenerator.getInstance().nextId(null));
|
|
||||||
rootCompany.setTenantId(resolveTenantId());
|
|
||||||
rootCompany.setParentId(BladeConstant.TOP_PARENT_ID);
|
|
||||||
rootCompany.setAncestors(String.valueOf(BladeConstant.TOP_PARENT_ID));
|
|
||||||
rootCompany.setDeptName(OAConvertConstant.ROOT_COMPANY_NAME);
|
|
||||||
rootCompany.setFullName(OAConvertConstant.ROOT_COMPANY_NAME);
|
|
||||||
rootCompany.setShortName(OAConvertConstant.ROOT_COMPANY_NAME);
|
|
||||||
rootCompany.setDeptCode("OACROOT");
|
|
||||||
rootCompany.setParentCode(String.valueOf(BladeConstant.TOP_PARENT_ID));
|
|
||||||
rootCompany.setBelongCompanyCode("OACROOT");
|
|
||||||
rootCompany.setDeptCategory(DeptCategory.COMPANY.getCode());
|
|
||||||
rootCompany.setSort(0);
|
|
||||||
rootCompany.setStatus(DataStatusEnum.ENABLE.getCode());
|
|
||||||
rootCompany.setIsDeleted(BladeConstant.DB_NOT_DELETED);
|
|
||||||
rootCompany.setIsOa(YES);
|
|
||||||
rootCompany.setIsPlatformCompany(0);
|
|
||||||
rootCompany.setSyncTime(new Date());
|
|
||||||
deptService.save(rootCompany);
|
|
||||||
ComposeLogUtil.getLastLog().info("已创建顶级组织:{}", OAConvertConstant.ROOT_COMPANY_NAME);
|
|
||||||
return rootCompany;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Dept upsertOrg(String name, String oaCode, Dept existing, Dept parent, String tenantId,
|
private Dept upsertOrg(String name, String oaCode, Dept existing, Dept parent, String tenantId,
|
||||||
|
|||||||
+195
-52
@@ -29,7 +29,8 @@ import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
|||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springblade.core.log.exception.ServiceException;
|
import org.springblade.core.log.exception.ServiceException;
|
||||||
import org.springblade.core.mp.base.BaseServiceImpl;
|
import org.springblade.core.mp.base.BaseServiceImpl;
|
||||||
import org.springblade.core.tool.utils.BeanUtil;
|
import org.springblade.core.tool.utils.BeanUtil;
|
||||||
@@ -44,6 +45,7 @@ import org.springblade.system.pojo.entity.Region;
|
|||||||
import org.springblade.system.pojo.vo.PortTerminalVO;
|
import org.springblade.system.pojo.vo.PortTerminalVO;
|
||||||
import org.springblade.system.service.IPortTerminalService;
|
import org.springblade.system.service.IPortTerminalService;
|
||||||
import org.springblade.system.service.IRegionService;
|
import org.springblade.system.service.IRegionService;
|
||||||
|
import org.springframework.dao.DataAccessException;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
@@ -66,8 +68,9 @@ import java.util.regex.Pattern;
|
|||||||
*
|
*
|
||||||
* @author Chill
|
* @author Chill
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
@AllArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class PortTerminalServiceImpl extends BaseServiceImpl<PortTerminalMapper, PortTerminal> implements IPortTerminalService {
|
public class PortTerminalServiceImpl extends BaseServiceImpl<PortTerminalMapper, PortTerminal> implements IPortTerminalService {
|
||||||
|
|
||||||
private static final String CATEGORY_PORT = "港口";
|
private static final String CATEGORY_PORT = "港口";
|
||||||
@@ -76,8 +79,12 @@ public class PortTerminalServiceImpl extends BaseServiceImpl<PortTerminalMapper,
|
|||||||
private static final String SOURCE_INITIAL_OLD = "初始导入";
|
private static final String SOURCE_INITIAL_OLD = "初始导入";
|
||||||
private static final String SOURCE_BATCH = "批量导入";
|
private static final String SOURCE_BATCH = "批量导入";
|
||||||
private static final String SOURCE_MANUAL = "手工导入";
|
private static final String SOURCE_MANUAL = "手工导入";
|
||||||
|
private static final String SOURCE_MANUAL_OLD = "手动录入";
|
||||||
private static final int STATUS_ENABLED = 1;
|
private static final int STATUS_ENABLED = 1;
|
||||||
private static final int STATUS_DISABLED = 2;
|
private static final int STATUS_DISABLED = 2;
|
||||||
|
private static final int PROVINCE_REGION_LEVEL = 1;
|
||||||
|
private static final int CITY_REGION_LEVEL = 2;
|
||||||
|
private static final int DISTRICT_REGION_LEVEL = 3;
|
||||||
private static final int CODE_MAX_LENGTH = 30;
|
private static final int CODE_MAX_LENGTH = 30;
|
||||||
private static final int NAME_MAX_LENGTH = 100;
|
private static final int NAME_MAX_LENGTH = 100;
|
||||||
private static final int REGION_MAX_LENGTH = 50;
|
private static final int REGION_MAX_LENGTH = 50;
|
||||||
@@ -102,6 +109,7 @@ public class PortTerminalServiceImpl extends BaseServiceImpl<PortTerminalMapper,
|
|||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public boolean submit(PortTerminal portTerminal) {
|
public boolean submit(PortTerminal portTerminal) {
|
||||||
prepare(portTerminal, SOURCE_MANUAL);
|
prepare(portTerminal, SOURCE_MANUAL);
|
||||||
|
prepareSubmitTarget(portTerminal);
|
||||||
validate(portTerminal);
|
validate(portTerminal);
|
||||||
return saveOrUpdate(portTerminal);
|
return saveOrUpdate(portTerminal);
|
||||||
}
|
}
|
||||||
@@ -206,7 +214,9 @@ public class PortTerminalServiceImpl extends BaseServiceImpl<PortTerminalMapper,
|
|||||||
? PORT_CODE_PATTERN.matcher(code).matches()
|
? PORT_CODE_PATTERN.matcher(code).matches()
|
||||||
: TERMINAL_CODE_PATTERN.matcher(code).matches();
|
: TERMINAL_CODE_PATTERN.matcher(code).matches();
|
||||||
if (!codeValid) {
|
if (!codeValid) {
|
||||||
String message = CATEGORY_PORT.equals(category) ? "港口编码为5位大写字母" : "码头编码格式为港口编码-码头标识";
|
String message = CATEGORY_PORT.equals(category)
|
||||||
|
? "港口编码为5位大写字母,当前为「" + code + "」"
|
||||||
|
: "码头编码格式为港口编码-码头标识,当前为「" + code + "」";
|
||||||
failureList.add(buildFailure(data, index, message));
|
failureList.add(buildFailure(data, index, message));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -239,8 +249,11 @@ public class PortTerminalServiceImpl extends BaseServiceImpl<PortTerminalMapper,
|
|||||||
portTerminal.setDataSource(SOURCE_BATCH);
|
portTerminal.setDataSource(SOURCE_BATCH);
|
||||||
portTerminal.setStatus(STATUS_ENABLED);
|
portTerminal.setStatus(STATUS_ENABLED);
|
||||||
prepare(portTerminal, SOURCE_BATCH);
|
prepare(portTerminal, SOURCE_BATCH);
|
||||||
|
prepareSubmitTarget(portTerminal);
|
||||||
validate(portTerminal);
|
validate(portTerminal);
|
||||||
save(portTerminal);
|
if (!saveOrUpdate(portTerminal)) {
|
||||||
|
throw new ServiceException("港口码头保存失败");
|
||||||
|
}
|
||||||
batchPortMap.put(portTerminal.getCode(), portTerminal);
|
batchPortMap.put(portTerminal.getCode(), portTerminal);
|
||||||
} catch (Exception exception) {
|
} catch (Exception exception) {
|
||||||
failureList.add(buildFailure(data, index, resolveMessage(exception)));
|
failureList.add(buildFailure(data, index, resolveMessage(exception)));
|
||||||
@@ -265,8 +278,11 @@ public class PortTerminalServiceImpl extends BaseServiceImpl<PortTerminalMapper,
|
|||||||
portTerminal.setDataSource(SOURCE_BATCH);
|
portTerminal.setDataSource(SOURCE_BATCH);
|
||||||
portTerminal.setStatus(STATUS_ENABLED);
|
portTerminal.setStatus(STATUS_ENABLED);
|
||||||
prepareTerminal(portTerminal, batchPortMap);
|
prepareTerminal(portTerminal, batchPortMap);
|
||||||
|
prepareSubmitTarget(portTerminal);
|
||||||
validate(portTerminal);
|
validate(portTerminal);
|
||||||
save(portTerminal);
|
if (!saveOrUpdate(portTerminal)) {
|
||||||
|
throw new ServiceException("港口码头保存失败");
|
||||||
|
}
|
||||||
} catch (ParentPortNotFoundException exception) {
|
} catch (ParentPortNotFoundException exception) {
|
||||||
// 上级港口就声明在本文件里,只是那一行自己校验失败(已被标红)。
|
// 上级港口就声明在本文件里,只是那一行自己校验失败(已被标红)。
|
||||||
// 此时码头行本身没有问题,不再连带报错,避免用户看到"两行都错"的假象。
|
// 此时码头行本身没有问题,不再连带报错,避免用户看到"两行都错"的假象。
|
||||||
@@ -308,6 +324,12 @@ public class PortTerminalServiceImpl extends BaseServiceImpl<PortTerminalMapper,
|
|||||||
if (Func.isNotEmpty(parent.getCountry())) {
|
if (Func.isNotEmpty(parent.getCountry())) {
|
||||||
portTerminal.setCountry(parent.getCountry());
|
portTerminal.setCountry(parent.getCountry());
|
||||||
}
|
}
|
||||||
|
if (Func.isNotEmpty(parent.getProvinceCode())) {
|
||||||
|
portTerminal.setProvinceCode(parent.getProvinceCode());
|
||||||
|
}
|
||||||
|
if (Func.isNotEmpty(parent.getProvinceName())) {
|
||||||
|
portTerminal.setProvinceName(parent.getProvinceName());
|
||||||
|
}
|
||||||
if (Func.isNotEmpty(parent.getCity())) {
|
if (Func.isNotEmpty(parent.getCity())) {
|
||||||
portTerminal.setCity(parent.getCity());
|
portTerminal.setCity(parent.getCity());
|
||||||
}
|
}
|
||||||
@@ -341,10 +363,59 @@ public class PortTerminalServiceImpl extends BaseServiceImpl<PortTerminalMapper,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析异常信息,非业务异常统一提示导入失败。
|
* 解析异常信息:优先取业务异常,其次解包数据访问异常,避免失败明细只剩「导入失败」。
|
||||||
*/
|
*/
|
||||||
private String resolveMessage(Exception exception) {
|
private String resolveMessage(Exception exception) {
|
||||||
return exception instanceof ServiceException ? exception.getMessage() : "导入失败";
|
ServiceException serviceException = findCause(exception, ServiceException.class);
|
||||||
|
if (serviceException != null && Func.isNotEmpty(serviceException.getMessage())
|
||||||
|
&& !(serviceException instanceof ImportFailureException)) {
|
||||||
|
return serviceException.getMessage();
|
||||||
|
}
|
||||||
|
DataAccessException dataAccessException = findCause(exception, DataAccessException.class);
|
||||||
|
if (dataAccessException != null) {
|
||||||
|
Throwable root = rootCause(dataAccessException);
|
||||||
|
String rootMessage = root.getMessage();
|
||||||
|
log.warn("港口码头导入数据访问异常", exception);
|
||||||
|
if (Func.isNotEmpty(rootMessage)) {
|
||||||
|
if (rootMessage.contains("Duplicate") || rootMessage.contains("duplicate") || rootMessage.contains("uk_port_terminal_code")) {
|
||||||
|
return "该编码已存在";
|
||||||
|
}
|
||||||
|
return "数据保存失败:" + shortenMessage(rootMessage);
|
||||||
|
}
|
||||||
|
return "数据保存失败";
|
||||||
|
}
|
||||||
|
Throwable root = rootCause(exception);
|
||||||
|
String rootMessage = root.getMessage();
|
||||||
|
log.warn("港口码头导入非业务异常, type={}", root.getClass().getName(), exception);
|
||||||
|
if (Func.isNotEmpty(rootMessage)) {
|
||||||
|
return shortenMessage(rootMessage);
|
||||||
|
}
|
||||||
|
return "导入失败:" + root.getClass().getSimpleName();
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T extends Throwable> T findCause(Throwable throwable, Class<T> type) {
|
||||||
|
for (Throwable current = throwable; current != null; current = current.getCause()) {
|
||||||
|
if (type.isInstance(current)) {
|
||||||
|
return type.cast(current);
|
||||||
|
}
|
||||||
|
if (current.getCause() == current) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Throwable rootCause(Throwable throwable) {
|
||||||
|
Throwable root = throwable;
|
||||||
|
while (root.getCause() != null && root.getCause() != root) {
|
||||||
|
root = root.getCause();
|
||||||
|
}
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String shortenMessage(String message) {
|
||||||
|
String value = trimToEmpty(message);
|
||||||
|
return value.length() > 200 ? value.substring(0, 200) + "..." : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -370,6 +441,26 @@ public class PortTerminalServiceImpl extends BaseServiceImpl<PortTerminalMapper,
|
|||||||
return validRange(value, min, max) ? value.setScale(6, RoundingMode.HALF_UP) : null;
|
return validRange(value, min, max) ? value.setScale(6, RoundingMode.HALF_UP) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编码已存在且为软删除时:取消软删除并复用原主键,后续按更新落库;
|
||||||
|
* 编码存在且未删除时仍报「该编码已存在」。
|
||||||
|
*/
|
||||||
|
private void prepareSubmitTarget(PortTerminal portTerminal) {
|
||||||
|
if (Func.isNotEmpty(portTerminal.getId())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PortTerminal existingPortTerminal = baseMapper.selectByCodeIncludingDeleted(portTerminal.getCode());
|
||||||
|
if (existingPortTerminal == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!Objects.equals(existingPortTerminal.getIsDeleted(), 1)) {
|
||||||
|
throw new ServiceException("该编码已存在");
|
||||||
|
}
|
||||||
|
baseMapper.restoreById(existingPortTerminal.getId());
|
||||||
|
portTerminal.setId(existingPortTerminal.getId());
|
||||||
|
portTerminal.setIsDeleted(0);
|
||||||
|
}
|
||||||
|
|
||||||
private void prepare(PortTerminal portTerminal, String defaultDataSource) {
|
private void prepare(PortTerminal portTerminal, String defaultDataSource) {
|
||||||
prepare(portTerminal, defaultDataSource, false);
|
prepare(portTerminal, defaultDataSource, false);
|
||||||
}
|
}
|
||||||
@@ -386,6 +477,8 @@ public class PortTerminalServiceImpl extends BaseServiceImpl<PortTerminalMapper,
|
|||||||
portTerminal.setCategory(trimToEmpty(portTerminal.getCategory()));
|
portTerminal.setCategory(trimToEmpty(portTerminal.getCategory()));
|
||||||
portTerminal.setName(trimToEmpty(portTerminal.getName()));
|
portTerminal.setName(trimToEmpty(portTerminal.getName()));
|
||||||
portTerminal.setCountry(trimToEmpty(portTerminal.getCountry()));
|
portTerminal.setCountry(trimToEmpty(portTerminal.getCountry()));
|
||||||
|
portTerminal.setProvinceCode(trimToNull(portTerminal.getProvinceCode()));
|
||||||
|
portTerminal.setProvinceName(trimToNull(portTerminal.getProvinceName()));
|
||||||
portTerminal.setCity(trimToEmpty(portTerminal.getCity()));
|
portTerminal.setCity(trimToEmpty(portTerminal.getCity()));
|
||||||
portTerminal.setDistrictCode(trimToNull(portTerminal.getDistrictCode()));
|
portTerminal.setDistrictCode(trimToNull(portTerminal.getDistrictCode()));
|
||||||
portTerminal.setDistrictName(trimToNull(portTerminal.getDistrictName()));
|
portTerminal.setDistrictName(trimToNull(portTerminal.getDistrictName()));
|
||||||
@@ -431,68 +524,99 @@ public class PortTerminalServiceImpl extends BaseServiceImpl<PortTerminalMapper,
|
|||||||
if (!CATEGORY_PORT.equals(parent.getCategory())) {
|
if (!CATEGORY_PORT.equals(parent.getCategory())) {
|
||||||
throw new ServiceException("上级港口类型不正确");
|
throw new ServiceException("上级港口类型不正确");
|
||||||
}
|
}
|
||||||
portTerminal.setParentId(parent.getId());
|
applyParent(portTerminal, parent);
|
||||||
portTerminal.setParentCode(parent.getCode());
|
if (Func.isEmpty(portTerminal.getDistrictCode()) && Func.isNotEmpty(portTerminal.getRegionCode())) {
|
||||||
portTerminal.setParentName(parent.getName());
|
|
||||||
// 父港口区域信息为空时保留码头自身填写的值,避免历史数据(区县为空)导致码头无法导入。
|
|
||||||
if (Func.isNotEmpty(parent.getCountry())) {
|
|
||||||
portTerminal.setCountry(parent.getCountry());
|
|
||||||
}
|
|
||||||
if (Func.isNotEmpty(parent.getCity())) {
|
|
||||||
portTerminal.setCity(parent.getCity());
|
|
||||||
}
|
|
||||||
if (Func.isNotEmpty(parent.getDistrictCode())) {
|
|
||||||
portTerminal.setDistrictCode(parent.getDistrictCode());
|
|
||||||
portTerminal.setRegionCode(parent.getDistrictCode());
|
|
||||||
} else if (Func.isNotEmpty(portTerminal.getRegionCode())) {
|
|
||||||
portTerminal.setDistrictCode(portTerminal.getRegionCode());
|
portTerminal.setDistrictCode(portTerminal.getRegionCode());
|
||||||
}
|
}
|
||||||
if (Func.isNotEmpty(parent.getDistrictName())) {
|
|
||||||
portTerminal.setDistrictName(parent.getDistrictName());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fillRegion(PortTerminal portTerminal) {
|
private void fillRegion(PortTerminal portTerminal) {
|
||||||
|
Region province = null;
|
||||||
|
if (Func.isNotEmpty(portTerminal.getProvinceCode())) {
|
||||||
|
province = regionService.getById(portTerminal.getProvinceCode());
|
||||||
|
}
|
||||||
|
if (Func.isEmpty(province) && Func.isNotEmpty(portTerminal.getProvinceName())) {
|
||||||
|
province = findRegionByName(portTerminal.getProvinceName(), PROVINCE_REGION_LEVEL, null);
|
||||||
|
}
|
||||||
|
if (Func.isEmpty(province)) {
|
||||||
|
throw new ServiceException("请选择所属省份");
|
||||||
|
}
|
||||||
|
|
||||||
|
Region city = null;
|
||||||
|
if (Func.isNotEmpty(portTerminal.getCity())) {
|
||||||
|
city = findRegionByName(portTerminal.getCity(), CITY_REGION_LEVEL, province.getCode());
|
||||||
|
}
|
||||||
|
if (Func.isEmpty(city)) {
|
||||||
|
throw new ServiceException("请选择城市");
|
||||||
|
}
|
||||||
|
if (!Objects.equals(city.getParentCode(), province.getCode())) {
|
||||||
|
throw new ServiceException("所属城市与所属省份不匹配");
|
||||||
|
}
|
||||||
|
|
||||||
Region district = null;
|
Region district = null;
|
||||||
if (Func.isNotEmpty(portTerminal.getDistrictCode())) {
|
if (Func.isNotEmpty(portTerminal.getDistrictCode())) {
|
||||||
district = regionService.getById(portTerminal.getDistrictCode());
|
district = regionService.getById(portTerminal.getDistrictCode());
|
||||||
}
|
}
|
||||||
if (Func.isEmpty(district) && Func.isNotEmpty(portTerminal.getDistrictName())) {
|
if (Func.isEmpty(district) && Func.isNotEmpty(portTerminal.getDistrictName())) {
|
||||||
if (Func.isNotEmpty(portTerminal.getCity())) {
|
district = findRegionByName(portTerminal.getDistrictName(), DISTRICT_REGION_LEVEL, city.getCode());
|
||||||
List<Region> cityList = regionService.list(Wrappers.<Region>lambdaQuery()
|
|
||||||
.eq(Region::getName, portTerminal.getCity())
|
|
||||||
.eq(Region::getRegionLevel, 2));
|
|
||||||
for (Region city : cityList) {
|
|
||||||
district = regionService.getOne(Wrappers.<Region>lambdaQuery()
|
|
||||||
.eq(Region::getParentCode, city.getCode())
|
|
||||||
.eq(Region::getName, portTerminal.getDistrictName()), false);
|
|
||||||
if (Func.isNotEmpty(district)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (Func.isEmpty(district)) {
|
|
||||||
district = regionService.getOne(Wrappers.<Region>lambdaQuery()
|
|
||||||
.eq(Region::getName, portTerminal.getDistrictName())
|
|
||||||
.eq(Region::getRegionLevel, 3), false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (Func.isEmpty(district)) {
|
if (Func.isEmpty(district)) {
|
||||||
throw new ServiceException("请选择区县");
|
throw new ServiceException("请选择区县");
|
||||||
}
|
}
|
||||||
Region city = regionService.getById(district.getParentCode());
|
if (!Objects.equals(district.getParentCode(), city.getCode())) {
|
||||||
if (Func.isEmpty(city)) {
|
throw new ServiceException("所属区县与所属城市不匹配");
|
||||||
throw new ServiceException("区县所属城市不存在");
|
|
||||||
}
|
|
||||||
if (Func.isNotEmpty(portTerminal.getCity()) && !Objects.equals(portTerminal.getCity(), city.getName())) {
|
|
||||||
throw new ServiceException("区县与城市不匹配");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
portTerminal.setProvinceCode(province.getCode());
|
||||||
|
portTerminal.setProvinceName(province.getName());
|
||||||
portTerminal.setCity(city.getName());
|
portTerminal.setCity(city.getName());
|
||||||
portTerminal.setDistrictCode(district.getCode());
|
portTerminal.setDistrictCode(district.getCode());
|
||||||
portTerminal.setDistrictName(district.getName());
|
portTerminal.setDistrictName(district.getName());
|
||||||
portTerminal.setRegionCode(district.getCode());
|
portTerminal.setRegionCode(district.getCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按名称查找区划,兼容「福建/福建省」「泉州/泉州市」等简写。
|
||||||
|
*/
|
||||||
|
private Region findRegionByName(String name, int regionLevel, String parentCode) {
|
||||||
|
String normalized = trimToEmpty(name);
|
||||||
|
if (Func.isEmpty(normalized)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (String candidate : regionNameCandidates(normalized)) {
|
||||||
|
LambdaQueryWrapper<Region> queryWrapper = Wrappers.<Region>lambdaQuery()
|
||||||
|
.eq(Region::getRegionLevel, regionLevel)
|
||||||
|
.eq(Region::getName, candidate);
|
||||||
|
if (Func.isNotEmpty(parentCode)) {
|
||||||
|
queryWrapper.eq(Region::getParentCode, parentCode);
|
||||||
|
}
|
||||||
|
Region region = regionService.getOne(queryWrapper, false);
|
||||||
|
if (Func.isNotEmpty(region)) {
|
||||||
|
return region;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> regionNameCandidates(String name) {
|
||||||
|
List<String> candidates = new ArrayList<>();
|
||||||
|
candidates.add(name);
|
||||||
|
if (name.endsWith("省") || name.endsWith("市") || name.endsWith("区") || name.endsWith("县") || name.endsWith("州")) {
|
||||||
|
String shortName = name.substring(0, name.length() - 1);
|
||||||
|
if (Func.isNotEmpty(shortName) && !candidates.contains(shortName)) {
|
||||||
|
candidates.add(shortName);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (String suffix : List.of("省", "市", "区", "县", "州")) {
|
||||||
|
String fullName = name + suffix;
|
||||||
|
if (!candidates.contains(fullName)) {
|
||||||
|
candidates.add(fullName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return candidates;
|
||||||
|
}
|
||||||
|
|
||||||
private void validate(PortTerminal portTerminal) {
|
private void validate(PortTerminal portTerminal) {
|
||||||
if (!CATEGORY_PORT.equals(portTerminal.getCategory()) && !CATEGORY_TERMINAL.equals(portTerminal.getCategory())) {
|
if (!CATEGORY_PORT.equals(portTerminal.getCategory()) && !CATEGORY_TERMINAL.equals(portTerminal.getCategory())) {
|
||||||
throw new ServiceException("类型只能为港口或码头");
|
throw new ServiceException("类型只能为港口或码头");
|
||||||
@@ -506,16 +630,17 @@ public class PortTerminalServiceImpl extends BaseServiceImpl<PortTerminalMapper,
|
|||||||
validateLength(portTerminal.getCode(), CODE_MAX_LENGTH, "编码不能超过30字");
|
validateLength(portTerminal.getCode(), CODE_MAX_LENGTH, "编码不能超过30字");
|
||||||
validateLength(portTerminal.getName(), NAME_MAX_LENGTH, "港口/码头名称不能超过100字");
|
validateLength(portTerminal.getName(), NAME_MAX_LENGTH, "港口/码头名称不能超过100字");
|
||||||
validateLength(portTerminal.getCountry(), REGION_MAX_LENGTH, "国家不能超过50字");
|
validateLength(portTerminal.getCountry(), REGION_MAX_LENGTH, "国家不能超过50字");
|
||||||
|
validateLength(portTerminal.getProvinceName(), REGION_MAX_LENGTH, "所属省份不能超过50字");
|
||||||
validateLength(portTerminal.getCity(), REGION_MAX_LENGTH, "城市不能超过50字");
|
validateLength(portTerminal.getCity(), REGION_MAX_LENGTH, "城市不能超过50字");
|
||||||
validateLength(portTerminal.getDistrictName(), REGION_MAX_LENGTH, "区县不能超过50字");
|
validateLength(portTerminal.getDistrictName(), REGION_MAX_LENGTH, "区县不能超过50字");
|
||||||
validateLength(portTerminal.getRegionCode(), REGION_CODE_MAX_LENGTH, "行政区划编码不能超过32字");
|
validateLength(portTerminal.getRegionCode(), REGION_CODE_MAX_LENGTH, "行政区划编码不能超过32字");
|
||||||
validateLength(portTerminal.getDetailAddress(), DETAIL_ADDRESS_MAX_LENGTH, "详细地址不能超过255字");
|
validateLength(portTerminal.getDetailAddress(), DETAIL_ADDRESS_MAX_LENGTH, "详细地址不能超过255字");
|
||||||
validateLength(portTerminal.getRemark(), REMARK_MAX_LENGTH, "备注不能超过200个字");
|
validateLength(portTerminal.getRemark(), REMARK_MAX_LENGTH, "备注不能超过200个字");
|
||||||
if (CATEGORY_PORT.equals(portTerminal.getCategory()) && !PORT_CODE_PATTERN.matcher(portTerminal.getCode()).matches()) {
|
if (CATEGORY_PORT.equals(portTerminal.getCategory()) && !PORT_CODE_PATTERN.matcher(portTerminal.getCode()).matches()) {
|
||||||
throw new ServiceException("港口编码为5位大写字母");
|
throw new ServiceException("港口编码为5位大写字母,当前为「" + portTerminal.getCode() + "」");
|
||||||
}
|
}
|
||||||
if (CATEGORY_TERMINAL.equals(portTerminal.getCategory()) && !TERMINAL_CODE_PATTERN.matcher(portTerminal.getCode()).matches()) {
|
if (CATEGORY_TERMINAL.equals(portTerminal.getCategory()) && !TERMINAL_CODE_PATTERN.matcher(portTerminal.getCode()).matches()) {
|
||||||
throw new ServiceException("码头编码格式为港口编码-码头标识");
|
throw new ServiceException("码头编码格式为港口编码-码头标识,当前为「" + portTerminal.getCode() + "」");
|
||||||
}
|
}
|
||||||
if (CATEGORY_TERMINAL.equals(portTerminal.getCategory()) && !portTerminal.getCode().startsWith(portTerminal.getParentCode() + "-")) {
|
if (CATEGORY_TERMINAL.equals(portTerminal.getCategory()) && !portTerminal.getCode().startsWith(portTerminal.getParentCode() + "-")) {
|
||||||
throw new ServiceException("码头编码必须以所属港口编码开头");
|
throw new ServiceException("码头编码必须以所属港口编码开头");
|
||||||
@@ -523,12 +648,24 @@ public class PortTerminalServiceImpl extends BaseServiceImpl<PortTerminalMapper,
|
|||||||
if (Func.isEmpty(portTerminal.getCountry())) {
|
if (Func.isEmpty(portTerminal.getCountry())) {
|
||||||
throw new ServiceException("国家不能为空");
|
throw new ServiceException("国家不能为空");
|
||||||
}
|
}
|
||||||
|
if (Func.isEmpty(portTerminal.getProvinceCode()) && Func.isEmpty(portTerminal.getProvinceName())) {
|
||||||
|
throw new ServiceException("所属省份不能为空");
|
||||||
|
}
|
||||||
if (Func.isEmpty(portTerminal.getCity())) {
|
if (Func.isEmpty(portTerminal.getCity())) {
|
||||||
throw new ServiceException("城市不能为空");
|
throw new ServiceException("城市不能为空");
|
||||||
}
|
}
|
||||||
if (Func.isEmpty(portTerminal.getDistrictCode())) {
|
if (Func.isEmpty(portTerminal.getDistrictCode())) {
|
||||||
throw new ServiceException("区县不能为空");
|
throw new ServiceException("区县不能为空");
|
||||||
}
|
}
|
||||||
|
if (Func.isEmpty(portTerminal.getDetailAddress())) {
|
||||||
|
throw new ServiceException("详细地址不能为空");
|
||||||
|
}
|
||||||
|
if (Func.isEmpty(portTerminal.getLongitude())) {
|
||||||
|
throw new ServiceException("经度不能为空");
|
||||||
|
}
|
||||||
|
if (Func.isEmpty(portTerminal.getLatitude())) {
|
||||||
|
throw new ServiceException("纬度不能为空");
|
||||||
|
}
|
||||||
validateDataSource(portTerminal.getDataSource());
|
validateDataSource(portTerminal.getDataSource());
|
||||||
validateStatus(portTerminal.getStatus());
|
validateStatus(portTerminal.getStatus());
|
||||||
validateRange(portTerminal.getLongitude(), MIN_LONGITUDE, MAX_LONGITUDE, "经度范围为 -180 到 180");
|
validateRange(portTerminal.getLongitude(), MIN_LONGITUDE, MAX_LONGITUDE, "经度范围为 -180 到 180");
|
||||||
@@ -593,7 +730,13 @@ public class PortTerminalServiceImpl extends BaseServiceImpl<PortTerminalMapper,
|
|||||||
|
|
||||||
private String normalizeDataSource(String dataSource) {
|
private String normalizeDataSource(String dataSource) {
|
||||||
String value = trimToEmpty(dataSource);
|
String value = trimToEmpty(dataSource);
|
||||||
return SOURCE_INITIAL_OLD.equals(value) ? SOURCE_INITIAL : value;
|
if (SOURCE_INITIAL_OLD.equals(value)) {
|
||||||
|
return SOURCE_INITIAL;
|
||||||
|
}
|
||||||
|
if (SOURCE_MANUAL_OLD.equals(value)) {
|
||||||
|
return SOURCE_MANUAL;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validateEnabledTerminal(Long parentId) {
|
private void validateEnabledTerminal(Long parentId) {
|
||||||
@@ -611,7 +754,7 @@ public class PortTerminalServiceImpl extends BaseServiceImpl<PortTerminalMapper,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String trimToEmpty(String value) {
|
private String trimToEmpty(String value) {
|
||||||
return value == null ? "" : value.trim();
|
return value == null ? "" : value.replace("\uFEFF", "").strip();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String trimToNull(String value) {
|
private String trimToNull(String value) {
|
||||||
|
|||||||
+23
-11
@@ -102,6 +102,7 @@ public class RailwayStationServiceImpl extends BaseServiceImpl<RailwayStationMap
|
|||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public boolean submit(RailwayStation railwayStation) {
|
public boolean submit(RailwayStation railwayStation) {
|
||||||
prepare(railwayStation, SOURCE_MANUAL);
|
prepare(railwayStation, SOURCE_MANUAL);
|
||||||
|
prepareSubmitTarget(railwayStation);
|
||||||
validate(railwayStation);
|
validate(railwayStation);
|
||||||
return saveOrUpdate(railwayStation);
|
return saveOrUpdate(railwayStation);
|
||||||
}
|
}
|
||||||
@@ -170,7 +171,8 @@ public class RailwayStationServiceImpl extends BaseServiceImpl<RailwayStationMap
|
|||||||
throw new org.springblade.common.excel.ImportFailureException(data);
|
throw new org.springblade.common.excel.ImportFailureException(data);
|
||||||
}
|
}
|
||||||
for (RailwayStation railwayStation : railwayStationList) {
|
for (RailwayStation railwayStation : railwayStationList) {
|
||||||
prepareImportTarget(railwayStation);
|
// 编码/TMIS 命中逻辑删除记录时复用原主键,恢复后更新,避免唯一索引冲突。
|
||||||
|
prepareSubmitTarget(railwayStation);
|
||||||
if (!saveOrUpdate(railwayStation)) {
|
if (!saveOrUpdate(railwayStation)) {
|
||||||
throw new ServiceException("铁路车站保存失败");
|
throw new ServiceException("铁路车站保存失败");
|
||||||
}
|
}
|
||||||
@@ -178,11 +180,18 @@ public class RailwayStationServiceImpl extends BaseServiceImpl<RailwayStationMap
|
|||||||
return errorList;
|
return errorList;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void prepareImportTarget(RailwayStation railwayStation) {
|
/**
|
||||||
|
* 编码或 TMIS 已存在且为软删除时:取消软删除并复用原主键,后续按更新落库;
|
||||||
|
* 存在且未删除时仍报「该编码已存在」。
|
||||||
|
*/
|
||||||
|
private void prepareSubmitTarget(RailwayStation railwayStation) {
|
||||||
if (Func.isNotEmpty(railwayStation.getId())) {
|
if (Func.isNotEmpty(railwayStation.getId())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
RailwayStation existingRailwayStation = baseMapper.selectByCodeIncludingDeleted(railwayStation.getCode());
|
RailwayStation existingRailwayStation = baseMapper.selectByCodeIncludingDeleted(railwayStation.getCode());
|
||||||
|
if (existingRailwayStation == null && Func.isNotEmpty(railwayStation.getTmisCode())) {
|
||||||
|
existingRailwayStation = baseMapper.selectByTmisCodeIncludingDeleted(railwayStation.getTmisCode());
|
||||||
|
}
|
||||||
if (existingRailwayStation == null) {
|
if (existingRailwayStation == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -207,7 +216,7 @@ public class RailwayStationServiceImpl extends BaseServiceImpl<RailwayStationMap
|
|||||||
private void normalizeImportRailwayStation(RailwayStation railwayStation) {
|
private void normalizeImportRailwayStation(RailwayStation railwayStation) {
|
||||||
railwayStation.setTmisCode(trimToEmpty(railwayStation.getTmisCode()));
|
railwayStation.setTmisCode(trimToEmpty(railwayStation.getTmisCode()));
|
||||||
railwayStation.setCode(CODE_PREFIX + railwayStation.getTmisCode());
|
railwayStation.setCode(CODE_PREFIX + railwayStation.getTmisCode());
|
||||||
railwayStation.setTelegraphCode(trimToEmpty(railwayStation.getTelegraphCode()).toUpperCase(Locale.ROOT));
|
railwayStation.setTelegraphCode(normalizeTelegraphCode(railwayStation.getTelegraphCode()));
|
||||||
railwayStation.setName(trimToEmpty(railwayStation.getName()));
|
railwayStation.setName(trimToEmpty(railwayStation.getName()));
|
||||||
railwayStation.setProvinceCode(trimToNull(railwayStation.getProvinceCode()));
|
railwayStation.setProvinceCode(trimToNull(railwayStation.getProvinceCode()));
|
||||||
railwayStation.setProvinceName(trimToNull(railwayStation.getProvinceName()));
|
railwayStation.setProvinceName(trimToNull(railwayStation.getProvinceName()));
|
||||||
@@ -247,9 +256,7 @@ public class RailwayStationServiceImpl extends BaseServiceImpl<RailwayStationMap
|
|||||||
validateImportUnique(RailwayStation::getTmisCode, railwayStation.getTmisCode(), "该TMIS国标编码已存在", validationErrors);
|
validateImportUnique(RailwayStation::getTmisCode, railwayStation.getTmisCode(), "该TMIS国标编码已存在", validationErrors);
|
||||||
validateImportUnique(RailwayStation::getCode, railwayStation.getCode(), "该编码已存在", validationErrors);
|
validateImportUnique(RailwayStation::getCode, railwayStation.getCode(), "该编码已存在", validationErrors);
|
||||||
}
|
}
|
||||||
if (Func.isEmpty(railwayStation.getTelegraphCode())) {
|
if (Func.isNotEmpty(railwayStation.getTelegraphCode())) {
|
||||||
addValidationError(validationErrors, "电报略码不能为空");
|
|
||||||
} else {
|
|
||||||
if (!TELEGRAPH_CODE_PATTERN.matcher(railwayStation.getTelegraphCode()).matches()) {
|
if (!TELEGRAPH_CODE_PATTERN.matcher(railwayStation.getTelegraphCode()).matches()) {
|
||||||
addValidationError(validationErrors, "电报略码为3位大写字母");
|
addValidationError(validationErrors, "电报略码为3位大写字母");
|
||||||
}
|
}
|
||||||
@@ -377,7 +384,7 @@ public class RailwayStationServiceImpl extends BaseServiceImpl<RailwayStationMap
|
|||||||
private void prepare(RailwayStation railwayStation, String defaultDataSource) {
|
private void prepare(RailwayStation railwayStation, String defaultDataSource) {
|
||||||
railwayStation.setTmisCode(trimToEmpty(railwayStation.getTmisCode()));
|
railwayStation.setTmisCode(trimToEmpty(railwayStation.getTmisCode()));
|
||||||
railwayStation.setCode(CODE_PREFIX + railwayStation.getTmisCode());
|
railwayStation.setCode(CODE_PREFIX + railwayStation.getTmisCode());
|
||||||
railwayStation.setTelegraphCode(trimToEmpty(railwayStation.getTelegraphCode()).toUpperCase(Locale.ROOT));
|
railwayStation.setTelegraphCode(normalizeTelegraphCode(railwayStation.getTelegraphCode()));
|
||||||
railwayStation.setName(trimToEmpty(railwayStation.getName()));
|
railwayStation.setName(trimToEmpty(railwayStation.getName()));
|
||||||
railwayStation.setRailwayLine(trimToNull(railwayStation.getRailwayLine()));
|
railwayStation.setRailwayLine(trimToNull(railwayStation.getRailwayLine()));
|
||||||
railwayStation.setProvinceCode(trimToNull(railwayStation.getProvinceCode()));
|
railwayStation.setProvinceCode(trimToNull(railwayStation.getProvinceCode()));
|
||||||
@@ -410,10 +417,8 @@ public class RailwayStationServiceImpl extends BaseServiceImpl<RailwayStationMap
|
|||||||
if (!TMIS_CODE_PATTERN.matcher(railwayStation.getTmisCode()).matches()) {
|
if (!TMIS_CODE_PATTERN.matcher(railwayStation.getTmisCode()).matches()) {
|
||||||
throw new ServiceException("TMIS国标编码为5位数字");
|
throw new ServiceException("TMIS国标编码为5位数字");
|
||||||
}
|
}
|
||||||
if (Func.isEmpty(railwayStation.getTelegraphCode())) {
|
if (Func.isNotEmpty(railwayStation.getTelegraphCode())
|
||||||
throw new ServiceException("电报略码格式不正确或已存在");
|
&& !TELEGRAPH_CODE_PATTERN.matcher(railwayStation.getTelegraphCode()).matches()) {
|
||||||
}
|
|
||||||
if (!TELEGRAPH_CODE_PATTERN.matcher(railwayStation.getTelegraphCode()).matches()) {
|
|
||||||
throw new ServiceException("电报略码格式不正确或已存在");
|
throw new ServiceException("电报略码格式不正确或已存在");
|
||||||
}
|
}
|
||||||
if (Func.isEmpty(railwayStation.getName())) {
|
if (Func.isEmpty(railwayStation.getName())) {
|
||||||
@@ -446,7 +451,9 @@ public class RailwayStationServiceImpl extends BaseServiceImpl<RailwayStationMap
|
|||||||
validateDataSource(railwayStation.getDataSource());
|
validateDataSource(railwayStation.getDataSource());
|
||||||
validateStatus(railwayStation.getStatus());
|
validateStatus(railwayStation.getStatus());
|
||||||
validateUnique(railwayStation, RailwayStation::getTmisCode, railwayStation.getTmisCode(), "该TMIS编码已存在");
|
validateUnique(railwayStation, RailwayStation::getTmisCode, railwayStation.getTmisCode(), "该TMIS编码已存在");
|
||||||
|
if (Func.isNotEmpty(railwayStation.getTelegraphCode())) {
|
||||||
validateUnique(railwayStation, RailwayStation::getTelegraphCode, railwayStation.getTelegraphCode(), "电报略码格式不正确或已存在");
|
validateUnique(railwayStation, RailwayStation::getTelegraphCode, railwayStation.getTelegraphCode(), "电报略码格式不正确或已存在");
|
||||||
|
}
|
||||||
validateUnique(railwayStation, RailwayStation::getCode, railwayStation.getCode(), "该编码已存在");
|
validateUnique(railwayStation, RailwayStation::getCode, railwayStation.getCode(), "该编码已存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -562,6 +569,11 @@ public class RailwayStationServiceImpl extends BaseServiceImpl<RailwayStationMap
|
|||||||
return trimValue.isEmpty() ? null : trimValue;
|
return trimValue.isEmpty() ? null : trimValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String normalizeTelegraphCode(String value) {
|
||||||
|
String telegraphCode = trimToNull(value);
|
||||||
|
return telegraphCode == null ? null : telegraphCode.toUpperCase(Locale.ROOT);
|
||||||
|
}
|
||||||
|
|
||||||
private String normalizeDataSource(String dataSource) {
|
private String normalizeDataSource(String dataSource) {
|
||||||
String value = trimToEmpty(dataSource);
|
String value = trimToEmpty(dataSource);
|
||||||
if (SOURCE_INITIAL_OLD.equals(value)) {
|
if (SOURCE_INITIAL_OLD.equals(value)) {
|
||||||
|
|||||||
+51
-39
@@ -18,9 +18,6 @@
|
|||||||
* Using this software signifies agreement to this License, and the software
|
* Using this software signifies agreement to this License, and the software
|
||||||
* must not be used for illegal purposes.
|
* must not be used for illegal purposes.
|
||||||
* <p>
|
* <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)
|
* Author: Chill Zhuang (bladejava@qq.com)
|
||||||
*/
|
*/
|
||||||
package org.springblade.system.service.impl;
|
package org.springblade.system.service.impl;
|
||||||
@@ -28,7 +25,6 @@ package org.springblade.system.service.impl;
|
|||||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springblade.core.cache.utils.CacheUtil;
|
|
||||||
import org.springblade.core.log.exception.ServiceException;
|
import org.springblade.core.log.exception.ServiceException;
|
||||||
import org.springblade.core.redis.cache.BladeRedis;
|
import org.springblade.core.redis.cache.BladeRedis;
|
||||||
import org.springblade.core.secure.utils.AuthUtil;
|
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.core.tool.utils.StringUtil;
|
||||||
import org.springblade.resource.feign.ISmsClient;
|
import org.springblade.resource.feign.ISmsClient;
|
||||||
import org.springblade.resource.utils.SmsUtil;
|
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.PhoneChangeDTO;
|
||||||
import org.springblade.system.pojo.dto.PhoneVerifyDTO;
|
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.IUserPhoneService;
|
||||||
import org.springblade.system.service.IUserService;
|
import org.springblade.system.utils.MpUserAuth;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import static org.springblade.core.cache.constant.CacheConstant.USER_CACHE;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户手机号变更服务实现
|
* 小程序用户手机号变更服务实现(写入 blade_mp_user)
|
||||||
*
|
*
|
||||||
* @author Chill
|
* @author Chill
|
||||||
*/
|
*/
|
||||||
@@ -68,24 +64,22 @@ public class UserPhoneServiceImpl implements IUserPhoneService {
|
|||||||
/**
|
/**
|
||||||
* 原手机号已校验凭证(Redis)
|
* 原手机号已校验凭证(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 Duration PHONE_CHANGE_VERIFIED_TTL = Duration.ofMinutes(15);
|
||||||
|
|
||||||
private static final Pattern MOBILE_PATTERN = Pattern.compile("^1[3-9]\\d{9}$");
|
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 ISmsClient smsClient;
|
||||||
private final BladeRedis bladeRedis;
|
private final BladeRedis bladeRedis;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public R sendCode(String phone) {
|
public R sendCode(String phone) {
|
||||||
|
assertMpLogin();
|
||||||
String normalizedPhone = normalizePhone(phone);
|
String normalizedPhone = normalizePhone(phone);
|
||||||
Long userId = AuthUtil.getUserId();
|
Long userId = AuthUtil.getUserId();
|
||||||
if (Func.isEmpty(userId)) {
|
MpUser currentUser = requireCurrentUser(userId);
|
||||||
throw new ServiceException("请先登录");
|
|
||||||
}
|
|
||||||
User currentUser = requireCurrentUser(userId);
|
|
||||||
String tenantId = Func.toStr(currentUser.getTenantId(), AuthUtil.getTenantId());
|
String tenantId = Func.toStr(currentUser.getTenantId(), AuthUtil.getTenantId());
|
||||||
boolean isCurrentPhone = StringUtil.equals(normalizedPhone, Func.toStr(currentUser.getPhone()));
|
boolean isCurrentPhone = StringUtil.equals(normalizedPhone, Func.toStr(currentUser.getPhone()));
|
||||||
if (!isCurrentPhone) {
|
if (!isCurrentPhone) {
|
||||||
@@ -100,16 +94,14 @@ public class UserPhoneServiceImpl implements IUserPhoneService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean verifyOldPhone(PhoneVerifyDTO phoneVerify) {
|
public boolean verifyOldPhone(PhoneVerifyDTO phoneVerify) {
|
||||||
|
assertMpLogin();
|
||||||
Long userId = AuthUtil.getUserId();
|
Long userId = AuthUtil.getUserId();
|
||||||
if (Func.isEmpty(userId)) {
|
|
||||||
throw new ServiceException("请先登录");
|
|
||||||
}
|
|
||||||
String id = Func.toStr(phoneVerify.getId()).trim();
|
String id = Func.toStr(phoneVerify.getId()).trim();
|
||||||
String code = Func.toStr(phoneVerify.getCode()).trim();
|
String code = Func.toStr(phoneVerify.getCode()).trim();
|
||||||
if (StringUtil.isBlank(id) || StringUtil.isBlank(code)) {
|
if (StringUtil.isBlank(id) || StringUtil.isBlank(code)) {
|
||||||
throw new ServiceException("请先获取并填写验证码");
|
throw new ServiceException("请先获取并填写验证码");
|
||||||
}
|
}
|
||||||
User currentUser = requireCurrentUser(userId);
|
MpUser currentUser = requireCurrentUser(userId);
|
||||||
String oldPhone = Func.toStr(currentUser.getPhone()).trim();
|
String oldPhone = Func.toStr(currentUser.getPhone()).trim();
|
||||||
if (StringUtil.isBlank(oldPhone)) {
|
if (StringUtil.isBlank(oldPhone)) {
|
||||||
throw new ServiceException("当前账号未绑定手机号");
|
throw new ServiceException("当前账号未绑定手机号");
|
||||||
@@ -122,10 +114,8 @@ public class UserPhoneServiceImpl implements IUserPhoneService {
|
|||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public boolean changePhone(PhoneChangeDTO phoneChange) {
|
public boolean changePhone(PhoneChangeDTO phoneChange) {
|
||||||
|
assertMpLogin();
|
||||||
Long userId = AuthUtil.getUserId();
|
Long userId = AuthUtil.getUserId();
|
||||||
if (Func.isEmpty(userId)) {
|
|
||||||
throw new ServiceException("请先登录");
|
|
||||||
}
|
|
||||||
String verified = Func.toStr(bladeRedis.get(PHONE_CHANGE_VERIFIED_KEY + userId));
|
String verified = Func.toStr(bladeRedis.get(PHONE_CHANGE_VERIFIED_KEY + userId));
|
||||||
if (!StringUtil.equals(verified, "1")) {
|
if (!StringUtil.equals(verified, "1")) {
|
||||||
throw new ServiceException("请先完成原手机号验证");
|
throw new ServiceException("请先完成原手机号验证");
|
||||||
@@ -136,7 +126,7 @@ public class UserPhoneServiceImpl implements IUserPhoneService {
|
|||||||
if (StringUtil.isBlank(id) || StringUtil.isBlank(code)) {
|
if (StringUtil.isBlank(id) || StringUtil.isBlank(code)) {
|
||||||
throw new ServiceException("请先获取并填写验证码");
|
throw new ServiceException("请先获取并填写验证码");
|
||||||
}
|
}
|
||||||
User currentUser = requireCurrentUser(userId);
|
MpUser currentUser = requireCurrentUser(userId);
|
||||||
String oldPhone = Func.toStr(currentUser.getPhone()).trim();
|
String oldPhone = Func.toStr(currentUser.getPhone()).trim();
|
||||||
if (StringUtil.equals(newPhone, oldPhone)) {
|
if (StringUtil.equals(newPhone, oldPhone)) {
|
||||||
throw new ServiceException("新手机号不可与当前手机号相同");
|
throw new ServiceException("新手机号不可与当前手机号相同");
|
||||||
@@ -145,27 +135,49 @@ public class UserPhoneServiceImpl implements IUserPhoneService {
|
|||||||
assertPhoneAvailable(tenantId, newPhone, userId);
|
assertPhoneAvailable(tenantId, newPhone, userId);
|
||||||
validateSms(tenantId, id, code, newPhone);
|
validateSms(tenantId, id, code, newPhone);
|
||||||
|
|
||||||
User updateUser = new User();
|
MpUser updateUser = new MpUser();
|
||||||
updateUser.setId(userId);
|
updateUser.setId(userId);
|
||||||
|
updateUser.setTenantId(tenantId);
|
||||||
updateUser.setPhone(newPhone);
|
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()))) {
|
if (StringUtil.isNotBlank(oldPhone) && StringUtil.equals(oldPhone, Func.toStr(currentUser.getAccount()))) {
|
||||||
assertAccountAvailable(tenantId, newPhone, userId);
|
assertAccountAvailable(tenantId, newPhone, userId);
|
||||||
updateUser.setAccount(newPhone);
|
updateUser.setAccount(newPhone);
|
||||||
|
} else {
|
||||||
|
updateUser.setAccount(currentUser.getAccount());
|
||||||
}
|
}
|
||||||
boolean updated = userService.updateById(updateUser);
|
boolean updated = mpUserService.updateUser(updateUser);
|
||||||
if (!updated) {
|
if (!updated) {
|
||||||
throw new ServiceException("手机号修改失败");
|
throw new ServiceException("手机号修改失败");
|
||||||
}
|
}
|
||||||
bladeRedis.del(PHONE_CHANGE_VERIFIED_KEY + userId);
|
bladeRedis.del(PHONE_CHANGE_VERIFIED_KEY + userId);
|
||||||
CacheUtil.clear(USER_CACHE);
|
MpUserCache.clear();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private User requireCurrentUser(Long userId) {
|
private void assertMpLogin() {
|
||||||
User user = userService.getById(userId);
|
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) {
|
if (user == null) {
|
||||||
throw new ServiceException("用户不存在");
|
throw new ServiceException("小程序用户不存在");
|
||||||
}
|
}
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
@@ -178,11 +190,11 @@ public class UserPhoneServiceImpl implements IUserPhoneService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void assertPhoneAvailable(String tenantId, String phone, Long excludeUserId) {
|
private void assertPhoneAvailable(String tenantId, String phone, Long excludeUserId) {
|
||||||
Long phoneCount = userService.count(
|
Long phoneCount = mpUserService.count(
|
||||||
Wrappers.<User>lambdaQuery()
|
Wrappers.<MpUser>lambdaQuery()
|
||||||
.eq(User::getTenantId, tenantId)
|
.eq(MpUser::getTenantId, tenantId)
|
||||||
.eq(User::getPhone, phone)
|
.eq(MpUser::getPhone, phone)
|
||||||
.ne(User::getId, excludeUserId)
|
.ne(MpUser::getId, excludeUserId)
|
||||||
);
|
);
|
||||||
if (phoneCount != null && phoneCount > 0L) {
|
if (phoneCount != null && phoneCount > 0L) {
|
||||||
throw new ServiceException(StringUtil.format("当前手机 [{}] 已存在!", phone));
|
throw new ServiceException(StringUtil.format("当前手机 [{}] 已存在!", phone));
|
||||||
@@ -190,11 +202,11 @@ public class UserPhoneServiceImpl implements IUserPhoneService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void assertAccountAvailable(String tenantId, String account, Long excludeUserId) {
|
private void assertAccountAvailable(String tenantId, String account, Long excludeUserId) {
|
||||||
Long accountCount = userService.count(
|
Long accountCount = mpUserService.count(
|
||||||
Wrappers.<User>lambdaQuery()
|
Wrappers.<MpUser>lambdaQuery()
|
||||||
.eq(User::getTenantId, tenantId)
|
.eq(MpUser::getTenantId, tenantId)
|
||||||
.eq(User::getAccount, account)
|
.eq(MpUser::getAccount, account)
|
||||||
.ne(User::getId, excludeUserId)
|
.ne(MpUser::getId, excludeUserId)
|
||||||
);
|
);
|
||||||
if (accountCount != null && accountCount > 0L) {
|
if (accountCount != null && accountCount > 0L) {
|
||||||
throw new ServiceException(StringUtil.format("当前用户 [{}] 已存在!", account));
|
throw new ServiceException(StringUtil.format("当前用户 [{}] 已存在!", account));
|
||||||
|
|||||||
+74
-1
@@ -133,8 +133,12 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implement
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IPage<UserVO> selectPage(Map<String, Object> user, Query query) {
|
public IPage<UserVO> selectPage(Map<String, Object> user, Query query) {
|
||||||
|
String account = Func.toStr(user.remove("account")).trim();
|
||||||
|
String realName = Func.toStr(user.remove("realName")).trim();
|
||||||
|
String phone = Func.toStr(user.remove("phone")).trim();
|
||||||
QueryWrapper<User> queryWrapper = Condition.getQueryWrapper(user, User.class);
|
QueryWrapper<User> queryWrapper = Condition.getQueryWrapper(user, User.class);
|
||||||
applyTenantScope(queryWrapper);
|
applyTenantScope(queryWrapper);
|
||||||
|
applyUserFuzzyFilters(queryWrapper, account, realName, phone);
|
||||||
return UserWrapper.build().pageVO(page(Condition.getPage(query), queryWrapper));
|
return UserWrapper.build().pageVO(page(Condition.getPage(query), queryWrapper));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,6 +339,23 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implement
|
|||||||
return submitUserInfo(user) && submitUserDept(user) && submitUserScopes(user);
|
return submitUserInfo(user) && submitUserDept(user) && submitUserScopes(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public boolean changeUserStatus(Long userId, Integer status) {
|
||||||
|
if (Func.isEmpty(userId)) {
|
||||||
|
throw new ServiceException("用户主键不能为空");
|
||||||
|
}
|
||||||
|
if (!Objects.equals(status, DataStatusEnum.ENABLE.getCode()) && !Objects.equals(status, 0)
|
||||||
|
&& !Objects.equals(status, DataStatusEnum.DISABLE.getCode())) {
|
||||||
|
throw new ServiceException("启停状态不正确");
|
||||||
|
}
|
||||||
|
TenantGuard.verify(this, userId, USER);
|
||||||
|
Integer targetStatus = Objects.equals(status, DataStatusEnum.ENABLE.getCode())
|
||||||
|
? DataStatusEnum.ENABLE.getCode() : 0;
|
||||||
|
CacheUtil.clear(USER_CACHE);
|
||||||
|
return changeStatus(Collections.singletonList(userId), targetStatus);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean updateUserInfo(User user) {
|
public boolean updateUserInfo(User user) {
|
||||||
// 用户修改自身信息强制指定当前请求账号的ID
|
// 用户修改自身信息强制指定当前请求账号的ID
|
||||||
@@ -360,7 +381,14 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implement
|
|||||||
throw new ServiceException(StringUtil.format("当前手机 [{}] 已存在!", user.getPhone()));
|
throw new ServiceException(StringUtil.format("当前手机 [{}] 已存在!", user.getPhone()));
|
||||||
}
|
}
|
||||||
user.setPassword(null);
|
user.setPassword(null);
|
||||||
return updateById(user);
|
Integer originalSex = user.getSex();
|
||||||
|
Integer normalizedSex = normalizeUserSex(originalSex);
|
||||||
|
user.setSex(normalizedSex);
|
||||||
|
boolean updated = updateById(user);
|
||||||
|
if (user.getId() != null && originalSex != null && normalizedSex == null) {
|
||||||
|
this.update(Wrappers.<User>lambdaUpdate().set(User::getSex, null).eq(User::getId, user.getId()));
|
||||||
|
}
|
||||||
|
return updated;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean submitUserDept(User user) {
|
private boolean submitUserDept(User user) {
|
||||||
@@ -378,6 +406,7 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implement
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IPage<User> selectUserPage(IPage<User> page, User user, Long deptId, String tenantId) {
|
public IPage<User> selectUserPage(IPage<User> page, User user, Long deptId, String tenantId) {
|
||||||
|
normalizeUserSearch(user);
|
||||||
List<Long> deptIdList = SysCache.getDeptChildIds(deptId);
|
List<Long> deptIdList = SysCache.getDeptChildIds(deptId);
|
||||||
return page.setRecords(baseMapper.selectUserPage(page, user, deptIdList, tenantId));
|
return page.setRecords(baseMapper.selectUserPage(page, user, deptIdList, tenantId));
|
||||||
}
|
}
|
||||||
@@ -713,8 +742,12 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implement
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<UserExcel> exportUser(Map<String, Object> user) {
|
public List<UserExcel> exportUser(Map<String, Object> user) {
|
||||||
|
String account = Func.toStr(user.remove("account")).trim();
|
||||||
|
String realName = Func.toStr(user.remove("realName")).trim();
|
||||||
|
String phone = Func.toStr(user.remove("phone")).trim();
|
||||||
QueryWrapper<User> queryWrapper = Condition.getQueryWrapper(user, User.class);
|
QueryWrapper<User> queryWrapper = Condition.getQueryWrapper(user, User.class);
|
||||||
applyTenantScope(queryWrapper);
|
applyTenantScope(queryWrapper);
|
||||||
|
applyUserFuzzyFilters(queryWrapper, account, realName, phone);
|
||||||
queryWrapper.lambda().eq(User::getIsDeleted, BladeConstant.DB_NOT_DELETED);
|
queryWrapper.lambda().eq(User::getIsDeleted, BladeConstant.DB_NOT_DELETED);
|
||||||
return exportUser(queryWrapper);
|
return exportUser(queryWrapper);
|
||||||
}
|
}
|
||||||
@@ -1077,6 +1110,7 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implement
|
|||||||
throw new ServiceException(StringUtil.format("当前手机 [{}] 已存在!", user.getPhone()));
|
throw new ServiceException(StringUtil.format("当前手机 [{}] 已存在!", user.getPhone()));
|
||||||
}
|
}
|
||||||
CacheUtil.clear(USER_CACHE);
|
CacheUtil.clear(USER_CACHE);
|
||||||
|
user.setSex(normalizeUserSex(user.getSex()));
|
||||||
return save(user) && submitUserDept(user) && submitUserScopes(user);
|
return save(user) && submitUserDept(user) && submitUserScopes(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1087,6 +1121,16 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implement
|
|||||||
if (user.getIncludeNewCustomer() == null) user.setIncludeNewCustomer(0);
|
if (user.getIncludeNewCustomer() == null) user.setIncludeNewCustomer(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 性别仅保留男 / 女 / 未知;空值或历史占位 -1 不回填,按空处理。
|
||||||
|
*/
|
||||||
|
private Integer normalizeUserSex(Integer sex) {
|
||||||
|
if (Objects.equals(sex, 1) || Objects.equals(sex, 2) || Objects.equals(sex, 3)) {
|
||||||
|
return sex;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean submitUserScopes(User user) {
|
private boolean submitUserScopes(User user) {
|
||||||
userDataScopeMapper.delete(Wrappers.<UserDataScope>lambdaQuery().eq(UserDataScope::getUserId, user.getId()));
|
userDataScopeMapper.delete(Wrappers.<UserDataScope>lambdaQuery().eq(UserDataScope::getUserId, user.getId()));
|
||||||
userCustomerScopeMapper.delete(Wrappers.<UserCustomerScope>lambdaQuery().eq(UserCustomerScope::getUserId, user.getId()));
|
userCustomerScopeMapper.delete(Wrappers.<UserCustomerScope>lambdaQuery().eq(UserCustomerScope::getUserId, user.getId()));
|
||||||
@@ -1127,6 +1171,35 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implement
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 归一化用户列表搜索条件,避免首尾空格导致模糊匹配失效。
|
||||||
|
*/
|
||||||
|
private void normalizeUserSearch(User user) {
|
||||||
|
if (user == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (user.getAccount() != null) {
|
||||||
|
user.setAccount(user.getAccount().trim());
|
||||||
|
}
|
||||||
|
if (user.getRealName() != null) {
|
||||||
|
user.setRealName(user.getRealName().trim());
|
||||||
|
}
|
||||||
|
if (user.getPhone() != null) {
|
||||||
|
user.setPhone(user.getPhone().trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 账号名、姓名、手机号统一按模糊匹配过滤。
|
||||||
|
*/
|
||||||
|
private void applyUserFuzzyFilters(QueryWrapper<User> queryWrapper, String account, String realName, String phone) {
|
||||||
|
queryWrapper.lambda().like(StringUtil.isNotBlank(account), User::getAccount, account);
|
||||||
|
if (StringUtil.isNotBlank(realName)) {
|
||||||
|
queryWrapper.lambda().and(wrapper -> wrapper.like(User::getRealName, realName).or().like(User::getName, realName));
|
||||||
|
}
|
||||||
|
queryWrapper.lambda().like(StringUtil.isNotBlank(phone), User::getPhone, phone);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void decryptPhone(IPhone user) {
|
public void decryptPhone(IPhone user) {
|
||||||
|
|||||||
+97
@@ -0,0 +1,97 @@
|
|||||||
|
/**
|
||||||
|
* BladeX Commercial License Agreement
|
||||||
|
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||||
|
* <p>
|
||||||
|
* Use of this software is governed by the Commercial License Agreement
|
||||||
|
* obtained after purchasing a license from BladeX.
|
||||||
|
* <p>
|
||||||
|
* 1. This software is for development use only under a valid license
|
||||||
|
* from BladeX.
|
||||||
|
* <p>
|
||||||
|
* 2. Redistribution of this software's source code to any third party
|
||||||
|
* without a commercial license is strictly prohibited.
|
||||||
|
* <p>
|
||||||
|
* 3. Licensees may copyright their own code but cannot use segments
|
||||||
|
* from this software for such purposes. Copyright of this software
|
||||||
|
* remains with BladeX.
|
||||||
|
* <p>
|
||||||
|
* Using this software signifies agreement to this License, and the software
|
||||||
|
* must not be used for illegal purposes.
|
||||||
|
* <p>
|
||||||
|
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||||
|
* not liable for any claims arising from secondary or illegal development.
|
||||||
|
* <p>
|
||||||
|
* Author: Chill Zhuang (bladejava@qq.com)
|
||||||
|
*/
|
||||||
|
package org.springblade.system.util;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springblade.thirdparty.oa.constant.OAConvertConstant;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组织编码工具:上级编码直接拼接固定 3 位分段数字(不足补零,中间不加 "-")
|
||||||
|
*
|
||||||
|
* @author Chill
|
||||||
|
*/
|
||||||
|
public final class DeptCodeUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分段数字位数
|
||||||
|
*/
|
||||||
|
public static final int SEGMENT_WIDTH = 3;
|
||||||
|
|
||||||
|
private DeptCodeUtil() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成子级编码:上级编码 + 三位数字
|
||||||
|
*/
|
||||||
|
public static String formatChildCode(String parentCode, int serial) {
|
||||||
|
return parentCode + String.format("%0" + SEGMENT_WIDTH + "d", serial);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否符合「上级编码 + 三位数字」格式(恰好多出 3 位数字)
|
||||||
|
*/
|
||||||
|
public static boolean matchesChildCode(String parentCode, String deptCode) {
|
||||||
|
if (StringUtils.isBlank(parentCode) || StringUtils.isBlank(deptCode)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return deptCode.matches("^" + Pattern.quote(parentCode) + "\\d{" + SEGMENT_WIDTH + "}$");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否为公司编码(GWWL + 三位数字)
|
||||||
|
*/
|
||||||
|
public static boolean isCompanyCode(String deptCode) {
|
||||||
|
return matchesChildCode(OAConvertConstant.OA_SYNC_ROOT_DEPT_CODE, deptCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在已有编码中取下一分段序号(从 1 起)
|
||||||
|
*/
|
||||||
|
public static int nextSerial(String parentCode, Collection<String> existingCodes) {
|
||||||
|
if (StringUtils.isBlank(parentCode)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
Pattern pattern = Pattern.compile("^" + Pattern.quote(parentCode) + "(\\d{" + SEGMENT_WIDTH + "})$");
|
||||||
|
int maxSerial = 0;
|
||||||
|
if (existingCodes != null) {
|
||||||
|
for (String code : existingCodes) {
|
||||||
|
if (StringUtils.isBlank(code)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Matcher matcher = pattern.matcher(code.trim());
|
||||||
|
if (!matcher.matches()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
maxSerial = Math.max(maxSerial, Integer.parseInt(matcher.group(1)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return maxSerial + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
+78
@@ -0,0 +1,78 @@
|
|||||||
|
/**
|
||||||
|
* 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.wrapper;
|
||||||
|
|
||||||
|
import org.springblade.core.mp.support.BaseEntityWrapper;
|
||||||
|
import org.springblade.core.tool.utils.BeanUtil;
|
||||||
|
import org.springblade.core.tool.utils.Func;
|
||||||
|
import org.springblade.system.cache.DictCache;
|
||||||
|
import org.springblade.system.cache.UserCache;
|
||||||
|
import org.springblade.system.pojo.entity.MpUser;
|
||||||
|
import org.springblade.system.pojo.enums.DictEnum;
|
||||||
|
import org.springblade.system.pojo.enums.MpUserIdentity;
|
||||||
|
import org.springblade.system.pojo.vo.MpUserVO;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小程序用户包装类
|
||||||
|
*
|
||||||
|
* @author Chill
|
||||||
|
*/
|
||||||
|
public class MpUserWrapper extends BaseEntityWrapper<MpUser, MpUserVO> {
|
||||||
|
|
||||||
|
public static MpUserWrapper build() {
|
||||||
|
return new MpUserWrapper();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MpUserVO entityVO(MpUser mpUser) {
|
||||||
|
MpUserVO mpUserVO = Objects.requireNonNull(BeanUtil.copyProperties(mpUser, MpUserVO.class));
|
||||||
|
// 密码哈希不下发前端
|
||||||
|
mpUserVO.setPassword(null);
|
||||||
|
mpUserVO.setIdentitiesName(resolveIdentitiesName(mpUser.getIdentities()));
|
||||||
|
if (mpUser.getSex() != null) {
|
||||||
|
mpUserVO.setSexName(DictCache.getValue(DictEnum.SEX, mpUser.getSex()));
|
||||||
|
}
|
||||||
|
mpUserVO.setCreateUserName(UserCache.getUserRealName(mpUser.getCreateUser()));
|
||||||
|
mpUserVO.setUpdateUserName(UserCache.getUserRealName(mpUser.getUpdateUser()));
|
||||||
|
return mpUserVO;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveIdentitiesName(String identities) {
|
||||||
|
List<String> identityList = MpUserIdentity.split(identities);
|
||||||
|
if (identityList.isEmpty()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return identityList.stream()
|
||||||
|
.map(code -> {
|
||||||
|
MpUserIdentity identity = MpUserIdentity.of(code);
|
||||||
|
return identity == null ? code : identity.getLabel();
|
||||||
|
})
|
||||||
|
.filter(Func::isNotBlank)
|
||||||
|
.collect(Collectors.joining(","));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+5
@@ -38,6 +38,7 @@ import org.springblade.system.pojo.enums.DictEnum;
|
|||||||
import org.springblade.system.pojo.vo.UserVO;
|
import org.springblade.system.pojo.vo.UserVO;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 包装类,返回视图层所需的字段
|
* 包装类,返回视图层所需的字段
|
||||||
@@ -81,7 +82,11 @@ public class UserWrapper extends BaseEntityWrapper<User, UserVO> {
|
|||||||
userVO.setPostId(StringPool.EMPTY);
|
userVO.setPostId(StringPool.EMPTY);
|
||||||
userVO.setPostName("暂未分配");
|
userVO.setPostName("暂未分配");
|
||||||
}
|
}
|
||||||
|
if (user.getSex() == null || Objects.equals(user.getSex(), -1)) {
|
||||||
|
userVO.setSexName("");
|
||||||
|
} else {
|
||||||
userVO.setSexName(DictCache.getValue(DictEnum.SEX, user.getSex()));
|
userVO.setSexName(DictCache.getValue(DictEnum.SEX, user.getSex()));
|
||||||
|
}
|
||||||
userVO.setUserTypeName(DictCache.getValue(DictEnum.USER_TYPE, user.getUserType()));
|
userVO.setUserTypeName(DictCache.getValue(DictEnum.USER_TYPE, user.getUserType()));
|
||||||
return userVO;
|
return userVO;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,15 @@
|
|||||||
<groupId>io.minio</groupId>
|
<groupId>io.minio</groupId>
|
||||||
<artifactId>minio</artifactId>
|
<artifactId>minio</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 测试:本模块此前的 src/test 下只有 main() 手跑脚本,没有可自动执行的测试。
|
||||||
|
认证改造的提交/审核链路带状态机语义(同对象至多一条待审、提交不得改生效快照
|
||||||
|
等),必须用可重复执行的单元测试守住,故引入 JUnit 5 与 Mockito。 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
+58
@@ -0,0 +1,58 @@
|
|||||||
|
/**
|
||||||
|
* BladeX Commercial License Agreement
|
||||||
|
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||||
|
* <p>
|
||||||
|
* Use of this software is governed by the Commercial License Agreement
|
||||||
|
* obtained after purchasing a license from BladeX.
|
||||||
|
* <p>
|
||||||
|
* 1. This software is for development use only under a valid license
|
||||||
|
* from BladeX.
|
||||||
|
* <p>
|
||||||
|
* 2. Redistribution of this software's source code to any third party
|
||||||
|
* without a commercial license is strictly prohibited.
|
||||||
|
* <p>
|
||||||
|
* 3. Licensees may copyright their own code but cannot use segments
|
||||||
|
* from this software for such purposes. Copyright of this software
|
||||||
|
* remains with BladeX.
|
||||||
|
* <p>
|
||||||
|
* Using this software signifies agreement to this License, and the software
|
||||||
|
* must not be used for illegal purposes.
|
||||||
|
* <p>
|
||||||
|
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||||
|
* not liable for any claims arising from secondary or illegal development.
|
||||||
|
* <p>
|
||||||
|
* Author: Chill Zhuang (bladejava@qq.com)
|
||||||
|
*/
|
||||||
|
package org.springblade.transport.config;
|
||||||
|
|
||||||
|
import org.springblade.core.tool.utils.Func;
|
||||||
|
import org.springframework.web.bind.WebDataBinder;
|
||||||
|
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||||
|
import org.springframework.web.bind.annotation.InitBinder;
|
||||||
|
|
||||||
|
import java.beans.PropertyEditorSupport;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 列表查询 deptId 绑定:用户可能绑定多个组织,前端会传入逗号分隔字符串。
|
||||||
|
* 组织筛选项为单选,这里取第一个有效 ID,避免 Long 类型转换失败。
|
||||||
|
*
|
||||||
|
* @author Chill
|
||||||
|
*/
|
||||||
|
@ControllerAdvice(basePackages = "org.springblade.transport.controller")
|
||||||
|
public class DeptIdBindingAdvice {
|
||||||
|
|
||||||
|
@InitBinder
|
||||||
|
public void initBinder(WebDataBinder binder) {
|
||||||
|
binder.registerCustomEditor(Long.class, "deptId", new PropertyEditorSupport() {
|
||||||
|
@Override
|
||||||
|
public void setAsText(String text) {
|
||||||
|
if (Func.isBlank(text)) {
|
||||||
|
setValue(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setValue(Func.firstLong(text));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+85
-4
@@ -5,28 +5,109 @@
|
|||||||
package org.springblade.transport.config;
|
package org.springblade.transport.config;
|
||||||
|
|
||||||
import io.minio.MinioClient;
|
import io.minio.MinioClient;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import okhttp3.HttpUrl;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import org.springblade.core.log.exception.ServiceException;
|
||||||
|
import org.springblade.core.tool.utils.StringUtil;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 凭证图片 MinIO 客户端配置。
|
* 凭证图片 MinIO 客户端配置。
|
||||||
* <p>
|
* <p>
|
||||||
* 连接参数由 Nacos 的 file.storage.minio 配置提供。
|
* 连接参数由 Nacos 的 file.storage.minio 配置提供。
|
||||||
* 未配置 {@code file.storage.minio.endpoint} 时不注册该客户端,
|
* <ul>
|
||||||
* 以免凭证上传功能缺失配置导致整个 blade-transport 服务无法启动;
|
* <li>{@code endpoint}:建议内网地址;若误配为 https://host/oss,会自动剥离 path 并用拦截器补回</li>
|
||||||
* 此时凭证相关接口会在调用时给出明确提示,而非启动即失败。
|
* <li>{@code transform-endpoint}:外网地址(可带 /oss),用于浏览器预签名</li>
|
||||||
|
* </ul>
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
@Configuration
|
@Configuration
|
||||||
@ConditionalOnProperty(prefix = "file.storage.minio", name = "endpoint")
|
@ConditionalOnProperty(prefix = "file.storage.minio", name = "endpoint")
|
||||||
public class VoucherMinioConfig {
|
public class VoucherMinioConfig {
|
||||||
|
|
||||||
|
private static final String DEFAULT_REGION = "us-east-1";
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public MinioClient voucherMinioClient(
|
public MinioClient voucherMinioClient(
|
||||||
@Value("${file.storage.minio.endpoint:${minio.endpoint:}}") String endpoint,
|
@Value("${file.storage.minio.endpoint:${minio.endpoint:}}") String endpoint,
|
||||||
@Value("${file.storage.minio.access-key-id:${minio.access-key:}}") String accessKey,
|
@Value("${file.storage.minio.access-key-id:${minio.access-key:}}") String accessKey,
|
||||||
@Value("${file.storage.minio.access-key-secret:${minio.secret-key:}}") String secretKey) {
|
@Value("${file.storage.minio.access-key-secret:${minio.secret-key:}}") String secretKey) {
|
||||||
return MinioClient.builder().endpoint(endpoint).credentials(accessKey, secretKey).build();
|
String origin = resolveClientEndpoint(endpoint);
|
||||||
|
String pathPrefix = resolvePathPrefix(endpoint);
|
||||||
|
if (StringUtil.isNotBlank(pathPrefix)) {
|
||||||
|
log.warn("MinIO endpoint 含 path,将按 origin={} pathPrefix={} 访问;建议 endpoint 使用内网地址",
|
||||||
|
origin, pathPrefix);
|
||||||
|
}
|
||||||
|
return create(endpoint, accessKey, secretKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MinioClient create(String endpoint, String accessKey, String secretKey) {
|
||||||
|
String origin = resolveClientEndpoint(endpoint);
|
||||||
|
String pathPrefix = resolvePathPrefix(endpoint);
|
||||||
|
MinioClient.Builder builder = MinioClient.builder()
|
||||||
|
.endpoint(origin)
|
||||||
|
.credentials(accessKey, secretKey)
|
||||||
|
.region(DEFAULT_REGION);
|
||||||
|
if (StringUtil.isNotBlank(pathPrefix)) {
|
||||||
|
builder.httpClient(buildHttpClient(pathPrefix), true);
|
||||||
|
}
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MinIO SDK 不允许 endpoint 带 path(例如 https://host/oss)。
|
||||||
|
*/
|
||||||
|
public static String resolveClientEndpoint(String endpoint) {
|
||||||
|
if (StringUtil.isBlank(endpoint)) {
|
||||||
|
throw new ServiceException("MinIO endpoint 不能为空");
|
||||||
|
}
|
||||||
|
URI uri = URI.create(endpoint.trim());
|
||||||
|
if (StringUtil.isBlank(uri.getScheme()) || StringUtil.isBlank(uri.getHost())) {
|
||||||
|
throw new ServiceException("MinIO endpoint 配置不正确: " + endpoint);
|
||||||
|
}
|
||||||
|
StringBuilder origin = new StringBuilder();
|
||||||
|
origin.append(uri.getScheme()).append("://").append(uri.getHost());
|
||||||
|
if (uri.getPort() > 0) {
|
||||||
|
origin.append(':').append(uri.getPort());
|
||||||
|
}
|
||||||
|
return origin.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String resolvePathPrefix(String endpoint) {
|
||||||
|
URI uri = URI.create(endpoint.trim());
|
||||||
|
String path = uri.getPath();
|
||||||
|
if (StringUtil.isBlank(path) || "/".equals(path)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
String normalized = path.endsWith("/") ? path.substring(0, path.length() - 1) : path;
|
||||||
|
return normalized.startsWith("/") ? normalized : "/" + normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static OkHttpClient buildHttpClient(String pathPrefix) {
|
||||||
|
return new OkHttpClient.Builder()
|
||||||
|
.connectTimeout(30, TimeUnit.SECONDS)
|
||||||
|
.writeTimeout(30, TimeUnit.SECONDS)
|
||||||
|
.readTimeout(30, TimeUnit.SECONDS)
|
||||||
|
.addInterceptor(chain -> {
|
||||||
|
Request original = chain.request();
|
||||||
|
HttpUrl originalUrl = original.url();
|
||||||
|
String encodedPath = originalUrl.encodedPath();
|
||||||
|
if (encodedPath.startsWith(pathPrefix + "/") || encodedPath.equals(pathPrefix)) {
|
||||||
|
return chain.proceed(original);
|
||||||
|
}
|
||||||
|
HttpUrl newUrl = originalUrl.newBuilder()
|
||||||
|
.encodedPath(pathPrefix + encodedPath)
|
||||||
|
.build();
|
||||||
|
return chain.proceed(original.newBuilder().url(newUrl).build());
|
||||||
|
})
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+103
@@ -0,0 +1,103 @@
|
|||||||
|
/**
|
||||||
|
* BladeX Commercial License Agreement
|
||||||
|
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||||
|
* <p>
|
||||||
|
* Use of this software is governed by the Commercial License Agreement
|
||||||
|
* obtained after purchasing a license from BladeX.
|
||||||
|
* <p>
|
||||||
|
* 1. This software is for development use only under a valid license
|
||||||
|
* from BladeX.
|
||||||
|
* <p>
|
||||||
|
* 2. Redistribution of this software's source code to any third party
|
||||||
|
* without a commercial license is strictly prohibited.
|
||||||
|
* <p>
|
||||||
|
* 3. Licensees may copyright their own code but cannot use segments
|
||||||
|
* from this software for such purposes. Copyright of this software
|
||||||
|
* remains with BladeX.
|
||||||
|
* <p>
|
||||||
|
* Using this software signifies agreement to this License, and the software
|
||||||
|
* must not be used for illegal purposes.
|
||||||
|
* <p>
|
||||||
|
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||||
|
* not liable for any claims arising from secondary or illegal development.
|
||||||
|
* <p>
|
||||||
|
* Author: Chill Zhuang (bladejava@qq.com)
|
||||||
|
*/
|
||||||
|
package org.springblade.transport.controller;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.springblade.core.boot.ctrl.BladeController;
|
||||||
|
import org.springblade.core.mp.support.Condition;
|
||||||
|
import org.springblade.core.mp.support.Query;
|
||||||
|
import org.springblade.core.secure.annotation.PreAuth;
|
||||||
|
import org.springblade.core.tool.api.R;
|
||||||
|
import org.springblade.transport.pojo.vo.CertAuditDetailVO;
|
||||||
|
import org.springblade.transport.pojo.vo.TransportCertRecordVO;
|
||||||
|
import org.springblade.transport.service.ICertAuditService;
|
||||||
|
import org.springblade.transport.support.CertAuditPermissions;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 认证审核(管理端)
|
||||||
|
* <p>
|
||||||
|
* 审核对象是**一条认证记录**,不是「一辆车」或「一个人」。
|
||||||
|
* 这与旧的车辆认证审核接口有本质区别:旧接口作用于车辆行并原地覆盖,
|
||||||
|
* 在「生效值 / 待审值分离」之后语义已不成立,故已弃用。
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@AllArgsConstructor
|
||||||
|
@RequestMapping({"/certification-audit", "/blade-transport/certification-audit"})
|
||||||
|
@Tag(name = "认证审核", description = "管理端司机认证与车辆认证审核")
|
||||||
|
public class CertificationAuditController extends BladeController {
|
||||||
|
|
||||||
|
private final ICertAuditService certAuditService;
|
||||||
|
|
||||||
|
@GetMapping("/pending-page")
|
||||||
|
@ApiOperationSupport(order = 1)
|
||||||
|
@Operation(summary = "认证记录列表", description = "按认证对象类型与审核状态分页查询,按提交时间倒序;状态不传时查审核中的记录。列表同时下发该记录司机的姓名与手机号(司机档案当前值)")
|
||||||
|
@PreAuth(CertAuditPermissions.AUDIT_VIEW)
|
||||||
|
public R<IPage<TransportCertRecordVO>> pendingPage(
|
||||||
|
@Parameter(description = "认证对象类型:driver-司机,vehicle-车辆", required = true)
|
||||||
|
@RequestParam String bizType,
|
||||||
|
@Parameter(description = "展示键模糊匹配:提交时的司机姓名 / 车牌号")
|
||||||
|
@RequestParam(required = false) String bizKey,
|
||||||
|
@Parameter(description = "司机姓名模糊匹配:当前姓名,同时兼顾提交时的旧名")
|
||||||
|
@RequestParam(required = false) String driverName,
|
||||||
|
@Parameter(description = "司机手机号模糊匹配:支持只输后四位")
|
||||||
|
@RequestParam(required = false) String driverMobile,
|
||||||
|
@Parameter(description = "审核状态:0审核中 1已通过 2已驳回 3已作废;不传按0(审核中)处理")
|
||||||
|
@RequestParam(required = false) Integer status,
|
||||||
|
Query query) {
|
||||||
|
return R.data(certAuditService.pendingPage(bizType, bizKey, driverName, driverMobile, status, Condition.getPage(query)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/detail")
|
||||||
|
@ApiOperationSupport(order = 2)
|
||||||
|
@Operation(summary = "审核详情", description = "同时返回待审快照与当前生效值,供前端做字段级对比")
|
||||||
|
@PreAuth(CertAuditPermissions.AUDIT_VIEW)
|
||||||
|
public R<CertAuditDetailVO> detail(
|
||||||
|
@Parameter(description = "认证记录主键", required = true) @RequestParam Long id) {
|
||||||
|
return R.data(certAuditService.detail(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/audit")
|
||||||
|
@ApiOperationSupport(order = 3)
|
||||||
|
@Operation(summary = "审核认证记录", description = "通过使该记录成为生效快照;驳回只终结该记录,生效快照不变")
|
||||||
|
@PreAuth(CertAuditPermissions.AUDIT_ACTION)
|
||||||
|
public R<Boolean> audit(
|
||||||
|
@Parameter(description = "认证记录主键", required = true) @RequestParam Long id,
|
||||||
|
@Parameter(description = "是否通过", required = true) @RequestParam Boolean pass,
|
||||||
|
@Parameter(description = "驳回原因,驳回时必填") @RequestParam(required = false) String rejectReason) {
|
||||||
|
return R.status(certAuditService.audit(id, Boolean.TRUE.equals(pass), rejectReason));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+9
-1
@@ -177,8 +177,16 @@ public class ContractManageController extends BladeController {
|
|||||||
return R.data(contractManageService.expireStats(contractManage));
|
return R.data(contractManageService.expireStats(contractManage));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/export-contract-manage")
|
@GetMapping("/next-contract-no")
|
||||||
@ApiOperationSupport(order = 16)
|
@ApiOperationSupport(order = 16)
|
||||||
|
@Operation(summary = "预览下一合同编号", description = "按合同负责部门编码前缀+年份+三位流水预览")
|
||||||
|
public R<String> nextContractNo(
|
||||||
|
@Parameter(description = "合同负责部门ID", required = true) @RequestParam Long organizationId) {
|
||||||
|
return R.data(contractManageService.nextContractNo(organizationId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/export-contract-manage")
|
||||||
|
@ApiOperationSupport(order = 17)
|
||||||
@Operation(summary = "导出合同管理")
|
@Operation(summary = "导出合同管理")
|
||||||
public void exportContractManage(ContractManageVO contractManage, @RequestParam(required = false) String ids, HttpServletResponse response) {
|
public void exportContractManage(ContractManageVO contractManage, @RequestParam(required = false) String ids, HttpServletResponse response) {
|
||||||
List<ContractManageExcel> list = contractManageService.exportContractManage(contractManage, ids);
|
List<ContractManageExcel> list = contractManageService.exportContractManage(contractManage, ids);
|
||||||
|
|||||||
+153
@@ -0,0 +1,153 @@
|
|||||||
|
/**
|
||||||
|
* BladeX Commercial License Agreement
|
||||||
|
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||||
|
* <p>
|
||||||
|
* Use of this software is governed by the Commercial License Agreement
|
||||||
|
* obtained after purchasing a license from BladeX.
|
||||||
|
* <p>
|
||||||
|
* 1. This software is for development use only under a valid license
|
||||||
|
* from BladeX.
|
||||||
|
* <p>
|
||||||
|
* 2. Redistribution of this software's source code to any third party
|
||||||
|
* without a commercial license is strictly prohibited.
|
||||||
|
* <p>
|
||||||
|
* 3. Licensees may copyright their own code but cannot use segments
|
||||||
|
* from this software for such purposes. Copyright of this software
|
||||||
|
* remains with BladeX.
|
||||||
|
* <p>
|
||||||
|
* Using this software signifies agreement to this License, and the software
|
||||||
|
* must not be used for illegal purposes.
|
||||||
|
* <p>
|
||||||
|
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||||
|
* not liable for any claims arising from secondary or illegal development.
|
||||||
|
* <p>
|
||||||
|
* Author: Chill Zhuang (bladejava@qq.com)
|
||||||
|
*/
|
||||||
|
package org.springblade.transport.controller;
|
||||||
|
|
||||||
|
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.springblade.core.boot.ctrl.BladeController;
|
||||||
|
import org.springblade.core.tool.api.R;
|
||||||
|
import org.springblade.transport.ocr.constant.BaiduOcrType;
|
||||||
|
import org.springblade.transport.ocr.service.IBaiduOcrService;
|
||||||
|
import org.springblade.transport.pojo.dto.OcrRecognizeRequest;
|
||||||
|
import org.springblade.transport.pojo.dto.PersonCertSubmitRequest;
|
||||||
|
import org.springblade.transport.pojo.dto.VehicleCertSubmitRequest;
|
||||||
|
import org.springblade.transport.pojo.vo.BaiduOcrResultVO;
|
||||||
|
import org.springblade.transport.pojo.vo.TransportCertRecordVO;
|
||||||
|
import org.springblade.transport.service.IDriverCertService;
|
||||||
|
import org.springblade.transport.support.OcrTypeMapping;
|
||||||
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PutMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 司机端认证(小程序)
|
||||||
|
* <p>
|
||||||
|
* 对外路径:{@code /api/blade-transport/driver/auth/**}
|
||||||
|
* 同时兼容未去前缀直连 {@code /blade-transport/driver/auth/**}。
|
||||||
|
* <p>
|
||||||
|
* 提交审核**不影响**该对象当前的可用性:待审内容只落认证记录,
|
||||||
|
* 审核通过前对外呈现的仍是旧的生效值。
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@AllArgsConstructor
|
||||||
|
@RequestMapping({"/driver/auth", "/blade-transport/driver/auth"})
|
||||||
|
@Tag(name = "司机端认证", description = "小程序司机个人认证与车辆认证")
|
||||||
|
public class DriverAuthController extends BladeController {
|
||||||
|
|
||||||
|
private final IDriverCertService driverCertService;
|
||||||
|
private final IBaiduOcrService baiduOcrService;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------ 个人认证
|
||||||
|
|
||||||
|
@PostMapping("/person")
|
||||||
|
@ApiOperationSupport(order = 1)
|
||||||
|
@Operation(summary = "提交个人认证", description = "三证(身份证+驾驶证+从业资格证)一起提交,生成待审记录")
|
||||||
|
public R<Long> submitPerson(@RequestBody PersonCertSubmitRequest request) {
|
||||||
|
return R.data(driverCertService.submitPersonCert(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/person")
|
||||||
|
@ApiOperationSupport(order = 2)
|
||||||
|
@Operation(summary = "个人认证生效值", description = "返回当前生效快照的内容;从未认证通过时 data 为 null")
|
||||||
|
public R<PersonCertSubmitRequest> person() {
|
||||||
|
return R.data(driverCertService.getEffectivePersonCert());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------ 车辆认证
|
||||||
|
|
||||||
|
@PostMapping("/vehicle")
|
||||||
|
@ApiOperationSupport(order = 3)
|
||||||
|
@Operation(summary = "提交车辆认证", description = "生成待审记录;不改变该车已生效的认证内容")
|
||||||
|
public R<Long> submitVehicle(@RequestBody VehicleCertSubmitRequest request) {
|
||||||
|
return R.data(driverCertService.submitVehicleCert(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/vehicle")
|
||||||
|
@ApiOperationSupport(order = 4)
|
||||||
|
@Operation(summary = "重新认证车辆", description = "与提交同一语义,语义上用于已有车辆重新认证")
|
||||||
|
public R<Long> updateVehicle(@RequestBody VehicleCertSubmitRequest request) {
|
||||||
|
return R.data(driverCertService.submitVehicleCert(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/vehicle/{id}")
|
||||||
|
@ApiOperationSupport(order = 5)
|
||||||
|
@Operation(summary = "删除车辆认证", description = "对车辆做逻辑删除;认证记录保留,不物理删除")
|
||||||
|
public R<Boolean> deleteVehicle(@PathVariable Long id) {
|
||||||
|
return R.status(driverCertService.deleteVehicleCert(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/vehicle/effective")
|
||||||
|
@ApiOperationSupport(order = 5)
|
||||||
|
@Operation(summary = "车辆认证生效值", description = "返回该车当前生效快照的内容")
|
||||||
|
public R<VehicleCertSubmitRequest> vehicleEffective(@RequestParam Long vehicleId) {
|
||||||
|
return R.data(driverCertService.getEffectiveVehicleCert(vehicleId));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------ 认证记录
|
||||||
|
|
||||||
|
@GetMapping("/record/list")
|
||||||
|
@ApiOperationSupport(order = 6)
|
||||||
|
@Operation(summary = "认证记录列表", description = "按提交时间倒序;只能查本人或本人绑定车辆的记录")
|
||||||
|
public R<List<TransportCertRecordVO>> recordList(@RequestParam String bizType,
|
||||||
|
@RequestParam Long bizId) {
|
||||||
|
return R.data(driverCertService.listRecords(bizType, bizId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/record/detail")
|
||||||
|
@ApiOperationSupport(order = 7)
|
||||||
|
@Operation(summary = "认证记录详情", description = "内容取自该次提交的完整快照")
|
||||||
|
public R<TransportCertRecordVO> recordDetail(@RequestParam Long id) {
|
||||||
|
return R.data(driverCertService.getRecord(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------ OCR
|
||||||
|
|
||||||
|
@PostMapping("/ocr")
|
||||||
|
@ApiOperationSupport(order = 8)
|
||||||
|
@Operation(summary = "证件OCR识别",
|
||||||
|
description = "按小程序既有契约收 {imageUrl, type, side?},转调百度OCR。"
|
||||||
|
+ "type 用小程序命名:idcard-身份证,driver-驾驶证,vehicle-行驶证,road_transport-道路运输证")
|
||||||
|
public R<BaiduOcrResultVO> ocr(@RequestBody OcrRecognizeRequest request) {
|
||||||
|
// 两侧对「驾驶证 / 行驶证」的用词正好相反,故必须经显式映射,不能把入参直接透传下游
|
||||||
|
BaiduOcrType ocrType = OcrTypeMapping.toBaiduType(request.getType());
|
||||||
|
// 未指定时传 null,不要在这里补 front:道路运输证没有正副面参数,
|
||||||
|
// 补了会变成「传了 side 但该证件不支持」,让这条链路恒失败。
|
||||||
|
// 「默认取正面」由知晓证件类型的一侧(OCR 服务)决定。
|
||||||
|
String side = OcrTypeMapping.normalizeSide(request.getSide());
|
||||||
|
return R.data(baiduOcrService.recognizeUrl(ocrType, side, request.getImageUrl()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+12
-4
@@ -38,6 +38,7 @@ import org.springblade.transport.pojo.vo.DriverNodePunchVO;
|
|||||||
import org.springblade.transport.pojo.vo.DriverWaybillCardVO;
|
import org.springblade.transport.pojo.vo.DriverWaybillCardVO;
|
||||||
import org.springblade.transport.pojo.vo.DriverWaybillPreviewVO;
|
import org.springblade.transport.pojo.vo.DriverWaybillPreviewVO;
|
||||||
import org.springblade.transport.pojo.vo.DriverWaybillTabCountsVO;
|
import org.springblade.transport.pojo.vo.DriverWaybillTabCountsVO;
|
||||||
|
import org.springblade.transport.pojo.vo.WaybillTrackVO;
|
||||||
import org.springblade.transport.service.IDriverWaybillService;
|
import org.springblade.transport.service.IDriverWaybillService;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@@ -64,14 +65,14 @@ public class DriverWaybillController extends BladeController {
|
|||||||
|
|
||||||
@GetMapping("/current-task")
|
@GetMapping("/current-task")
|
||||||
@ApiOperationSupport(order = 1)
|
@ApiOperationSupport(order = 1)
|
||||||
@Operation(summary = "首页:当前运输中任务", description = "当前司机绑定车牌下 businessStatus=running 的最新一条运单")
|
@Operation(summary = "首页:当前运输中任务", description = "当前用户手机号匹配运单 driverPhone 且 businessStatus=running 的最新一条运单")
|
||||||
public R<DriverWaybillCardVO> currentTask() {
|
public R<DriverWaybillCardVO> currentTask() {
|
||||||
return R.data(driverWaybillService.currentTask());
|
return R.data(driverWaybillService.currentTask());
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/pending-preview")
|
@GetMapping("/pending-preview")
|
||||||
@ApiOperationSupport(order = 2)
|
@ApiOperationSupport(order = 2)
|
||||||
@Operation(summary = "首页:待接运单预览", description = "当前司机绑定车牌下 businessStatus=pending 的预览列表与总数")
|
@Operation(summary = "首页:待接运单预览", description = "当前用户手机号匹配运单 driverPhone 且 businessStatus=pending 的预览列表与总数")
|
||||||
public R<DriverWaybillPreviewVO> pendingPreview(
|
public R<DriverWaybillPreviewVO> pendingPreview(
|
||||||
@Parameter(description = "预览条数,默认 2") @RequestParam(required = false) Integer size) {
|
@Parameter(description = "预览条数,默认 2") @RequestParam(required = false) Integer size) {
|
||||||
return R.data(driverWaybillService.pendingPreview(size));
|
return R.data(driverWaybillService.pendingPreview(size));
|
||||||
@@ -79,14 +80,14 @@ public class DriverWaybillController extends BladeController {
|
|||||||
|
|
||||||
@GetMapping("/counts")
|
@GetMapping("/counts")
|
||||||
@ApiOperationSupport(order = 3)
|
@ApiOperationSupport(order = 3)
|
||||||
@Operation(summary = "运单 Tab 统计", description = "仅统计当前司机绑定车牌对应的运单:全部 / 待接单 / 进行中 / 已完成")
|
@Operation(summary = "运单 Tab 统计", description = "仅统计当前用户手机号匹配运单 driverPhone 的运单:全部 / 待接单 / 进行中 / 已完成")
|
||||||
public R<DriverWaybillTabCountsVO> counts() {
|
public R<DriverWaybillTabCountsVO> counts() {
|
||||||
return R.data(driverWaybillService.tabCounts());
|
return R.data(driverWaybillService.tabCounts());
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/page")
|
@GetMapping("/page")
|
||||||
@ApiOperationSupport(order = 4)
|
@ApiOperationSupport(order = 4)
|
||||||
@Operation(summary = "运单分页列表", description = "仅返回当前司机绑定车牌(driving_vehicle)匹配运单 vehicleNo/trailerVehicleNo 的数据;status:空=全部,0待接单,1运输中,2已完成")
|
@Operation(summary = "运单分页列表", description = "仅返回运单 driverPhone 等于当前用户手机号的数据;status:空=全部,0待接单,1运输中,2已完成")
|
||||||
public R<IPage<DriverWaybillCardVO>> page(
|
public R<IPage<DriverWaybillCardVO>> page(
|
||||||
@Parameter(description = "当前页") @RequestParam(required = false) Integer current,
|
@Parameter(description = "当前页") @RequestParam(required = false) Integer current,
|
||||||
@Parameter(description = "每页条数") @RequestParam(required = false) Integer size,
|
@Parameter(description = "每页条数") @RequestParam(required = false) Integer size,
|
||||||
@@ -104,6 +105,13 @@ public class DriverWaybillController extends BladeController {
|
|||||||
return R.data(driverWaybillService.detail(id));
|
return R.data(driverWaybillService.detail(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/track")
|
||||||
|
@ApiOperationSupport(order = 11)
|
||||||
|
@Operation(summary = "司机端行驶轨迹", description = "按当前用户手机号校验归属后,根据打卡记录/计划时间自动推导起止日期并查询 LBS 轨迹(WGS-84)")
|
||||||
|
public R<WaybillTrackVO> track(@Parameter(description = "运单ID", required = true) @RequestParam Long id) {
|
||||||
|
return R.data(driverWaybillService.track(id));
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/accept")
|
@PostMapping("/accept")
|
||||||
@ApiOperationSupport(order = 6)
|
@ApiOperationSupport(order = 6)
|
||||||
@Operation(summary = "司机确认接单", description = "过程配置接单为「是」时,司机确认接单后运单进入进行中")
|
@Operation(summary = "司机确认接单", description = "过程配置接单为「是」时,司机确认接单后运单进入进行中")
|
||||||
|
|||||||
+12
@@ -30,11 +30,14 @@ import lombok.AllArgsConstructor;
|
|||||||
import org.springblade.core.boot.ctrl.BladeController;
|
import org.springblade.core.boot.ctrl.BladeController;
|
||||||
import org.springblade.core.mp.support.Condition;
|
import org.springblade.core.mp.support.Condition;
|
||||||
import org.springblade.core.mp.support.Query;
|
import org.springblade.core.mp.support.Query;
|
||||||
|
import org.springblade.core.secure.annotation.PreAuth;
|
||||||
import org.springblade.core.tool.api.R;
|
import org.springblade.core.tool.api.R;
|
||||||
import org.springblade.transport.pojo.dto.ExceptionDisposalFollowRequest;
|
import org.springblade.transport.pojo.dto.ExceptionDisposalFollowRequest;
|
||||||
|
import org.springblade.transport.pojo.dto.ExceptionDisposalUpdateRequest;
|
||||||
import org.springblade.transport.pojo.entity.ExceptionDisposal;
|
import org.springblade.transport.pojo.entity.ExceptionDisposal;
|
||||||
import org.springblade.transport.pojo.vo.ExceptionDisposalVO;
|
import org.springblade.transport.pojo.vo.ExceptionDisposalVO;
|
||||||
import org.springblade.transport.service.IExceptionDisposalService;
|
import org.springblade.transport.service.IExceptionDisposalService;
|
||||||
|
import org.springblade.transport.support.ExceptionDisposalPermissions;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
@@ -102,4 +105,13 @@ public class ExceptionDisposalController extends BladeController {
|
|||||||
return R.success("批量完成成功");
|
return R.success("批量完成成功");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/update")
|
||||||
|
@PreAuth(ExceptionDisposalPermissions.EDIT)
|
||||||
|
@ApiOperationSupport(order = 7)
|
||||||
|
@Operation(summary = "编辑异常处置", description = "运管员订正上报内容;不改动处置状态")
|
||||||
|
public R update(@RequestBody ExceptionDisposalUpdateRequest request) {
|
||||||
|
exceptionDisposalService.update(request);
|
||||||
|
return R.success("编辑成功");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-1
@@ -30,6 +30,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
|||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import org.springblade.core.boot.ctrl.BladeController;
|
import org.springblade.core.boot.ctrl.BladeController;
|
||||||
import org.springblade.core.tool.api.R;
|
import org.springblade.core.tool.api.R;
|
||||||
|
import org.springblade.transport.pojo.dto.WaybillPunchRecordSaveRequest;
|
||||||
import org.springblade.transport.pojo.entity.Waybill;
|
import org.springblade.transport.pojo.entity.Waybill;
|
||||||
import org.springblade.transport.pojo.vo.AdminDriverOptionVO;
|
import org.springblade.transport.pojo.vo.AdminDriverOptionVO;
|
||||||
import org.springblade.transport.pojo.vo.AdminHomeStatsVO;
|
import org.springblade.transport.pojo.vo.AdminHomeStatsVO;
|
||||||
@@ -37,7 +38,9 @@ import org.springblade.transport.pojo.vo.AdminHomeVO;
|
|||||||
import org.springblade.transport.pojo.vo.AdminVehicleOptionVO;
|
import org.springblade.transport.pojo.vo.AdminVehicleOptionVO;
|
||||||
import org.springblade.transport.pojo.vo.AdminWaybillCardVO;
|
import org.springblade.transport.pojo.vo.AdminWaybillCardVO;
|
||||||
import org.springblade.transport.pojo.vo.AdminWaybillDetailVO;
|
import org.springblade.transport.pojo.vo.AdminWaybillDetailVO;
|
||||||
|
import org.springblade.transport.pojo.vo.WaybillPunchRecordsVO;
|
||||||
import org.springblade.transport.service.IManageWaybillService;
|
import org.springblade.transport.service.IManageWaybillService;
|
||||||
|
import org.springblade.transport.service.IWaybillService;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
@@ -62,6 +65,7 @@ import java.util.List;
|
|||||||
public class ManageWaybillController extends BladeController {
|
public class ManageWaybillController extends BladeController {
|
||||||
|
|
||||||
private final IManageWaybillService manageWaybillService;
|
private final IManageWaybillService manageWaybillService;
|
||||||
|
private final IWaybillService waybillService;
|
||||||
|
|
||||||
@GetMapping("/stats")
|
@GetMapping("/stats")
|
||||||
@ApiOperationSupport(order = 1)
|
@ApiOperationSupport(order = 1)
|
||||||
@@ -72,7 +76,7 @@ public class ManageWaybillController extends BladeController {
|
|||||||
|
|
||||||
@GetMapping("/home")
|
@GetMapping("/home")
|
||||||
@ApiOperationSupport(order = 2)
|
@ApiOperationSupport(order = 2)
|
||||||
@Operation(summary = "首页聚合", description = "统计 + 异常/风险角标 + 待处理事项(异常处置≠已完成)+ 当前用户名")
|
@Operation(summary = "首页聚合", description = "统计 + 异常/风险/重新派单角标 + 待处理事项(异常处置≠已完成,或司机已拒单待重新派单)+ 当前用户名")
|
||||||
public R<AdminHomeVO> home() {
|
public R<AdminHomeVO> home() {
|
||||||
return R.data(manageWaybillService.home());
|
return R.data(manageWaybillService.home());
|
||||||
}
|
}
|
||||||
@@ -140,4 +144,19 @@ public class ManageWaybillController extends BladeController {
|
|||||||
return R.data(manageWaybillService.searchVehicles(keyword));
|
return R.data(manageWaybillService.searchVehicles(keyword));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/punch-records")
|
||||||
|
@ApiOperationSupport(order = 9)
|
||||||
|
@Operation(summary = "打卡记录", description = "小程序调度端:仅需登录态;按过程配置返回节点/在途打卡流水")
|
||||||
|
public R<WaybillPunchRecordsVO> punchRecords(
|
||||||
|
@Parameter(description = "运单ID", required = true) @RequestParam Long waybillId) {
|
||||||
|
return R.data(waybillService.listPunchRecords(waybillId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/punch-record/save")
|
||||||
|
@ApiOperationSupport(order = 10)
|
||||||
|
@Operation(summary = "编辑打卡记录", description = "小程序调度端:补录/订正/取消打卡,仅需登录态")
|
||||||
|
public R savePunchRecord(@RequestBody WaybillPunchRecordSaveRequest request) {
|
||||||
|
return R.status(waybillService.savePunchRecord(request));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-1
@@ -35,6 +35,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
|||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springblade.core.boot.ctrl.BladeController;
|
import org.springblade.core.boot.ctrl.BladeController;
|
||||||
import org.springblade.core.excel.util.ExcelUtil;
|
import org.springblade.core.excel.util.ExcelUtil;
|
||||||
import org.springblade.core.mp.support.Condition;
|
import org.springblade.core.mp.support.Condition;
|
||||||
@@ -67,6 +68,7 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
|
@Slf4j
|
||||||
@PreAuth(menu = "transport_vehicle")
|
@PreAuth(menu = "transport_vehicle")
|
||||||
@RequestMapping("/transport-vehicle")
|
@RequestMapping("/transport-vehicle")
|
||||||
@Tag(name = "车辆管理", description = "车辆管理")
|
@Tag(name = "车辆管理", description = "车辆管理")
|
||||||
@@ -132,11 +134,27 @@ public class TransportVehicleController extends BladeController {
|
|||||||
return R.status(transportVehicleService.changeStatus(id, status));
|
return R.status(transportVehicleService.changeStatus(id, status));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审核车辆认证(已弃用)
|
||||||
|
*
|
||||||
|
* @deprecated 本接口作用于**车辆行**并原地覆盖认证状态,在「生效值 / 待审值分离」
|
||||||
|
* 之后语义已不成立:一次审核应对应**一条认证记录**,且通过才建立生效快照、
|
||||||
|
* 驳回不得影响既得认证。请改用
|
||||||
|
* {@code POST /blade-transport/certification-audit/audit}。
|
||||||
|
* <p>
|
||||||
|
* 保留而非删除,是因为它可能仍被小程序历史版本或其它系统调用;
|
||||||
|
* 调用时记录告警日志。它只写展示缓存,**不再影响可用性判定**
|
||||||
|
* (可用性只看 {@code effective_cert_record_id})。
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
@PostMapping("/audit-certification")
|
@PostMapping("/audit-certification")
|
||||||
@ApiOperationSupport(order = 6)
|
@ApiOperationSupport(order = 6)
|
||||||
@Operation(summary = "审核车辆认证")
|
@Operation(summary = "审核车辆认证(已弃用)",
|
||||||
|
description = "已弃用:作用于车辆行且原地覆盖。请改用 /blade-transport/certification-audit/audit")
|
||||||
public R auditCertification(@RequestParam Long id, @RequestParam Integer certificationStatus,
|
public R auditCertification(@RequestParam Long id, @RequestParam Integer certificationStatus,
|
||||||
@RequestParam(required = false) String rejectReason) {
|
@RequestParam(required = false) String rejectReason) {
|
||||||
|
log.warn("[认证审核] 调用了已弃用接口 audit-certification,vehicleId={}, status={};"
|
||||||
|
+ "请改用 /blade-transport/certification-audit/audit", id, certificationStatus);
|
||||||
return R.status(transportVehicleService.auditCertification(id, certificationStatus, rejectReason));
|
return R.status(transportVehicleService.auditCertification(id, certificationStatus, rejectReason));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+7
-1
@@ -29,10 +29,16 @@ import org.springframework.web.bind.annotation.RestController;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对外路径:{@code /blade-transport/voucher-manage/**}
|
||||||
|
* <p>
|
||||||
|
* 网关 StripPrefix 后落入 {@code /voucher-manage/**};
|
||||||
|
* 同时兼容未去前缀直连 {@code /blade-transport/voucher-manage/**},避免 404 落到静态资源。
|
||||||
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@PreAuth(menu = "voucher_manage")
|
@PreAuth(menu = "voucher_manage")
|
||||||
@RequestMapping("/voucher-manage")
|
@RequestMapping({"/voucher-manage", "/blade-transport/voucher-manage"})
|
||||||
@Tag(name = "凭证管理", description = "执行凭证批次管理")
|
@Tag(name = "凭证管理", description = "执行凭证批次管理")
|
||||||
public class VoucherManageController extends BladeController {
|
public class VoucherManageController extends BladeController {
|
||||||
private final IVoucherManageService voucherManageService;
|
private final IVoucherManageService voucherManageService;
|
||||||
|
|||||||
+34
-2
@@ -23,6 +23,7 @@
|
|||||||
package org.springblade.transport.controller;
|
package org.springblade.transport.controller;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
|
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
|
||||||
import cn.idev.excel.write.handler.SheetWriteHandler;
|
import cn.idev.excel.write.handler.SheetWriteHandler;
|
||||||
import cn.idev.excel.write.metadata.holder.WriteSheetHolder;
|
import cn.idev.excel.write.metadata.holder.WriteSheetHolder;
|
||||||
@@ -43,6 +44,7 @@ import org.springblade.core.secure.annotation.PreAuth;
|
|||||||
import org.springblade.core.tool.api.R;
|
import org.springblade.core.tool.api.R;
|
||||||
import org.springblade.core.tool.utils.DateUtil;
|
import org.springblade.core.tool.utils.DateUtil;
|
||||||
import org.springblade.core.tool.utils.Func;
|
import org.springblade.core.tool.utils.Func;
|
||||||
|
import org.springblade.system.cache.UserCache;
|
||||||
import org.springblade.transport.excel.WaybillExcel;
|
import org.springblade.transport.excel.WaybillExcel;
|
||||||
import org.springblade.transport.excel.WaybillImportBatchExcel;
|
import org.springblade.transport.excel.WaybillImportBatchExcel;
|
||||||
import org.springblade.transport.pojo.entity.ContractManage;
|
import org.springblade.transport.pojo.entity.ContractManage;
|
||||||
@@ -50,6 +52,7 @@ import org.springblade.transport.pojo.entity.CustomerArchive;
|
|||||||
import org.springblade.transport.pojo.entity.ProjectApply;
|
import org.springblade.transport.pojo.entity.ProjectApply;
|
||||||
import org.springblade.transport.pojo.entity.TransportPlan;
|
import org.springblade.transport.pojo.entity.TransportPlan;
|
||||||
import org.springblade.transport.pojo.entity.Waybill;
|
import org.springblade.transport.pojo.entity.Waybill;
|
||||||
|
import org.springblade.transport.pojo.entity.WaybillImportBatch;
|
||||||
import org.springblade.transport.pojo.dto.WaybillImportBatchRequest;
|
import org.springblade.transport.pojo.dto.WaybillImportBatchRequest;
|
||||||
import org.springblade.transport.pojo.vo.BusinessRemoveResultVO;
|
import org.springblade.transport.pojo.vo.BusinessRemoveResultVO;
|
||||||
import org.springblade.transport.pojo.vo.LoadingManageVO;
|
import org.springblade.transport.pojo.vo.LoadingManageVO;
|
||||||
@@ -60,6 +63,7 @@ import org.springblade.transport.pojo.vo.WaybillTrackVO;
|
|||||||
import org.springblade.transport.pojo.vo.WaybillPunchRecordsVO;
|
import org.springblade.transport.pojo.vo.WaybillPunchRecordsVO;
|
||||||
import org.springblade.transport.pojo.vo.WaybillVO;
|
import org.springblade.transport.pojo.vo.WaybillVO;
|
||||||
import org.springblade.transport.pojo.dto.WaybillMileageRequest;
|
import org.springblade.transport.pojo.dto.WaybillMileageRequest;
|
||||||
|
import org.springblade.transport.pojo.dto.WaybillPunchRecordSaveRequest;
|
||||||
import org.springblade.transport.service.IContractManageService;
|
import org.springblade.transport.service.IContractManageService;
|
||||||
import org.springblade.transport.service.ICustomerArchiveService;
|
import org.springblade.transport.service.ICustomerArchiveService;
|
||||||
import org.springblade.transport.service.IProjectApplyService;
|
import org.springblade.transport.service.IProjectApplyService;
|
||||||
@@ -76,8 +80,11 @@ import org.springframework.web.multipart.MultipartFile;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 运单管理 控制器
|
* 运单管理 控制器
|
||||||
@@ -113,6 +120,13 @@ public class WaybillController extends BladeController {
|
|||||||
return R.data(waybillService.listPunchRecords(waybillId));
|
return R.data(waybillService.listPunchRecords(waybillId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/punch-record/save")
|
||||||
|
@ApiOperationSupport(order = 2)
|
||||||
|
@Operation(summary = "编辑打卡记录", description = "按过程配置补录/修正节点或在途打卡;punched=false 表示取消打卡,清除该节点打卡流水")
|
||||||
|
public R savePunchRecord(@RequestBody WaybillPunchRecordSaveRequest request) {
|
||||||
|
return R.status(waybillService.savePunchRecord(request));
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/locate")
|
@PostMapping("/locate")
|
||||||
@ApiOperationSupport(order = 1)
|
@ApiOperationSupport(order = 1)
|
||||||
@Operation(summary = "车辆实时定位", description = "按运单绑定车牌调用 LBS_LOCATE")
|
@Operation(summary = "车辆实时定位", description = "按运单绑定车牌调用 LBS_LOCATE")
|
||||||
@@ -151,10 +165,22 @@ public class WaybillController extends BladeController {
|
|||||||
.map(contract -> option(contract.getId(), "contractName", contract.getContractName(), "projectId", contract.getProjectId()))
|
.map(contract -> option(contract.getId(), "contractName", contract.getContractName(), "projectId", contract.getProjectId()))
|
||||||
.toList());
|
.toList());
|
||||||
options.put("carriers", customerArchiveService.list().stream()
|
options.put("carriers", customerArchiveService.list().stream()
|
||||||
.filter(customer -> "承运商".equals(customer.getCustomerType()) && Integer.valueOf(1).equals(customer.getStatus()))
|
.filter(customer -> isCarrierCustomer(customer) && Integer.valueOf(1).equals(customer.getStatus()))
|
||||||
.map(customer -> option(customer.getId(), "name", customer.getFullName(), null, null))
|
.map(customer -> option(customer.getId(), "name", customer.getFullName(), null, null))
|
||||||
.toList());
|
.toList());
|
||||||
options.put("creators", new ArrayList<>());
|
// 创建人选项取已有导入批次的创建人,保证筛选项与列表数据可对应。
|
||||||
|
Set<Long> creatorIds = new LinkedHashSet<>();
|
||||||
|
waybillImportBatchService.list(Wrappers.<WaybillImportBatch>lambdaQuery()
|
||||||
|
.select(WaybillImportBatch::getCreateUser)
|
||||||
|
.eq(WaybillImportBatch::getIsDeleted, 0)
|
||||||
|
.isNotNull(WaybillImportBatch::getCreateUser))
|
||||||
|
.stream()
|
||||||
|
.map(WaybillImportBatch::getCreateUser)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.forEach(creatorIds::add);
|
||||||
|
options.put("creators", creatorIds.stream()
|
||||||
|
.map(userId -> option(userId, "name", UserCache.getUserRealName(userId), null, null))
|
||||||
|
.toList());
|
||||||
options.put("plans", transportPlanService.list().stream()
|
options.put("plans", transportPlanService.list().stream()
|
||||||
.map(plan -> option(plan.getId(), "planName", plan.getPlanName(), "projectId", plan.getProjectId()))
|
.map(plan -> option(plan.getId(), "planName", plan.getPlanName(), "projectId", plan.getProjectId()))
|
||||||
.toList());
|
.toList());
|
||||||
@@ -364,6 +390,12 @@ public class WaybillController extends BladeController {
|
|||||||
return option;
|
return option;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 客商类型支持多选(逗号分隔),包含「承运商」即视为承运商。 */
|
||||||
|
private boolean isCarrierCustomer(CustomerArchive customer) {
|
||||||
|
String type = Func.toStr(customer.getCustomerType()).replace(",", ",");
|
||||||
|
return Func.toStrList(type).stream().anyMatch("承运商"::equals);
|
||||||
|
}
|
||||||
|
|
||||||
private static final class TextColumnStyleHandler implements SheetWriteHandler {
|
private static final class TextColumnStyleHandler implements SheetWriteHandler {
|
||||||
|
|
||||||
private final int[] columnIndexes;
|
private final int[] columnIndexes;
|
||||||
|
|||||||
+8
-2
@@ -56,7 +56,7 @@ public class ContractManageExcel implements Serializable {
|
|||||||
private String contractName;
|
private String contractName;
|
||||||
@ExcelProperty("所属项目")
|
@ExcelProperty("所属项目")
|
||||||
private String projectName;
|
private String projectName;
|
||||||
@ExcelProperty("所属组织")
|
@ExcelProperty("合同负责部门")
|
||||||
private String organizationName;
|
private String organizationName;
|
||||||
@ExcelProperty("合同类别")
|
@ExcelProperty("合同类别")
|
||||||
private String contractCategory;
|
private String contractCategory;
|
||||||
@@ -76,6 +76,10 @@ public class ContractManageExcel implements Serializable {
|
|||||||
private LocalDate temporaryEndDate;
|
private LocalDate temporaryEndDate;
|
||||||
@ExcelProperty("经办人")
|
@ExcelProperty("经办人")
|
||||||
private String handlerUserName;
|
private String handlerUserName;
|
||||||
|
@ExcelProperty("签订日期")
|
||||||
|
private LocalDate signDate;
|
||||||
|
@ExcelProperty("申请日期")
|
||||||
|
private LocalDate applyDate;
|
||||||
@ExcelProperty("合同阶段")
|
@ExcelProperty("合同阶段")
|
||||||
private String contractStage;
|
private String contractStage;
|
||||||
@ExcelProperty("审核状态")
|
@ExcelProperty("审核状态")
|
||||||
@@ -105,7 +109,9 @@ public class ContractManageExcel implements Serializable {
|
|||||||
private String currentNode;
|
private String currentNode;
|
||||||
@ExcelProperty("当前处理人")
|
@ExcelProperty("当前处理人")
|
||||||
private String currentProcessor;
|
private String currentProcessor;
|
||||||
@ExcelProperty("备注")
|
@ExcelProperty("合同主要内容")
|
||||||
|
private String mainContent;
|
||||||
|
@ExcelProperty("其他事项说明")
|
||||||
private String remark;
|
private String remark;
|
||||||
@ExcelProperty("创建人")
|
@ExcelProperty("创建人")
|
||||||
private String createUserName;
|
private String createUserName;
|
||||||
|
|||||||
+13
-1
@@ -46,6 +46,12 @@
|
|||||||
<result column="emergency_contact_name" property="emergencyContactName"/>
|
<result column="emergency_contact_name" property="emergencyContactName"/>
|
||||||
<result column="emergency_contact_mobile" property="emergencyContactMobile"/>
|
<result column="emergency_contact_mobile" property="emergencyContactMobile"/>
|
||||||
<result column="remark" property="remark"/>
|
<result column="remark" property="remark"/>
|
||||||
|
<result column="id_card_valid_start" property="idCardValidStart"/>
|
||||||
|
<result column="id_card_valid_end" property="idCardValidEnd"/>
|
||||||
|
<result column="id_card_long_term" property="idCardLongTerm"/>
|
||||||
|
<result column="certification_status" property="certificationStatus"/>
|
||||||
|
<result column="certification_reject_reason" property="certificationRejectReason"/>
|
||||||
|
<result column="effective_cert_record_id" property="effectiveCertRecordId"/>
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<sql id="BaseColumn">
|
<sql id="BaseColumn">
|
||||||
@@ -70,6 +76,9 @@
|
|||||||
posts,
|
posts,
|
||||||
id_card_front,
|
id_card_front,
|
||||||
id_card_back,
|
id_card_back,
|
||||||
|
id_card_valid_start,
|
||||||
|
id_card_valid_end,
|
||||||
|
id_card_long_term,
|
||||||
head_photo,
|
head_photo,
|
||||||
driving_type,
|
driving_type,
|
||||||
driving_license_no,
|
driving_license_no,
|
||||||
@@ -91,7 +100,10 @@
|
|||||||
organization_name,
|
organization_name,
|
||||||
emergency_contact_name,
|
emergency_contact_name,
|
||||||
emergency_contact_mobile,
|
emergency_contact_mobile,
|
||||||
remark
|
remark,
|
||||||
|
certification_status,
|
||||||
|
certification_reject_reason,
|
||||||
|
effective_cert_record_id
|
||||||
</sql>
|
</sql>
|
||||||
|
|
||||||
<select id="selectByIdCardNoIncludingDeleted" resultType="org.springblade.transport.pojo.entity.Driver">
|
<select id="selectByIdCardNoIncludingDeleted" resultType="org.springblade.transport.pojo.entity.Driver">
|
||||||
|
|||||||
+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>
|
||||||
|
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||||
|
* not liable for any claims arising from secondary or illegal development.
|
||||||
|
* <p>
|
||||||
|
* Author: Chill Zhuang (bladejava@qq.com)
|
||||||
|
*/
|
||||||
|
package org.springblade.transport.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import org.springblade.transport.pojo.entity.TransportCertRecord;
|
||||||
|
import org.springblade.transport.pojo.vo.TransportCertRecordVO;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 认证记录 Mapper 接口
|
||||||
|
*
|
||||||
|
* @author Chill
|
||||||
|
*/
|
||||||
|
public interface TransportCertRecordMapper extends BaseMapper<TransportCertRecord> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审核列表分页:认证记录 + 该记录的司机姓名与手机号
|
||||||
|
* <p>
|
||||||
|
* 必须走自定义 SQL 而不是单表 Wrapper:司机姓名与手机号取的是司机档案的**当前**值,
|
||||||
|
* 且「按司机姓名/手机号筛选」必须发生在**分页之前**——若在 Java 层补字段再过滤,
|
||||||
|
* 就变成「先分页再过滤」,每页条数与总数都不准。
|
||||||
|
*
|
||||||
|
* @param page 分页参数
|
||||||
|
* @param bizType 认证对象类型:driver-司机,vehicle-车辆
|
||||||
|
* @param status 审核状态:0审核中 1已通过 2已驳回 3已作废
|
||||||
|
* @param bizKey 展示键模糊匹配(提交时的司机姓名 / 车牌号),可为空
|
||||||
|
* @param driverName 司机姓名模糊匹配(当前姓名,并兼顾提交时的旧名),可为空
|
||||||
|
* @param driverMobile 司机手机号模糊匹配,可为空
|
||||||
|
* @return 认证记录列表
|
||||||
|
*/
|
||||||
|
List<TransportCertRecordVO> selectAuditPage(IPage<TransportCertRecordVO> page,
|
||||||
|
@Param("bizType") String bizType,
|
||||||
|
@Param("status") Integer status,
|
||||||
|
@Param("bizKey") String bizKey,
|
||||||
|
@Param("driverName") String driverName,
|
||||||
|
@Param("driverMobile") String driverMobile);
|
||||||
|
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user