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

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
@@ -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()));
}