✨ 小程序用户支持组织与数据权限,调度端按项目业务部门收口运单

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-22 10:59:21 +08:00
parent 167cea020c
commit 83b2144407
24 changed files with 717 additions and 36 deletions
@@ -56,6 +56,9 @@ public class TripQueryVO implements Serializable {
@Schema(description = "可见组织 ID 集合:非空时只出这些组织的运单行,空集合表示不按组织过滤")
private List<Long> deptIds;
@Schema(description = "可见项目 ID 集合:非空时只出这些项目的运单行,空集合表示不按项目过滤(小程序调度端优先用此口径)")
private List<Long> projectIds;
@Schema(description = "状态:0待接单/1进行中/2已完成/3已取消,不传为全部(不含已取消)")
private Integer status;
@@ -26,6 +26,7 @@ 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.springblade.system.pojo.vo.MpUserDataScopeVO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@@ -53,6 +54,7 @@ public interface IMpUserClient {
String UPDATE_USER = API_PREFIX + "/update-user";
String BIND_OPENID = API_PREFIX + "/bind-openid";
String SET_PASSWORD = API_PREFIX + "/set-password";
String RESOLVE_DATA_SCOPE = API_PREFIX + "/resolve-data-scope";
/**
* 按主键查询
@@ -60,6 +62,12 @@ public interface IMpUserClient {
@GetMapping(USER_BY_ID)
R<MpUser> userById(@RequestParam("userId") Long userId);
/**
* 解析小程序用户可见组织(所属组织 + 数据权限范围 + 数据层级范围)
*/
@GetMapping(RESOLVE_DATA_SCOPE)
R<MpUserDataScopeVO> resolveDataScope(@RequestParam("userId") Long userId);
/**
* 按租户 + 手机号查询
*/
@@ -25,6 +25,7 @@
*/
package org.springblade.system.pojo.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -35,6 +36,7 @@ import org.springblade.core.tool.sensitive.SensitiveType;
import java.io.Serial;
import java.util.Date;
import java.util.List;
/**
* 小程序用户实体(仅用于小程序登录,与 blade_user 分离)
@@ -113,10 +115,26 @@ public class MpUser extends TenantEntity {
@Schema(description = "小程序身份")
private String identities;
/**
* 部门id
* 部门id(多选,逗号分隔)
*/
@Schema(description = "部门id")
private String deptId;
/**
* 数据权限范围:1-仅所属组织,2-全部组织,3-自定义
*/
@Schema(description = "数据权限范围")
private Integer dataScopeRange;
/**
* 数据层级范围:1-包含下级,2-仅本级
*/
@Schema(description = "数据层级范围")
private Integer dataLevelRange;
/**
* 自定义数据权限组织ID列表(dataScopeRange=3 时生效)
*/
@TableField(exist = false)
@Schema(description = "自定义数据权限组织ID")
private List<Long> dataScopeDeptIds;
/**
* 岗位id
*/
@@ -0,0 +1,44 @@
/**
* 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>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.system.pojo.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 小程序用户组织数据权限
*
* @author Chill
*/
@Data
@TableName("blade_mp_user_data_scope")
@Schema(description = "小程序用户组织数据权限")
public class MpUserDataScope implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@TableId(type = IdType.ASSIGN_ID)
@Schema(description = "主键")
private Long id;
@Schema(description = "小程序用户主键")
private Long userId;
@Schema(description = "组织主键")
private Long deptId;
}
@@ -0,0 +1,91 @@
/**
* 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.vo;
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>
* allDept=true 表示全部组织(查询不过滤);否则按 deptIds 收口,空列表表示无可见组织。
*
* @author Chill
*/
@Data
@Schema(description = "小程序用户数据权限")
public class MpUserDataScopeVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 是否全部组织(不过滤)
*/
@Schema(description = "是否全部组织")
private boolean allDept;
/**
* 可见组织 ID;allDept=false 时生效,空表示无权限
*/
@Schema(description = "可见组织ID")
private List<Long> deptIds = new ArrayList<>();
/**
* 全部组织
*/
public static MpUserDataScopeVO all() {
MpUserDataScopeVO scope = new MpUserDataScopeVO();
scope.setAllDept(true);
return scope;
}
/**
* 无可见组织
*/
public static MpUserDataScopeVO empty() {
MpUserDataScopeVO scope = new MpUserDataScopeVO();
scope.setAllDept(false);
scope.setDeptIds(new ArrayList<>());
return scope;
}
/**
* 指定可见组织
*/
public static MpUserDataScopeVO of(List<Long> deptIds) {
MpUserDataScopeVO scope = new MpUserDataScopeVO();
scope.setAllDept(false);
scope.setDeptIds(deptIds == null ? new ArrayList<>() : new ArrayList<>(deptIds));
return scope;
}
}
@@ -42,6 +42,13 @@ public class MpUserVO extends MpUser {
@Serial
private static final long serialVersionUID = 1L;
/**
* 所属组织名称(逗号分隔)
*/
@TableField(exist = false)
@Schema(description = "所属组织名称")
private String deptName;
/**
* 身份名称(逗号分隔中文)
*/
@@ -71,7 +71,8 @@ public class MpUserController extends BladeController {
@ApiOperationSupport(order = 1)
@Operation(summary = "详情", description = "传入id")
public R<MpUserVO> detail(MpUser mpUser) {
MpUser detail = mpUserService.getOne(Condition.getQueryWrapper(mpUser));
Long id = mpUser == null ? null : mpUser.getId();
MpUser detail = id == null ? null : mpUserService.detail(id);
if (detail == null) {
return R.fail("小程序用户不存在");
}
@@ -28,10 +28,12 @@ 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.pojo.vo.MpUserDataScopeVO;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
@@ -53,6 +55,12 @@ public class MpUserClient implements IMpUserClient {
return R.data(mpUserService.getById(userId));
}
@Override
@GetMapping(RESOLVE_DATA_SCOPE)
public R<MpUserDataScopeVO> resolveDataScope(@RequestParam("userId") Long userId) {
return R.data(mpUserService.resolveDataScope(userId));
}
@Override
@GetMapping(USER_BY_PHONE)
public R<MpUser> userByPhone(String tenantId, String phone) {
@@ -0,0 +1,21 @@
/**
* 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>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springblade.system.pojo.entity.MpUserDataScope;
/**
* 小程序用户组织数据权限 Mapper
*
* @author Chill
*/
public interface MpUserDataScopeMapper extends BaseMapper<MpUserDataScope> {
}
@@ -26,6 +26,7 @@ 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 org.springblade.system.pojo.vo.MpUserDataScopeVO;
import java.util.List;
import java.util.Map;
@@ -67,6 +68,18 @@ public interface IMpUserService extends BaseService<MpUser> {
*/
IPage<MpUser> selectMpUserPage(IPage<MpUser> page, MpUser query);
/**
* 详情(含自定义数据权限组织)
*/
MpUser detail(Long id);
/**
* 解析小程序用户可见组织(所属组织 + 数据权限范围 + 数据层级范围)
* <p>
* allDept=true 表示全部组织;否则 deptIds 为可见组织,空列表表示无权限。
*/
MpUserDataScopeVO resolveDataScope(Long userId);
/**
* 保存小程序用户
*/
@@ -35,15 +35,20 @@ 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.cache.SysCache;
import org.springblade.system.mapper.MpUserDataScopeMapper;
import org.springblade.system.mapper.MpUserMapper;
import org.springblade.system.pojo.entity.MpUser;
import org.springblade.system.pojo.entity.MpUserDataScope;
import org.springblade.system.pojo.entity.MpUserInfo;
import org.springblade.system.pojo.enums.MpUserIdentity;
import org.springblade.system.pojo.vo.MpUserDataScopeVO;
import org.springblade.system.service.IMpUserService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -57,6 +62,19 @@ import java.util.Objects;
@RequiredArgsConstructor
public class MpUserServiceImpl extends BaseServiceImpl<MpUserMapper, MpUser> implements IMpUserService {
/** 数据权限范围:仅所属组织 */
private static final int DATA_SCOPE_OWN = 1;
/** 数据权限范围:全部组织 */
private static final int DATA_SCOPE_ALL = 2;
/** 数据权限范围:自定义 */
private static final int DATA_SCOPE_CUSTOM = 3;
/** 数据层级范围:包含下级 */
private static final int DATA_LEVEL_INCLUDE_CHILDREN = 1;
/** 数据层级范围:仅本级 */
private static final int DATA_LEVEL_CURRENT_ONLY = 2;
private final MpUserDataScopeMapper mpUserDataScopeMapper;
@Override
public MpUser getByPhone(String tenantId, String phone) {
if (Func.isBlank(tenantId) || Func.isBlank(phone)) {
@@ -112,6 +130,64 @@ public class MpUserServiceImpl extends BaseServiceImpl<MpUserMapper, MpUser> imp
.orderByDesc(MpUser::getCreateTime));
}
@Override
public MpUser detail(Long id) {
if (Func.isEmpty(id)) {
return null;
}
return fillScopes(getById(id));
}
@Override
public MpUserDataScopeVO resolveDataScope(Long userId) {
if (Func.isEmpty(userId) || userId <= 0) {
return MpUserDataScopeVO.empty();
}
MpUser user = getById(userId);
if (user == null || Func.isEmpty(user.getId())) {
return MpUserDataScopeVO.empty();
}
Integer dataScopeRange = user.getDataScopeRange() == null ? DATA_SCOPE_OWN : user.getDataScopeRange();
Integer dataLevelRange = user.getDataLevelRange() == null ? DATA_LEVEL_INCLUDE_CHILDREN : user.getDataLevelRange();
if (Objects.equals(dataScopeRange, DATA_SCOPE_ALL)) {
return MpUserDataScopeVO.all();
}
List<Long> baseDeptIds;
if (Objects.equals(dataScopeRange, DATA_SCOPE_CUSTOM)) {
baseDeptIds = mpUserDataScopeMapper.selectList(Wrappers.<MpUserDataScope>lambdaQuery()
.eq(MpUserDataScope::getUserId, userId))
.stream()
.map(MpUserDataScope::getDeptId)
.filter(Objects::nonNull)
.distinct()
.toList();
} else {
// 仅所属组织
baseDeptIds = Func.toLongList(user.getDeptId());
}
if (Func.isEmpty(baseDeptIds)) {
return MpUserDataScopeVO.empty();
}
boolean includeChildren = !Objects.equals(dataLevelRange, DATA_LEVEL_CURRENT_ONLY);
LinkedHashSet<Long> allowedDeptIds = new LinkedHashSet<>();
for (Long deptId : baseDeptIds) {
if (Func.isEmpty(deptId) || deptId <= 0) {
continue;
}
if (includeChildren) {
List<Long> childIds = SysCache.getDeptChildIds(deptId);
if (Func.isNotEmpty(childIds)) {
allowedDeptIds.addAll(childIds);
} else {
allowedDeptIds.add(deptId);
}
} else {
allowedDeptIds.add(deptId);
}
}
return MpUserDataScopeVO.of(List.copyOf(allowedDeptIds));
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean submit(MpUser user) {
@@ -122,7 +198,7 @@ public class MpUserServiceImpl extends BaseServiceImpl<MpUserMapper, MpUser> imp
throw new ServiceException("该手机号已存在小程序用户");
}
encryptPasswordIfPresent(user);
boolean saved = save(user);
boolean saved = save(user) && submitScopes(user);
if (saved) {
MpUserCache.clear();
}
@@ -135,7 +211,18 @@ public class MpUserServiceImpl extends BaseServiceImpl<MpUserMapper, MpUser> imp
if (user == null || Func.isEmpty(user.getId())) {
throw new ServiceException("小程序用户主键不能为空");
}
MpUser dbUser = getById(user.getId());
if (dbUser == null || Func.isEmpty(dbUser.getId())) {
throw new ServiceException("小程序用户不存在");
}
prepare(user);
// 未改手机号(前端脱敏未提交 / 仍为脱敏值)时沿用库中原值,避免把 138****8000 写回
if (Func.isBlank(user.getPhone()) || isMaskedPhone(user.getPhone())) {
user.setPhone(dbUser.getPhone());
}
if (Func.isBlank(user.getEmail()) || isMaskedEmail(user.getEmail())) {
user.setEmail(dbUser.getEmail());
}
validate(user);
MpUser existed = getByPhone(user.getTenantId(), user.getPhone());
if (existed != null && Func.isNotEmpty(existed.getId()) && !existed.getId().equals(user.getId())) {
@@ -143,7 +230,7 @@ public class MpUserServiceImpl extends BaseServiceImpl<MpUserMapper, MpUser> imp
}
// 后台编辑不改密码,避免空串覆盖
user.setPassword(null);
boolean updated = updateById(user);
boolean updated = updateById(user) && submitScopes(user);
if (updated) {
MpUserCache.clear();
}
@@ -167,6 +254,7 @@ public class MpUserServiceImpl extends BaseServiceImpl<MpUserMapper, MpUser> imp
}
boolean removed = deleteLogic(ids);
if (removed) {
mpUserDataScopeMapper.delete(Wrappers.<MpUserDataScope>lambdaQuery().in(MpUserDataScope::getUserId, ids));
MpUserCache.clear();
}
return removed;
@@ -314,6 +402,15 @@ public class MpUserServiceImpl extends BaseServiceImpl<MpUserMapper, MpUser> imp
user.setPostId(trimToNull(user.getPostId()));
user.setRemark(trimToNull(user.getRemark()));
user.setIdentities(normalizeIdentities(user.getIdentities()));
if (user.getDataScopeRange() == null) {
user.setDataScopeRange(1);
}
if (user.getDataLevelRange() == null) {
user.setDataLevelRange(1);
}
if (!Objects.equals(user.getDataScopeRange(), 3)) {
user.setDataScopeDeptIds(List.of());
}
if (Func.isBlank(user.getAccount()) && Func.isNotBlank(user.getPhone())) {
user.setAccount(user.getPhone());
}
@@ -347,6 +444,9 @@ public class MpUserServiceImpl extends BaseServiceImpl<MpUserMapper, MpUser> imp
if (Func.isBlank(user.getPhone())) {
throw new ServiceException("手机号不能为空");
}
if (Func.isBlank(user.getDeptId())) {
throw new ServiceException("请选择所属组织");
}
if (Func.isBlank(user.getIdentities())) {
throw new ServiceException("小程序身份不能为空");
}
@@ -359,6 +459,42 @@ public class MpUserServiceImpl extends BaseServiceImpl<MpUserMapper, MpUser> imp
throw new ServiceException("不支持的小程序身份:" + code);
}
}
Integer dataScopeRange = user.getDataScopeRange();
if (dataScopeRange == null || (dataScopeRange != 1 && dataScopeRange != 2 && dataScopeRange != 3)) {
throw new ServiceException("数据权限范围不正确");
}
Integer dataLevelRange = user.getDataLevelRange();
if (dataLevelRange == null || (dataLevelRange != 1 && dataLevelRange != 2)) {
throw new ServiceException("数据层级范围不正确");
}
if (Objects.equals(dataScopeRange, 3) && Func.isEmpty(user.getDataScopeDeptIds())) {
throw new ServiceException("请选择自定义数据权限组织");
}
}
private boolean submitScopes(MpUser user) {
if (user == null || Func.isEmpty(user.getId())) {
return false;
}
mpUserDataScopeMapper.delete(Wrappers.<MpUserDataScope>lambdaQuery().eq(MpUserDataScope::getUserId, user.getId()));
if (Objects.equals(user.getDataScopeRange(), 3) && Func.isNotEmpty(user.getDataScopeDeptIds())) {
user.getDataScopeDeptIds().stream().filter(Objects::nonNull).distinct().forEach(deptId -> {
MpUserDataScope scope = new MpUserDataScope();
scope.setUserId(user.getId());
scope.setDeptId(deptId);
mpUserDataScopeMapper.insert(scope);
});
}
return true;
}
private MpUser fillScopes(MpUser user) {
if (user == null || Func.isEmpty(user.getId())) {
return user;
}
user.setDataScopeDeptIds(mpUserDataScopeMapper.selectList(Wrappers.<MpUserDataScope>lambdaQuery()
.eq(MpUserDataScope::getUserId, user.getId())).stream().map(MpUserDataScope::getDeptId).toList());
return user;
}
private String normalizeIdentities(String identities) {
@@ -377,4 +513,18 @@ public class MpUserServiceImpl extends BaseServiceImpl<MpUserMapper, MpUser> imp
return trimValue.isEmpty() ? null : trimValue;
}
/**
* 判断是否为前端脱敏手机号(如 138****8000),不可直接落库。
*/
private boolean isMaskedPhone(String phone) {
return Func.isNotBlank(phone) && phone.contains("*");
}
/**
* 判断是否为前端脱敏邮箱。
*/
private boolean isMaskedEmail(String email) {
return Func.isNotBlank(email) && email.contains("*");
}
}
@@ -26,6 +26,7 @@ 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.SysCache;
import org.springblade.system.cache.UserCache;
import org.springblade.system.pojo.entity.MpUser;
import org.springblade.system.pojo.enums.DictEnum;
@@ -53,6 +54,9 @@ public class MpUserWrapper extends BaseEntityWrapper<MpUser, MpUserVO> {
// 密码哈希不下发前端
mpUserVO.setPassword(null);
mpUserVO.setIdentitiesName(resolveIdentitiesName(mpUser.getIdentities()));
if (Func.isNotBlank(mpUser.getDeptId())) {
mpUserVO.setDeptName(Func.join(SysCache.getDeptNames(mpUser.getDeptId())));
}
if (mpUser.getSex() != null) {
mpUserVO.setSexName(DictCache.getValue(DictEnum.SEX, mpUser.getSex()));
}
@@ -44,6 +44,7 @@ import org.springblade.transport.pojo.vo.ExceptionDisposalVO;
import org.springblade.transport.service.IDriverWaybillService;
import org.springblade.transport.service.IExceptionDisposalService;
import org.springblade.transport.service.IWaybillService;
import org.springblade.transport.support.MpUserDataScopeSupport;
import org.springblade.transport.support.WaybillProcessSupport;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -79,13 +80,16 @@ public class ExceptionDisposalServiceImpl
private final ExceptionDisposalFollowRecordMapper followRecordMapper;
private final IWaybillService waybillService;
private final IDriverWaybillService driverWaybillService;
private final MpUserDataScopeSupport mpUserDataScopeSupport;
public ExceptionDisposalServiceImpl(ExceptionDisposalFollowRecordMapper followRecordMapper,
IWaybillService waybillService,
IDriverWaybillService driverWaybillService) {
IDriverWaybillService driverWaybillService,
MpUserDataScopeSupport mpUserDataScopeSupport) {
this.followRecordMapper = followRecordMapper;
this.waybillService = waybillService;
this.driverWaybillService = driverWaybillService;
this.mpUserDataScopeSupport = mpUserDataScopeSupport;
}
@Override
@@ -409,6 +413,8 @@ public class ExceptionDisposalServiceImpl
.like(Func.isNotEmpty(query.getCarrierName()), ExceptionDisposal::getCarrierName, query.getCarrierName())
.eq(Func.isNotEmpty(query.getExceptionType()), ExceptionDisposal::getExceptionType, query.getExceptionType())
.eq(Func.isNotEmpty(query.getDisposalStatus()), ExceptionDisposal::getDisposalStatus, query.getDisposalStatus());
// 调度端列表按「权限组织 → 项目业务部门 → 项目」收口;司机端按运单查自己的异常时不套此过滤
mpUserDataScopeSupport.applyProjectScopeIfMpDispatcher(wrapper, ExceptionDisposal::getProjectId);
return wrapper.orderByDesc(ExceptionDisposal::getCreateTime);
}
@@ -33,6 +33,7 @@ import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.tool.utils.DateUtil;
import org.springblade.core.tool.utils.Func;
import org.springblade.system.cache.UserDisplayCache;
import org.springblade.system.utils.MpUserAuth;
import org.springblade.transport.pojo.entity.Driver;
import org.springblade.transport.pojo.entity.ExceptionDisposal;
import org.springblade.transport.pojo.entity.LoadingManage;
@@ -59,6 +60,7 @@ import org.springblade.transport.service.IManageWaybillService;
import org.springblade.transport.service.IRiskDisposalService;
import org.springblade.transport.service.ITripQueryService;
import org.springblade.transport.service.IWaybillService;
import org.springblade.transport.support.MpUserDataScopeSupport;
import org.springblade.transport.support.TransportBusinessSupport;
import org.springblade.transport.support.WaybillCargoSupport;
import org.springblade.transport.support.WaybillProcessSupport;
@@ -89,8 +91,8 @@ import java.util.stream.Collectors;
* 列表与详情与司机端同粒度:取数、状态聚合、接单需求合并都调共享运次读服务,
* 本类只做字段裁剪与调度端独有的筛选(组织类型 / 异常 / 创建时间 / 待重新派单)。
* <p>
* 小程序调度账号(如「小程序管理」)组织常与运单业务组织不一致,故不做 dept 过滤,
* 仅依赖租户隔离,口径接近后台 {@code /waybill-manage/list?allDept=1}。
* 小程序调度端:所属组织 + 数据权限范围 + 数据层级范围 → 业务部门在范围内的项目 → 再取这些项目的运单;
* 异常/风险与运单同一项目口径。系统用户仍按「所属组织 / 超管不过滤」。
*/
@Service
@RequiredArgsConstructor
@@ -137,13 +139,14 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
private final IDriverWaybillService driverWaybillService;
private final ITripQueryService tripQueryService;
private final ILoadingManageService loadingManageService;
private final MpUserDataScopeSupport mpUserDataScopeSupport;
@Override
public AdminHomeStatsVO stats() {
AdminHomeStatsVO vo = new AdminHomeStatsVO();
// 三档状态按运次计:有配载单的一趟车算一个,与列表和司机端同一份聚合
TripQueryVO query = TripQueryVO.forAdmin();
query.setDeptIds(manageableDeptIds());
applyAdminDataScope(query);
TripTabCountsVO tripCounts = tripQueryService.tabCounts(query);
vo.setPendingAccept(tripCounts.getPending());
vo.setTransporting(tripCounts.getDoing());
@@ -181,7 +184,7 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
// 组织类型 / 异常 / 创建时间都是子单行级条件,与状态、关键字、分页一起交给运次读服务,
// 一次取数取完整趟车:分组、分档、筛选、排序与分页都在它那边算完
TripQueryVO query = TripQueryVO.forAdmin();
query.setDeptIds(manageableDeptIds());
applyAdminDataScope(query);
query.setStatus(toTripStatus(status));
query.setKeyword(keyword);
query.setTransportOrgType(transportType);
@@ -229,7 +232,7 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
Set<Long> exceptionWaybillIds = loadIncompleteExceptionWaybillIds();
TripQueryVO query = TripQueryVO.forAdmin();
query.setDeptIds(manageableDeptIds());
applyAdminDataScope(query);
query.setKeyword(keyword);
// 待处理只要待接单与进行中两档;「未拒单」也是交给运次读服务按子单行筛
query.setStatuses(List.of(0, 1));
@@ -667,11 +670,8 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
SQL_MAX_REJECT_TIME + " AS driver_reject_time", SQL_MAX_CREATE_TIME + " AS create_time");
wrapper.eq("driver_accept_status", ACCEPT_REJECTED);
wrapper.in("business_status", STATUS_PENDING, STATUS_RUNNING);
// 这条查询不经运次读服务,组织收口得自己带上,否则待重派列表与首页重派角标会出别的组织的单
List<Long> deptIds = manageableDeptIds();
if (Func.isNotEmpty(deptIds)) {
wrapper.in("dept_id", deptIds);
}
// 这条查询不经运次读服务,数据权限收口得自己带上,否则待重派列表与首页重派角标会越权
applyAdminDataScope(wrapper);
wrapper.groupBy(TripAggregation.SQL_TRIP_KEY);
return wrapper;
}
@@ -884,10 +884,50 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
}
/**
* 调度端可见组织:普通用户(含各级角色)只出自己所属组织,超级管理员不过滤。
* <p>
* 小程序不校验「所属组织不能为空」:账号没有组织时不按组织收口,避免首页、待处理等接口直接报错。
* 详情与重新派单本身不走这里。
* 调度端数据权限落到运次查询条件:小程序按可见项目,系统用户按所属组织。
*/
private void applyAdminDataScope(TripQueryVO query) {
if (query == null) {
return;
}
if (MpUserAuth.isMpUser()) {
query.setProjectIds(manageableProjectIds());
return;
}
query.setDeptIds(manageableDeptIds());
}
/**
* 待重派等直查运单表时的数据权限:小程序按 project_id,系统用户按 dept_id。
*/
private void applyAdminDataScope(QueryWrapper<Waybill> wrapper) {
if (wrapper == null) {
return;
}
if (MpUserAuth.isMpUser()) {
List<Long> projectIds = manageableProjectIds();
if (Func.isNotEmpty(projectIds)) {
wrapper.in("project_id", projectIds);
}
return;
}
List<Long> deptIds = manageableDeptIds();
if (Func.isNotEmpty(deptIds)) {
wrapper.in("dept_id", deptIds);
}
}
/**
* 小程序调度端可见项目:所属组织 + 数据权限范围 + 数据层级范围 → 业务部门在范围内的项目。
* 空列表不过滤;[-1] 表示无权限。
*/
private List<Long> manageableProjectIds() {
List<Long> projectIds = mpUserDataScopeSupport.resolveVisibleProjectIdsForQuery();
return projectIds == null ? List.of() : projectIds;
}
/**
* 系统用户可见组织:普通账号只出所属组织,超管不过滤;组织为空不拦截。
*/
private List<Long> manageableDeptIds() {
if (TransportBusinessSupport.isAdministrator()) {
@@ -911,10 +951,12 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
}
private Set<Long> loadIncompleteExceptionWaybillIds() {
List<ExceptionDisposal> list = exceptionDisposalService.list(Wrappers.<ExceptionDisposal>lambdaQuery()
LambdaQueryWrapper<ExceptionDisposal> wrapper = Wrappers.<ExceptionDisposal>lambdaQuery()
.select(ExceptionDisposal::getWaybillId)
.in(ExceptionDisposal::getDisposalStatus, DISPOSAL_PENDING, DISPOSAL_PROCESSING)
.isNotNull(ExceptionDisposal::getWaybillId));
.isNotNull(ExceptionDisposal::getWaybillId);
applyExceptionDeptScope(wrapper);
List<ExceptionDisposal> list = exceptionDisposalService.list(wrapper);
Set<Long> ids = new HashSet<>();
for (ExceptionDisposal item : list) {
if (item.getWaybillId() != null) {
@@ -924,6 +966,38 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
return ids;
}
/** 异常与运单同一套项目口径(小程序);系统用户仍按组织 */
private void applyExceptionDeptScope(LambdaQueryWrapper<ExceptionDisposal> wrapper) {
if (MpUserAuth.isMpUser()) {
List<Long> projectIds = manageableProjectIds();
if (Func.isNotEmpty(projectIds)) {
wrapper.in(ExceptionDisposal::getProjectId, projectIds);
}
return;
}
List<Long> deptIds = manageableDeptIds();
if (Func.isNotEmpty(deptIds)) {
wrapper.in(ExceptionDisposal::getDeptId, deptIds);
}
}
/** 风险无项目字段:小程序按关联运单的项目收口;系统用户按组织 */
private void applyRiskDeptScope(LambdaQueryWrapper<RiskDisposal> wrapper) {
if (MpUserAuth.isMpUser()) {
List<Long> projectIds = manageableProjectIds();
if (Func.isNotEmpty(projectIds)) {
String inList = projectIds.stream().map(String::valueOf).collect(Collectors.joining(","));
wrapper.inSql(RiskDisposal::getWaybillId,
"SELECT id FROM blade_waybill WHERE is_deleted = 0 AND project_id IN (" + inList + ")");
}
return;
}
List<Long> deptIds = manageableDeptIds();
if (Func.isNotEmpty(deptIds)) {
wrapper.in(RiskDisposal::getDeptId, deptIds);
}
}
private AdminWaybillCardVO toCard(Waybill waybill, boolean hasException) {
AdminWaybillCardVO card = new AdminWaybillCardVO();
card.setId(waybill.getId());
@@ -1051,13 +1125,17 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
}
private long countIncompleteExceptions() {
return exceptionDisposalService.count(Wrappers.<ExceptionDisposal>lambdaQuery()
.in(ExceptionDisposal::getDisposalStatus, DISPOSAL_PENDING, DISPOSAL_PROCESSING));
LambdaQueryWrapper<ExceptionDisposal> wrapper = Wrappers.<ExceptionDisposal>lambdaQuery()
.in(ExceptionDisposal::getDisposalStatus, DISPOSAL_PENDING, DISPOSAL_PROCESSING);
applyExceptionDeptScope(wrapper);
return exceptionDisposalService.count(wrapper);
}
private long countPendingRisks() {
return riskDisposalService.count(Wrappers.<RiskDisposal>lambdaQuery()
.eq(RiskDisposal::getDisposalStatus, RISK_PENDING));
LambdaQueryWrapper<RiskDisposal> wrapper = Wrappers.<RiskDisposal>lambdaQuery()
.eq(RiskDisposal::getDisposalStatus, RISK_PENDING);
applyRiskDeptScope(wrapper);
return riskDisposalService.count(wrapper);
}
/**
@@ -1099,10 +1177,12 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
}
private List<TimedTodo> buildExceptionFeed() {
List<ExceptionDisposal> list = exceptionDisposalService.list(Wrappers.<ExceptionDisposal>lambdaQuery()
LambdaQueryWrapper<ExceptionDisposal> wrapper = Wrappers.<ExceptionDisposal>lambdaQuery()
.in(ExceptionDisposal::getDisposalStatus, DISPOSAL_PENDING, DISPOSAL_PROCESSING)
.orderByDesc(ExceptionDisposal::getReportTime)
.last("LIMIT " + FEED_LIMIT));
.last("LIMIT " + FEED_LIMIT);
applyExceptionDeptScope(wrapper);
List<ExceptionDisposal> list = exceptionDisposalService.list(wrapper);
return list.stream()
.map(disposal -> new TimedTodo(toTodoItem(disposal), exceptionTimeOf(disposal)))
.collect(Collectors.toList());
@@ -3,9 +3,9 @@ package org.springblade.transport.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
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.utils.BeanUtil;
import org.springblade.core.tool.utils.Func;
import org.springblade.system.cache.UserCache;
@@ -16,6 +16,7 @@ import org.springblade.transport.pojo.entity.RiskDisposal;
import org.springblade.transport.pojo.vo.RiskDisposalBatchResultVO;
import org.springblade.transport.pojo.vo.RiskDisposalVO;
import org.springblade.transport.service.IRiskDisposalService;
import org.springblade.transport.support.MpUserDataScopeSupport;
import org.springblade.transport.support.TransportBusinessSupport;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -25,12 +26,15 @@ import java.util.List;
import java.util.Objects;
@Service
@RequiredArgsConstructor
public class RiskDisposalServiceImpl extends BaseServiceImpl<RiskDisposalMapper, RiskDisposal> implements IRiskDisposalService {
private static final String STATUS_PENDING = "pending";
private static final String STATUS_PROCESSED = "processed";
private static final String STATUS_IGNORED = "ignored";
private final MpUserDataScopeSupport mpUserDataScopeSupport;
@Override
public IPage<RiskDisposalVO> selectPage(IPage<RiskDisposal> page, RiskDisposalVO query) {
IPage<RiskDisposal> entityPage = page(page, buildQuery(query));
@@ -84,15 +88,16 @@ public class RiskDisposalServiceImpl extends BaseServiceImpl<RiskDisposalMapper,
}
private LambdaQueryWrapper<RiskDisposal> buildQuery(RiskDisposalVO query) {
return Wrappers.<RiskDisposal>lambdaQuery()
LambdaQueryWrapper<RiskDisposal> wrapper = Wrappers.<RiskDisposal>lambdaQuery()
.eq(RiskDisposal::getIsDeleted, 0)
.like(Func.isNotEmpty(query.getRiskNo()), RiskDisposal::getRiskNo, query.getRiskNo())
.like(Func.isNotEmpty(query.getRuleName()), RiskDisposal::getRuleName, query.getRuleName())
.like(Func.isNotEmpty(query.getWaybillNo()), RiskDisposal::getWaybillNo, query.getWaybillNo())
.like(Func.isNotEmpty(query.getProjectName()), RiskDisposal::getProjectName, query.getProjectName())
.eq(Func.isNotEmpty(query.getRiskLevel()), RiskDisposal::getRiskLevel, query.getRiskLevel())
.eq(Func.isNotEmpty(query.getDisposalStatus()), RiskDisposal::getDisposalStatus, query.getDisposalStatus())
.orderByDesc(RiskDisposal::getCreateTime);
.eq(Func.isNotEmpty(query.getDisposalStatus()), RiskDisposal::getDisposalStatus, query.getDisposalStatus());
mpUserDataScopeSupport.applyWaybillProjectScopeIfMpDispatcher(wrapper, RiskDisposal::getWaybillId);
return wrapper.orderByDesc(RiskDisposal::getCreateTime);
}
private RiskDisposalVO toVO(RiskDisposal entity) {
@@ -244,6 +244,7 @@ public class TripQueryServiceImpl implements ITripQueryService {
.or().ne(Waybill::getDriverAcceptStatus, WaybillProcessSupport.ACCEPT_REJECTED));
applyScope(wrapper, condition);
applyDeptScope(wrapper, condition);
applyProjectScope(wrapper, condition);
applyOrgTypeFilter(wrapper, condition);
applyKeyword(wrapper, condition.getKeyword());
List<Waybill> rows = waybillService.list(wrapper);
@@ -264,6 +265,18 @@ public class TripQueryServiceImpl implements ITripQueryService {
wrapper.in(Waybill::getDeptId, condition.getDeptIds());
}
/**
* 项目收口:条件里给了项目 ID 集合时只出这些项目的运单行,空集合表示不过滤。
* <p>
* 小程序调度端先按用户数据权限解析可见项目(业务部门在范围内),再把项目 ID 传进来。
*/
private void applyProjectScope(LambdaQueryWrapper<Waybill> wrapper, TripQueryVO condition) {
if (Func.isEmpty(condition.getProjectIds())) {
return;
}
wrapper.in(Waybill::getProjectId, condition.getProjectIds());
}
/**
* 运输组织类型按子单行筛:它在同一趟车里必然是同一个值(配载的每张子单带同一个配载单号,
* 未配载的每张自成一趟车),所以这一层不会把一趟车拆开。
@@ -803,6 +816,7 @@ public class TripQueryServiceImpl implements ITripQueryService {
copy.setDriverPhone(source.getDriverPhone());
copy.setAllDrivers(source.isAllDrivers());
copy.setDeptIds(source.getDeptIds());
copy.setProjectIds(source.getProjectIds());
copy.setKeyword(source.getKeyword());
copy.setTransportOrgType(source.getTransportOrgType());
copy.setException(source.getException());
@@ -0,0 +1,184 @@
/**
* 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.support;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import lombok.RequiredArgsConstructor;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.Func;
import org.springblade.system.feign.IMpUserClient;
import org.springblade.system.pojo.enums.MpUserIdentity;
import org.springblade.system.pojo.vo.MpUserDataScopeVO;
import org.springblade.system.utils.MpUserAuth;
import org.springblade.transport.pojo.entity.ProjectApply;
import org.springblade.transport.service.IProjectApplyService;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* 小程序调度端数据权限。
* <p>
* 口径:所属组织 + 数据权限范围 + 数据层级范围 → 可见组织 →
* 业务部门落在可见组织内的项目 → 再按这些项目收口运单/异常/风险。
*
* @author Chill
*/
@Component
@RequiredArgsConstructor
public class MpUserDataScopeSupport {
/**
* 无权限时的占位 ID:查询 in(-1) 保证查不到数据(空列表在运次查询里表示不过滤)。
*/
private static final Long NONE_ID = -1L;
private final IMpUserClient mpUserClient;
private final IProjectApplyService projectApplyService;
/**
* 当前登录是否为小程序调度端用户(含 admin 身份)
*/
public boolean isMpDispatcher() {
return MpUserAuth.isMpUser()
&& MpUserIdentity.has(AuthUtil.getUserRole(), MpUserIdentity.ADMIN);
}
/**
* 解析当前小程序用户可见组织;非小程序用户返回 null(调用方走原逻辑)。
*/
public MpUserDataScopeVO resolveCurrentMpUserScope() {
if (!MpUserAuth.isMpUser()) {
return null;
}
return resolveByUserId(AuthUtil.getUserId());
}
/**
* 解析指定小程序用户可见组织;Feign 失败时按无权限处理。
*/
public MpUserDataScopeVO resolveByUserId(Long userId) {
if (Func.isEmpty(userId) || userId <= 0) {
return MpUserDataScopeVO.empty();
}
R<MpUserDataScopeVO> result = mpUserClient.resolveDataScope(userId);
if (result == null || !result.isSuccess() || result.getData() == null) {
return MpUserDataScopeVO.empty();
}
return result.getData();
}
/**
* 转为查询用的组织 ID 约定:空=不过滤;非空=in;无权限=[-1]。
*/
public List<Long> toQueryDeptIds(MpUserDataScopeVO scope) {
if (scope == null || scope.isAllDept()) {
return List.of();
}
if (Func.isEmpty(scope.getDeptIds())) {
return List.of(NONE_ID);
}
return scope.getDeptIds();
}
/**
* 解析当前小程序用户可见项目 ID(业务部门落在数据权限组织范围内)。
* <ul>
* <li>空列表 = 全部组织 / 不过滤</li>
* <li>非空 = 仅这些项目</li>
* <li>无权限或范围内无项目 = [-1]</li>
* </ul>
* 非小程序用户返回 null。
*/
public List<Long> resolveVisibleProjectIdsForQuery() {
MpUserDataScopeVO scope = resolveCurrentMpUserScope();
if (scope == null) {
return null;
}
return resolveProjectIds(scope);
}
/**
* 按数据权限结果解析可见项目 ID。
*/
public List<Long> resolveProjectIds(MpUserDataScopeVO scope) {
if (scope == null || scope.isAllDept()) {
return List.of();
}
List<Long> deptIds = scope.getDeptIds();
if (Func.isEmpty(deptIds)) {
return List.of(NONE_ID);
}
List<ProjectApply> projectList = projectApplyService.list(Wrappers.<ProjectApply>lambdaQuery()
.select(ProjectApply::getId)
.in(ProjectApply::getBusinessDeptId, deptIds));
if (Func.isEmpty(projectList)) {
return List.of(NONE_ID);
}
List<Long> projectIds = projectList.stream()
.map(ProjectApply::getId)
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
return Func.isEmpty(projectIds) ? List.of(NONE_ID) : projectIds;
}
/**
* 当前为小程序调度用户时,按可见项目写入 project 列;非调度用户不改动。
*/
public <T> void applyProjectScopeIfMpDispatcher(LambdaQueryWrapper<T> wrapper, SFunction<T, ?> projectColumn) {
if (!isMpDispatcher() || wrapper == null || projectColumn == null) {
return;
}
List<Long> projectIds = resolveVisibleProjectIdsForQuery();
if (Func.isEmpty(projectIds)) {
return;
}
wrapper.in(projectColumn, projectIds);
}
/**
* 风险等无 projectId 字段时:按关联运单的项目收口。
*/
public <T> void applyWaybillProjectScopeIfMpDispatcher(LambdaQueryWrapper<T> wrapper, SFunction<T, ?> waybillIdColumn) {
if (!isMpDispatcher() || wrapper == null || waybillIdColumn == null) {
return;
}
List<Long> projectIds = resolveVisibleProjectIdsForQuery();
if (Func.isEmpty(projectIds)) {
return;
}
String inList = projectIds.stream().map(String::valueOf).collect(Collectors.joining(","));
wrapper.inSql(waybillIdColumn,
"SELECT id FROM blade_waybill WHERE is_deleted = 0 AND project_id IN (" + inList + ")");
}
}
@@ -52,10 +52,13 @@ class ExceptionDisposalListQueryTest {
@BeforeEach
void setUp() throws Exception {
disposalMapper = mock(ExceptionDisposalMapper.class);
org.springblade.transport.support.MpUserDataScopeSupport scopeSupport =
mock(org.springblade.transport.support.MpUserDataScopeSupport.class);
service = new ExceptionDisposalServiceImpl(
mock(ExceptionDisposalFollowRecordMapper.class),
mock(IWaybillService.class),
mock(IDriverWaybillService.class));
mock(IDriverWaybillService.class),
scopeSupport);
inject("baseMapper", disposalMapper);
when(disposalMapper.selectPage(any(), any())).thenReturn(new Page<ExceptionDisposal>(1, 10));
}
@@ -71,7 +71,8 @@ class ExceptionDisposalReportGateTest {
waybillService = mock(IWaybillService.class);
driverWaybillService = mock(IDriverWaybillService.class);
service = new ExceptionDisposalServiceImpl(
mock(ExceptionDisposalFollowRecordMapper.class), waybillService, driverWaybillService);
mock(ExceptionDisposalFollowRecordMapper.class), waybillService, driverWaybillService,
mock(org.springblade.transport.support.MpUserDataScopeSupport.class));
inject("baseMapper", disposalMapper);
when(waybillService.getById(WAYBILL_ID)).thenReturn(waybill());
@@ -84,7 +84,8 @@ class ManageAdminHomeFeedTest {
mock(IRiskDisposalService.class),
mock(IDriverWaybillService.class),
mock(ITripQueryService.class),
mock(ILoadingManageService.class));
mock(ILoadingManageService.class),
mock(org.springblade.transport.support.MpUserDataScopeSupport.class));
when(exceptionService.list(ArgumentMatchers.<Wrapper<ExceptionDisposal>>any()))
.thenReturn(new ArrayList<>());
when(waybillService.list(ArgumentMatchers.<Wrapper<Waybill>>any()))
@@ -101,7 +101,8 @@ class ManageTripListDetailTest {
mock(IRiskDisposalService.class),
driverWaybillService,
tripQueryService,
loadingManageService);
loadingManageService,
mock(org.springblade.transport.support.MpUserDataScopeSupport.class));
when(exceptionDisposalService.list(ArgumentMatchers.<Wrapper<ExceptionDisposal>>any()))
.thenReturn(new ArrayList<>());
when(waybillService.list(ArgumentMatchers.<Wrapper<Waybill>>any())).thenReturn(new ArrayList<>());
@@ -29,6 +29,7 @@ import org.springblade.transport.service.ILoadingManageService;
import org.springblade.transport.service.IRiskDisposalService;
import org.springblade.transport.service.ITripQueryService;
import org.springblade.transport.service.IWaybillService;
import org.springblade.transport.support.MpUserDataScopeSupport;
import java.util.ArrayList;
import java.util.HashSet;
@@ -80,6 +81,8 @@ class ManageWaybillOrgScopeTest {
waybillService = mock(IWaybillService.class);
tripQueryService = mock(ITripQueryService.class);
IExceptionDisposalService exceptionDisposalService = mock(IExceptionDisposalService.class);
MpUserDataScopeSupport mpUserDataScopeSupport = mock(MpUserDataScopeSupport.class);
when(mpUserDataScopeSupport.resolveVisibleProjectIdsForQuery()).thenReturn(null);
service = new ManageWaybillServiceImpl(
waybillService,
mock(IDriverService.class),
@@ -87,7 +90,8 @@ class ManageWaybillOrgScopeTest {
mock(IRiskDisposalService.class),
mock(IDriverWaybillService.class),
tripQueryService,
mock(ILoadingManageService.class));
mock(ILoadingManageService.class),
mpUserDataScopeSupport);
when(exceptionDisposalService.list(ArgumentMatchers.<Wrapper<ExceptionDisposal>>any()))
.thenReturn(new ArrayList<>());
when(waybillService.list(ArgumentMatchers.<Wrapper<Waybill>>any())).thenReturn(new ArrayList<>());
@@ -18,6 +18,8 @@ CREATE TABLE IF NOT EXISTS `blade_mp_user` (
`openid` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信小程序 openid',
`identities` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '小程序身份,逗号分隔:driver,admin',
`dept_id` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '部门id',
`data_scope_range` tinyint NOT NULL DEFAULT 1 COMMENT '数据权限范围:1所属组织,2全部组织,3自定义',
`data_level_range` tinyint NOT NULL DEFAULT 1 COMMENT '数据层级范围:1包含下级,2仅本级',
`post_id` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '岗位id',
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
`create_user` bigint NULL DEFAULT NULL COMMENT '创建人',
@@ -0,0 +1,12 @@
-- 小程序用户:数据权限范围、数据层级范围、自定义组织数据权限
ALTER TABLE `blade_mp_user`
ADD COLUMN `data_scope_range` tinyint NOT NULL DEFAULT 1 COMMENT '数据权限范围:1所属组织,2全部组织,3自定义' AFTER `dept_id`,
ADD COLUMN `data_level_range` tinyint NOT NULL DEFAULT 1 COMMENT '数据层级范围:1包含下级,2仅本级' AFTER `data_scope_range`;
CREATE TABLE IF NOT EXISTS `blade_mp_user_data_scope` (
`id` bigint NOT NULL COMMENT '主键',
`user_id` bigint NOT NULL COMMENT '小程序用户主键',
`dept_id` bigint NOT NULL COMMENT '组织主键',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `uk_blade_mp_user_data_scope_user_dept` (`user_id`, `dept_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='小程序用户组织数据权限';